dbus/bus/config-parser.c
Simon McVittie ea0275aaea bus_config_parser_check_doctype: Remove, unused
We have never checked the <!DOCTYPE> of busconfig XML since the libxml
parser was removed in 2013, and the libxml parser was broken before
that anyway. The recommended Expat parser (our only parser since 2013)
does not appear to have ever validated this, so now does not seem like
the time to start. Just ignore the <!DOCTYPE> if there is one.

(We never validated this particularly strictly anyway;
<!DOCTYPE busconfig SYSTEM "http://example.com/bees"> would have been
treated as perfectly valid.)

Signed-off-by: Simon McVittie <smcv@collabora.com>
Reviewed-by: Philip Withnall <withnall@endlessm.com>
Bug: https://bugs.freedesktop.org/show_bug.cgi?id=107739
2018-08-30 17:39:38 +01:00

4030 lines
110 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 "apparmor.h"
#include <dbus/dbus-list.h>
#include <dbus/dbus-internals.h>
#include <dbus/dbus-misc.h>
#include <dbus/dbus-sysdeps.h>
#include <dbus/dbus-test-tap.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; /**< BusConfigServiceDirs 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;
}
/*
* Create a new BusConfigServiceDir. Takes ownership of path if #TRUE is returned.
*
* @returns #FALSE on OOM
*/
static BusConfigServiceDir *
bus_config_service_dir_new_take (char *path,
BusServiceDirFlags flags)
{
BusConfigServiceDir *self = dbus_new0 (BusConfigServiceDir, 1);
if (self == NULL)
return NULL;
self->path = path; /* take ownership */
self->flags = flags;
return self;
}
static void
bus_config_service_dir_free (BusConfigServiceDir *self)
{
dbus_free (self->path);
dbus_free (self);
}
static BusConfigServiceDir *
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))
{
BusConfigServiceDir *link_dir = link->data;
if (strcmp (dir, link_dir->path) == 0)
return link_dir;
}
return NULL;
}
static void
service_dirs_append_link_unique_or_free (DBusList **service_dirs,
DBusList *dir_link)
{
BusConfigServiceDir *dir = dir_link->data;
BusConfigServiceDir *already = service_dirs_find_dir (service_dirs,
dir->path);
if (already == NULL)
{
_dbus_list_append_link (service_dirs, dir_link);
}
else
{
/* BusServiceDirFlags are chosen such that the compatible thing to do
* is to "and" the flags. For example, if a directory is explicitly
* added as a <servicedir> (which is watched with inotify) and is also
* the transient service directory (which should not be watched),
* the compatible thing to do is to watch it. */
already->flags &= dir->flags;
bus_config_service_dir_free (dir_link->data);
_dbus_list_free_link (dir_link);
}
}
/*
* Consume links from dirs (a list of paths), converting them into links in
* service_dirs (a list of unique BusServiceDir).
*
* On success, return TRUE. dirs will be empty, and every original entry in
* dirs will have a corresponding service_dirs entry.
*
* On OOM, return FALSE. Each original entry of dirs has either been
* appended to service_dirs or left in dirs.
*/
static dbus_bool_t
service_dirs_absorb_string_list (DBusList **service_dirs,
DBusList **dirs,
BusServiceDirFlags flags)
{
DBusList *link;
_dbus_assert (service_dirs != NULL);
_dbus_assert (dirs != NULL);
while ((link = _dbus_list_pop_first_link (dirs)))
{
char *path = link->data;
BusConfigServiceDir *dir = bus_config_service_dir_new_take (path, flags);
if (dir == NULL)
{
/* OOM - roll back (this does not need to allocate memory) */
_dbus_list_prepend_link (service_dirs, link);
return FALSE;
}
/* Ownership of path has been taken by dir */
link->data = dir;
service_dirs_append_link_unique_or_free (service_dirs, link);
}
_dbus_assert (*dirs == NULL);
return TRUE;
}
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!
* Please keep these hard-coded values in sync with the comments
* in bus/system.conf.in. */
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;
parser->limits.max_containers_per_user = 16;
/* 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. The same applies to containers.
*/
parser->limits.max_completed_connections = 2048;
parser->limits.max_containers = 512;
/* Similarly max_connections_per_user / max_connections_per_container
* is the number of app-containers per user that would have to work
* together to DoS all the other processes of that user */
parser->limits.max_connections_per_container = 8;
/* Someone trying to do a denial of service attack can make us store
* this much data per app-container */
parser->limits.max_container_metadata_bytes = 4096;
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_clear_full (&parser->listen_on, dbus_free);
_dbus_list_clear_full (&parser->service_dirs,
(DBusFreeFunction) bus_config_service_dir_free);
_dbus_list_clear_full (&parser->conf_dirs, dbus_free);
_dbus_list_clear_full (&parser->mechanisms, dbus_free);
_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);
}
}
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)
{
DBusError local_error = DBUS_ERROR_INIT;
DBusList *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_set_up_transient_session_servicedirs (&dirs, &local_error))
{
if (!service_dirs_absorb_string_list (&parser->service_dirs, &dirs,
BUS_SERVICE_DIR_FLAGS_NO_WATCH |
BUS_SERVICE_DIR_FLAGS_STRICT_NAMING))
{
BUS_SET_OOM (error);
_dbus_list_clear_full (&dirs, dbus_free);
return FALSE;
}
}
else
{
/* Failing to set these up isn't fatal */
_dbus_warn ("Unable to set up transient service directory: %s",
local_error.message);
dbus_error_free (&local_error);
}
_dbus_assert (dirs == NULL);
if (!_dbus_get_standard_session_servicedirs (&dirs))
{
BUS_SET_OOM (error);
return FALSE;
}
/* We have traditionally watched the standard session service
* directories with inotify, and allowed service files whose names do not
* match the bus name */
if (!service_dirs_absorb_string_list (&parser->service_dirs, &dirs,
BUS_SERVICE_DIR_FLAGS_NONE))
{
BUS_SET_OOM (error);
_dbus_list_clear_full (&dirs, dbus_free);
return FALSE;
}
_dbus_assert (dirs == NULL);
return TRUE;
}
else if (element_type == ELEMENT_STANDARD_SYSTEM_SERVICEDIRS)
{
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;
}
/* We have traditionally watched the standard system service
* directories with inotify, and allowed service files whose names do not
* match the bus name (the servicehelper won't successfully activate
* them, but we do still parse them) */
if (!service_dirs_absorb_string_list (&parser->service_dirs, &dirs,
BUS_SERVICE_DIR_FLAGS_NONE))
{
BUS_SET_OOM (error);
_dbus_list_clear_full (&dirs, dbus_free);
return FALSE;
}
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",
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",
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 if (element_type == ELEMENT_APPARMOR)
{
Element *e;
const char *mode;
if ((e = push_element (parser, ELEMENT_APPARMOR)) == NULL)
{
BUS_SET_OOM (error);
return FALSE;
}
if (!locate_attributes (parser, "apparmor",
attribute_names,
attribute_values,
error,
"mode", &mode,
NULL))
return FALSE;
return bus_apparmor_set_mode_from_config (mode, error);
}
else
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"Element <%s> not allowed inside <%s> in configuration file",
element_name, "busconfig");
return FALSE;
}
}
/*
* Parse an attribute named name, whose content is content, or NULL if
* missing. It is meant to be a (long) integer between min and max inclusive.
* If it is missing, use def as the default value (which does not
* necessarily need to be between min and max).
*/
static dbus_bool_t
parse_int_attribute (const char *name,
const char *content,
long min,
long max,
long def,
long *value,
DBusError *error)
{
DBusString parse_string;
*value = def;
if (content == NULL)
return TRUE;
_dbus_string_init_const (&parse_string, content);
if (!_dbus_string_parse_int (&parse_string, 0, value, NULL) ||
*value < min || *value > max)
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"Bad value \"%s\" for %s attribute, must be an "
"integer in range %ld to %ld inclusive",
content, name, min, max);
return FALSE;
}
return TRUE;
}
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;
/* Group: send_ attributes */
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 *send_requested_reply;
const char *send_broadcast;
/* TRUE if any send_ attribute is present */
dbus_bool_t any_send_attribute;
/* Group: receive_ attributes */
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 *receive_requested_reply;
/* TRUE if any receive_ attribute is present */
dbus_bool_t any_receive_attribute;
/* Group: message-matching modifiers that can go on send_ or receive_ */
const char *eavesdrop;
const char *max_fds_attr;
long max_fds = DBUS_MAXIMUM_MESSAGE_UNIX_FDS;
const char *min_fds_attr;
long min_fds = 0;
/* TRUE if any message-matching modifier is present */
dbus_bool_t any_message_attribute;
/* Non-message-related attributes */
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,
"send_broadcast", &send_broadcast,
"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,
"max_fds", &max_fds_attr,
"min_fds", &min_fds_attr,
"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;
any_send_attribute = (send_destination != NULL ||
send_broadcast != NULL ||
send_path != NULL ||
send_type != NULL ||
send_interface != NULL ||
send_member != NULL ||
send_error != NULL ||
send_requested_reply != NULL);
any_receive_attribute = (receive_sender != NULL ||
receive_path != NULL ||
receive_type != NULL ||
receive_interface != NULL ||
receive_member != NULL ||
receive_error != NULL ||
receive_requested_reply != NULL ||
/* <allow eavesdrop="true"/> means the same as
* <allow receive_sender="*" eavesdrop="true"/>,
* but <allow send_anything="anything"/> can also
* take the eavesdrop attribute and still counts
* as a send rule. */
(!any_send_attribute && eavesdrop != NULL));
any_message_attribute = (any_send_attribute ||
any_receive_attribute ||
eavesdrop != NULL ||
max_fds_attr != NULL ||
min_fds_attr != NULL);
if (!(any_send_attribute ||
any_receive_attribute ||
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, send_broadcast, eavesdrop
* base receive_ with receive_sender, receive_path, receive_type, receive_requested_reply, eavesdrop
*
* user, group, own, own_prefix must occur alone
*/
if (any_message_attribute +
((own != NULL) +
(own_prefix != NULL) +
(user != NULL) +
(group != NULL)) > 1)
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"Invalid combination of attributes on element <%s>: "
"own, own_prefix, user, group and the message-related "
"attributes cannot be combined",
element_name);
return FALSE;
}
if (any_send_attribute && any_receive_attribute)
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"Invalid combination of attributes on element <%s>: "
"send and receive attributes cannot be combined",
element_name);
return FALSE;
}
if ((send_member != NULL || send_interface != NULL) && send_error != NULL)
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"Invalid combination of attributes on element <%s>: "
"send_error cannot be combined with send_member or "
"send_interface",
element_name);
return FALSE;
}
if ((receive_member != NULL || receive_interface != NULL) &&
receive_error != NULL)
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"Invalid combination of attributes on element <%s>: "
"receive_error cannot be combined with receive_member "
"or receive_interface",
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 (any_send_attribute)
{
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_broadcast &&
!(strcmp (send_broadcast, "true") == 0 ||
strcmp (send_broadcast, "false") == 0))
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"Bad value \"%s\" for %s attribute, must be true or false",
send_broadcast, "send_broadcast");
return FALSE;
}
if (send_destination != NULL &&
send_broadcast != NULL &&
strcmp (send_broadcast, "true") == 0)
{
/* Broadcast messages have no destination, so this cannot
* possibly match */
dbus_set_error (error, DBUS_ERROR_FAILED,
"Rule with send_broadcast=\"true\" and "
"send_destination=\"%s\" cannot match anything",
send_destination);
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;
}
/* Matching only messages with DBUS_MAXIMUM_MESSAGE_UNIX_FDS or fewer
* fds is the same as matching all messages, so we always set a maximum,
* but perhaps an unrealistically high one. */
if (!parse_int_attribute ("max_fds", max_fds_attr,
0, DBUS_MAXIMUM_MESSAGE_UNIX_FDS,
DBUS_MAXIMUM_MESSAGE_UNIX_FDS, &max_fds,
error) ||
!parse_int_attribute ("min_fds", min_fds_attr,
0, DBUS_MAXIMUM_MESSAGE_UNIX_FDS, 0, &min_fds,
error))
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);
if (send_broadcast)
{
if (strcmp (send_broadcast, "true") == 0)
rule->d.send.broadcast = BUS_POLICY_TRISTATE_TRUE;
else
rule->d.send.broadcast = BUS_POLICY_TRISTATE_FALSE;
}
else
{
rule->d.send.broadcast = BUS_POLICY_TRISTATE_ANY;
}
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);
rule->d.send.max_fds = max_fds;
rule->d.send.min_fds = min_fds;
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 (any_receive_attribute)
{
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;
}
if (!parse_int_attribute ("max_fds", max_fds_attr,
0, DBUS_MAXIMUM_MESSAGE_UNIX_FDS,
DBUS_MAXIMUM_MESSAGE_UNIX_FDS, &max_fds,
error) ||
!parse_int_attribute ("min_fds", min_fds_attr,
0, DBUS_MAXIMUM_MESSAGE_UNIX_FDS, 0, &min_fds,
error))
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);
rule->d.receive.max_fds = max_fds;
rule->d.receive.min_fds = min_fds;
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>",
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>",
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:
default:
/* 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);
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 if (strcmp (name, "max_containers") == 0)
{
must_be_positive = TRUE;
must_be_int = TRUE;
parser->limits.max_containers = value;
}
else if (strcmp (name, "max_containers_per_user") == 0)
{
must_be_positive = TRUE;
must_be_int = TRUE;
parser->limits.max_containers_per_user = value;
}
else if (strcmp (name, "max_container_metadata_bytes") == 0)
{
must_be_positive = TRUE;
must_be_int = TRUE;
parser->limits.max_container_metadata_bytes = value;
}
else if (strcmp (name, "max_connections_per_container") == 0)
{
must_be_positive = TRUE;
must_be_int = TRUE;
parser->limits.max_connections_per_container = 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);
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:
default:
_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:
case ELEMENT_APPARMOR:
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))
{
if (!_dbus_string_copy (filename, 0, full_path, 0))
return FALSE;
}
else
{
if (!_dbus_string_copy (basedir, 0, full_path, 0))
return FALSE;
if (!_dbus_concat_dir_and_file (full_path, filename))
return FALSE;
}
if (!_dbus_replace_install_prefix (full_path))
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)
{
if (dbus_error_has_name (error, DBUS_ERROR_FILE_NOT_FOUND))
{
dbus_error_free (error);
goto success;
}
else
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 use both syslog and stderr here, because this is
* the configuration parser, so we don't yet know whether
* this bus is going to want to write to syslog! Err on
* the side of making sure the message gets to the sysadmin
* somehow. */
_dbus_init_system_log ("dbus-daemon",
DBUS_LOG_FLAGS_STDERR | DBUS_LOG_FLAGS_SYSTEM_LOG);
_dbus_log (DBUS_SYSTEM_LOG_INFO,
"Encountered error '%s' while parsing '%s'",
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;
}
success:
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);
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:
default:
_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:
case ELEMENT_APPARMOR:
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;
BusConfigServiceDir *dir;
DBusString full_path;
DBusList *link;
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;
}
/* <servicedir/> has traditionally implied that we watch the
* directory with inotify, and allow service files whose names do not
* match the bus name */
dir = bus_config_service_dir_new_take (s, BUS_SERVICE_DIR_FLAGS_NONE);
if (dir == NULL)
{
_dbus_string_free (&full_path);
dbus_free (s);
goto nomem;
}
link = _dbus_list_alloc_link (dir);
if (link == NULL)
{
_dbus_string_free (&full_path);
bus_config_service_dir_free (dir);
goto nomem;
}
/* cannot fail */
service_dirs_append_link_unique_or_free (&parser->service_dirs, link);
_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;
}
/*
* Return a list of the directories that should be watched with inotify,
* as strings. The list might be empty and is in arbitrary order.
*
* The list must be empty on entry. On success, the links are owned by the
* caller and must be freed, but the data in each link remains owned by
* the BusConfigParser and must not be freed: in GObject-Introspection
* notation, it is (transfer container).
*/
dbus_bool_t
bus_config_parser_get_watched_dirs (BusConfigParser *parser,
DBusList **watched_dirs)
{
DBusList *link;
_dbus_assert (*watched_dirs == NULL);
for (link = _dbus_list_get_first_link (&parser->conf_dirs);
link != NULL;
link = _dbus_list_get_next_link (&parser->conf_dirs, link))
{
if (!_dbus_list_append (watched_dirs, link->data))
goto oom;
}
for (link = _dbus_list_get_first_link (&parser->service_dirs);
link != NULL;
link = _dbus_list_get_next_link (&parser->service_dirs, link))
{
BusConfigServiceDir *dir = link->data;
if (dir->flags & BUS_SERVICE_DIR_FLAGS_NO_WATCH)
continue;
if (!_dbus_list_append (watched_dirs, dir->path))
goto oom;
}
return TRUE;
oom:
_dbus_list_clear (watched_dirs);
return FALSE;
}
#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 {
const 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_test_fatal ("couldn't init string");
if (!_dbus_string_append (&service_name, checks[i].name))
_dbus_test_fatal ("couldn't append string");
ret = bus_policy_check_can_own (policy, &service_name);
_dbus_test_diag (" Check name %s: %s", checks[i].name,
ret ? "allowed" : "not allowed");
if (checks[i].allowed && !ret)
{
_dbus_warn ("Cannot own %s", checks[i].name);
return FALSE;
}
if (!checks[i].allowed && ret)
{
_dbus_warn ("Can own %s", 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",
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");
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_test_fatal ("didn't allocate test_directory");
_dbus_string_init_const (&filename, subdir);
if (!_dbus_string_copy (test_base_dir, 0,
&test_directory, 0))
_dbus_test_fatal ("couldn't copy test_base_dir to test_directory");
if (!_dbus_concat_dir_and_file (&test_directory, &filename))
_dbus_test_fatal ("couldn't allocate full path");
_dbus_string_free (&filename);
if (!_dbus_string_init (&filename))
_dbus_test_fatal ("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",
_dbus_string_get_const_data (&test_directory),
error.message);
dbus_error_free (&error);
goto failed;
}
if (validity == VALID)
_dbus_test_diag ("Testing valid files:");
else if (validity == INVALID)
_dbus_test_diag ("Testing invalid files:");
else
_dbus_test_diag ("Testing unknown files:");
next:
while (_dbus_directory_get_next_file (dir, &filename, &error))
{
DBusString full_path;
LoaderOomData d;
if (!_dbus_string_init (&full_path))
_dbus_test_fatal ("couldn't init string");
if (!_dbus_string_copy (&test_directory, 0, &full_path, 0))
_dbus_test_fatal ("couldn't copy dir to full_path");
if (!_dbus_concat_dir_and_file (&full_path, &filename))
_dbus_test_fatal ("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;
}
_dbus_test_diag (" %s", _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_test_fatal ("test failed");
_dbus_string_free (&full_path);
}
if (dbus_error_is_set (&error))
{
_dbus_warn ("Could not get next file in %s: %s",
_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;
case ELEMENT_NONE:
case ELEMENT_BUSCONFIG:
case ELEMENT_USER:
case ELEMENT_LISTEN:
case ELEMENT_AUTH:
case ELEMENT_ALLOW:
case ELEMENT_DENY:
case ELEMENT_FORK:
case ELEMENT_PIDFILE:
case ELEMENT_SERVICEDIR:
case ELEMENT_SERVICEHELPER:
case ELEMENT_INCLUDEDIR:
case ELEMENT_CONFIGTYPE:
case ELEMENT_SELINUX:
case ELEMENT_ASSOCIATE:
case ELEMENT_STANDARD_SESSION_SERVICEDIRS:
case ELEMENT_STANDARD_SYSTEM_SERVICEDIRS:
case ELEMENT_KEEP_UMASK:
case ELEMENT_SYSLOG:
case ELEMENT_ALLOW_ANONYMOUS:
case ELEMENT_APPARMOR:
default:
/* do nothing: nothing in the Element struct for these types */
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
lists_of_service_dirs_equal (DBusList *a,
DBusList *b)
{
DBusList *ia;
DBusList *ib;
ia = a;
ib = b;
while (ia != NULL && ib != NULL)
{
BusConfigServiceDir *da = ia->data;
BusConfigServiceDir *db = ib->data;
if (strcmp (da->path, db->path))
return FALSE;
if (da->flags != db->flags)
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_service_dirs_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_test_fatal ("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",
_dbus_string_get_const_data (target_directory),
error.message);
dbus_error_free (&error);
goto finished;
}
_dbus_test_diag ("Comparing equivalent files:");
next:
while (_dbus_directory_get_next_file (dir, &filename, &error))
{
DBusString full_path;
if (!_dbus_string_init (&full_path))
_dbus_test_fatal ("couldn't init string");
if (!_dbus_string_copy (target_directory, 0, &full_path, 0))
_dbus_test_fatal ("couldn't copy dir to full_path");
if (!_dbus_concat_dir_and_file (&full_path, &filename))
_dbus_test_fatal ("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;
}
_dbus_test_diag (" %s", _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",
_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_test_fatal ("didn't allocate test_directory");
_dbus_string_init_const (&filename, subdir);
if (!_dbus_string_copy (test_base_dir, 0,
&test_directory, 0))
_dbus_test_fatal ("couldn't copy test_base_dir to test_directory");
if (!_dbus_concat_dir_and_file (&test_directory, &filename))
_dbus_test_fatal ("couldn't allocate full path");
_dbus_string_free (&filename);
if (!_dbus_string_init (&filename))
_dbus_test_fatal ("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",
_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_test_fatal ("couldn't init string");
if (!_dbus_string_copy (&test_directory, 0, &full_path, 0))
_dbus_test_fatal ("couldn't copy dir to full_path");
if (!_dbus_concat_dir_and_file (&full_path, &filename))
_dbus_test_fatal ("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[] =
{
/* will be filled in test_default_session_servicedirs() */
#ifdef DBUS_WIN
NULL, /* install root-based */
NULL, /* CommonProgramFiles-based */
#else
NULL, /* XDG_RUNTIME_DIR-based */
NULL, /* XDG_DATA_HOME-based */
NULL, /* XDG_DATA_DIRS-based */
NULL, /* XDG_DATA_DIRS-based */
DBUS_DATADIR "/dbus-1/services",
#endif
NULL
};
static dbus_bool_t
test_default_session_servicedirs (const DBusString *test_base_dir)
{
BusConfigParser *parser = NULL;
DBusError error = DBUS_ERROR_INIT;
DBusList **dirs;
DBusList *watched_dirs = NULL;
DBusList *link;
DBusString tmp;
DBusString full_path;
DBusString progs;
DBusString install_root_based;
DBusString runtime_dir_based;
DBusString data_home_based;
DBusString data_dirs_based;
DBusString data_dirs_based2;
int i;
dbus_bool_t ret = FALSE;
#ifdef DBUS_WIN
const char *common_progs;
#else
const char *dbus_test_builddir;
const char *xdg_data_home;
const char *xdg_runtime_dir;
#endif
/* On each platform we don't actually use all of these, but it's easier to
* handle the deallocation if we always allocate them, whether needed or
* not */
if (!_dbus_string_init (&full_path) ||
!_dbus_string_init (&progs) ||
!_dbus_string_init (&install_root_based) ||
!_dbus_string_init (&runtime_dir_based) ||
!_dbus_string_init (&data_home_based) ||
!_dbus_string_init (&data_dirs_based) ||
!_dbus_string_init (&data_dirs_based2))
_dbus_test_fatal ("OOM allocating strings");
if (!_dbus_string_copy (test_base_dir, 0,
&full_path, 0))
_dbus_test_fatal ("couldn't copy test_base_dir to full_path");
_dbus_string_init_const (&tmp, "valid-config-files");
if (!_dbus_concat_dir_and_file (&full_path, &tmp))
_dbus_test_fatal ("couldn't allocate full path");
_dbus_string_init_const (&tmp, "standard-session-dirs.conf");
if (!_dbus_concat_dir_and_file (&full_path, &tmp))
_dbus_test_fatal ("couldn't allocate full path");
#ifdef DBUS_WIN
if (!_dbus_string_append (&install_root_based, DBUS_DATADIR) ||
!_dbus_string_append (&install_root_based, "/dbus-1/services") ||
!_dbus_replace_install_prefix (&install_root_based))
goto out;
_dbus_assert (_dbus_path_is_absolute (&install_root_based));
test_session_service_dir_matches[0] = _dbus_string_get_const_data (
&install_root_based);
common_progs = _dbus_getenv ("CommonProgramFiles");
if (common_progs)
{
if (!_dbus_string_append (&progs, common_progs))
goto out;
if (!_dbus_string_append (&progs, "/dbus-1/services"))
goto out;
test_session_service_dir_matches[1] = _dbus_string_get_const_data(&progs);
}
#else
dbus_test_builddir = _dbus_getenv ("DBUS_TEST_BUILDDIR");
xdg_data_home = _dbus_getenv ("XDG_DATA_HOME");
xdg_runtime_dir = _dbus_getenv ("XDG_RUNTIME_DIR");
if (dbus_test_builddir == NULL || xdg_data_home == NULL ||
xdg_runtime_dir == NULL)
{
_dbus_test_diag ("Not testing default session service directories because a "
"build-time testing environment variable is not set: "
"see AM_TESTS_ENVIRONMENT in tests/Makefile.am");
ret = TRUE;
goto out;
}
if (!_dbus_string_append (&data_dirs_based, dbus_test_builddir) ||
!_dbus_string_append (&data_dirs_based, "/XDG_DATA_DIRS/dbus-1/services") ||
!_dbus_string_append (&data_dirs_based2, dbus_test_builddir) ||
!_dbus_string_append (&data_dirs_based2, "/XDG_DATA_DIRS2/dbus-1/services") ||
!_dbus_string_append (&runtime_dir_based, xdg_runtime_dir) ||
!_dbus_string_append (&data_home_based, xdg_data_home) ||
!_dbus_string_append (&data_home_based, "/dbus-1/services"))
_dbus_test_fatal ("out of memory");
if (!_dbus_ensure_directory (&runtime_dir_based, NULL))
_dbus_test_fatal ("Unable to create fake XDG_RUNTIME_DIR");
if (!_dbus_string_append (&runtime_dir_based, "/dbus-1/services"))
_dbus_test_fatal ("out of memory");
/* Sanity check: the Makefile sets this up. We assume that if this is
* right, the XDG_DATA_DIRS will be too. */
if (!_dbus_string_starts_with_c_str (&data_home_based, dbus_test_builddir))
_dbus_test_fatal ("$XDG_DATA_HOME should start with $DBUS_TEST_BUILDDIR");
if (!_dbus_string_starts_with_c_str (&runtime_dir_based, dbus_test_builddir))
_dbus_test_fatal ("$XDG_RUNTIME_DIR should start with $DBUS_TEST_BUILDDIR");
test_session_service_dir_matches[0] = _dbus_string_get_const_data (
&runtime_dir_based);
test_session_service_dir_matches[1] = _dbus_string_get_const_data (
&data_home_based);
test_session_service_dir_matches[2] = _dbus_string_get_const_data (
&data_dirs_based);
test_session_service_dir_matches[3] = _dbus_string_get_const_data (
&data_dirs_based2);
#endif
parser = bus_config_load (&full_path, TRUE, NULL, &error);
if (parser == NULL)
_dbus_test_fatal ("%s", error.message);
dirs = bus_config_parser_get_service_dirs (parser);
for (link = _dbus_list_get_first_link (dirs), i = 0;
link != NULL;
link = _dbus_list_get_next_link (dirs, link), i++)
{
BusConfigServiceDir *dir = link->data;
BusServiceDirFlags expected = BUS_SERVICE_DIR_FLAGS_NONE;
_dbus_test_diag (" test service dir: '%s'", dir->path);
_dbus_test_diag (" current standard service dir: '%s'", test_session_service_dir_matches[i]);
if (test_session_service_dir_matches[i] == NULL)
{
_dbus_test_diag ("more directories parsed than in match set");
goto out;
}
if (strcmp (test_session_service_dir_matches[i], dir->path) != 0)
{
_dbus_test_diag ("'%s' directory does not match '%s' in the match set",
dir->path, test_session_service_dir_matches[i]);
goto out;
}
#ifndef DBUS_WIN
/* On Unix we expect the first directory in the search path to be
* in the XDG_RUNTIME_DIR, and we expect it to have special flags */
if (i == 0)
expected = (BUS_SERVICE_DIR_FLAGS_NO_WATCH |
BUS_SERVICE_DIR_FLAGS_STRICT_NAMING);
#endif
if (dir->flags != expected)
{
_dbus_test_diag ("'%s' directory has flags 0x%x, should be 0x%x",
dir->path, dir->flags, expected);
goto out;
}
}
if (test_session_service_dir_matches[i] != NULL)
{
_dbus_test_diag ("extra data %s in the match set was not matched",
test_session_service_dir_matches[i]);
goto out;
}
if (!bus_config_parser_get_watched_dirs (parser, &watched_dirs))
_dbus_test_fatal ("out of memory");
#ifdef DBUS_WIN
/* We expect all directories to be watched (not that it matters on Windows,
* because we don't know how) */
i = 0;
#else
/* We expect all directories except the first to be watched, because
* the first one is transient */
i = 1;
#endif
for (link = _dbus_list_get_first_link (&watched_dirs);
link != NULL;
link = _dbus_list_get_next_link (&watched_dirs, link), i++)
{
_dbus_test_diag (" watched service dir: '%s'", (const char *) link->data);
_dbus_test_diag (" current standard service dir: '%s'",
test_session_service_dir_matches[i]);
if (test_session_service_dir_matches[i] == NULL)
{
_dbus_test_diag ("more directories parsed than in match set");
goto out;
}
if (strcmp (test_session_service_dir_matches[i],
(const char *) link->data) != 0)
{
_dbus_test_diag ("'%s' directory does not match '%s' in the match set",
(const char *) link->data,
test_session_service_dir_matches[i]);
goto out;
}
}
if (test_session_service_dir_matches[i] != NULL)
{
_dbus_test_diag ("extra data %s in the match set was not matched",
test_session_service_dir_matches[i]);
goto out;
}
ret = TRUE;
out:
if (parser != NULL)
bus_config_parser_unref (parser);
_dbus_list_clear (&watched_dirs);
_dbus_string_free (&full_path);
_dbus_string_free (&install_root_based);
_dbus_string_free (&progs);
_dbus_string_free (&runtime_dir_based);
_dbus_string_free (&data_home_based);
_dbus_string_free (&data_dirs_based);
_dbus_string_free (&data_dirs_based2);
return ret;
}
#ifndef DBUS_WIN
static const char *test_system_service_dir_matches[] =
{
"/usr/local/share/dbus-1/system-services",
"/usr/share/dbus-1/system-services",
DBUS_DATADIR"/dbus-1/system-services",
"/lib/dbus-1/system-services",
NULL
};
static dbus_bool_t
test_default_system_servicedirs (void)
{
DBusList *dirs;
DBusList *link;
int i;
dirs = NULL;
if (!_dbus_get_standard_system_servicedirs (&dirs))
_dbus_test_fatal ("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)))
{
_dbus_test_diag (" test service dir: %s", (char *)link->data);
if (test_system_service_dir_matches[i] == NULL)
{
_dbus_test_diag ("more directories parsed than in match set");
dbus_free (link->data);
_dbus_list_free_link (link);
return FALSE;
}
if (strcmp (test_system_service_dir_matches[i],
(char *)link->data) != 0)
{
_dbus_test_diag ("%s directory does not match %s in the match set",
(char *)link->data,
test_system_service_dir_matches[i]);
dbus_free (link->data);
_dbus_list_free_link (link);
return FALSE;
}
++i;
dbus_free (link->data);
_dbus_list_free_link (link);
}
if (test_system_service_dir_matches[i] != NULL)
{
_dbus_test_diag ("extra data %s in the match set was not matched",
test_system_service_dir_matches[i]);
return FALSE;
}
return TRUE;
}
#endif
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)
{
_dbus_test_diag ("No test data");
return TRUE;
}
if (!test_default_session_servicedirs (test_data_dir))
return FALSE;
#ifdef DBUS_WIN
_dbus_test_diag ("default system service dir skipped");
#else
if (!test_default_system_servicedirs())
return FALSE;
#endif
if (!process_test_valid_subdir (test_data_dir, "valid-config-files", VALID))
return FALSE;
#ifndef DBUS_WIN
if (!process_test_valid_subdir (test_data_dir, "valid-config-files-system", VALID))
return FALSE;
#endif
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 */