dbus/bus/containers.c
Simon McVittie 6130ac4267 build: Define DBUS_INT64_MODIFIER, analogous to G_GINT64_MODIFIER
Using PRId64, etc. to print dbus_int64_t or dbus_uint64_t is not 100%
portable. On platforms where both long and long long are 64-bit (such as
Linux and macOS), we will prefer to define dbus_int64_t as long.
If the operating system has chosen to define int64_t as long long,
which is apparently the case on macOS, then the compiler can warn that
we are passing a long argument to PRId64, which is "lld" and therefore
expects a long long argument (even though that ends up with the same
bit-pattern being used).

We can't necessarily just use int64_t and uint64_t directly, even if all
our supported platforms have them available now, because swapping
dbus_int64_t between long and long long might change C++ name mangling,
causing ABI breaks in third-party libraries if they define C++ functions
that take a dbus_int64_t argument.

Signed-off-by: Simon McVittie <smcv@collabora.com>
2022-04-19 18:27:47 +00:00

1476 lines
46 KiB
C

/* containers.c - restricted bus servers for containers
*
* Copyright © 2017 Collabora Ltd.
*
* 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 "containers.h"
#include "dbus/dbus-internals.h"
#ifdef DBUS_ENABLE_CONTAINERS
#error This feature is not ready for production use
#ifndef DBUS_UNIX
# error DBUS_ENABLE_CONTAINERS requires DBUS_UNIX
#endif
#include <sys/types.h>
#include "dbus/dbus-hash.h"
#include "dbus/dbus-message-internal.h"
#include "dbus/dbus-sysdeps-unix.h"
#include "connection.h"
#include "dispatch.h"
#include "driver.h"
#include "utils.h"
/*
* A container instance groups together a per-app-container server with
* all the connections for which it is responsible.
*/
typedef struct
{
int refcount;
char *path;
char *type;
char *name;
DBusVariant *metadata;
BusContext *context;
BusContainers *containers;
DBusServer *server;
DBusConnection *creator;
/* List of owned DBusConnection, removed when the DBusConnection is
* removed from the bus */
DBusList *connections;
unsigned long uid;
unsigned announced:1;
} BusContainerInstance;
/* Data attached to a DBusConnection that has created container instances. */
typedef struct
{
/* List of instances created by this connection; unowned.
* The BusContainerInstance removes itself from here on destruction. */
DBusList *instances;
} BusContainerCreatorData;
/* Data slot on DBusConnection, holding BusContainerCreatorData */
static dbus_int32_t container_creator_data_slot = -1;
/*
* Singleton data structure encapsulating the container-related parts of
* a BusContext.
*/
struct BusContainers
{
int refcount;
/* path borrowed from BusContainerInstance => unowned BusContainerInstance
* The BusContainerInstance removes itself from here on destruction. */
DBusHashTable *instances_by_path;
/* uid => (void *) (uintptr_t) number of containers */
DBusHashTable *n_containers_by_user;
DBusString address_template;
dbus_uint64_t next_container_id;
};
/* Data slot on DBusConnection, holding BusContainerInstance */
static dbus_int32_t contained_data_slot = -1;
BusContainers *
bus_containers_new (void)
{
/* We allocate the hash table lazily, expecting that the common case will
* be a connection where this feature is never used */
BusContainers *self = NULL;
DBusString invalid = _DBUS_STRING_INIT_INVALID;
/* One reference per BusContainers, unless we ran out of memory the first
* time we tried to allocate it, in which case it will be -1 when we
* free the BusContainers */
if (!dbus_connection_allocate_data_slot (&contained_data_slot))
goto oom;
/* Ditto */
if (!dbus_connection_allocate_data_slot (&container_creator_data_slot))
goto oom;
self = dbus_new0 (BusContainers, 1);
if (self == NULL)
goto oom;
self->refcount = 1;
self->instances_by_path = NULL;
self->next_container_id = DBUS_UINT64_CONSTANT (0);
self->address_template = invalid;
/* Make it mutable */
if (!_dbus_string_init (&self->address_template))
goto oom;
if (_dbus_getuid () == 0)
{
DBusString dir;
/* System bus (we haven't dropped privileges at this point), or
* root's session bus. Use random socket paths resembling
* /run/dbus/containers/dbus-abcdef, which is next to /run/dbus/pid
* (if not using the Red Hat init scripts, which use a different
* pid file for historical reasons).
*
* We rely on the tmpfiles.d snippet or an OS-specific init script to
* have created this directory with the appropriate owner; if it hasn't,
* creating container sockets will just fail. */
_dbus_string_init_const (&dir, DBUS_RUNSTATEDIR "/dbus/containers");
/* We specifically use paths, because an abstract socket that you can't
* bind-mount is not particularly useful. */
if (!_dbus_string_append (&self->address_template, "unix:dir=") ||
!_dbus_address_append_escaped (&self->address_template, &dir))
goto oom;
}
else
{
/* Otherwise defer creating the directory for sockets until we need it,
* so that we can have better error behaviour */
}
return self;
oom:
if (self != NULL)
{
/* This will free the data slot too */
bus_containers_unref (self);
}
else
{
if (contained_data_slot != -1)
dbus_connection_free_data_slot (&contained_data_slot);
if (container_creator_data_slot != -1)
dbus_connection_free_data_slot (&container_creator_data_slot);
}
return NULL;
}
BusContainers *
bus_containers_ref (BusContainers *self)
{
_dbus_assert (self->refcount > 0);
_dbus_assert (self->refcount < _DBUS_INT_MAX);
self->refcount++;
return self;
}
void
bus_containers_unref (BusContainers *self)
{
_dbus_assert (self != NULL);
_dbus_assert (self->refcount > 0);
if (--self->refcount == 0)
{
_dbus_clear_hash_table (&self->instances_by_path);
_dbus_clear_hash_table (&self->n_containers_by_user);
_dbus_string_free (&self->address_template);
dbus_free (self);
if (contained_data_slot != -1)
dbus_connection_free_data_slot (&contained_data_slot);
if (container_creator_data_slot != -1)
dbus_connection_free_data_slot (&container_creator_data_slot);
}
}
static BusContainerInstance *
bus_container_instance_ref (BusContainerInstance *self)
{
_dbus_assert (self->refcount > 0);
_dbus_assert (self->refcount < _DBUS_INT_MAX);
self->refcount++;
return self;
}
static dbus_bool_t
bus_container_instance_emit_removed (BusContainerInstance *self)
{
BusTransaction *transaction = NULL;
DBusMessage *message = NULL;
DBusError error = DBUS_ERROR_INIT;
transaction = bus_transaction_new (self->context);
if (transaction == NULL)
goto oom;
message = dbus_message_new_signal (DBUS_PATH_DBUS,
DBUS_INTERFACE_CONTAINERS1,
"InstanceRemoved");
if (message == NULL ||
!dbus_message_set_sender (message, DBUS_SERVICE_DBUS) ||
!dbus_message_append_args (message,
DBUS_TYPE_OBJECT_PATH, &self->path,
DBUS_TYPE_INVALID) ||
!bus_transaction_capture (transaction, NULL, NULL, message))
goto oom;
if (!bus_dispatch_matches (transaction, NULL, NULL, message, &error))
{
if (dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY))
goto oom;
/* This can't actually happen, because all of the error cases in
* bus_dispatch_matches() are only if there is an addressed recipient
* (a unicast message), which there is not in this case. But if it
* somehow does happen, we don't want to stay in the OOM-retry loop,
* because waiting for more memory will not help; so continue to
* execute the transaction anyway. */
_dbus_warn ("Failed to send InstanceRemoved for a reason "
"other than OOM: %s: %s", error.name, error.message);
dbus_error_free (&error);
}
bus_transaction_execute_and_free (transaction);
dbus_message_unref (message);
_DBUS_ASSERT_ERROR_IS_CLEAR (&error);
return TRUE;
oom:
dbus_error_free (&error);
dbus_clear_message (&message);
if (transaction != NULL)
bus_transaction_cancel_and_free (transaction);
return FALSE;
}
static void
bus_container_instance_unref (BusContainerInstance *self)
{
_dbus_assert (self->refcount > 0);
if (--self->refcount == 0)
{
BusContainerCreatorData *creator_data;
/* If we announced the container instance in a reply from
* AddServer() (which is also the time at which it becomes
* available for the querying methods), then we have to emit
* InstanceRemoved for it.
*
* Similar to bus/connection.c dropping well-known name ownership,
* this isn't really a situation where we can "fail", because
* this last-unref is likely to be happening as a result of a
* connection disconnecting; so we use a retry loop on OOM. */
for (; self->announced; _dbus_wait_for_memory ())
{
if (bus_container_instance_emit_removed (self))
self->announced = FALSE;
}
/* As long as the server is listening, the BusContainerInstance can't
* be freed, because the DBusServer holds a reference to the
* BusContainerInstance */
_dbus_assert (self->server == NULL);
/* Similarly, as long as there are connections, the BusContainerInstance
* can't be freed, because each connection holds a reference to the
* BusContainerInstance */
_dbus_assert (self->connections == NULL);
creator_data = dbus_connection_get_data (self->creator,
container_creator_data_slot);
_dbus_assert (creator_data != NULL);
_dbus_list_remove (&creator_data->instances, self);
/* It's OK to do this even if we were never added to instances_by_path,
* because the paths are globally unique. */
if (self->path != NULL && self->containers->instances_by_path != NULL &&
_dbus_hash_table_remove_string (self->containers->instances_by_path,
self->path))
{
DBusHashIter entry;
uintptr_t n = 0;
if (!_dbus_hash_iter_lookup (self->containers->n_containers_by_user,
(void *) (uintptr_t) self->uid,
FALSE, &entry))
_dbus_assert_not_reached ("Container should not be placed in "
"instances_by_path until its "
"n_containers_by_user entry has "
"been allocated");
n = (uintptr_t) _dbus_hash_iter_get_value (&entry);
_dbus_assert (n > 0);
n -= 1;
_dbus_hash_iter_set_value (&entry, (void *) n);
}
_dbus_clear_variant (&self->metadata);
bus_context_unref (self->context);
bus_containers_unref (self->containers);
dbus_connection_unref (self->creator);
dbus_free (self->path);
dbus_free (self->type);
dbus_free (self->name);
dbus_free (self);
}
}
static inline void
bus_clear_container_instance (BusContainerInstance **instance_p)
{
_dbus_clear_pointer_impl (BusContainerInstance, instance_p,
bus_container_instance_unref);
}
static void
bus_container_instance_stop_listening (BusContainerInstance *self)
{
/* In case the DBusServer holds the last reference to self */
bus_container_instance_ref (self);
if (self->server != NULL)
{
dbus_server_set_new_connection_function (self->server, NULL, NULL, NULL);
dbus_server_disconnect (self->server);
dbus_clear_server (&self->server);
}
bus_container_instance_unref (self);
}
static BusContainerInstance *
bus_container_instance_new (BusContext *context,
BusContainers *containers,
DBusConnection *creator,
DBusError *error)
{
BusContainerInstance *self = NULL;
DBusString path = _DBUS_STRING_INIT_INVALID;
_dbus_assert (context != NULL);
_dbus_assert (containers != NULL);
_dbus_assert (creator != NULL);
_DBUS_ASSERT_ERROR_IS_CLEAR (error);
if (!_dbus_string_init (&path))
{
BUS_SET_OOM (error);
goto fail;
}
self = dbus_new0 (BusContainerInstance, 1);
if (self == NULL)
{
BUS_SET_OOM (error);
goto fail;
}
self->refcount = 1;
self->type = NULL;
self->name = NULL;
self->metadata = NULL;
self->context = bus_context_ref (context);
self->containers = bus_containers_ref (containers);
self->server = NULL;
self->creator = dbus_connection_ref (creator);
if (containers->next_container_id >=
DBUS_UINT64_CONSTANT (0xFFFFFFFFFFFFFFFF))
{
/* We can't increment it any further without wrapping around */
dbus_set_error (error, DBUS_ERROR_LIMITS_EXCEEDED,
"Too many containers created during the lifetime of "
"this bus");
goto fail;
}
if (!_dbus_string_append_printf (&path,
"/org/freedesktop/DBus/Containers1/c%" DBUS_INT64_MODIFIER "u",
containers->next_container_id++))
{
BUS_SET_OOM (error);
goto fail;
}
if (!_dbus_string_steal_data (&path, &self->path))
goto fail;
_dbus_string_free (&path);
return self;
fail:
_dbus_string_free (&path);
if (self != NULL)
bus_container_instance_unref (self);
return NULL;
}
static void
bus_container_creator_data_free (BusContainerCreatorData *self)
{
/* Each instance holds a ref to the creator, so there should be
* nothing here */
_dbus_assert (self->instances == NULL);
dbus_free (self);
}
/* We only accept EXTERNAL authentication, because Unix platforms that are
* sufficiently capable to have app-containers ought to have it. */
static const char * const auth_mechanisms[] =
{
"EXTERNAL",
NULL
};
/* Statically assert that we can store a uid in a void *, losslessly.
*
* In practice this is always true on Unix. For now we don't support this
* feature on systems where it isn't. */
_DBUS_STATIC_ASSERT (sizeof (uid_t) <= sizeof (uintptr_t));
/* True by definition. */
_DBUS_STATIC_ASSERT (sizeof (void *) == sizeof (uintptr_t));
static dbus_bool_t
allow_same_uid_only (DBusConnection *connection,
unsigned long uid,
void *data)
{
return (uid == (uintptr_t) data);
}
static void
bus_container_instance_lost_connection (BusContainerInstance *instance,
DBusConnection *connection)
{
bus_container_instance_ref (instance);
dbus_connection_ref (connection);
/* This is O(n), but we don't expect to have many connections per
* container instance. */
if (_dbus_list_remove (&instance->connections, connection))
dbus_connection_unref (connection);
/* We don't set connection's contained_data_slot to NULL, to make sure
* that once we have marked a connection as belonging to a container,
* there is no going back: even if we somehow keep a reference to it
* around, it will never be treated as uncontained. The connection's
* reference to the instance will be cleaned up on last-unref, and
* the list removal above ensures that the instance does not hold a
* circular ref to the connection, so the last-unref will happen. */
dbus_connection_unref (connection);
bus_container_instance_unref (instance);
}
static void
new_connection_cb (DBusServer *server,
DBusConnection *new_connection,
void *data)
{
BusContainerInstance *instance = data;
int limit = bus_context_get_max_connections_per_container (instance->context);
/* This is O(n), but we assume n is small in practice. */
if (_dbus_list_get_length (&instance->connections) >= limit)
{
/* We can't send this error to the new connection, so just log it */
bus_context_log (instance->context, DBUS_SYSTEM_LOG_WARNING,
"Closing connection to container server "
"%s (%s \"%s\") because it would exceed resource limit "
"(max_connections_per_container=%d)",
instance->path, instance->type, instance->name, limit);
return;
}
if (!dbus_connection_set_data (new_connection, contained_data_slot,
bus_container_instance_ref (instance),
(DBusFreeFunction) bus_container_instance_unref))
{
bus_container_instance_unref (instance);
bus_container_instance_lost_connection (instance, new_connection);
return;
}
if (_dbus_list_append (&instance->connections, new_connection))
{
dbus_connection_ref (new_connection);
}
else
{
bus_container_instance_lost_connection (instance, new_connection);
return;
}
/* If this fails it logs a warning, so we don't need to do that.
* We don't know how to undo this, so do it last (apart from things that
* cannot fail) */
if (!bus_context_add_incoming_connection (instance->context, new_connection))
{
bus_container_instance_lost_connection (instance, new_connection);
return;
}
/* We'd like to check the uid here, but we can't yet. Instead clear the
* BusContext's unix_user_function, which results in us getting the
* default behaviour: only the user that owns the bus can connect.
*
* TODO: For the system bus we might want a way to opt-in to allowing
* other uids, in which case we would refrain from overwriting the
* BusContext's unix_user_function; but that isn't part of the
* lowest-common-denominator implementation. */
dbus_connection_set_unix_user_function (new_connection,
allow_same_uid_only,
/* The static assertion above
* allow_same_uid_only ensures that
* this cast does not lose
* information */
(void *) (uintptr_t) instance->uid,
NULL);
}
static const char *
bus_containers_ensure_address_template (BusContainers *self,
DBusError *error)
{
DBusString dir = _DBUS_STRING_INIT_INVALID;
const char *ret = NULL;
const char *runtime_dir;
/* Early-return if we already did this */
if (_dbus_string_get_length (&self->address_template) > 0)
return _dbus_string_get_const_data (&self->address_template);
runtime_dir = _dbus_getenv ("XDG_RUNTIME_DIR");
if (runtime_dir != NULL)
{
if (!_dbus_string_init (&dir))
{
BUS_SET_OOM (error);
goto out;
}
/* We listen on a random socket path resembling
* /run/user/1000/dbus-1/containers/dbus-abcdef, chosen to share
* the dbus-1 directory with the dbus-1/services used for transient
* session services. */
if (!_dbus_string_append (&dir, runtime_dir) ||
!_dbus_string_append (&dir, "/dbus-1"))
{
BUS_SET_OOM (error);
goto out;
}
if (!_dbus_ensure_directory (&dir, error))
goto out;
if (!_dbus_string_append (&dir, "/containers"))
{
BUS_SET_OOM (error);
goto out;
}
if (!_dbus_ensure_directory (&dir, error))
goto out;
}
else
{
/* No XDG_RUNTIME_DIR, so don't do anything special or clever: just
* use a random socket like /tmp/dbus-abcdef. */
const char *tmpdir;
tmpdir = _dbus_get_tmpdir ();
_dbus_string_init_const (&dir, tmpdir);
}
/* We specifically use paths, even on Linux (unix:dir= not unix:tmpdir=),
* because an abstract socket that you can't bind-mount is not useful
* when you want something you can bind-mount into a container. */
if (!_dbus_string_append (&self->address_template, "unix:dir=") ||
!_dbus_address_append_escaped (&self->address_template, &dir))
{
_dbus_string_set_length (&self->address_template, 0);
BUS_SET_OOM (error);
goto out;
}
ret = _dbus_string_get_const_data (&self->address_template);
out:
_dbus_string_free (&dir);
return ret;
}
static dbus_bool_t
bus_container_instance_listen (BusContainerInstance *self,
DBusError *error)
{
BusContainers *containers = bus_context_get_containers (self->context);
const char *address;
address = bus_containers_ensure_address_template (containers, error);
if (address == NULL)
return FALSE;
self->server = dbus_server_listen (address, error);
if (self->server == NULL)
return FALSE;
if (!bus_context_setup_server (self->context, self->server, error))
return FALSE;
if (!dbus_server_set_auth_mechanisms (self->server,
(const char **) auth_mechanisms))
{
BUS_SET_OOM (error);
return FALSE;
}
/* Cannot fail because the memory it uses was already allocated */
dbus_server_set_new_connection_function (self->server, new_connection_cb,
bus_container_instance_ref (self),
(DBusFreeFunction) bus_container_instance_unref);
return TRUE;
}
dbus_bool_t
bus_containers_handle_add_server (DBusConnection *connection,
BusTransaction *transaction,
DBusMessage *message,
DBusError *error)
{
BusContainerCreatorData *creator_data;
DBusMessageIter iter;
DBusMessageIter dict_iter;
DBusMessageIter writer;
DBusMessageIter array_writer;
const char *type;
const char *name;
const char *path;
BusContainerInstance *instance = NULL;
BusContext *context;
BusContainers *containers;
char *address = NULL;
DBusAddressEntry **entries = NULL;
int n_entries;
DBusMessage *reply = NULL;
int metadata_size;
int limit;
DBusHashIter n_containers_by_user_entry;
uintptr_t this_user_containers = 0;
context = bus_transaction_get_context (transaction);
containers = bus_context_get_containers (context);
creator_data = dbus_connection_get_data (connection,
container_creator_data_slot);
if (creator_data == NULL)
{
creator_data = dbus_new0 (BusContainerCreatorData, 1);
if (creator_data == NULL)
goto oom;
creator_data->instances = NULL;
if (!dbus_connection_set_data (connection, container_creator_data_slot,
creator_data,
(DBusFreeFunction) bus_container_creator_data_free))
{
bus_container_creator_data_free (creator_data);
goto oom;
}
}
instance = bus_container_instance_new (context, containers, connection,
error);
if (instance == NULL)
goto fail;
if (!dbus_connection_get_unix_user (connection, &instance->uid))
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"Unable to determine user ID of caller");
goto fail;
}
/* We already checked this in bus_driver_handle_message() */
_dbus_assert (dbus_message_has_signature (message, "ssa{sv}a{sv}"));
/* Argument 0: Container type */
if (!dbus_message_iter_init (message, &iter))
_dbus_assert_not_reached ("Message type was already checked");
_dbus_assert (dbus_message_iter_get_arg_type (&iter) == DBUS_TYPE_STRING);
dbus_message_iter_get_basic (&iter, &type);
instance->type = _dbus_strdup (type);
if (instance->type == NULL)
goto oom;
if (!dbus_validate_interface (type, NULL))
{
dbus_set_error (error, DBUS_ERROR_INVALID_ARGS,
"The container type identifier must have the "
"syntax of an interface name");
goto fail;
}
/* Argument 1: Name as defined by container manager */
if (!dbus_message_iter_next (&iter))
_dbus_assert_not_reached ("Message type was already checked");
_dbus_assert (dbus_message_iter_get_arg_type (&iter) == DBUS_TYPE_STRING);
dbus_message_iter_get_basic (&iter, &name);
instance->name = _dbus_strdup (name);
if (instance->name == NULL)
goto oom;
/* Argument 2: Metadata as defined by container manager */
if (!dbus_message_iter_next (&iter))
_dbus_assert_not_reached ("Message type was already checked");
_dbus_assert (dbus_message_iter_get_arg_type (&iter) == DBUS_TYPE_ARRAY);
instance->metadata = _dbus_variant_read (&iter);
_dbus_assert (strcmp (_dbus_variant_get_signature (instance->metadata),
"a{sv}") == 0);
/* For simplicity we don't count the size of the BusContainerInstance
* itself, the object path, lengths, the non-payload parts of the DBusString,
* NUL terminators and so on. That overhead is O(1) and relatively small.
* This cannot overflow because all parts came from a message, and messages
* are constrained to be orders of magnitude smaller than the maximum
* int value. */
metadata_size = _dbus_variant_get_length (instance->metadata) +
(int) strlen (type) +
(int) strlen (name);
limit = bus_context_get_max_container_metadata_bytes (context);
if (metadata_size > limit)
{
DBusError local_error = DBUS_ERROR_INIT;
dbus_set_error (&local_error, DBUS_ERROR_LIMITS_EXCEEDED,
"Connection \"%s\" (%s) is not allowed to set "
"%d bytes of container metadata "
"(max_container_metadata_bytes=%d)",
bus_connection_get_name (connection),
bus_connection_get_loginfo (connection),
metadata_size, limit);
bus_context_log_literal (context, DBUS_SYSTEM_LOG_WARNING,
local_error.message);
dbus_move_error (&local_error, error);
goto fail;
}
/* Argument 3: Named parameters */
if (!dbus_message_iter_next (&iter))
_dbus_assert_not_reached ("Message type was already checked");
_dbus_assert (dbus_message_iter_get_arg_type (&iter) == DBUS_TYPE_ARRAY);
dbus_message_iter_recurse (&iter, &dict_iter);
while (dbus_message_iter_get_arg_type (&dict_iter) != DBUS_TYPE_INVALID)
{
DBusMessageIter pair_iter;
const char *param_name;
_dbus_assert (dbus_message_iter_get_arg_type (&dict_iter) ==
DBUS_TYPE_DICT_ENTRY);
dbus_message_iter_recurse (&dict_iter, &pair_iter);
_dbus_assert (dbus_message_iter_get_arg_type (&pair_iter) ==
DBUS_TYPE_STRING);
dbus_message_iter_get_basic (&pair_iter, &param_name);
/* If we supported any named parameters, we'd copy them into the data
* structure here; but we don't, so fail instead. */
dbus_set_error (error, DBUS_ERROR_INVALID_ARGS,
"Named parameter %s is not understood", param_name);
goto fail;
}
/* End of arguments */
_dbus_assert (!dbus_message_iter_has_next (&iter));
if (containers->instances_by_path == NULL)
{
containers->instances_by_path = _dbus_hash_table_new (DBUS_HASH_STRING,
NULL, NULL);
if (containers->instances_by_path == NULL)
goto oom;
}
if (containers->n_containers_by_user == NULL)
{
containers->n_containers_by_user = _dbus_hash_table_new (DBUS_HASH_UINTPTR,
NULL, NULL);
if (containers->n_containers_by_user == NULL)
goto oom;
}
limit = bus_context_get_max_containers (context);
if (_dbus_hash_table_get_n_entries (containers->instances_by_path) >= limit)
{
DBusError local_error = DBUS_ERROR_INIT;
dbus_set_error (&local_error, DBUS_ERROR_LIMITS_EXCEEDED,
"Connection \"%s\" (%s) is not allowed to create more "
"container servers (max_containers=%d)",
bus_connection_get_name (connection),
bus_connection_get_loginfo (connection),
limit);
bus_context_log_literal (context, DBUS_SYSTEM_LOG_WARNING,
local_error.message);
dbus_move_error (&local_error, error);
goto fail;
}
if (!_dbus_hash_iter_lookup (containers->n_containers_by_user,
/* We statically assert that a uid fits in a
* uintptr_t, so this can't lose information */
(void *) (uintptr_t) instance->uid, TRUE,
&n_containers_by_user_entry))
goto oom;
this_user_containers = (uintptr_t) _dbus_hash_iter_get_value (&n_containers_by_user_entry);
limit = bus_context_get_max_containers_per_user (context);
/* We need to be careful with types here because this_user_containers is
* unsigned. */
if (limit <= 0 || this_user_containers >= (unsigned) limit)
{
DBusError local_error = DBUS_ERROR_INIT;
dbus_set_error (&local_error, DBUS_ERROR_LIMITS_EXCEEDED,
"Connection \"%s\" (%s) is not allowed to create more "
"container servers for uid %lu "
"(max_containers_per_user=%d)",
bus_connection_get_name (connection),
bus_connection_get_loginfo (connection),
instance->uid, limit);
bus_context_log_literal (context, DBUS_SYSTEM_LOG_WARNING,
local_error.message);
dbus_move_error (&local_error, error);
goto fail;
}
if (!_dbus_hash_table_insert_string (containers->instances_by_path,
instance->path, instance))
goto oom;
/* This cannot fail (we already allocated the memory) so we can do it after
* we already succeeded in adding it to instances_by_path. The matching
* decrement is done whenever we remove it from instances_by_path. */
this_user_containers += 1;
_dbus_hash_iter_set_value (&n_containers_by_user_entry,
(void *) this_user_containers);
if (!_dbus_list_append (&creator_data->instances, instance))
goto oom;
/* This part is separated out because we eventually want to be able to
* accept a fd-passed server socket in the named parameters, instead of
* creating our own server, and defer listening on it until later */
if (!bus_container_instance_listen (instance, error))
goto fail;
address = dbus_server_get_address (instance->server);
if (!dbus_parse_address (address, &entries, &n_entries, error))
_dbus_assert_not_reached ("listening on unix:dir= should yield a valid address");
_dbus_assert (n_entries == 1);
_dbus_assert (strcmp (dbus_address_entry_get_method (entries[0]), "unix") == 0);
path = dbus_address_entry_get_value (entries[0], "path");
_dbus_assert (path != NULL);
reply = dbus_message_new_method_return (message);
if (!dbus_message_append_args (reply,
DBUS_TYPE_OBJECT_PATH, &instance->path,
DBUS_TYPE_INVALID))
goto oom;
dbus_message_iter_init_append (reply, &writer);
if (!dbus_message_iter_open_container (&writer, DBUS_TYPE_ARRAY,
DBUS_TYPE_BYTE_AS_STRING,
&array_writer))
goto oom;
if (!dbus_message_iter_append_fixed_array (&array_writer, DBUS_TYPE_BYTE,
&path, strlen (path) + 1))
{
dbus_message_iter_abandon_container (&writer, &array_writer);
goto oom;
}
if (!dbus_message_iter_close_container (&writer, &array_writer))
goto oom;
if (!dbus_message_append_args (reply,
DBUS_TYPE_STRING, &address,
DBUS_TYPE_INVALID))
goto oom;
_dbus_assert (dbus_message_has_signature (reply, "oays"));
if (! bus_transaction_send_from_driver (transaction, connection, reply))
goto oom;
instance->announced = TRUE;
dbus_message_unref (reply);
bus_container_instance_unref (instance);
dbus_address_entries_free (entries);
dbus_free (address);
return TRUE;
oom:
BUS_SET_OOM (error);
/* fall through */
fail:
if (instance != NULL)
bus_container_instance_stop_listening (instance);
dbus_clear_message (&reply);
dbus_clear_address_entries (&entries);
bus_clear_container_instance (&instance);
dbus_free (address);
return FALSE;
}
dbus_bool_t
bus_containers_supported_arguments_getter (BusContext *context,
DBusMessageIter *var_iter)
{
DBusMessageIter arr_iter;
/* There are none so far */
return dbus_message_iter_open_container (var_iter, DBUS_TYPE_ARRAY,
DBUS_TYPE_STRING_AS_STRING,
&arr_iter) &&
dbus_message_iter_close_container (var_iter, &arr_iter);
}
dbus_bool_t
bus_containers_handle_stop_instance (DBusConnection *connection,
BusTransaction *transaction,
DBusMessage *message,
DBusError *error)
{
BusContext *context;
BusContainers *containers;
BusContainerInstance *instance = NULL;
DBusList *iter;
const char *path;
unsigned long uid;
if (!dbus_message_get_args (message, error,
DBUS_TYPE_OBJECT_PATH, &path,
DBUS_TYPE_INVALID))
goto failed;
context = bus_transaction_get_context (transaction);
containers = bus_context_get_containers (context);
if (containers->instances_by_path != NULL)
{
instance = _dbus_hash_table_lookup_string (containers->instances_by_path,
path);
}
if (instance == NULL)
{
dbus_set_error (error, DBUS_ERROR_NOT_CONTAINER,
"There is no container with path '%s'", path);
goto failed;
}
if (!dbus_connection_get_unix_user (connection, &uid))
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"Unable to determine user ID of caller");
goto failed;
}
if (uid != instance->uid)
{
dbus_set_error (error, DBUS_ERROR_ACCESS_DENIED,
"User %lu cannot stop a container server started by "
"user %lu", uid, instance->uid);
goto failed;
}
bus_container_instance_ref (instance);
bus_container_instance_stop_listening (instance);
for (iter = _dbus_list_get_first_link (&instance->connections);
iter != NULL;
iter = _dbus_list_get_next_link (&instance->connections, iter))
dbus_connection_close (iter->data);
bus_container_instance_unref (instance);
if (!bus_driver_send_ack_reply (connection, transaction, message, error))
goto failed;
return TRUE;
failed:
_DBUS_ASSERT_ERROR_IS_SET (error);
return FALSE;
}
dbus_bool_t
bus_containers_handle_stop_listening (DBusConnection *connection,
BusTransaction *transaction,
DBusMessage *message,
DBusError *error)
{
BusContext *context;
BusContainers *containers;
BusContainerInstance *instance = NULL;
const char *path;
unsigned long uid;
if (!dbus_message_get_args (message, error,
DBUS_TYPE_OBJECT_PATH, &path,
DBUS_TYPE_INVALID))
goto failed;
context = bus_transaction_get_context (transaction);
containers = bus_context_get_containers (context);
if (containers->instances_by_path != NULL)
{
instance = _dbus_hash_table_lookup_string (containers->instances_by_path,
path);
}
if (instance == NULL)
{
dbus_set_error (error, DBUS_ERROR_NOT_CONTAINER,
"There is no container with path '%s'", path);
goto failed;
}
if (!dbus_connection_get_unix_user (connection, &uid))
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"Unable to determine user ID of caller");
goto failed;
}
if (uid != instance->uid)
{
dbus_set_error (error, DBUS_ERROR_ACCESS_DENIED,
"User %lu cannot stop a container server started by "
"user %lu", uid, instance->uid);
goto failed;
}
bus_container_instance_ref (instance);
bus_container_instance_stop_listening (instance);
bus_container_instance_unref (instance);
if (!bus_driver_send_ack_reply (connection, transaction, message, error))
goto failed;
return TRUE;
failed:
_DBUS_ASSERT_ERROR_IS_SET (error);
return FALSE;
}
/*
* This accepts a NULL connection so that it can be used when checking
* whether to allow sending or receiving a message, which might involve
* the dbus-daemon itself as a message sender or recipient.
*/
static BusContainerInstance *
connection_get_instance (DBusConnection *connection)
{
if (connection == NULL)
return NULL;
if (contained_data_slot == -1)
return NULL;
return dbus_connection_get_data (connection, contained_data_slot);
}
dbus_bool_t
bus_containers_handle_get_connection_instance (DBusConnection *caller,
BusTransaction *transaction,
DBusMessage *message,
DBusError *error)
{
BusContainerInstance *instance;
BusDriverFound found;
DBusConnection *subject;
DBusMessage *reply = NULL;
DBusMessageIter writer;
DBusMessageIter arr_writer;
const char *bus_name;
_DBUS_ASSERT_ERROR_IS_CLEAR (error);
found = bus_driver_get_conn_helper (caller, message, "container instance",
&bus_name, &subject, error);
switch (found)
{
case BUS_DRIVER_FOUND_SELF:
dbus_set_error (error, DBUS_ERROR_NOT_CONTAINER,
"The message bus is not in a container");
goto failed;
case BUS_DRIVER_FOUND_PEER:
break;
case BUS_DRIVER_FOUND_ERROR:
/* fall through */
default:
goto failed;
}
instance = connection_get_instance (subject);
if (instance == NULL)
{
dbus_set_error (error, DBUS_ERROR_NOT_CONTAINER,
"Connection '%s' is not in a container", bus_name);
goto failed;
}
reply = dbus_message_new_method_return (message);
if (reply == NULL)
goto oom;
if (!dbus_message_append_args (reply,
DBUS_TYPE_OBJECT_PATH, &instance->path,
DBUS_TYPE_INVALID))
goto oom;
dbus_message_iter_init_append (reply, &writer);
if (!dbus_message_iter_open_container (&writer, DBUS_TYPE_ARRAY, "{sv}",
&arr_writer))
goto oom;
if (!bus_driver_fill_connection_credentials (NULL, instance->creator,
&arr_writer))
{
dbus_message_iter_abandon_container (&writer, &arr_writer);
goto oom;
}
if (!dbus_message_iter_close_container (&writer, &arr_writer))
goto oom;
if (!dbus_message_append_args (reply,
DBUS_TYPE_STRING, &instance->type,
DBUS_TYPE_STRING, &instance->name,
DBUS_TYPE_INVALID))
goto oom;
dbus_message_iter_init_append (reply, &writer);
if (!_dbus_variant_write (instance->metadata, &writer))
goto oom;
if (!bus_transaction_send_from_driver (transaction, caller, reply))
goto oom;
dbus_message_unref (reply);
return TRUE;
oom:
BUS_SET_OOM (error);
/* fall through */
failed:
_DBUS_ASSERT_ERROR_IS_SET (error);
dbus_clear_message (&reply);
return FALSE;
}
dbus_bool_t
bus_containers_handle_get_instance_info (DBusConnection *connection,
BusTransaction *transaction,
DBusMessage *message,
DBusError *error)
{
BusContext *context;
BusContainers *containers;
BusContainerInstance *instance = NULL;
DBusMessage *reply = NULL;
DBusMessageIter writer;
DBusMessageIter arr_writer;
const char *path;
if (!dbus_message_get_args (message, error,
DBUS_TYPE_OBJECT_PATH, &path,
DBUS_TYPE_INVALID))
goto failed;
context = bus_transaction_get_context (transaction);
containers = bus_context_get_containers (context);
if (containers->instances_by_path != NULL)
{
instance = _dbus_hash_table_lookup_string (containers->instances_by_path,
path);
}
if (instance == NULL)
{
dbus_set_error (error, DBUS_ERROR_NOT_CONTAINER,
"There is no container with path '%s'", path);
goto failed;
}
reply = dbus_message_new_method_return (message);
if (reply == NULL)
goto oom;
dbus_message_iter_init_append (reply, &writer);
if (!dbus_message_iter_open_container (&writer, DBUS_TYPE_ARRAY, "{sv}",
&arr_writer))
goto oom;
if (!bus_driver_fill_connection_credentials (NULL, instance->creator,
&arr_writer))
{
dbus_message_iter_abandon_container (&writer, &arr_writer);
goto oom;
}
if (!dbus_message_iter_close_container (&writer, &arr_writer))
goto oom;
if (!dbus_message_append_args (reply,
DBUS_TYPE_STRING, &instance->type,
DBUS_TYPE_STRING, &instance->name,
DBUS_TYPE_INVALID))
goto oom;
dbus_message_iter_init_append (reply, &writer);
if (!_dbus_variant_write (instance->metadata, &writer))
goto oom;
if (!bus_transaction_send_from_driver (transaction, connection, reply))
goto oom;
dbus_message_unref (reply);
return TRUE;
oom:
BUS_SET_OOM (error);
/* fall through */
failed:
_DBUS_ASSERT_ERROR_IS_SET (error);
dbus_clear_message (&reply);
return FALSE;
}
dbus_bool_t
bus_containers_handle_request_header (DBusConnection *caller,
BusTransaction *transaction,
DBusMessage *message,
DBusError *error)
{
DBusMessage *reply = NULL;
dbus_bool_t ret = FALSE;
reply = dbus_message_new_method_return (message);
/* We prepare the transaction before carrying out its side-effects,
* because otherwise it isn't transactional */
if (reply == NULL ||
!bus_transaction_send_from_driver (transaction, caller, reply))
{
BUS_SET_OOM (error);
goto out;
}
bus_connection_request_headers (caller,
BUS_EXTRA_HEADERS_CONTAINER_INSTANCE);
ret = TRUE;
out:
dbus_clear_message (&reply);
return ret;
}
void
bus_containers_stop_listening (BusContainers *self)
{
if (self->instances_by_path != NULL)
{
DBusHashIter iter;
_dbus_hash_iter_init (self->instances_by_path, &iter);
while (_dbus_hash_iter_next (&iter))
{
BusContainerInstance *instance = _dbus_hash_iter_get_value (&iter);
bus_container_instance_stop_listening (instance);
}
}
}
#else
BusContainers *
bus_containers_new (void)
{
/* Return an arbitrary non-NULL pointer just to indicate that we didn't
* fail. There is no valid operation to do with it on this platform,
* other than unreffing it, which does nothing. */
return (BusContainers *) 1;
}
BusContainers *
bus_containers_ref (BusContainers *self)
{
_dbus_assert (self == (BusContainers *) 1);
return self;
}
void
bus_containers_unref (BusContainers *self)
{
_dbus_assert (self == (BusContainers *) 1);
}
void
bus_containers_stop_listening (BusContainers *self)
{
_dbus_assert (self == (BusContainers *) 1);
}
#endif /* DBUS_ENABLE_CONTAINERS */
void
bus_containers_remove_connection (BusContainers *self,
DBusConnection *connection)
{
#ifdef DBUS_ENABLE_CONTAINERS
BusContainerCreatorData *creator_data;
BusContainerInstance *instance;
dbus_connection_ref (connection);
creator_data = dbus_connection_get_data (connection,
container_creator_data_slot);
if (creator_data != NULL)
{
DBusList *iter;
DBusList *next;
for (iter = _dbus_list_get_first_link (&creator_data->instances);
iter != NULL;
iter = next)
{
instance = iter->data;
/* Remember where we got to before we do something that might free
* iter and instance */
next = _dbus_list_get_next_link (&creator_data->instances, iter);
_dbus_assert (instance->creator == connection);
/* This will invalidate iter and instance if there are no open
* connections to this instance */
bus_container_instance_stop_listening (instance);
}
}
instance = connection_get_instance (connection);
if (instance != NULL)
bus_container_instance_lost_connection (instance, connection);
dbus_connection_unref (connection);
#endif /* DBUS_ENABLE_CONTAINERS */
}
dbus_bool_t
bus_containers_connection_is_contained (DBusConnection *connection,
const char **path,
const char **type,
const char **name)
{
#ifdef DBUS_ENABLE_CONTAINERS
BusContainerInstance *instance;
instance = connection_get_instance (connection);
if (instance != NULL)
{
if (path != NULL)
*path = instance->path;
if (type != NULL)
*type = instance->type;
if (name != NULL)
*name = instance->name;
return TRUE;
}
#endif /* DBUS_ENABLE_CONTAINERS */
return FALSE;
}