#include "services.h"
#include "test.h"
#include "utils.h"
+#include <dbus/dbus-connection-internal.h>
#include <dbus/dbus-internals.h>
#include <dbus/dbus-hash.h>
#include <dbus/dbus-list.h>
DBusConnection *connection;
dbus_bool_t auto_activation;
+
+ dbus_bool_t is_put_back;
};
typedef struct
BusPendingActivationEntry *entry = link->data;
DBusList *next = _dbus_list_get_next_link (&pending_activation->entries, link);
- if (entry->auto_activation && (entry->connection == NULL || dbus_connection_get_is_connected (entry->connection)))
+ if (entry->auto_activation && !entry->is_put_back &&
+ (entry->connection == NULL || dbus_connection_get_is_connected (entry->connection)))
{
DBusConnection *addressed_recipient;
DBusError error;
addressed_recipient = bus_service_get_primary_owners_connection (service);
/* Resume dispatching where we left off in bus_dispatch() */
- if (!bus_dispatch_matches (transaction,
- entry->connection,
- addressed_recipient,
- entry->activation_message, &error))
+ switch (bus_dispatch_matches (transaction,
+ entry->connection,
+ addressed_recipient,
+ entry->activation_message, error))
{
+ case BUS_RESULT_TRUE:
+ break;
+ case BUS_RESULT_FALSE:
/* If permission is denied, we just want to return the error
* to the original method invoker; in particular, we don't
* want to make the RequestName call fail with that error
link = next;
continue;
+ case BUS_RESULT_LATER:
+ {
+ DBusList *putback_message_link = link;
+ DBusMessage *last_inserted_message = NULL;
+
+ /* NULL entry->connection implies sending pending ActivationRequest message to systemd */
+ if (entry->connection == NULL)
+ {
+ _dbus_assert_not_reached ("bus_dispatch_matches returned BUS_RESULT_LATER unexpectedly when sender is NULL");
+ link = next;
+ continue;
+ }
+
+ /**
+ * Getting here means that policy check result is not yet available and dispatching
+ * messages from entry->connection has been disabled.
+ * Let's put back all messages for the given connection in the incoming queue and mark
+ * this entry as put back so they are not handled twice.
+ */
+ while (putback_message_link != NULL)
+ {
+ BusPendingActivationEntry *putback_message = putback_message_link->data;
+ if (putback_message->connection == entry->connection)
+ {
+ if (!_dbus_connection_putback_message (putback_message->connection, last_inserted_message,
+ putback_message->activation_message, error))
+ goto error;
+ last_inserted_message = putback_message->activation_message;
+ putback_message->is_put_back = TRUE;
+ }
+
+ putback_message_link = _dbus_list_get_next_link(&pending_activation->entries, putback_message_link);
+ }
+ break;
+ }
}
}
return TRUE;
error:
+ /* remove all messages that have been put to connections' incoming queues */
+ link = _dbus_list_get_first_link (&pending_activation->entries);
+ while (link != NULL)
+ {
+ BusPendingActivationEntry *entry = link->data;
+ if (entry->is_put_back)
+ {
+ _dbus_connection_remove_message(entry->connection, entry->activation_message);
+ entry->is_put_back = FALSE;
+ }
+ link = _dbus_list_get_next_link(&pending_activation->entries, link);
+ }
+
return FALSE;
}
service_name,
entry->systemd_service);
/* Wonderful, systemd is connected, let's just send the msg */
- retval = bus_dispatch_matches (activation_transaction, NULL, bus_service_get_primary_owners_connection (service),
- message, error);
+ switch (bus_dispatch_matches (activation_transaction, NULL,
+ bus_service_get_primary_owners_connection (service),
+ message, error))
+ {
+ case BUS_RESULT_TRUE:
+ retval = TRUE;
+ break;
+ case BUS_RESULT_FALSE:
+ retval = FALSE;
+ break;
+ case BUS_RESULT_LATER:
+ _dbus_verbose("Unexpectedly need time to check message from bus driver to systemd - dropping the message.\n");
+ retval = FALSE;
+ break;
+ }
}
else
{
return check->cynara;
}
+static void
+bus_check_enable_dispatch_callback (BusDeferredMessage *deferred_message,
+ BusResult result)
+{
+ _dbus_verbose("bus_check_enable_dispatch_callback called deferred_message=%p\n", deferred_message);
+ _dbus_connection_enable_dispatch(deferred_message->sender);
+}
+
+void
+bus_deferred_message_disable_sender (BusDeferredMessage *deferred_message)
+{
+ _dbus_assert(deferred_message != NULL);
+ _dbus_assert(deferred_message->sender != NULL);
+
+ _dbus_connection_disable_dispatch(deferred_message->sender);
+ deferred_message->response_callback = bus_check_enable_dispatch_callback;
+}
+
+#ifdef DBUS_ENABLE_EMBEDDED_TESTS
+dbus_bool_t (*bus_check_test_override) (DBusConnection *connection,
+ const char *privilege);
+#endif
+
BusResult
bus_check_privilege (BusCheck *check,
DBusMessage *message,
return BUS_RESULT_FALSE;
}
+#ifdef DBUS_ENABLE_EMBEDDED_TESTS
+ if (bus_check_test_override)
+ return bus_check_test_override (connection, privilege);
+#endif
+
/* ask policy checkers */
#ifdef DBUS_ENABLE_CYNARA
cynara = bus_check_get_cynara(check);
}
}
+BusDeferredMessageStatus
+bus_deferred_message_get_status (BusDeferredMessage *deferred_message)
+{
+ return deferred_message->status;
+}
+
void
bus_deferred_message_response_received (BusDeferredMessage *deferred_message,
BusResult result)
BusDeferredMessageStatus check_type,
BusDeferredMessage **deferred_message);
+
BusDeferredMessage *bus_deferred_message_new (DBusMessage *message,
DBusConnection *sender,
DBusConnection *addressed_recipient,
void bus_deferred_message_unref (BusDeferredMessage *deferred_message);
void bus_deferred_message_response_received (BusDeferredMessage *deferred_message,
BusResult result);
+void bus_deferred_message_disable_sender (BusDeferredMessage *deferred_message);
+
+BusDeferredMessageStatus bus_deferred_message_get_status (BusDeferredMessage *deferred_message);
+
+#ifdef DBUS_ENABLE_EMBEDDED_TESTS
+extern dbus_bool_t (*bus_check_test_override) (DBusConnection *connection,
+ const char *privilege);
+#endif
+
#endif /* BUS_CHECK_H */
return TRUE;
}
-dbus_bool_t
+BusResult
bus_dispatch_matches (BusTransaction *transaction,
DBusConnection *sender,
DBusConnection *addressed_recipient,
case BUS_RESULT_FALSE:
return BUS_RESULT_FALSE;
case BUS_RESULT_LATER:
- dbus_set_error (error,
- DBUS_ERROR_ACCESS_DENIED,
- "Rejecting message because time is needed to check security policy");
- return BUS_RESULT_FALSE;
+ {
+ BusDeferredMessageStatus status;
+ status = bus_deferred_message_get_status(deferred_message);
+
+ if (status & BUS_DEFERRED_MESSAGE_CHECK_SEND)
+ {
+ /* send rule result not available - disable dispatching messages from the sender */
+ bus_deferred_message_disable_sender(deferred_message);
+ return BUS_RESULT_LATER;
+ }
+ else if (status & BUS_DEFERRED_MESSAGE_CHECK_RECEIVE)
+ {
+ dbus_set_error (error, DBUS_ERROR_ACCESS_DENIED,
+ "Rejecting message because time is needed to check security policy");
+ return BUS_RESULT_FALSE;
+ }
+ else
+ {
+ _dbus_verbose("deferred message has no status field set to send or receive unexpectedly\n");
+ return BUS_RESULT_FALSE;
+ }
+ }
}
if (dbus_message_contains_unix_fds (message) &&
DBUS_ERROR_NOT_SUPPORTED,
"Tried to send message with Unix file descriptors"
"to a client that doesn't support that.");
- return FALSE;
- }
+ return BUS_RESULT_FALSE;
+ }
/* Dispatch the message */
if (!bus_transaction_send (transaction, addressed_recipient, message))
{
BUS_SET_OOM (error);
- return FALSE;
+ return BUS_RESULT_FALSE;
}
}
&recipients))
{
BUS_SET_OOM (error);
- return FALSE;
+ return BUS_RESULT_FALSE;
}
link = _dbus_list_get_first_link (&recipients);
if (dbus_error_is_set (&tmp_error))
{
dbus_move_error (&tmp_error, error);
- return FALSE;
+ return BUS_RESULT_FALSE;
}
else
- return TRUE;
+ return BUS_RESULT_TRUE;
}
static DBusHandlerResult
_dbus_verbose ("Security policy rejected message\n");
goto out;
case BUS_RESULT_LATER:
- dbus_set_error (&error,
- DBUS_ERROR_ACCESS_DENIED,
- "Rejecting message because time is needed to check security policy");
- _dbus_verbose ("Security policy needs time to check policy. Dropping message\n");
+ /* Disable dispatching messages from the sender,
+ * roll back and dispatch the message once the policy result is available */
+ bus_deferred_message_disable_sender(deferred_message);
+ bus_transaction_cancel_and_free (transaction);
+ transaction = NULL;
+ result = DBUS_HANDLER_RESULT_LATER;
goto out;
}
* addressed_recipient == NULL), and match it against other connections'
* match rules.
*/
- if (!bus_dispatch_matches (transaction, connection, addressed_recipient, message, &error))
- goto out;
+ switch (bus_dispatch_matches (transaction, connection, addressed_recipient, message, &error))
+ {
+ case BUS_RESULT_TRUE:
+ case BUS_RESULT_FALSE:
+ break;
+ case BUS_RESULT_LATER:
+ /* Roll back and dispatch the message once the policy result is available */
+ bus_transaction_cancel_and_free (transaction);
+ transaction = NULL;
+ result = DBUS_HANDLER_RESULT_LATER;
+ break;
+ }
out:
if (dbus_error_is_set (&error))
return TRUE;
}
+typedef struct {
+ DBusTimeout *timeout;
+ DBusConnection *connection;
+ dbus_bool_t timedout;
+ int check_counter;
+} BusTestCheckData;
+
+static BusTestCheckData *cdata;
+
+static dbus_bool_t
+bus_dispatch_test_check_timeout (void *data)
+{
+ _dbus_verbose ("timeout triggered - pretend that privilege check result is available\n");
+
+ /* should only happen once during the test */
+ _dbus_assert (!cdata->timedout);
+ cdata->timedout = TRUE;
+ _dbus_connection_enable_dispatch (cdata->connection);
+
+ /* don't call this again */
+ _dbus_loop_remove_timeout (bus_connection_get_loop (cdata->connection),
+ cdata->timeout);
+ dbus_connection_unref (cdata->connection);
+ cdata->connection = NULL;
+ return TRUE;
+}
+
+static dbus_bool_t
+bus_dispatch_test_check_override (DBusConnection *connection,
+ const char *privilege)
+{
+ _dbus_verbose ("overriding privilege check %s #%d\n", privilege, cdata->check_counter);
+ cdata->check_counter++;
+ if (!cdata->timedout)
+ {
+ dbus_bool_t added;
+
+ /* Should be the first privilege check for the "Echo" method. */
+ _dbus_assert (cdata->check_counter == 1);
+ cdata->timeout = _dbus_timeout_new (1, bus_dispatch_test_check_timeout,
+ NULL, NULL);
+ _dbus_assert (cdata->timeout);
+ added = _dbus_loop_add_timeout (bus_connection_get_loop (connection),
+ cdata->timeout);
+ _dbus_assert (added);
+ cdata->connection = connection;
+ dbus_connection_ref (connection);
+ _dbus_connection_disable_dispatch (connection);
+ return BUS_RESULT_LATER;
+ }
+ else
+ {
+ /* Should only be checked one more time, and this time succeeds. */
+ _dbus_assert (cdata->check_counter == 2);
+ return BUS_RESULT_TRUE;
+ }
+}
+
+static dbus_bool_t
+bus_dispatch_test_check (const DBusString *test_data_dir)
+{
+ const char *filename = "valid-config-files/debug-check-some.conf";
+ BusContext *context;
+ DBusConnection *foo;
+ DBusError error;
+ dbus_bool_t result = TRUE;
+ BusTestCheckData data;
+
+ /* save the config name for the activation helper */
+ if (!setenv_TEST_LAUNCH_HELPER_CONFIG (test_data_dir, filename))
+ _dbus_assert_not_reached ("no memory setting TEST_LAUNCH_HELPER_CONFIG");
+
+ dbus_error_init (&error);
+
+ context = bus_context_new_test (test_data_dir, filename);
+ if (context == NULL)
+ return FALSE;
+
+ foo = dbus_connection_open_private (TEST_DEBUG_PIPE, &error);
+ if (foo == NULL)
+ _dbus_assert_not_reached ("could not alloc connection");
+
+ if (!bus_setup_debug_client (foo))
+ _dbus_assert_not_reached ("could not set up connection");
+
+ spin_connection_until_authenticated (context, foo);
+
+ if (!check_hello_message (context, foo))
+ _dbus_assert_not_reached ("hello message failed");
+
+ if (!check_double_hello_message (context, foo))
+ _dbus_assert_not_reached ("double hello message failed");
+
+ if (!check_add_match_all (context, foo))
+ _dbus_assert_not_reached ("AddMatch message failed");
+
+ /*
+ * Cause bus_check_send_privilege() to return BUS_RESULT_LATER in the
+ * first call, then BUS_RESULT_TRUE.
+ */
+ cdata = &data;
+ memset (cdata, 0, sizeof(*cdata));
+ bus_check_test_override = bus_dispatch_test_check_override;
+
+ result = check_existent_service_auto_start (context, foo);
+
+ _dbus_assert (cdata->check_counter == 2);
+ _dbus_assert (cdata->timedout);
+ _dbus_assert (cdata->timeout);
+ _dbus_assert (!cdata->connection);
+ _dbus_timeout_unref (cdata->timeout);
+
+ kill_client_connection_unchecked (foo);
+
+ bus_context_unref (context);
+
+ return result;
+}
+
dbus_bool_t
bus_dispatch_test (const DBusString *test_data_dir)
{
+ _dbus_verbose ("<check> tests\n");
+ if (!bus_dispatch_test_check (test_data_dir))
+ return FALSE;
+
/* run normal activation tests */
_dbus_verbose ("Normal activation tests\n");
if (!bus_dispatch_test_conf (test_data_dir,
dbus_bool_t bus_dispatch_add_connection (DBusConnection *connection);
void bus_dispatch_remove_connection (DBusConnection *connection);
-dbus_bool_t bus_dispatch_matches (BusTransaction *transaction,
+BusResult bus_dispatch_matches (BusTransaction *transaction,
DBusConnection *sender,
DBusConnection *recipient,
DBusMessage *message,
if (!bus_transaction_capture (transaction, NULL, message))
goto oom;
- retval = bus_dispatch_matches (transaction, NULL, NULL, message, error);
+ switch (bus_dispatch_matches (transaction, NULL, NULL, message, error))
+ {
+ case BUS_RESULT_TRUE:
+ retval = TRUE;
+ break;
+ case BUS_RESULT_FALSE:
+ retval = FALSE;
+ break;
+ default:
+ /* should never happen */
+ _dbus_assert_not_reached ("bus_dispatch_matches returned BUS_RESULT_LATER unexpectedly");
+ retval = FALSE;
+ break;
+ }
dbus_message_unref (message);
return retval;
dbus_bool_t _dbus_connection_get_linux_security_label (DBusConnection *connection,
char **label_p);
+DBUS_PRIVATE_EXPORT
+void _dbus_connection_enable_dispatch (DBusConnection *connection);
+DBUS_PRIVATE_EXPORT
+void _dbus_connection_disable_dispatch (DBusConnection *connection);
+DBUS_PRIVATE_EXPORT
+dbus_bool_t _dbus_connection_putback_message (DBusConnection *connection,
+ DBusMessage *after_message,
+ DBusMessage *message,
+ DBusError *error);
+
+DBUS_PRIVATE_EXPORT
+dbus_bool_t _dbus_connection_remove_message (DBusConnection *connection,
+ DBusMessage *message);
+
/* if DBUS_ENABLE_STATS */
DBUS_PRIVATE_EXPORT
void _dbus_connection_get_stats (DBusConnection *connection,
*/
dbus_bool_t dispatch_acquired; /**< Someone has dispatch path (can drain incoming queue) */
dbus_bool_t io_path_acquired; /**< Someone has transport io path (can use the transport to read/write messages) */
-
+
+ unsigned int dispatch_disabled : 1; /**< if true, then dispatching incoming messages is stopped until enabled again */
unsigned int shareable : 1; /**< #TRUE if libdbus owns a reference to the connection and can return it from dbus_connection_open() more than once */
unsigned int exit_on_disconnect : 1; /**< If #TRUE, exit after handling disconnect signal */
(*connection->wakeup_main_function) (connection->wakeup_main_data);
}
+static void
+_dbus_connection_set_dispatch(DBusConnection *connection,
+ dbus_bool_t disabled)
+{
+ CONNECTION_LOCK (connection);
+ if (connection->dispatch_disabled != disabled)
+ {
+ DBusDispatchStatus status;
+
+ connection->dispatch_disabled = disabled;
+ status = _dbus_connection_get_dispatch_status_unlocked (connection);
+ _dbus_connection_update_dispatch_status_and_unlock (connection, status);
+ }
+ else
+ {
+ CONNECTION_UNLOCK (connection);
+ }
+}
+
+
+void
+_dbus_connection_enable_dispatch (DBusConnection *connection)
+{
+ _dbus_connection_set_dispatch (connection, FALSE);
+}
+
+void
+ _dbus_connection_disable_dispatch (DBusConnection *connection)
+{
+ _dbus_connection_set_dispatch (connection, TRUE);
+}
+
+
#ifdef DBUS_ENABLE_EMBEDDED_TESTS
/**
* Gets the locks so we can examine them
"_dbus_connection_putback_message_link_unlocked");
}
+dbus_bool_t
+_dbus_connection_putback_message (DBusConnection *connection,
+ DBusMessage *after_message,
+ DBusMessage *message,
+ DBusError *error)
+{
+ DBusDispatchStatus status;
+ DBusList *message_link = _dbus_list_alloc_link (message);
+ DBusList *after_link;
+ if (message_link == NULL)
+ {
+ _DBUS_SET_OOM (error);
+ return FALSE;
+ }
+ dbus_message_ref (message);
+
+ CONNECTION_LOCK (connection);
+ _dbus_connection_acquire_dispatch (connection);
+ HAVE_LOCK_CHECK (connection);
+
+ after_link = _dbus_list_find_first(&connection->incoming_messages, after_message);
+ _dbus_list_insert_after_link (&connection->incoming_messages, after_link, message_link);
+ connection->n_incoming += 1;
+
+ _dbus_verbose ("Message %p (%s %s %s '%s') put back into queue %p, %d incoming\n",
+ message_link->data,
+ dbus_message_type_to_string (dbus_message_get_type (message_link->data)),
+ dbus_message_get_interface (message_link->data) ?
+ dbus_message_get_interface (message_link->data) :
+ "no interface",
+ dbus_message_get_member (message_link->data) ?
+ dbus_message_get_member (message_link->data) :
+ "no member",
+ dbus_message_get_signature (message_link->data),
+ connection, connection->n_incoming);
+
+ _dbus_message_trace_ref (message_link->data, -1, -1,
+ "_dbus_connection_putback_message");
+
+ _dbus_connection_release_dispatch (connection);
+
+ status = _dbus_connection_get_dispatch_status_unlocked (connection);
+ _dbus_connection_update_dispatch_status_and_unlock (connection, status);
+
+ return TRUE;
+}
+
+dbus_bool_t
+_dbus_connection_remove_message (DBusConnection *connection,
+ DBusMessage *message)
+{
+ DBusDispatchStatus status;
+ dbus_bool_t removed;
+
+ CONNECTION_LOCK (connection);
+ _dbus_connection_acquire_dispatch (connection);
+ HAVE_LOCK_CHECK (connection);
+
+ removed = _dbus_list_remove(&connection->incoming_messages, message);
+
+ if (removed)
+ {
+ connection->n_incoming -= 1;
+ dbus_message_unref(message);
+ _dbus_verbose ("Message %p removed from incoming queue\n", message);
+ }
+ else
+ _dbus_verbose ("Message %p not found in the incoming queue\n", message);
+
+ _dbus_connection_release_dispatch (connection);
+
+ status = _dbus_connection_get_dispatch_status_unlocked (connection);
+ _dbus_connection_update_dispatch_status_and_unlock (connection, status);
+ return removed;
+}
+
/**
* Returns the first-received message from the incoming message queue,
* removing it from the queue. The caller owns a reference to the
_dbus_connection_get_dispatch_status_unlocked (DBusConnection *connection)
{
HAVE_LOCK_CHECK (connection);
-
- if (connection->n_incoming > 0)
+ if (connection->dispatch_disabled && dbus_connection_get_is_connected(connection))
+ return DBUS_DISPATCH_COMPLETE;
+ else if (connection->n_incoming > 0)
return DBUS_DISPATCH_DATA_REMAINS;
else if (!_dbus_transport_queue_messages (connection->transport))
return DBUS_DISPATCH_NEED_MEMORY;
CONNECTION_LOCK (connection);
+ if (result == DBUS_HANDLER_RESULT_LATER)
+ goto out;
if (result == DBUS_HANDLER_RESULT_NEED_MEMORY)
{
_dbus_verbose ("No memory\n");
connection);
out:
- if (result == DBUS_HANDLER_RESULT_NEED_MEMORY)
+ if (result == DBUS_HANDLER_RESULT_LATER ||
+ result == DBUS_HANDLER_RESULT_NEED_MEMORY)
{
- _dbus_verbose ("out of memory\n");
+ if (result == DBUS_HANDLER_RESULT_NEED_MEMORY)
+ _dbus_verbose ("out of memory\n");
/* Put message back, and we'll start over.
* Yes this means handlers must be idempotent if they
}
/**
+ * Finds a value in the list. Returns the first link
+ * with value equal to the given data pointer.
+ * This is a linear-time operation.
+ * Returns #NULL if no value found that matches.
+ *
+ * @param list address of the list head.
+ * @param data the value to find.
+ * @returns the link if found
+ */
+DBusList*
+_dbus_list_find_first (DBusList **list,
+ void *data)
+{
+ DBusList *link;
+
+ link = _dbus_list_get_first_link (list);
+
+ while (link != NULL)
+ {
+ if (link->data == data)
+ return link;
+
+ link = _dbus_list_get_next_link (list, link);
+ }
+
+ return NULL;
+}
+
+/**
* Finds a value in the list. Returns the last link
* with value equal to the given data pointer.
* This is a linear-time operation.
void _dbus_list_remove_link (DBusList **list,
DBusList *link);
DBUS_PRIVATE_EXPORT
+DBusList* _dbus_list_find_first (DBusList **list,
+ void *data);
+DBUS_PRIVATE_EXPORT
DBusList* _dbus_list_find_last (DBusList **list,
void *data);
DBUS_PRIVATE_EXPORT
{
DBUS_HANDLER_RESULT_HANDLED, /**< Message has had its effect - no need to run more handlers. */
DBUS_HANDLER_RESULT_NOT_YET_HANDLED, /**< Message has not had any effect - see if other handlers want it. */
- DBUS_HANDLER_RESULT_NEED_MEMORY /**< Need more memory in order to return #DBUS_HANDLER_RESULT_HANDLED or #DBUS_HANDLER_RESULT_NOT_YET_HANDLED. Please try again later with more memory. */
+ DBUS_HANDLER_RESULT_NEED_MEMORY, /**< Need more memory in order to return #DBUS_HANDLER_RESULT_HANDLED or #DBUS_HANDLER_RESULT_NOT_YET_HANDLED. Please try again later with more memory. */
+ DBUS_HANDLER_RESULT_LATER /**< Message dispatch deferred due to pending policy check */
} DBusHandlerResult;
/* Bus names */