dbus/bus/config-parser.c
Simon McVittie 02e1ddf91e Revert "config: change default auth_timeout to 5 seconds"
This reverts commit 54d26df52b.

It appears this change may cause intermittent slow or failed boot,
more commonly on slower/older machines, in at least Mageia and
possibly also Debian. This would indicate that while the system
is under load, system services are not completing authentication
within 5 seconds.

This change was not the main part of fixing CVE-2014-3639, but does
help to mitigate that attack. As such, increasing this timeout makes
the denial of service attack described by CVE-2014-3639 somewhat
more effective: a local user connecting to the system bus repeatedly
from many parallel processes can cause other users' attempts to
connect to take longer.

If your machine boots reliably with the shorter timeout, and
resilience against local denial of service attacks is important
to you, putting this in /etc/dbus-1/system-local.conf
or a file matching /etc/dbus-1/system.d/*.conf can restore
the lower limit:

    <busconfig>
      <limit name="auth_timeout">5000</limit>
    </busconfig>

Bug: https://bugs.freedesktop.org/show_bug.cgi?id=86431
2014-11-22 10:49:21 +00:00

3656 lines
97 KiB
C

/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/* config-parser.c XML-library-agnostic configuration file parser
*
* Copyright (C) 2003, 2004 Red Hat, Inc.
*
* Licensed under the Academic Free License version 2.1
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include <config.h>
#include "config-parser-common.h"
#include "config-parser.h"
#include "test.h"
#include "utils.h"
#include "policy.h"
#include "selinux.h"
#include <dbus/dbus-list.h>
#include <dbus/dbus-internals.h>
#include <dbus/dbus-misc.h>
#include <dbus/dbus-sysdeps.h>
#include <string.h>
typedef enum
{
/* we ignore policies for unknown groups/users */
POLICY_IGNORED,
/* non-ignored */
POLICY_DEFAULT,
POLICY_MANDATORY,
POLICY_USER,
POLICY_GROUP,
POLICY_CONSOLE
} PolicyType;
typedef struct
{
ElementType type;
unsigned int had_content : 1;
union
{
struct
{
unsigned int ignore_missing : 1;
unsigned int if_selinux_enabled : 1;
unsigned int selinux_root_relative : 1;
} include;
struct
{
PolicyType type;
unsigned long gid_uid_or_at_console;
} policy;
struct
{
char *name;
long value;
} limit;
} d;
} Element;
/**
* Parser for bus configuration file.
*/
struct BusConfigParser
{
int refcount; /**< Reference count */
DBusString basedir; /**< Directory we resolve paths relative to */
DBusList *stack; /**< stack of Element */
char *user; /**< user to run as */
char *servicehelper; /**< location of the setuid helper */
char *bus_type; /**< Message bus type */
DBusList *listen_on; /**< List of addresses to listen to */
DBusList *mechanisms; /**< Auth mechanisms */
DBusList *service_dirs; /**< Directories to look for session services in */
DBusList *conf_dirs; /**< Directories to look for policy configuration in */
BusPolicy *policy; /**< Security policy */
BusLimits limits; /**< Limits */
char *pidfile; /**< PID file */
DBusList *included_files; /**< Included files stack */
DBusHashTable *service_context_table; /**< Map service names to SELinux contexts */
unsigned int fork : 1; /**< TRUE to fork into daemon mode */
unsigned int syslog : 1; /**< TRUE to enable syslog */
unsigned int keep_umask : 1; /**< TRUE to keep original umask when forking */
unsigned int is_toplevel : 1; /**< FALSE if we are a sub-config-file inside another one */
unsigned int allow_anonymous : 1; /**< TRUE to allow anonymous connections */
};
static Element*
push_element (BusConfigParser *parser,
ElementType type)
{
Element *e;
_dbus_assert (type != ELEMENT_NONE);
e = dbus_new0 (Element, 1);
if (e == NULL)
return NULL;
if (!_dbus_list_append (&parser->stack, e))
{
dbus_free (e);
return NULL;
}
e->type = type;
return e;
}
static void
element_free (Element *e)
{
if (e->type == ELEMENT_LIMIT)
dbus_free (e->d.limit.name);
dbus_free (e);
}
static void
pop_element (BusConfigParser *parser)
{
Element *e;
e = _dbus_list_pop_last (&parser->stack);
element_free (e);
}
static Element*
peek_element (BusConfigParser *parser)
{
Element *e;
e = _dbus_list_get_last (&parser->stack);
return e;
}
static ElementType
top_element_type (BusConfigParser *parser)
{
Element *e;
e = _dbus_list_get_last (&parser->stack);
if (e)
return e->type;
else
return ELEMENT_NONE;
}
static dbus_bool_t
merge_service_context_hash (DBusHashTable *dest,
DBusHashTable *from)
{
DBusHashIter iter;
char *service_copy;
char *context_copy;
service_copy = NULL;
context_copy = NULL;
_dbus_hash_iter_init (from, &iter);
while (_dbus_hash_iter_next (&iter))
{
const char *service = _dbus_hash_iter_get_string_key (&iter);
const char *context = _dbus_hash_iter_get_value (&iter);
service_copy = _dbus_strdup (service);
if (service_copy == NULL)
goto fail;
context_copy = _dbus_strdup (context);
if (context_copy == NULL)
goto fail;
if (!_dbus_hash_table_insert_string (dest, service_copy, context_copy))
goto fail;
service_copy = NULL;
context_copy = NULL;
}
return TRUE;
fail:
if (service_copy)
dbus_free (service_copy);
if (context_copy)
dbus_free (context_copy);
return FALSE;
}
static dbus_bool_t
service_dirs_find_dir (DBusList **service_dirs,
const char *dir)
{
DBusList *link;
_dbus_assert (dir != NULL);
for (link = *service_dirs; link; link = _dbus_list_get_next_link(service_dirs, link))
{
const char *link_dir;
link_dir = (const char *)link->data;
if (strcmp (dir, link_dir) == 0)
return TRUE;
}
return FALSE;
}
static dbus_bool_t
service_dirs_append_unique_or_free (DBusList **service_dirs,
char *dir)
{
if (!service_dirs_find_dir (service_dirs, dir))
return _dbus_list_append (service_dirs, dir);
dbus_free (dir);
return TRUE;
}
static void
service_dirs_append_link_unique_or_free (DBusList **service_dirs,
DBusList *dir_link)
{
if (!service_dirs_find_dir (service_dirs, dir_link->data))
{
_dbus_list_append_link (service_dirs, dir_link);
}
else
{
dbus_free (dir_link->data);
_dbus_list_free_link (dir_link);
}
}
static dbus_bool_t
merge_included (BusConfigParser *parser,
BusConfigParser *included,
DBusError *error)
{
DBusList *link;
if (!bus_policy_merge (parser->policy,
included->policy))
{
BUS_SET_OOM (error);
return FALSE;
}
if (!merge_service_context_hash (parser->service_context_table,
included->service_context_table))
{
BUS_SET_OOM (error);
return FALSE;
}
if (included->user != NULL)
{
dbus_free (parser->user);
parser->user = included->user;
included->user = NULL;
}
if (included->bus_type != NULL)
{
dbus_free (parser->bus_type);
parser->bus_type = included->bus_type;
included->bus_type = NULL;
}
if (included->fork)
parser->fork = TRUE;
if (included->keep_umask)
parser->keep_umask = TRUE;
if (included->allow_anonymous)
parser->allow_anonymous = TRUE;
if (included->pidfile != NULL)
{
dbus_free (parser->pidfile);
parser->pidfile = included->pidfile;
included->pidfile = NULL;
}
if (included->servicehelper != NULL)
{
dbus_free (parser->servicehelper);
parser->servicehelper = included->servicehelper;
included->servicehelper = NULL;
}
while ((link = _dbus_list_pop_first_link (&included->listen_on)))
_dbus_list_append_link (&parser->listen_on, link);
while ((link = _dbus_list_pop_first_link (&included->mechanisms)))
_dbus_list_append_link (&parser->mechanisms, link);
while ((link = _dbus_list_pop_first_link (&included->service_dirs)))
service_dirs_append_link_unique_or_free (&parser->service_dirs, link);
while ((link = _dbus_list_pop_first_link (&included->conf_dirs)))
_dbus_list_append_link (&parser->conf_dirs, link);
return TRUE;
}
static dbus_bool_t
seen_include (BusConfigParser *parser,
const DBusString *file)
{
DBusList *iter;
iter = parser->included_files;
while (iter != NULL)
{
if (! strcmp (_dbus_string_get_const_data (file), iter->data))
return TRUE;
iter = _dbus_list_get_next_link (&parser->included_files, iter);
}
return FALSE;
}
BusConfigParser*
bus_config_parser_new (const DBusString *basedir,
dbus_bool_t is_toplevel,
const BusConfigParser *parent)
{
BusConfigParser *parser;
parser = dbus_new0 (BusConfigParser, 1);
if (parser == NULL)
return NULL;
parser->is_toplevel = !!is_toplevel;
if (!_dbus_string_init (&parser->basedir))
{
dbus_free (parser);
return NULL;
}
if (((parser->policy = bus_policy_new ()) == NULL) ||
!_dbus_string_copy (basedir, 0, &parser->basedir, 0) ||
((parser->service_context_table = _dbus_hash_table_new (DBUS_HASH_STRING,
dbus_free,
dbus_free)) == NULL))
{
if (parser->policy)
bus_policy_unref (parser->policy);
_dbus_string_free (&parser->basedir);
dbus_free (parser);
return NULL;
}
if (parent != NULL)
{
/* Initialize the parser's limits from the parent. */
parser->limits = parent->limits;
/* Use the parent's list of included_files to avoid
circular inclusions. */
parser->included_files = parent->included_files;
}
else
{
/* Make up some numbers! woot! */
parser->limits.max_incoming_bytes = _DBUS_ONE_MEGABYTE * 127;
parser->limits.max_outgoing_bytes = _DBUS_ONE_MEGABYTE * 127;
parser->limits.max_message_size = _DBUS_ONE_MEGABYTE * 32;
/* We set relatively conservative values here since due to the
way SCM_RIGHTS works we need to preallocate an array for the
maximum number of file descriptors we can receive. Picking a
high value here thus translates directly to more memory
allocation. */
parser->limits.max_incoming_unix_fds = DBUS_DEFAULT_MESSAGE_UNIX_FDS*4;
parser->limits.max_outgoing_unix_fds = DBUS_DEFAULT_MESSAGE_UNIX_FDS*4;
parser->limits.max_message_unix_fds = DBUS_DEFAULT_MESSAGE_UNIX_FDS;
/* Making this long means the user has to wait longer for an error
* message if something screws up, but making it too short means
* they might see a false failure.
*/
parser->limits.activation_timeout = 25000; /* 25 seconds */
/* Making this long risks making a DOS attack easier, but too short
* and legitimate auth will fail. If interactive auth (ask user for
* password) is allowed, then potentially it has to be quite long.
*/
parser->limits.auth_timeout = 30000; /* 30 seconds */
/* Do not allow a fd to stay forever in dbus-daemon
* https://bugs.freedesktop.org/show_bug.cgi?id=80559
*/
parser->limits.pending_fd_timeout = 150000; /* 2.5 minutes */
parser->limits.max_incomplete_connections = 64;
parser->limits.max_connections_per_user = 256;
/* Note that max_completed_connections / max_connections_per_user
* is the number of users that would have to work together to
* DOS all the other users.
*/
parser->limits.max_completed_connections = 2048;
parser->limits.max_pending_activations = 512;
parser->limits.max_services_per_connection = 512;
/* For this one, keep in mind that it isn't only the memory used
* by the match rules, but slowdown from linearly walking a big
* list of them. A client adding more than this is almost
* certainly a bad idea for that reason, and should change to a
* smaller number of wider-net match rules - getting every last
* message to the bus is probably better than having a thousand
* match rules.
*/
parser->limits.max_match_rules_per_connection = 512;
parser->limits.reply_timeout = -1; /* never */
/* this is effectively a limit on message queue size for messages
* that require a reply
*/
parser->limits.max_replies_per_connection = 128;
}
parser->refcount = 1;
return parser;
}
BusConfigParser *
bus_config_parser_ref (BusConfigParser *parser)
{
_dbus_assert (parser->refcount > 0);
parser->refcount += 1;
return parser;
}
void
bus_config_parser_unref (BusConfigParser *parser)
{
_dbus_assert (parser->refcount > 0);
parser->refcount -= 1;
if (parser->refcount == 0)
{
while (parser->stack != NULL)
pop_element (parser);
dbus_free (parser->user);
dbus_free (parser->servicehelper);
dbus_free (parser->bus_type);
dbus_free (parser->pidfile);
_dbus_list_foreach (&parser->listen_on,
(DBusForeachFunction) dbus_free,
NULL);
_dbus_list_clear (&parser->listen_on);
_dbus_list_foreach (&parser->service_dirs,
(DBusForeachFunction) dbus_free,
NULL);
_dbus_list_clear (&parser->service_dirs);
_dbus_list_foreach (&parser->conf_dirs,
(DBusForeachFunction) dbus_free,
NULL);
_dbus_list_clear (&parser->conf_dirs);
_dbus_list_foreach (&parser->mechanisms,
(DBusForeachFunction) dbus_free,
NULL);
_dbus_list_clear (&parser->mechanisms);
_dbus_string_free (&parser->basedir);
if (parser->policy)
bus_policy_unref (parser->policy);
if (parser->service_context_table)
_dbus_hash_table_unref (parser->service_context_table);
dbus_free (parser);
}
}
dbus_bool_t
bus_config_parser_check_doctype (BusConfigParser *parser,
const char *doctype,
DBusError *error)
{
_DBUS_ASSERT_ERROR_IS_CLEAR (error);
if (strcmp (doctype, "busconfig") != 0)
{
dbus_set_error (error,
DBUS_ERROR_FAILED,
"Configuration file has the wrong document type %s",
doctype);
return FALSE;
}
else
return TRUE;
}
typedef struct
{
const char *name;
const char **retloc;
} LocateAttr;
static dbus_bool_t
locate_attributes (BusConfigParser *parser,
const char *element_name,
const char **attribute_names,
const char **attribute_values,
DBusError *error,
const char *first_attribute_name,
const char **first_attribute_retloc,
...)
{
va_list args;
const char *name;
const char **retloc;
int n_attrs;
#define MAX_ATTRS 24
LocateAttr attrs[MAX_ATTRS];
dbus_bool_t retval;
int i;
_dbus_assert (first_attribute_name != NULL);
_dbus_assert (first_attribute_retloc != NULL);
retval = TRUE;
n_attrs = 1;
attrs[0].name = first_attribute_name;
attrs[0].retloc = first_attribute_retloc;
*first_attribute_retloc = NULL;
va_start (args, first_attribute_retloc);
name = va_arg (args, const char*);
retloc = va_arg (args, const char**);
while (name != NULL)
{
_dbus_assert (retloc != NULL);
_dbus_assert (n_attrs < MAX_ATTRS);
attrs[n_attrs].name = name;
attrs[n_attrs].retloc = retloc;
n_attrs += 1;
*retloc = NULL;
name = va_arg (args, const char*);
retloc = va_arg (args, const char**);
}
va_end (args);
i = 0;
while (attribute_names[i])
{
int j;
dbus_bool_t found;
found = FALSE;
j = 0;
while (j < n_attrs)
{
if (strcmp (attrs[j].name, attribute_names[i]) == 0)
{
retloc = attrs[j].retloc;
if (*retloc != NULL)
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"Attribute \"%s\" repeated twice on the same <%s> element",
attrs[j].name, element_name);
retval = FALSE;
goto out;
}
*retloc = attribute_values[i];
found = TRUE;
}
++j;
}
if (!found)
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"Attribute \"%s\" is invalid on <%s> element in this context",
attribute_names[i], element_name);
retval = FALSE;
goto out;
}
++i;
}
out:
return retval;
}
static dbus_bool_t
check_no_attributes (BusConfigParser *parser,
const char *element_name,
const char **attribute_names,
const char **attribute_values,
DBusError *error)
{
if (attribute_names[0] != NULL)
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"Attribute \"%s\" is invalid on <%s> element in this context",
attribute_names[0], element_name);
return FALSE;
}
return TRUE;
}
static dbus_bool_t
start_busconfig_child (BusConfigParser *parser,
const char *element_name,
const char **attribute_names,
const char **attribute_values,
DBusError *error)
{
ElementType element_type;
element_type = bus_config_parser_element_name_to_type (element_name);
if (element_type == ELEMENT_USER)
{
if (!check_no_attributes (parser, "user", attribute_names, attribute_values, error))
return FALSE;
if (push_element (parser, ELEMENT_USER) == NULL)
{
BUS_SET_OOM (error);
return FALSE;
}
return TRUE;
}
else if (element_type == ELEMENT_CONFIGTYPE)
{
if (!check_no_attributes (parser, "type", attribute_names, attribute_values, error))
return FALSE;
if (push_element (parser, ELEMENT_CONFIGTYPE) == NULL)
{
BUS_SET_OOM (error);
return FALSE;
}
return TRUE;
}
else if (element_type == ELEMENT_FORK)
{
if (!check_no_attributes (parser, "fork", attribute_names, attribute_values, error))
return FALSE;
if (push_element (parser, ELEMENT_FORK) == NULL)
{
BUS_SET_OOM (error);
return FALSE;
}
parser->fork = TRUE;
return TRUE;
}
else if (element_type == ELEMENT_SYSLOG)
{
if (!check_no_attributes (parser, "syslog", attribute_names, attribute_values, error))
return FALSE;
if (push_element (parser, ELEMENT_SYSLOG) == NULL)
{
BUS_SET_OOM (error);
return FALSE;
}
parser->syslog = TRUE;
return TRUE;
}
else if (element_type == ELEMENT_KEEP_UMASK)
{
if (!check_no_attributes (parser, "keep_umask", attribute_names, attribute_values, error))
return FALSE;
if (push_element (parser, ELEMENT_KEEP_UMASK) == NULL)
{
BUS_SET_OOM (error);
return FALSE;
}
parser->keep_umask = TRUE;
return TRUE;
}
else if (element_type == ELEMENT_PIDFILE)
{
if (!check_no_attributes (parser, "pidfile", attribute_names, attribute_values, error))
return FALSE;
if (push_element (parser, ELEMENT_PIDFILE) == NULL)
{
BUS_SET_OOM (error);
return FALSE;
}
return TRUE;
}
else if (element_type == ELEMENT_LISTEN)
{
if (!check_no_attributes (parser, "listen", attribute_names, attribute_values, error))
return FALSE;
if (push_element (parser, ELEMENT_LISTEN) == NULL)
{
BUS_SET_OOM (error);
return FALSE;
}
return TRUE;
}
else if (element_type == ELEMENT_AUTH)
{
if (!check_no_attributes (parser, "auth", attribute_names, attribute_values, error))
return FALSE;
if (push_element (parser, ELEMENT_AUTH) == NULL)
{
BUS_SET_OOM (error);
return FALSE;
}
return TRUE;
}
else if (element_type == ELEMENT_SERVICEHELPER)
{
if (!check_no_attributes (parser, "servicehelper", attribute_names, attribute_values, error))
return FALSE;
if (push_element (parser, ELEMENT_SERVICEHELPER) == NULL)
{
BUS_SET_OOM (error);
return FALSE;
}
return TRUE;
}
else if (element_type == ELEMENT_INCLUDEDIR)
{
if (!check_no_attributes (parser, "includedir", attribute_names, attribute_values, error))
return FALSE;
if (push_element (parser, ELEMENT_INCLUDEDIR) == NULL)
{
BUS_SET_OOM (error);
return FALSE;
}
return TRUE;
}
else if (element_type == ELEMENT_STANDARD_SESSION_SERVICEDIRS)
{
DBusList *link;
DBusList *dirs;
dirs = NULL;
if (!check_no_attributes (parser, "standard_session_servicedirs", attribute_names, attribute_values, error))
return FALSE;
if (push_element (parser, ELEMENT_STANDARD_SESSION_SERVICEDIRS) == NULL)
{
BUS_SET_OOM (error);
return FALSE;
}
if (!_dbus_get_standard_session_servicedirs (&dirs))
{
BUS_SET_OOM (error);
return FALSE;
}
while ((link = _dbus_list_pop_first_link (&dirs)))
service_dirs_append_link_unique_or_free (&parser->service_dirs, link);
return TRUE;
}
else if (element_type == ELEMENT_STANDARD_SYSTEM_SERVICEDIRS)
{
DBusList *link;
DBusList *dirs;
dirs = NULL;
if (!check_no_attributes (parser, "standard_system_servicedirs", attribute_names, attribute_values, error))
return FALSE;
if (push_element (parser, ELEMENT_STANDARD_SYSTEM_SERVICEDIRS) == NULL)
{
BUS_SET_OOM (error);
return FALSE;
}
if (!_dbus_get_standard_system_servicedirs (&dirs))
{
BUS_SET_OOM (error);
return FALSE;
}
while ((link = _dbus_list_pop_first_link (&dirs)))
service_dirs_append_link_unique_or_free (&parser->service_dirs, link);
return TRUE;
}
else if (element_type == ELEMENT_ALLOW_ANONYMOUS)
{
if (!check_no_attributes (parser, "allow_anonymous", attribute_names, attribute_values, error))
return FALSE;
if (push_element (parser, ELEMENT_ALLOW_ANONYMOUS) == NULL)
{
BUS_SET_OOM (error);
return FALSE;
}
parser->allow_anonymous = TRUE;
return TRUE;
}
else if (element_type == ELEMENT_SERVICEDIR)
{
if (!check_no_attributes (parser, "servicedir", attribute_names, attribute_values, error))
return FALSE;
if (push_element (parser, ELEMENT_SERVICEDIR) == NULL)
{
BUS_SET_OOM (error);
return FALSE;
}
return TRUE;
}
else if (element_type == ELEMENT_INCLUDE)
{
Element *e;
const char *if_selinux_enabled;
const char *ignore_missing;
const char *selinux_root_relative;
if ((e = push_element (parser, ELEMENT_INCLUDE)) == NULL)
{
BUS_SET_OOM (error);
return FALSE;
}
e->d.include.ignore_missing = FALSE;
e->d.include.if_selinux_enabled = FALSE;
e->d.include.selinux_root_relative = FALSE;
if (!locate_attributes (parser, "include",
attribute_names,
attribute_values,
error,
"ignore_missing", &ignore_missing,
"if_selinux_enabled", &if_selinux_enabled,
"selinux_root_relative", &selinux_root_relative,
NULL))
return FALSE;
if (ignore_missing != NULL)
{
if (strcmp (ignore_missing, "yes") == 0)
e->d.include.ignore_missing = TRUE;
else if (strcmp (ignore_missing, "no") == 0)
e->d.include.ignore_missing = FALSE;
else
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"ignore_missing attribute must have value \"yes\" or \"no\"");
return FALSE;
}
}
if (if_selinux_enabled != NULL)
{
if (strcmp (if_selinux_enabled, "yes") == 0)
e->d.include.if_selinux_enabled = TRUE;
else if (strcmp (if_selinux_enabled, "no") == 0)
e->d.include.if_selinux_enabled = FALSE;
else
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"if_selinux_enabled attribute must have value"
" \"yes\" or \"no\"");
return FALSE;
}
}
if (selinux_root_relative != NULL)
{
if (strcmp (selinux_root_relative, "yes") == 0)
e->d.include.selinux_root_relative = TRUE;
else if (strcmp (selinux_root_relative, "no") == 0)
e->d.include.selinux_root_relative = FALSE;
else
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"selinux_root_relative attribute must have value"
" \"yes\" or \"no\"");
return FALSE;
}
}
return TRUE;
}
else if (element_type == ELEMENT_POLICY)
{
Element *e;
const char *context;
const char *user;
const char *group;
const char *at_console;
if ((e = push_element (parser, ELEMENT_POLICY)) == NULL)
{
BUS_SET_OOM (error);
return FALSE;
}
e->d.policy.type = POLICY_IGNORED;
if (!locate_attributes (parser, "policy",
attribute_names,
attribute_values,
error,
"context", &context,
"user", &user,
"group", &group,
"at_console", &at_console,
NULL))
return FALSE;
if (((context && user) ||
(context && group) ||
(context && at_console)) ||
((user && group) ||
(user && at_console)) ||
(group && at_console) ||
!(context || user || group || at_console))
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"<policy> element must have exactly one of (context|user|group|at_console) attributes");
return FALSE;
}
if (context != NULL)
{
if (strcmp (context, "default") == 0)
{
e->d.policy.type = POLICY_DEFAULT;
}
else if (strcmp (context, "mandatory") == 0)
{
e->d.policy.type = POLICY_MANDATORY;
}
else
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"context attribute on <policy> must have the value \"default\" or \"mandatory\", not \"%s\"",
context);
return FALSE;
}
}
else if (user != NULL)
{
DBusString username;
_dbus_string_init_const (&username, user);
if (_dbus_parse_unix_user_from_config (&username,
&e->d.policy.gid_uid_or_at_console))
e->d.policy.type = POLICY_USER;
else
_dbus_warn ("Unknown username \"%s\" in message bus configuration file\n",
user);
}
else if (group != NULL)
{
DBusString group_name;
_dbus_string_init_const (&group_name, group);
if (_dbus_parse_unix_group_from_config (&group_name,
&e->d.policy.gid_uid_or_at_console))
e->d.policy.type = POLICY_GROUP;
else
_dbus_warn ("Unknown group \"%s\" in message bus configuration file\n",
group);
}
else if (at_console != NULL)
{
dbus_bool_t t;
t = (strcmp (at_console, "true") == 0);
if (t || strcmp (at_console, "false") == 0)
{
e->d.policy.gid_uid_or_at_console = t;
e->d.policy.type = POLICY_CONSOLE;
}
else
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"Unknown value \"%s\" for at_console in message bus configuration file",
at_console);
return FALSE;
}
}
else
{
_dbus_assert_not_reached ("all <policy> attributes null and we didn't set error");
}
return TRUE;
}
else if (element_type == ELEMENT_LIMIT)
{
Element *e;
const char *name;
if ((e = push_element (parser, ELEMENT_LIMIT)) == NULL)
{
BUS_SET_OOM (error);
return FALSE;
}
if (!locate_attributes (parser, "limit",
attribute_names,
attribute_values,
error,
"name", &name,
NULL))
return FALSE;
if (name == NULL)
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"<limit> element must have a \"name\" attribute");
return FALSE;
}
e->d.limit.name = _dbus_strdup (name);
if (e->d.limit.name == NULL)
{
BUS_SET_OOM (error);
return FALSE;
}
return TRUE;
}
else if (element_type == ELEMENT_SELINUX)
{
if (!check_no_attributes (parser, "selinux", attribute_names, attribute_values, error))
return FALSE;
if (push_element (parser, ELEMENT_SELINUX) == NULL)
{
BUS_SET_OOM (error);
return FALSE;
}
return TRUE;
}
else
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"Element <%s> not allowed inside <%s> in configuration file",
element_name, "busconfig");
return FALSE;
}
}
static dbus_bool_t
append_rule_from_element (BusConfigParser *parser,
const char *element_name,
const char **attribute_names,
const char **attribute_values,
dbus_bool_t allow,
DBusError *error)
{
const char *log;
const char *send_interface;
const char *send_member;
const char *send_error;
const char *send_destination;
const char *send_path;
const char *send_type;
const char *receive_interface;
const char *receive_member;
const char *receive_error;
const char *receive_sender;
const char *receive_path;
const char *receive_type;
const char *eavesdrop;
const char *send_requested_reply;
const char *receive_requested_reply;
const char *own;
const char *own_prefix;
const char *user;
const char *group;
BusPolicyRule *rule;
if (!locate_attributes (parser, element_name,
attribute_names,
attribute_values,
error,
"send_interface", &send_interface,
"send_member", &send_member,
"send_error", &send_error,
"send_destination", &send_destination,
"send_path", &send_path,
"send_type", &send_type,
"receive_interface", &receive_interface,
"receive_member", &receive_member,
"receive_error", &receive_error,
"receive_sender", &receive_sender,
"receive_path", &receive_path,
"receive_type", &receive_type,
"eavesdrop", &eavesdrop,
"send_requested_reply", &send_requested_reply,
"receive_requested_reply", &receive_requested_reply,
"own", &own,
"own_prefix", &own_prefix,
"user", &user,
"group", &group,
"log", &log,
NULL))
return FALSE;
if (!(send_interface || send_member || send_error || send_destination ||
send_type || send_path ||
receive_interface || receive_member || receive_error || receive_sender ||
receive_type || receive_path || eavesdrop ||
send_requested_reply || receive_requested_reply ||
own || own_prefix || user || group))
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"Element <%s> must have one or more attributes",
element_name);
return FALSE;
}
if ((send_member && (send_interface == NULL && send_path == NULL)) ||
(receive_member && (receive_interface == NULL && receive_path == NULL)))
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"On element <%s>, if you specify a member you must specify an interface or a path. Keep in mind that not all messages have an interface field.",
element_name);
return FALSE;
}
/* Allowed combinations of elements are:
*
* base, must be all send or all receive:
* nothing
* interface
* interface + member
* error
*
* base send_ can combine with send_destination, send_path, send_type, send_requested_reply
* base receive_ with receive_sender, receive_path, receive_type, receive_requested_reply, eavesdrop
*
* user, group, own, own_prefix must occur alone
*
* Pretty sure the below stuff is broken, FIXME think about it more.
*/
if ((send_interface && (send_error ||
receive_interface ||
receive_member ||
receive_error ||
receive_sender ||
receive_requested_reply ||
own || own_prefix ||
user ||
group)) ||
(send_member && (send_error ||
receive_interface ||
receive_member ||
receive_error ||
receive_sender ||
receive_requested_reply ||
own || own_prefix ||
user ||
group)) ||
(send_error && (receive_interface ||
receive_member ||
receive_error ||
receive_sender ||
receive_requested_reply ||
own || own_prefix ||
user ||
group)) ||
(send_destination && (receive_interface ||
receive_member ||
receive_error ||
receive_sender ||
receive_requested_reply ||
own || own_prefix ||
user ||
group)) ||
(send_type && (receive_interface ||
receive_member ||
receive_error ||
receive_sender ||
receive_requested_reply ||
own || own_prefix ||
user ||
group)) ||
(send_path && (receive_interface ||
receive_member ||
receive_error ||
receive_sender ||
receive_requested_reply ||
own || own_prefix ||
user ||
group)) ||
(send_requested_reply && (receive_interface ||
receive_member ||
receive_error ||
receive_sender ||
receive_requested_reply ||
own || own_prefix ||
user ||
group)) ||
(receive_interface && (receive_error ||
own || own_prefix ||
user ||
group)) ||
(receive_member && (receive_error ||
own || own_prefix ||
user ||
group)) ||
(receive_error && (own || own_prefix ||
user ||
group)) ||
(eavesdrop && (own || own_prefix ||
user ||
group)) ||
(receive_requested_reply && (own || own_prefix ||
user ||
group)) ||
(own && (own_prefix || user || group)) ||
(own_prefix && (own || user || group)) ||
(user && group))
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"Invalid combination of attributes on element <%s>",
element_name);
return FALSE;
}
rule = NULL;
/* In BusPolicyRule, NULL represents wildcard.
* In the config file, '*' represents it.
*/
#define IS_WILDCARD(str) ((str) && ((str)[0]) == '*' && ((str)[1]) == '\0')
if (send_interface || send_member || send_error || send_destination ||
send_path || send_type || send_requested_reply)
{
int message_type;
if (IS_WILDCARD (send_interface))
send_interface = NULL;
if (IS_WILDCARD (send_member))
send_member = NULL;
if (IS_WILDCARD (send_error))
send_error = NULL;
if (IS_WILDCARD (send_destination))
send_destination = NULL;
if (IS_WILDCARD (send_path))
send_path = NULL;
if (IS_WILDCARD (send_type))
send_type = NULL;
message_type = DBUS_MESSAGE_TYPE_INVALID;
if (send_type != NULL)
{
message_type = dbus_message_type_from_string (send_type);
if (message_type == DBUS_MESSAGE_TYPE_INVALID)
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"Bad message type \"%s\"",
send_type);
return FALSE;
}
}
if (eavesdrop &&
!(strcmp (eavesdrop, "true") == 0 ||
strcmp (eavesdrop, "false") == 0))
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"Bad value \"%s\" for %s attribute, must be true or false",
"eavesdrop", eavesdrop);
return FALSE;
}
if (send_requested_reply &&
!(strcmp (send_requested_reply, "true") == 0 ||
strcmp (send_requested_reply, "false") == 0))
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"Bad value \"%s\" for %s attribute, must be true or false",
"send_requested_reply", send_requested_reply);
return FALSE;
}
rule = bus_policy_rule_new (BUS_POLICY_RULE_SEND, allow);
if (rule == NULL)
goto nomem;
if (eavesdrop)
rule->d.send.eavesdrop = (strcmp (eavesdrop, "true") == 0);
if (log)
rule->d.send.log = (strcmp (log, "true") == 0);
if (send_requested_reply)
rule->d.send.requested_reply = (strcmp (send_requested_reply, "true") == 0);
rule->d.send.message_type = message_type;
rule->d.send.path = _dbus_strdup (send_path);
rule->d.send.interface = _dbus_strdup (send_interface);
rule->d.send.member = _dbus_strdup (send_member);
rule->d.send.error = _dbus_strdup (send_error);
rule->d.send.destination = _dbus_strdup (send_destination);
if (send_path && rule->d.send.path == NULL)
goto nomem;
if (send_interface && rule->d.send.interface == NULL)
goto nomem;
if (send_member && rule->d.send.member == NULL)
goto nomem;
if (send_error && rule->d.send.error == NULL)
goto nomem;
if (send_destination && rule->d.send.destination == NULL)
goto nomem;
}
else if (receive_interface || receive_member || receive_error || receive_sender ||
receive_path || receive_type || eavesdrop || receive_requested_reply)
{
int message_type;
if (IS_WILDCARD (receive_interface))
receive_interface = NULL;
if (IS_WILDCARD (receive_member))
receive_member = NULL;
if (IS_WILDCARD (receive_error))
receive_error = NULL;
if (IS_WILDCARD (receive_sender))
receive_sender = NULL;
if (IS_WILDCARD (receive_path))
receive_path = NULL;
if (IS_WILDCARD (receive_type))
receive_type = NULL;
message_type = DBUS_MESSAGE_TYPE_INVALID;
if (receive_type != NULL)
{
message_type = dbus_message_type_from_string (receive_type);
if (message_type == DBUS_MESSAGE_TYPE_INVALID)
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"Bad message type \"%s\"",
receive_type);
return FALSE;
}
}
if (eavesdrop &&
!(strcmp (eavesdrop, "true") == 0 ||
strcmp (eavesdrop, "false") == 0))
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"Bad value \"%s\" for %s attribute, must be true or false",
"eavesdrop", eavesdrop);
return FALSE;
}
if (receive_requested_reply &&
!(strcmp (receive_requested_reply, "true") == 0 ||
strcmp (receive_requested_reply, "false") == 0))
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"Bad value \"%s\" for %s attribute, must be true or false",
"receive_requested_reply", receive_requested_reply);
return FALSE;
}
rule = bus_policy_rule_new (BUS_POLICY_RULE_RECEIVE, allow);
if (rule == NULL)
goto nomem;
if (eavesdrop)
rule->d.receive.eavesdrop = (strcmp (eavesdrop, "true") == 0);
if (receive_requested_reply)
rule->d.receive.requested_reply = (strcmp (receive_requested_reply, "true") == 0);
rule->d.receive.message_type = message_type;
rule->d.receive.path = _dbus_strdup (receive_path);
rule->d.receive.interface = _dbus_strdup (receive_interface);
rule->d.receive.member = _dbus_strdup (receive_member);
rule->d.receive.error = _dbus_strdup (receive_error);
rule->d.receive.origin = _dbus_strdup (receive_sender);
if (receive_path && rule->d.receive.path == NULL)
goto nomem;
if (receive_interface && rule->d.receive.interface == NULL)
goto nomem;
if (receive_member && rule->d.receive.member == NULL)
goto nomem;
if (receive_error && rule->d.receive.error == NULL)
goto nomem;
if (receive_sender && rule->d.receive.origin == NULL)
goto nomem;
}
else if (own || own_prefix)
{
rule = bus_policy_rule_new (BUS_POLICY_RULE_OWN, allow);
if (rule == NULL)
goto nomem;
if (own)
{
if (IS_WILDCARD (own))
own = NULL;
rule->d.own.prefix = 0;
rule->d.own.service_name = _dbus_strdup (own);
if (own && rule->d.own.service_name == NULL)
goto nomem;
}
else
{
rule->d.own.prefix = 1;
rule->d.own.service_name = _dbus_strdup (own_prefix);
if (rule->d.own.service_name == NULL)
goto nomem;
}
}
else if (user)
{
if (IS_WILDCARD (user))
{
rule = bus_policy_rule_new (BUS_POLICY_RULE_USER, allow);
if (rule == NULL)
goto nomem;
rule->d.user.uid = DBUS_UID_UNSET;
}
else
{
DBusString username;
dbus_uid_t uid;
_dbus_string_init_const (&username, user);
if (_dbus_parse_unix_user_from_config (&username, &uid))
{
rule = bus_policy_rule_new (BUS_POLICY_RULE_USER, allow);
if (rule == NULL)
goto nomem;
rule->d.user.uid = uid;
}
else
{
_dbus_warn ("Unknown username \"%s\" on element <%s>\n",
user, element_name);
}
}
}
else if (group)
{
if (IS_WILDCARD (group))
{
rule = bus_policy_rule_new (BUS_POLICY_RULE_GROUP, allow);
if (rule == NULL)
goto nomem;
rule->d.group.gid = DBUS_GID_UNSET;
}
else
{
DBusString groupname;
dbus_gid_t gid;
_dbus_string_init_const (&groupname, group);
if (_dbus_parse_unix_group_from_config (&groupname, &gid))
{
rule = bus_policy_rule_new (BUS_POLICY_RULE_GROUP, allow);
if (rule == NULL)
goto nomem;
rule->d.group.gid = gid;
}
else
{
_dbus_warn ("Unknown group \"%s\" on element <%s>\n",
group, element_name);
}
}
}
else
_dbus_assert_not_reached ("Did not handle some combination of attributes on <allow> or <deny>");
if (rule != NULL)
{
Element *pe;
pe = peek_element (parser);
_dbus_assert (pe != NULL);
_dbus_assert (pe->type == ELEMENT_POLICY);
switch (pe->d.policy.type)
{
case POLICY_IGNORED:
/* drop the rule on the floor */
break;
case POLICY_DEFAULT:
if (!bus_policy_append_default_rule (parser->policy, rule))
goto nomem;
break;
case POLICY_MANDATORY:
if (!bus_policy_append_mandatory_rule (parser->policy, rule))
goto nomem;
break;
case POLICY_USER:
if (!BUS_POLICY_RULE_IS_PER_CLIENT (rule))
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"<%s> rule cannot be per-user because it has bus-global semantics",
element_name);
goto failed;
}
if (!bus_policy_append_user_rule (parser->policy, pe->d.policy.gid_uid_or_at_console,
rule))
goto nomem;
break;
case POLICY_GROUP:
if (!BUS_POLICY_RULE_IS_PER_CLIENT (rule))
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"<%s> rule cannot be per-group because it has bus-global semantics",
element_name);
goto failed;
}
if (!bus_policy_append_group_rule (parser->policy, pe->d.policy.gid_uid_or_at_console,
rule))
goto nomem;
break;
case POLICY_CONSOLE:
if (!bus_policy_append_console_rule (parser->policy, pe->d.policy.gid_uid_or_at_console,
rule))
goto nomem;
break;
}
bus_policy_rule_unref (rule);
rule = NULL;
}
return TRUE;
nomem:
BUS_SET_OOM (error);
failed:
if (rule)
bus_policy_rule_unref (rule);
return FALSE;
}
static dbus_bool_t
start_policy_child (BusConfigParser *parser,
const char *element_name,
const char **attribute_names,
const char **attribute_values,
DBusError *error)
{
if (strcmp (element_name, "allow") == 0)
{
if (!append_rule_from_element (parser, element_name,
attribute_names, attribute_values,
TRUE, error))
return FALSE;
if (push_element (parser, ELEMENT_ALLOW) == NULL)
{
BUS_SET_OOM (error);
return FALSE;
}
return TRUE;
}
else if (strcmp (element_name, "deny") == 0)
{
if (!append_rule_from_element (parser, element_name,
attribute_names, attribute_values,
FALSE, error))
return FALSE;
if (push_element (parser, ELEMENT_DENY) == NULL)
{
BUS_SET_OOM (error);
return FALSE;
}
return TRUE;
}
else
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"Element <%s> not allowed inside <%s> in configuration file",
element_name, "policy");
return FALSE;
}
}
static dbus_bool_t
start_selinux_child (BusConfigParser *parser,
const char *element_name,
const char **attribute_names,
const char **attribute_values,
DBusError *error)
{
char *own_copy;
char *context_copy;
own_copy = NULL;
context_copy = NULL;
if (strcmp (element_name, "associate") == 0)
{
const char *own;
const char *context;
if (!locate_attributes (parser, "associate",
attribute_names,
attribute_values,
error,
"own", &own,
"context", &context,
NULL))
return FALSE;
if (push_element (parser, ELEMENT_ASSOCIATE) == NULL)
{
BUS_SET_OOM (error);
return FALSE;
}
if (own == NULL || context == NULL)
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"Element <associate> must have attributes own=\"<servicename>\" and context=\"<selinux context>\"");
return FALSE;
}
own_copy = _dbus_strdup (own);
if (own_copy == NULL)
goto oom;
context_copy = _dbus_strdup (context);
if (context_copy == NULL)
goto oom;
if (!_dbus_hash_table_insert_string (parser->service_context_table,
own_copy, context_copy))
goto oom;
return TRUE;
}
else
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"Element <%s> not allowed inside <%s> in configuration file",
element_name, "selinux");
return FALSE;
}
oom:
if (own_copy)
dbus_free (own_copy);
if (context_copy)
dbus_free (context_copy);
BUS_SET_OOM (error);
return FALSE;
}
dbus_bool_t
bus_config_parser_start_element (BusConfigParser *parser,
const char *element_name,
const char **attribute_names,
const char **attribute_values,
DBusError *error)
{
ElementType t;
_DBUS_ASSERT_ERROR_IS_CLEAR (error);
/* printf ("START: %s\n", element_name); */
t = top_element_type (parser);
if (t == ELEMENT_NONE)
{
if (strcmp (element_name, "busconfig") == 0)
{
if (!check_no_attributes (parser, "busconfig", attribute_names, attribute_values, error))
return FALSE;
if (push_element (parser, ELEMENT_BUSCONFIG) == NULL)
{
BUS_SET_OOM (error);
return FALSE;
}
return TRUE;
}
else
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"Unknown element <%s> at root of configuration file",
element_name);
return FALSE;
}
}
else if (t == ELEMENT_BUSCONFIG)
{
return start_busconfig_child (parser, element_name,
attribute_names, attribute_values,
error);
}
else if (t == ELEMENT_POLICY)
{
return start_policy_child (parser, element_name,
attribute_names, attribute_values,
error);
}
else if (t == ELEMENT_SELINUX)
{
return start_selinux_child (parser, element_name,
attribute_names, attribute_values,
error);
}
else
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"Element <%s> is not allowed in this context",
element_name);
return FALSE;
}
}
static dbus_bool_t
set_limit (BusConfigParser *parser,
const char *name,
long value,
DBusError *error)
{
dbus_bool_t must_be_positive;
dbus_bool_t must_be_int;
must_be_int = FALSE;
must_be_positive = FALSE;
if (strcmp (name, "max_incoming_bytes") == 0)
{
must_be_positive = TRUE;
parser->limits.max_incoming_bytes = value;
}
else if (strcmp (name, "max_incoming_unix_fds") == 0)
{
must_be_positive = TRUE;
parser->limits.max_incoming_unix_fds = value;
}
else if (strcmp (name, "max_outgoing_bytes") == 0)
{
must_be_positive = TRUE;
parser->limits.max_outgoing_bytes = value;
}
else if (strcmp (name, "max_outgoing_unix_fds") == 0)
{
must_be_positive = TRUE;
parser->limits.max_outgoing_unix_fds = value;
}
else if (strcmp (name, "max_message_size") == 0)
{
must_be_positive = TRUE;
parser->limits.max_message_size = value;
}
else if (strcmp (name, "max_message_unix_fds") == 0)
{
must_be_positive = TRUE;
parser->limits.max_message_unix_fds = value;
}
else if (strcmp (name, "service_start_timeout") == 0)
{
must_be_positive = TRUE;
must_be_int = TRUE;
parser->limits.activation_timeout = value;
}
else if (strcmp (name, "auth_timeout") == 0)
{
must_be_positive = TRUE;
must_be_int = TRUE;
parser->limits.auth_timeout = value;
}
else if (strcmp (name, "pending_fd_timeout") == 0)
{
must_be_positive = TRUE;
must_be_int = TRUE;
parser->limits.pending_fd_timeout = value;
}
else if (strcmp (name, "reply_timeout") == 0)
{
must_be_positive = TRUE;
must_be_int = TRUE;
parser->limits.reply_timeout = value;
}
else if (strcmp (name, "max_completed_connections") == 0)
{
must_be_positive = TRUE;
must_be_int = TRUE;
parser->limits.max_completed_connections = value;
}
else if (strcmp (name, "max_incomplete_connections") == 0)
{
must_be_positive = TRUE;
must_be_int = TRUE;
parser->limits.max_incomplete_connections = value;
}
else if (strcmp (name, "max_connections_per_user") == 0)
{
must_be_positive = TRUE;
must_be_int = TRUE;
parser->limits.max_connections_per_user = value;
}
else if (strcmp (name, "max_pending_service_starts") == 0)
{
must_be_positive = TRUE;
must_be_int = TRUE;
parser->limits.max_pending_activations = value;
}
else if (strcmp (name, "max_names_per_connection") == 0)
{
must_be_positive = TRUE;
must_be_int = TRUE;
parser->limits.max_services_per_connection = value;
}
else if (strcmp (name, "max_match_rules_per_connection") == 0)
{
must_be_positive = TRUE;
must_be_int = TRUE;
parser->limits.max_match_rules_per_connection = value;
}
else if (strcmp (name, "max_replies_per_connection") == 0)
{
must_be_positive = TRUE;
must_be_int = TRUE;
parser->limits.max_replies_per_connection = value;
}
else
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"There is no limit called \"%s\"\n",
name);
return FALSE;
}
if (must_be_positive && value < 0)
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"<limit name=\"%s\"> must be a positive number\n",
name);
return FALSE;
}
if (must_be_int &&
(value < _DBUS_INT_MIN || value > _DBUS_INT_MAX))
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"<limit name=\"%s\"> value is too large\n",
name);
return FALSE;
}
return TRUE;
}
dbus_bool_t
bus_config_parser_end_element (BusConfigParser *parser,
const char *element_name,
DBusError *error)
{
ElementType t;
const char *n;
Element *e;
_DBUS_ASSERT_ERROR_IS_CLEAR (error);
/* printf ("END: %s\n", element_name); */
t = top_element_type (parser);
if (t == ELEMENT_NONE)
{
/* should probably be an assertion failure but
* being paranoid about XML parsers
*/
dbus_set_error (error, DBUS_ERROR_FAILED,
"XML parser ended element with no element on the stack");
return FALSE;
}
n = bus_config_parser_element_type_to_name (t);
_dbus_assert (n != NULL);
if (strcmp (n, element_name) != 0)
{
/* should probably be an assertion failure but
* being paranoid about XML parsers
*/
dbus_set_error (error, DBUS_ERROR_FAILED,
"XML element <%s> ended but topmost element on the stack was <%s>",
element_name, n);
return FALSE;
}
e = peek_element (parser);
_dbus_assert (e != NULL);
switch (e->type)
{
case ELEMENT_NONE:
_dbus_assert_not_reached ("element in stack has no type");
break;
case ELEMENT_INCLUDE:
case ELEMENT_USER:
case ELEMENT_CONFIGTYPE:
case ELEMENT_LISTEN:
case ELEMENT_PIDFILE:
case ELEMENT_AUTH:
case ELEMENT_SERVICEDIR:
case ELEMENT_SERVICEHELPER:
case ELEMENT_INCLUDEDIR:
case ELEMENT_LIMIT:
if (!e->had_content)
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"XML element <%s> was expected to have content inside it",
bus_config_parser_element_type_to_name (e->type));
return FALSE;
}
if (e->type == ELEMENT_LIMIT)
{
if (!set_limit (parser, e->d.limit.name, e->d.limit.value,
error))
return FALSE;
}
break;
case ELEMENT_BUSCONFIG:
case ELEMENT_POLICY:
case ELEMENT_ALLOW:
case ELEMENT_DENY:
case ELEMENT_FORK:
case ELEMENT_SYSLOG:
case ELEMENT_KEEP_UMASK:
case ELEMENT_SELINUX:
case ELEMENT_ASSOCIATE:
case ELEMENT_STANDARD_SESSION_SERVICEDIRS:
case ELEMENT_STANDARD_SYSTEM_SERVICEDIRS:
case ELEMENT_ALLOW_ANONYMOUS:
break;
}
pop_element (parser);
return TRUE;
}
static dbus_bool_t
all_whitespace (const DBusString *str)
{
int i;
_dbus_string_skip_white (str, 0, &i);
return i == _dbus_string_get_length (str);
}
static dbus_bool_t
make_full_path (const DBusString *basedir,
const DBusString *filename,
DBusString *full_path)
{
if (_dbus_path_is_absolute (filename))
{
return _dbus_string_copy (filename, 0, full_path, 0);
}
else
{
if (!_dbus_string_copy (basedir, 0, full_path, 0))
return FALSE;
if (!_dbus_concat_dir_and_file (full_path, filename))
return FALSE;
return TRUE;
}
}
static dbus_bool_t
include_file (BusConfigParser *parser,
const DBusString *filename,
dbus_bool_t ignore_missing,
DBusError *error)
{
/* FIXME good test case for this would load each config file in the
* test suite both alone, and as an include, and check
* that the result is the same
*/
BusConfigParser *included;
const char *filename_str;
DBusError tmp_error;
dbus_error_init (&tmp_error);
filename_str = _dbus_string_get_const_data (filename);
/* Check to make sure this file hasn't already been included. */
if (seen_include (parser, filename))
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"Circular inclusion of file '%s'",
filename_str);
return FALSE;
}
if (! _dbus_list_append (&parser->included_files, (void *) filename_str))
{
BUS_SET_OOM (error);
return FALSE;
}
/* Since parser is passed in as the parent, included
inherits parser's limits. */
included = bus_config_load (filename, FALSE, parser, &tmp_error);
_dbus_list_pop_last (&parser->included_files);
if (included == NULL)
{
_DBUS_ASSERT_ERROR_IS_SET (&tmp_error);
if (dbus_error_has_name (&tmp_error, DBUS_ERROR_FILE_NOT_FOUND) &&
ignore_missing)
{
dbus_error_free (&tmp_error);
return TRUE;
}
else
{
dbus_move_error (&tmp_error, error);
return FALSE;
}
}
else
{
_DBUS_ASSERT_ERROR_IS_CLEAR (&tmp_error);
if (!merge_included (parser, included, error))
{
bus_config_parser_unref (included);
return FALSE;
}
/* Copy included's limits back to parser. */
parser->limits = included->limits;
bus_config_parser_unref (included);
return TRUE;
}
}
static dbus_bool_t
servicehelper_path (BusConfigParser *parser,
const DBusString *filename,
DBusError *error)
{
const char *filename_str;
char *servicehelper;
filename_str = _dbus_string_get_const_data (filename);
/* copy to avoid overwriting with NULL on OOM */
servicehelper = _dbus_strdup (filename_str);
/* check for OOM */
if (servicehelper == NULL)
{
BUS_SET_OOM (error);
return FALSE;
}
/* save the latest servicehelper only if not OOM */
dbus_free (parser->servicehelper);
parser->servicehelper = servicehelper;
/* We don't check whether the helper exists; instead we
* would just fail to ever activate anything if it doesn't.
* This allows an admin to fix the problem if it doesn't exist.
* It also allows the parser test suite to successfully parse
* test cases without installing the helper. ;-)
*/
return TRUE;
}
static dbus_bool_t
include_dir (BusConfigParser *parser,
const DBusString *dirname,
DBusError *error)
{
DBusString filename;
dbus_bool_t retval;
DBusError tmp_error;
DBusDirIter *dir;
char *s;
if (!_dbus_string_init (&filename))
{
BUS_SET_OOM (error);
return FALSE;
}
retval = FALSE;
dir = _dbus_directory_open (dirname, error);
if (dir == NULL)
goto failed;
dbus_error_init (&tmp_error);
while (_dbus_directory_get_next_file (dir, &filename, &tmp_error))
{
DBusString full_path;
if (!_dbus_string_init (&full_path))
{
BUS_SET_OOM (error);
goto failed;
}
if (!_dbus_string_copy (dirname, 0, &full_path, 0))
{
BUS_SET_OOM (error);
_dbus_string_free (&full_path);
goto failed;
}
if (!_dbus_concat_dir_and_file (&full_path, &filename))
{
BUS_SET_OOM (error);
_dbus_string_free (&full_path);
goto failed;
}
if (_dbus_string_ends_with_c_str (&full_path, ".conf"))
{
if (!include_file (parser, &full_path, TRUE, error))
{
if (dbus_error_is_set (error))
{
/* We log to syslog unconditionally here, because this is
* the configuration parser, so we don't yet know whether
* this bus is going to want to write to syslog! (There's
* also some layer inversion going on, if we want to use
* the bus context.) */
_dbus_system_log (DBUS_SYSTEM_LOG_INFO,
"Encountered error '%s' while parsing '%s'\n",
error->message,
_dbus_string_get_const_data (&full_path));
dbus_error_free (error);
}
}
}
_dbus_string_free (&full_path);
}
if (dbus_error_is_set (&tmp_error))
{
dbus_move_error (&tmp_error, error);
goto failed;
}
if (!_dbus_string_copy_data (dirname, &s))
{
BUS_SET_OOM (error);
goto failed;
}
if (!_dbus_list_append (&parser->conf_dirs, s))
{
dbus_free (s);
BUS_SET_OOM (error);
goto failed;
}
retval = TRUE;
failed:
_dbus_string_free (&filename);
if (dir)
_dbus_directory_close (dir);
return retval;
}
dbus_bool_t
bus_config_parser_content (BusConfigParser *parser,
const DBusString *content,
DBusError *error)
{
Element *e;
_DBUS_ASSERT_ERROR_IS_CLEAR (error);
#if 0
{
const char *c_str;
_dbus_string_get_const_data (content, &c_str);
printf ("CONTENT %d bytes: %s\n", _dbus_string_get_length (content), c_str);
}
#endif
e = peek_element (parser);
if (e == NULL)
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"Text content outside of any XML element in configuration file");
return FALSE;
}
else if (e->had_content)
{
_dbus_assert_not_reached ("Element had multiple content blocks");
return FALSE;
}
switch (top_element_type (parser))
{
case ELEMENT_NONE:
_dbus_assert_not_reached ("element at top of stack has no type");
return FALSE;
case ELEMENT_BUSCONFIG:
case ELEMENT_POLICY:
case ELEMENT_ALLOW:
case ELEMENT_DENY:
case ELEMENT_FORK:
case ELEMENT_SYSLOG:
case ELEMENT_KEEP_UMASK:
case ELEMENT_STANDARD_SESSION_SERVICEDIRS:
case ELEMENT_STANDARD_SYSTEM_SERVICEDIRS:
case ELEMENT_ALLOW_ANONYMOUS:
case ELEMENT_SELINUX:
case ELEMENT_ASSOCIATE:
if (all_whitespace (content))
return TRUE;
else
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"No text content expected inside XML element %s in configuration file",
bus_config_parser_element_type_to_name (top_element_type (parser)));
return FALSE;
}
case ELEMENT_PIDFILE:
{
char *s;
e->had_content = TRUE;
if (!_dbus_string_copy_data (content, &s))
goto nomem;
dbus_free (parser->pidfile);
parser->pidfile = s;
}
break;
case ELEMENT_INCLUDE:
{
DBusString full_path, selinux_policy_root;
e->had_content = TRUE;
if (e->d.include.if_selinux_enabled
&& !bus_selinux_enabled ())
break;
if (!_dbus_string_init (&full_path))
goto nomem;
if (e->d.include.selinux_root_relative)
{
if (!bus_selinux_get_policy_root ())
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"Could not determine SELinux policy root for relative inclusion");
_dbus_string_free (&full_path);
return FALSE;
}
_dbus_string_init_const (&selinux_policy_root,
bus_selinux_get_policy_root ());
if (!make_full_path (&selinux_policy_root, content, &full_path))
{
_dbus_string_free (&full_path);
goto nomem;
}
}
else if (!make_full_path (&parser->basedir, content, &full_path))
{
_dbus_string_free (&full_path);
goto nomem;
}
if (!include_file (parser, &full_path,
e->d.include.ignore_missing, error))
{
_dbus_string_free (&full_path);
return FALSE;
}
_dbus_string_free (&full_path);
}
break;
case ELEMENT_SERVICEHELPER:
{
DBusString full_path;
e->had_content = TRUE;
if (!_dbus_string_init (&full_path))
goto nomem;
if (!make_full_path (&parser->basedir, content, &full_path))
{
_dbus_string_free (&full_path);
goto nomem;
}
if (!servicehelper_path (parser, &full_path, error))
{
_dbus_string_free (&full_path);
return FALSE;
}
_dbus_string_free (&full_path);
}
break;
case ELEMENT_INCLUDEDIR:
{
DBusString full_path;
e->had_content = TRUE;
if (!_dbus_string_init (&full_path))
goto nomem;
if (!make_full_path (&parser->basedir, content, &full_path))
{
_dbus_string_free (&full_path);
goto nomem;
}
if (!include_dir (parser, &full_path, error))
{
_dbus_string_free (&full_path);
return FALSE;
}
_dbus_string_free (&full_path);
}
break;
case ELEMENT_USER:
{
char *s;
e->had_content = TRUE;
if (!_dbus_string_copy_data (content, &s))
goto nomem;
dbus_free (parser->user);
parser->user = s;
}
break;
case ELEMENT_CONFIGTYPE:
{
char *s;
e->had_content = TRUE;
if (!_dbus_string_copy_data (content, &s))
goto nomem;
dbus_free (parser->bus_type);
parser->bus_type = s;
}
break;
case ELEMENT_LISTEN:
{
char *s;
e->had_content = TRUE;
if (!_dbus_string_copy_data (content, &s))
goto nomem;
if (!_dbus_list_append (&parser->listen_on,
s))
{
dbus_free (s);
goto nomem;
}
}
break;
case ELEMENT_AUTH:
{
char *s;
e->had_content = TRUE;
if (!_dbus_string_copy_data (content, &s))
goto nomem;
if (!_dbus_list_append (&parser->mechanisms,
s))
{
dbus_free (s);
goto nomem;
}
}
break;
case ELEMENT_SERVICEDIR:
{
char *s;
DBusString full_path;
e->had_content = TRUE;
if (!_dbus_string_init (&full_path))
goto nomem;
if (!make_full_path (&parser->basedir, content, &full_path))
{
_dbus_string_free (&full_path);
goto nomem;
}
if (!_dbus_string_copy_data (&full_path, &s))
{
_dbus_string_free (&full_path);
goto nomem;
}
/* _only_ extra session directories can be specified */
if (!service_dirs_append_unique_or_free (&parser->service_dirs, s))
{
_dbus_string_free (&full_path);
dbus_free (s);
goto nomem;
}
_dbus_string_free (&full_path);
}
break;
case ELEMENT_LIMIT:
{
long val;
e->had_content = TRUE;
val = 0;
if (!_dbus_string_parse_int (content, 0, &val, NULL))
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"<limit name=\"%s\"> element has invalid value (could not parse as integer)",
e->d.limit.name);
return FALSE;
}
e->d.limit.value = val;
_dbus_verbose ("Loaded value %ld for limit %s\n",
e->d.limit.value,
e->d.limit.name);
}
break;
}
_DBUS_ASSERT_ERROR_IS_CLEAR (error);
return TRUE;
nomem:
BUS_SET_OOM (error);
return FALSE;
}
dbus_bool_t
bus_config_parser_finished (BusConfigParser *parser,
DBusError *error)
{
_DBUS_ASSERT_ERROR_IS_CLEAR (error);
if (parser->stack != NULL)
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"Element <%s> was not closed in configuration file",
bus_config_parser_element_type_to_name (top_element_type (parser)));
return FALSE;
}
if (parser->is_toplevel && parser->listen_on == NULL)
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"Configuration file needs one or more <listen> elements giving addresses");
return FALSE;
}
return TRUE;
}
const char*
bus_config_parser_get_user (BusConfigParser *parser)
{
return parser->user;
}
const char*
bus_config_parser_get_type (BusConfigParser *parser)
{
return parser->bus_type;
}
DBusList**
bus_config_parser_get_addresses (BusConfigParser *parser)
{
return &parser->listen_on;
}
DBusList**
bus_config_parser_get_mechanisms (BusConfigParser *parser)
{
return &parser->mechanisms;
}
DBusList**
bus_config_parser_get_service_dirs (BusConfigParser *parser)
{
return &parser->service_dirs;
}
DBusList**
bus_config_parser_get_conf_dirs (BusConfigParser *parser)
{
return &parser->conf_dirs;
}
dbus_bool_t
bus_config_parser_get_fork (BusConfigParser *parser)
{
return parser->fork;
}
dbus_bool_t
bus_config_parser_get_syslog (BusConfigParser *parser)
{
return parser->syslog;
}
dbus_bool_t
bus_config_parser_get_keep_umask (BusConfigParser *parser)
{
return parser->keep_umask;
}
dbus_bool_t
bus_config_parser_get_allow_anonymous (BusConfigParser *parser)
{
return parser->allow_anonymous;
}
const char *
bus_config_parser_get_pidfile (BusConfigParser *parser)
{
return parser->pidfile;
}
const char *
bus_config_parser_get_servicehelper (BusConfigParser *parser)
{
return parser->servicehelper;
}
BusPolicy*
bus_config_parser_steal_policy (BusConfigParser *parser)
{
BusPolicy *policy;
_dbus_assert (parser->policy != NULL); /* can only steal the policy 1 time */
policy = parser->policy;
parser->policy = NULL;
return policy;
}
/* Overwrite any limits that were set in the configuration file */
void
bus_config_parser_get_limits (BusConfigParser *parser,
BusLimits *limits)
{
*limits = parser->limits;
}
DBusHashTable*
bus_config_parser_steal_service_context_table (BusConfigParser *parser)
{
DBusHashTable *table;
_dbus_assert (parser->service_context_table != NULL); /* can only steal once */
table = parser->service_context_table;
parser->service_context_table = NULL;
return table;
}
#ifdef DBUS_ENABLE_EMBEDDED_TESTS
#include <stdio.h>
typedef enum
{
VALID,
INVALID,
UNKNOWN
} Validity;
static dbus_bool_t
do_check_own_rules (BusPolicy *policy)
{
const struct {
char *name;
dbus_bool_t allowed;
} checks[] = {
{"org.freedesktop", FALSE},
{"org.freedesktop.ManySystem", FALSE},
{"org.freedesktop.ManySystems", TRUE},
{"org.freedesktop.ManySystems.foo", TRUE},
{"org.freedesktop.ManySystems.foo.bar", TRUE},
{"org.freedesktop.ManySystems2", FALSE},
{"org.freedesktop.ManySystems2.foo", FALSE},
{"org.freedesktop.ManySystems2.foo.bar", FALSE},
{NULL, FALSE}
};
int i = 0;
while (checks[i].name)
{
DBusString service_name;
dbus_bool_t ret;
if (!_dbus_string_init (&service_name))
_dbus_assert_not_reached ("couldn't init string");
if (!_dbus_string_append (&service_name, checks[i].name))
_dbus_assert_not_reached ("couldn't append string");
ret = bus_policy_check_can_own (policy, &service_name);
printf (" Check name %s: %s\n", checks[i].name,
ret ? "allowed" : "not allowed");
if (checks[i].allowed && !ret)
{
_dbus_warn ("Cannot own %s\n", checks[i].name);
return FALSE;
}
if (!checks[i].allowed && ret)
{
_dbus_warn ("Can own %s\n", checks[i].name);
return FALSE;
}
_dbus_string_free (&service_name);
i++;
}
return TRUE;
}
static dbus_bool_t
do_load (const DBusString *full_path,
Validity validity,
dbus_bool_t oom_possible,
dbus_bool_t check_own_rules)
{
BusConfigParser *parser;
DBusError error;
dbus_error_init (&error);
parser = bus_config_load (full_path, TRUE, NULL, &error);
if (parser == NULL)
{
_DBUS_ASSERT_ERROR_IS_SET (&error);
if (oom_possible &&
dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY))
{
_dbus_verbose ("Failed to load valid file due to OOM\n");
dbus_error_free (&error);
return TRUE;
}
else if (validity == VALID)
{
_dbus_warn ("Failed to load valid file but still had memory: %s\n",
error.message);
dbus_error_free (&error);
return FALSE;
}
else
{
dbus_error_free (&error);
return TRUE;
}
}
else
{
_DBUS_ASSERT_ERROR_IS_CLEAR (&error);
if (check_own_rules && do_check_own_rules (parser->policy) == FALSE)
{
return FALSE;
}
bus_config_parser_unref (parser);
if (validity == INVALID)
{
_dbus_warn ("Accepted invalid file\n");
return FALSE;
}
return TRUE;
}
}
typedef struct
{
const DBusString *full_path;
Validity validity;
dbus_bool_t check_own_rules;
} LoaderOomData;
static dbus_bool_t
check_loader_oom_func (void *data)
{
LoaderOomData *d = data;
return do_load (d->full_path, d->validity, TRUE, d->check_own_rules);
}
static dbus_bool_t
process_test_valid_subdir (const DBusString *test_base_dir,
const char *subdir,
Validity validity)
{
DBusString test_directory;
DBusString filename;
DBusDirIter *dir;
dbus_bool_t retval;
DBusError error;
retval = FALSE;
dir = NULL;
if (!_dbus_string_init (&test_directory))
_dbus_assert_not_reached ("didn't allocate test_directory\n");
_dbus_string_init_const (&filename, subdir);
if (!_dbus_string_copy (test_base_dir, 0,
&test_directory, 0))
_dbus_assert_not_reached ("couldn't copy test_base_dir to test_directory");
if (!_dbus_concat_dir_and_file (&test_directory, &filename))
_dbus_assert_not_reached ("couldn't allocate full path");
_dbus_string_free (&filename);
if (!_dbus_string_init (&filename))
_dbus_assert_not_reached ("didn't allocate filename string\n");
dbus_error_init (&error);
dir = _dbus_directory_open (&test_directory, &error);
if (dir == NULL)
{
_dbus_warn ("Could not open %s: %s\n",
_dbus_string_get_const_data (&test_directory),
error.message);
dbus_error_free (&error);
goto failed;
}
if (validity == VALID)
printf ("Testing valid files:\n");
else if (validity == INVALID)
printf ("Testing invalid files:\n");
else
printf ("Testing unknown files:\n");
next:
while (_dbus_directory_get_next_file (dir, &filename, &error))
{
DBusString full_path;
LoaderOomData d;
if (!_dbus_string_init (&full_path))
_dbus_assert_not_reached ("couldn't init string");
if (!_dbus_string_copy (&test_directory, 0, &full_path, 0))
_dbus_assert_not_reached ("couldn't copy dir to full_path");
if (!_dbus_concat_dir_and_file (&full_path, &filename))
_dbus_assert_not_reached ("couldn't concat file to dir");
if (!_dbus_string_ends_with_c_str (&full_path, ".conf"))
{
_dbus_verbose ("Skipping non-.conf file %s\n",
_dbus_string_get_const_data (&filename));
_dbus_string_free (&full_path);
goto next;
}
printf (" %s\n", _dbus_string_get_const_data (&filename));
_dbus_verbose (" expecting %s\n",
validity == VALID ? "valid" :
(validity == INVALID ? "invalid" :
(validity == UNKNOWN ? "unknown" : "???")));
d.full_path = &full_path;
d.validity = validity;
d.check_own_rules = _dbus_string_ends_with_c_str (&full_path,
"check-own-rules.conf");
/* FIXME hackaround for an expat problem, see
* https://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=124747
* http://freedesktop.org/pipermail/dbus/2004-May/001153.html
*/
/* if (!_dbus_test_oom_handling ("config-loader", check_loader_oom_func, &d)) */
if (!check_loader_oom_func (&d))
_dbus_assert_not_reached ("test failed");
_dbus_string_free (&full_path);
}
if (dbus_error_is_set (&error))
{
_dbus_warn ("Could not get next file in %s: %s\n",
_dbus_string_get_const_data (&test_directory),
error.message);
dbus_error_free (&error);
goto failed;
}
retval = TRUE;
failed:
if (dir)
_dbus_directory_close (dir);
_dbus_string_free (&test_directory);
_dbus_string_free (&filename);
return retval;
}
static dbus_bool_t
bools_equal (dbus_bool_t a,
dbus_bool_t b)
{
return a ? b : !b;
}
static dbus_bool_t
strings_equal_or_both_null (const char *a,
const char *b)
{
if (a == NULL || b == NULL)
return a == b;
else
return !strcmp (a, b);
}
static dbus_bool_t
elements_equal (const Element *a,
const Element *b)
{
if (a->type != b->type)
return FALSE;
if (!bools_equal (a->had_content, b->had_content))
return FALSE;
switch (a->type)
{
case ELEMENT_INCLUDE:
if (!bools_equal (a->d.include.ignore_missing,
b->d.include.ignore_missing))
return FALSE;
break;
case ELEMENT_POLICY:
if (a->d.policy.type != b->d.policy.type)
return FALSE;
if (a->d.policy.gid_uid_or_at_console != b->d.policy.gid_uid_or_at_console)
return FALSE;
break;
case ELEMENT_LIMIT:
if (strcmp (a->d.limit.name, b->d.limit.name))
return FALSE;
if (a->d.limit.value != b->d.limit.value)
return FALSE;
break;
default:
/* do nothing */
break;
}
return TRUE;
}
static dbus_bool_t
lists_of_elements_equal (DBusList *a,
DBusList *b)
{
DBusList *ia;
DBusList *ib;
ia = a;
ib = b;
while (ia != NULL && ib != NULL)
{
if (elements_equal (ia->data, ib->data))
return FALSE;
ia = _dbus_list_get_next_link (&a, ia);
ib = _dbus_list_get_next_link (&b, ib);
}
return ia == NULL && ib == NULL;
}
static dbus_bool_t
lists_of_c_strings_equal (DBusList *a,
DBusList *b)
{
DBusList *ia;
DBusList *ib;
ia = a;
ib = b;
while (ia != NULL && ib != NULL)
{
if (strcmp (ia->data, ib->data))
return FALSE;
ia = _dbus_list_get_next_link (&a, ia);
ib = _dbus_list_get_next_link (&b, ib);
}
return ia == NULL && ib == NULL;
}
static dbus_bool_t
limits_equal (const BusLimits *a,
const BusLimits *b)
{
return
(a->max_incoming_bytes == b->max_incoming_bytes
|| a->max_incoming_unix_fds == b->max_incoming_unix_fds
|| a->max_outgoing_bytes == b->max_outgoing_bytes
|| a->max_outgoing_unix_fds == b->max_outgoing_unix_fds
|| a->max_message_size == b->max_message_size
|| a->max_message_unix_fds == b->max_message_unix_fds
|| a->activation_timeout == b->activation_timeout
|| a->auth_timeout == b->auth_timeout
|| a->pending_fd_timeout == b->pending_fd_timeout
|| a->max_completed_connections == b->max_completed_connections
|| a->max_incomplete_connections == b->max_incomplete_connections
|| a->max_connections_per_user == b->max_connections_per_user
|| a->max_pending_activations == b->max_pending_activations
|| a->max_services_per_connection == b->max_services_per_connection
|| a->max_match_rules_per_connection == b->max_match_rules_per_connection
|| a->max_replies_per_connection == b->max_replies_per_connection
|| a->reply_timeout == b->reply_timeout);
}
static dbus_bool_t
config_parsers_equal (const BusConfigParser *a,
const BusConfigParser *b)
{
if (!_dbus_string_equal (&a->basedir, &b->basedir))
return FALSE;
if (!lists_of_elements_equal (a->stack, b->stack))
return FALSE;
if (!strings_equal_or_both_null (a->user, b->user))
return FALSE;
if (!lists_of_c_strings_equal (a->listen_on, b->listen_on))
return FALSE;
if (!lists_of_c_strings_equal (a->mechanisms, b->mechanisms))
return FALSE;
if (!lists_of_c_strings_equal (a->service_dirs, b->service_dirs))
return FALSE;
/* FIXME: compare policy */
/* FIXME: compare service selinux ID table */
if (! limits_equal (&a->limits, &b->limits))
return FALSE;
if (!strings_equal_or_both_null (a->pidfile, b->pidfile))
return FALSE;
if (! bools_equal (a->fork, b->fork))
return FALSE;
if (! bools_equal (a->keep_umask, b->keep_umask))
return FALSE;
if (! bools_equal (a->is_toplevel, b->is_toplevel))
return FALSE;
return TRUE;
}
static dbus_bool_t
all_are_equiv (const DBusString *target_directory)
{
DBusString filename;
DBusDirIter *dir;
BusConfigParser *first_parser;
BusConfigParser *parser;
DBusError error;
dbus_bool_t equal;
dbus_bool_t retval;
dir = NULL;
first_parser = NULL;
parser = NULL;
retval = FALSE;
if (!_dbus_string_init (&filename))
_dbus_assert_not_reached ("didn't allocate filename string");
dbus_error_init (&error);
dir = _dbus_directory_open (target_directory, &error);
if (dir == NULL)
{
_dbus_warn ("Could not open %s: %s\n",
_dbus_string_get_const_data (target_directory),
error.message);
dbus_error_free (&error);
goto finished;
}
printf ("Comparing equivalent files:\n");
next:
while (_dbus_directory_get_next_file (dir, &filename, &error))
{
DBusString full_path;
if (!_dbus_string_init (&full_path))
_dbus_assert_not_reached ("couldn't init string");
if (!_dbus_string_copy (target_directory, 0, &full_path, 0))
_dbus_assert_not_reached ("couldn't copy dir to full_path");
if (!_dbus_concat_dir_and_file (&full_path, &filename))
_dbus_assert_not_reached ("couldn't concat file to dir");
if (!_dbus_string_ends_with_c_str (&full_path, ".conf"))
{
_dbus_verbose ("Skipping non-.conf file %s\n",
_dbus_string_get_const_data (&filename));
_dbus_string_free (&full_path);
goto next;
}
printf (" %s\n", _dbus_string_get_const_data (&filename));
parser = bus_config_load (&full_path, TRUE, NULL, &error);
if (parser == NULL)
{
_dbus_warn ("Could not load file %s: %s\n",
_dbus_string_get_const_data (&full_path),
error.message);
_dbus_string_free (&full_path);
dbus_error_free (&error);
goto finished;
}
else if (first_parser == NULL)
{
_dbus_string_free (&full_path);
first_parser = parser;
}
else
{
_dbus_string_free (&full_path);
equal = config_parsers_equal (first_parser, parser);
bus_config_parser_unref (parser);
if (! equal)
goto finished;
}
}
retval = TRUE;
finished:
_dbus_string_free (&filename);
if (first_parser)
bus_config_parser_unref (first_parser);
if (dir)
_dbus_directory_close (dir);
return retval;
}
static dbus_bool_t
process_test_equiv_subdir (const DBusString *test_base_dir,
const char *subdir)
{
DBusString test_directory;
DBusString filename;
DBusDirIter *dir;
DBusError error;
dbus_bool_t equal;
dbus_bool_t retval;
dir = NULL;
retval = FALSE;
if (!_dbus_string_init (&test_directory))
_dbus_assert_not_reached ("didn't allocate test_directory");
_dbus_string_init_const (&filename, subdir);
if (!_dbus_string_copy (test_base_dir, 0,
&test_directory, 0))
_dbus_assert_not_reached ("couldn't copy test_base_dir to test_directory");
if (!_dbus_concat_dir_and_file (&test_directory, &filename))
_dbus_assert_not_reached ("couldn't allocate full path");
_dbus_string_free (&filename);
if (!_dbus_string_init (&filename))
_dbus_assert_not_reached ("didn't allocate filename string");
dbus_error_init (&error);
dir = _dbus_directory_open (&test_directory, &error);
if (dir == NULL)
{
_dbus_warn ("Could not open %s: %s\n",
_dbus_string_get_const_data (&test_directory),
error.message);
dbus_error_free (&error);
goto finished;
}
while (_dbus_directory_get_next_file (dir, &filename, &error))
{
DBusString full_path;
/* Skip CVS's magic directories! */
if (_dbus_string_equal_c_str (&filename, "CVS"))
continue;
if (!_dbus_string_init (&full_path))
_dbus_assert_not_reached ("couldn't init string");
if (!_dbus_string_copy (&test_directory, 0, &full_path, 0))
_dbus_assert_not_reached ("couldn't copy dir to full_path");
if (!_dbus_concat_dir_and_file (&full_path, &filename))
_dbus_assert_not_reached ("couldn't concat file to dir");
equal = all_are_equiv (&full_path);
_dbus_string_free (&full_path);
if (!equal)
goto finished;
}
retval = TRUE;
finished:
_dbus_string_free (&test_directory);
_dbus_string_free (&filename);
if (dir)
_dbus_directory_close (dir);
return retval;
}
static const char *test_session_service_dir_matches[] =
{
#ifdef DBUS_UNIX
"/testhome/foo/.testlocal/testshare/dbus-1/services",
"/testusr/testlocal/testshare/dbus-1/services",
"/testusr/testshare/dbus-1/services",
DBUS_DATADIR"/dbus-1/services",
#endif
/* will be filled in test_default_session_servicedirs() */
#ifdef DBUS_WIN
NULL,
NULL,
#endif
NULL
};
static dbus_bool_t
test_default_session_servicedirs (void)
{
DBusList *dirs;
DBusList *link;
DBusString progs;
int i;
#ifdef DBUS_WIN
const char *common_progs;
char buffer[1024];
if (_dbus_get_install_root(buffer, sizeof(buffer)))
{
strcat(buffer,DBUS_DATADIR);
strcat(buffer,"/dbus-1/services");
test_session_service_dir_matches[0] = buffer;
}
#endif
/* On Unix we don't actually use this variable, but it's easier to handle the
* deallocation if we always allocate it, whether needed or not */
if (!_dbus_string_init (&progs))
_dbus_assert_not_reached ("OOM allocating progs");
#ifndef DBUS_UNIX
common_progs = _dbus_getenv ("CommonProgramFiles");
if (common_progs)
{
if (!_dbus_string_append (&progs, common_progs))
{
_dbus_string_free (&progs);
return FALSE;
}
if (!_dbus_string_append (&progs, "/dbus-1/services"))
{
_dbus_string_free (&progs);
return FALSE;
}
test_session_service_dir_matches[1] = _dbus_string_get_const_data(&progs);
}
#endif
dirs = NULL;
printf ("Testing retrieving the default session service directories\n");
if (!_dbus_get_standard_session_servicedirs (&dirs))
_dbus_assert_not_reached ("couldn't get stardard dirs");
/* make sure our defaults end with share/dbus-1/service */
while ((link = _dbus_list_pop_first_link (&dirs)))
{
DBusString path;
printf (" default service dir: %s\n", (char *)link->data);
_dbus_string_init_const (&path, (char *)link->data);
if (!_dbus_string_ends_with_c_str (&path, "dbus-1/services"))
{
printf ("error with default session service directories\n");
dbus_free (link->data);
_dbus_list_free_link (link);
_dbus_string_free (&progs);
return FALSE;
}
dbus_free (link->data);
_dbus_list_free_link (link);
}
#ifdef DBUS_UNIX
if (!dbus_setenv ("XDG_DATA_HOME", "/testhome/foo/.testlocal/testshare"))
_dbus_assert_not_reached ("couldn't setenv XDG_DATA_HOME");
if (!dbus_setenv ("XDG_DATA_DIRS", ":/testusr/testlocal/testshare: :/testusr/testshare:"))
_dbus_assert_not_reached ("couldn't setenv XDG_DATA_DIRS");
#endif
if (!_dbus_get_standard_session_servicedirs (&dirs))
_dbus_assert_not_reached ("couldn't get stardard dirs");
/* make sure we read and parse the env variable correctly */
i = 0;
while ((link = _dbus_list_pop_first_link (&dirs)))
{
printf (" test service dir: %s\n", (char *)link->data);
if (test_session_service_dir_matches[i] == NULL)
{
printf ("more directories parsed than in match set\n");
dbus_free (link->data);
_dbus_list_free_link (link);
_dbus_string_free (&progs);
return FALSE;
}
if (strcmp (test_session_service_dir_matches[i],
(char *)link->data) != 0)
{
printf ("%s directory does not match %s in the match set\n",
(char *)link->data,
test_session_service_dir_matches[i]);
dbus_free (link->data);
_dbus_list_free_link (link);
_dbus_string_free (&progs);
return FALSE;
}
++i;
dbus_free (link->data);
_dbus_list_free_link (link);
}
if (test_session_service_dir_matches[i] != NULL)
{
printf ("extra data %s in the match set was not matched\n",
test_session_service_dir_matches[i]);
_dbus_string_free (&progs);
return FALSE;
}
_dbus_string_free (&progs);
return TRUE;
}
static const char *test_system_service_dir_matches[] =
{
#ifdef DBUS_UNIX
"/usr/local/share/dbus-1/system-services",
"/usr/share/dbus-1/system-services",
#endif
DBUS_DATADIR"/dbus-1/system-services",
#ifdef DBUS_UNIX
"/lib/dbus-1/system-services",
#endif
#ifdef DBUS_WIN
NULL,
#endif
NULL
};
static dbus_bool_t
test_default_system_servicedirs (void)
{
DBusList *dirs;
DBusList *link;
DBusString progs;
#ifndef DBUS_UNIX
const char *common_progs;
#endif
int i;
/* On Unix we don't actually use this variable, but it's easier to handle the
* deallocation if we always allocate it, whether needed or not */
if (!_dbus_string_init (&progs))
_dbus_assert_not_reached ("OOM allocating progs");
#ifndef DBUS_UNIX
common_progs = _dbus_getenv ("CommonProgramFiles");
if (common_progs)
{
if (!_dbus_string_append (&progs, common_progs))
{
_dbus_string_free (&progs);
return FALSE;
}
if (!_dbus_string_append (&progs, "/dbus-1/system-services"))
{
_dbus_string_free (&progs);
return FALSE;
}
test_system_service_dir_matches[1] = _dbus_string_get_const_data(&progs);
}
#endif
dirs = NULL;
printf ("Testing retrieving the default system service directories\n");
if (!_dbus_get_standard_system_servicedirs (&dirs))
_dbus_assert_not_reached ("couldn't get stardard dirs");
/* make sure our defaults end with share/dbus-1/system-service */
while ((link = _dbus_list_pop_first_link (&dirs)))
{
DBusString path;
printf (" default service dir: %s\n", (char *)link->data);
_dbus_string_init_const (&path, (char *)link->data);
if (!_dbus_string_ends_with_c_str (&path, "dbus-1/system-services"))
{
printf ("error with default system service directories\n");
dbus_free (link->data);
_dbus_list_free_link (link);
_dbus_string_free (&progs);
return FALSE;
}
dbus_free (link->data);
_dbus_list_free_link (link);
}
#ifdef DBUS_UNIX
if (!dbus_setenv ("XDG_DATA_HOME", "/testhome/foo/.testlocal/testshare"))
_dbus_assert_not_reached ("couldn't setenv XDG_DATA_HOME");
if (!dbus_setenv ("XDG_DATA_DIRS", ":/testusr/testlocal/testshare: :/testusr/testshare:"))
_dbus_assert_not_reached ("couldn't setenv XDG_DATA_DIRS");
#endif
if (!_dbus_get_standard_system_servicedirs (&dirs))
_dbus_assert_not_reached ("couldn't get stardard dirs");
/* make sure we read and parse the env variable correctly */
i = 0;
while ((link = _dbus_list_pop_first_link (&dirs)))
{
printf (" test service dir: %s\n", (char *)link->data);
if (test_system_service_dir_matches[i] == NULL)
{
printf ("more directories parsed than in match set\n");
dbus_free (link->data);
_dbus_list_free_link (link);
_dbus_string_free (&progs);
return FALSE;
}
if (strcmp (test_system_service_dir_matches[i],
(char *)link->data) != 0)
{
printf ("%s directory does not match %s in the match set\n",
(char *)link->data,
test_system_service_dir_matches[i]);
dbus_free (link->data);
_dbus_list_free_link (link);
_dbus_string_free (&progs);
return FALSE;
}
++i;
dbus_free (link->data);
_dbus_list_free_link (link);
}
if (test_system_service_dir_matches[i] != NULL)
{
printf ("extra data %s in the match set was not matched\n",
test_system_service_dir_matches[i]);
_dbus_string_free (&progs);
return FALSE;
}
_dbus_string_free (&progs);
return TRUE;
}
dbus_bool_t
bus_config_parser_test (const DBusString *test_data_dir)
{
if (test_data_dir == NULL ||
_dbus_string_get_length (test_data_dir) == 0)
{
printf ("No test data\n");
return TRUE;
}
if (!test_default_session_servicedirs())
return FALSE;
#ifdef DBUS_WIN
printf("default system service dir skipped\n");
#else
if (!test_default_system_servicedirs())
return FALSE;
#endif
if (!process_test_valid_subdir (test_data_dir, "valid-config-files", VALID))
return FALSE;
if (!process_test_valid_subdir (test_data_dir, "invalid-config-files", INVALID))
return FALSE;
if (!process_test_equiv_subdir (test_data_dir, "equiv-config-files"))
return FALSE;
return TRUE;
}
#endif /* DBUS_ENABLE_EMBEDDED_TESTS */