cynara integration: check policy right away for both send and receive
[platform/upstream/dbus.git] / bus / dispatch.c
index 5fc0d11..174e4df 100644 (file)
@@ -25,6 +25,7 @@
 
 #include <config.h>
 #include "dispatch.h"
+#include "check.h"
 #include "connection.h"
 #include "driver.h"
 #include "services.h"
 #include "utils.h"
 #include "bus.h"
 #include "signals.h"
+#include "dispatch.h"
 #include "test.h"
 #include <dbus/dbus-internals.h>
+#include <dbus/dbus-connection-internal.h>
 #include <dbus/dbus-misc.h>
 #include <string.h>
 
  * dbus_connection_open_private() does not block. */
 #define TEST_DEBUG_PIPE "debug-pipe:name=test-server"
 
+static inline const char *
+nonnull (const char *maybe_null,
+         const char *if_null)
+{
+  return (maybe_null ? maybe_null : if_null);
+}
+
 static dbus_bool_t
 send_one_message (DBusConnection *connection,
                   BusContext     *context,
@@ -56,21 +66,61 @@ send_one_message (DBusConnection *connection,
                   BusTransaction *transaction,
                   DBusError      *error)
 {
-  if (!bus_context_check_security_policy (context, transaction,
-                                          sender,
-                                          addressed_recipient,
-                                          connection,
-                                          message,
-                                          NULL))
-    return TRUE; /* silently don't send it */
+  DBusError stack_error = DBUS_ERROR_INIT;
+  BusDeferredMessage *deferred_message = NULL;
+  BusResult result;
+
+  result = bus_context_check_security_policy (context, transaction, sender, addressed_recipient,
+      connection, message, NULL, &stack_error, &deferred_message);
+
+  if (result == BUS_RESULT_FALSE)
+    {
+      if (!bus_transaction_capture_error_reply (transaction, &stack_error,
+                                                message))
+        {
+          bus_context_log (context, DBUS_SYSTEM_LOG_WARNING,
+                           "broadcast rejected, but not enough "
+                           "memory to tell monitors");
+        }
+
+      dbus_error_free (&stack_error);
+      return TRUE; /* don't send it but don't return an error either */
+    }
 
   if (dbus_message_contains_unix_fds(message) &&
       !dbus_connection_can_send_type(connection, DBUS_TYPE_UNIX_FD))
-    return TRUE; /* silently don't send it */
+    {
+      dbus_set_error (&stack_error, DBUS_ERROR_NOT_SUPPORTED,
+                      "broadcast cannot be delivered to %s (%s) because "
+                      "it does not support receiving Unix fds",
+                      bus_connection_get_name (connection),
+                      bus_connection_get_loginfo (connection));
+
+      if (!bus_transaction_capture_error_reply (transaction, &stack_error,
+                                                message))
+        {
+          bus_context_log (context, DBUS_SYSTEM_LOG_WARNING,
+                           "broadcast with Unix fd not delivered, but not "
+                           "enough memory to tell monitors");
+        }
+
+      dbus_error_free (&stack_error);
+      return TRUE; /* don't send it but don't return an error either */
+    }
+
+  if (result == BUS_RESULT_LATER)
+    {
+      if (!bus_deferred_message_queue_at_recipient(deferred_message, transaction, FALSE, FALSE))
+        {
+          BUS_SET_OOM (error);
+          return FALSE;
+        }
+      return TRUE; /* pretend to have sent it */
+    }
 
   if (!bus_transaction_send (transaction,
                              connection,
-                             message))
+                             message, FALSE))
     {
       BUS_SET_OOM (error);
       return FALSE;
@@ -79,12 +129,13 @@ send_one_message (DBusConnection *connection,
   return TRUE;
 }
 
-dbus_bool_t
-bus_dispatch_matches (BusTransaction *transaction,
-                      DBusConnection *sender,
-                      DBusConnection *addressed_recipient,
-                      DBusMessage    *message,
-                      DBusError      *error)
+BusResult
+bus_dispatch_matches (BusTransaction     *transaction,
+                      DBusConnection     *sender,
+                      DBusConnection     *addressed_recipient,
+                      DBusMessage        *message,
+                      BusDeferredMessage *dispatched_deferred_message,
+                      DBusError          *error)
 {
   DBusError tmp_error;
   BusConnections *connections;
@@ -92,6 +143,7 @@ bus_dispatch_matches (BusTransaction *transaction,
   BusMatchmaker *matchmaker;
   DBusList *link;
   BusContext *context;
+  BusDeferredMessage *deferred_message = NULL;
 
   _DBUS_ASSERT_ERROR_IS_CLEAR (error);
 
@@ -99,7 +151,11 @@ bus_dispatch_matches (BusTransaction *transaction,
    * or for signals with no particular recipient
    */
 
-  _dbus_assert (sender == NULL || bus_connection_is_active (sender));
+  /*
+   * With deferred messages, dispatch may happen when there is no sender anymore.
+   * Policy rules involving the sender should be checked before, anyway.
+   */
+/*  _dbus_assert (sender == NULL || bus_connection_is_active (sender)); */
   _dbus_assert (dbus_message_get_sender (message) != NULL);
 
   context = bus_transaction_get_context (transaction);
@@ -107,11 +163,115 @@ bus_dispatch_matches (BusTransaction *transaction,
   /* First, send the message to the addressed_recipient, if there is one. */
   if (addressed_recipient != NULL)
     {
-      if (!bus_context_check_security_policy (context, transaction,
-                                              sender, addressed_recipient,
-                                              addressed_recipient,
-                                              message, error))
-        return FALSE;
+      BusResult result;
+      /* To maintain message order message needs to be appended at the recipient if there are already
+       *  deferred messages and we are not doing deferred dispatch
+       */
+      if (dispatched_deferred_message == NULL && bus_connection_has_deferred_messages(addressed_recipient))
+        {
+          result = bus_context_check_security_policy(context, transaction,
+              sender, addressed_recipient, addressed_recipient, message, NULL, error,
+              &deferred_message);
+
+          if (deferred_message == NULL)
+            deferred_message = bus_deferred_message_new(message, sender,
+                  addressed_recipient, addressed_recipient, result);
+          else
+            bus_deferred_message_ref(deferred_message);
+
+          if (deferred_message == NULL)
+            {
+              BUS_SET_OOM(error);
+              return BUS_RESULT_FALSE;
+            }
+
+          if (!bus_deferred_message_queue_at_recipient(deferred_message, transaction, TRUE, FALSE))
+            {
+              bus_deferred_message_unref(deferred_message);
+              BUS_SET_OOM(error);
+              return BUS_RESULT_FALSE;
+            }
+
+          bus_deferred_message_unref(deferred_message);
+          return BUS_RESULT_TRUE; /* pretend to have sent it */
+        }
+
+      if (dispatched_deferred_message != NULL)
+        {
+          result = bus_deferred_message_get_response(dispatched_deferred_message);
+          if (result == BUS_RESULT_TRUE)
+            {
+              /* if we know the result of policy check we still need to check if message limits
+               * are not exceeded. It is also required to add entry in expected replies list if
+               * this is a method call
+               */
+              if (!bus_deferred_message_check_message_limits(dispatched_deferred_message, error))
+                return BUS_RESULT_FALSE;
+
+              if (!bus_deferred_message_expect_method_reply(dispatched_deferred_message, transaction, error))
+                return BUS_RESULT_FALSE;
+            }
+          else if (result == BUS_RESULT_FALSE)
+            {
+              bus_deferred_message_create_error(dispatched_deferred_message, "Rejected message", error);
+              return BUS_RESULT_FALSE;
+            }
+        }
+      else
+        result = BUS_RESULT_LATER;
+
+      if (result == BUS_RESULT_LATER)
+        result = bus_context_check_security_policy(context, transaction,
+            sender, addressed_recipient, addressed_recipient, message, NULL, error,
+            &deferred_message);
+
+      switch (result)
+        {
+        case BUS_RESULT_TRUE:
+          break;
+        case BUS_RESULT_FALSE:
+          return BUS_RESULT_FALSE;
+        case BUS_RESULT_LATER:
+          {
+            BusDeferredMessageStatus status;
+
+            if (dispatched_deferred_message != NULL)
+              {
+                /* for deferred dispatch prepend message at the recipient */
+                if (!bus_deferred_message_queue_at_recipient(deferred_message, transaction, TRUE, TRUE))
+                  {
+                    BUS_SET_OOM(error);
+                    return BUS_RESULT_FALSE;
+                  }
+                return BUS_RESULT_TRUE; /* pretend to have sent it */
+              }
+
+            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)
+              {
+                /* receive rule result not available - queue message at the recipient */
+                if (!bus_deferred_message_queue_at_recipient(deferred_message, transaction, TRUE, FALSE))
+                  {
+                    BUS_SET_OOM(error);
+                    return BUS_RESULT_FALSE;
+                  }
+
+                return BUS_RESULT_TRUE; /* pretend to have sent it */
+              }
+            else
+            {
+                _dbus_verbose("deferred message has no status field set unexpectedly\n");
+                return BUS_RESULT_FALSE;
+            }
+          }
+        }
 
       if (dbus_message_contains_unix_fds (message) &&
           !dbus_connection_can_send_type (addressed_recipient,
@@ -121,19 +281,20 @@ bus_dispatch_matches (BusTransaction *transaction,
                           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))
+      if (!bus_transaction_send(transaction, addressed_recipient, message,
+          dispatched_deferred_message != NULL ? TRUE : FALSE))
         {
           BUS_SET_OOM (error);
-          return FALSE;
+          return BUS_RESULT_FALSE;
         }
     }
 
   /* Now dispatch to others who look interested in this message */
-  connections = bus_transaction_get_connections (transaction);
+  connections = bus_context_get_connections (context);
   dbus_error_init (&tmp_error);
   matchmaker = bus_context_get_matchmaker (context);
 
@@ -143,7 +304,7 @@ bus_dispatch_matches (BusTransaction *transaction,
                                       &recipients))
     {
       BUS_SET_OOM (error);
-      return FALSE;
+      return BUS_RESULT_FALSE;
     }
 
   link = _dbus_list_get_first_link (&recipients);
@@ -165,10 +326,10 @@ bus_dispatch_matches (BusTransaction *transaction,
   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
@@ -200,6 +361,54 @@ bus_dispatch (DBusConnection *connection,
   /* Ref connection in case we disconnect it at some point in here */
   dbus_connection_ref (connection);
 
+  /* Monitors aren't meant to send messages to us. */
+  if (bus_connection_is_monitor (connection))
+    {
+      sender = bus_connection_get_name (connection);
+
+      /* should never happen */
+      if (sender == NULL)
+        sender = "(unknown)";
+
+      if (dbus_message_is_signal (message,
+                                  DBUS_INTERFACE_LOCAL,
+                                  "Disconnected"))
+        {
+          bus_context_log (context, DBUS_SYSTEM_LOG_INFO,
+                           "Monitoring connection %s closed.", sender);
+          bus_connection_disconnected (connection);
+          goto out;
+        }
+      else
+        {
+          /* Monitors are not allowed to send messages, because that
+           * probably indicates that the monitor is incorrectly replying
+           * to its eavesdropped messages, and we want the authors of
+           * such monitors to fix them.
+           */
+          bus_context_log (context, DBUS_SYSTEM_LOG_WARNING,
+                           "Monitoring connection %s (%s) is not allowed "
+                           "to send messages; closing it. Please fix the "
+                           "monitor to not do that. "
+                           "(message type=\"%s\" interface=\"%s\" "
+                           "member=\"%s\" error name=\"%s\" "
+                           "destination=\"%s\")",
+                           sender, bus_connection_get_loginfo (connection),
+                           dbus_message_type_to_string (
+                             dbus_message_get_type (message)),
+                           nonnull (dbus_message_get_interface (message),
+                                    "(unset)"),
+                           nonnull (dbus_message_get_member (message),
+                                    "(unset)"),
+                           nonnull (dbus_message_get_error_name (message),
+                                    "(unset)"),
+                           nonnull (dbus_message_get_destination (message),
+                                    DBUS_SERVICE_DBUS));
+          dbus_connection_close (connection);
+          goto out;
+        }
+    }
+
   service_name = dbus_message_get_destination (message);
 
 #ifdef DBUS_ENABLE_VERBOSE_MODE
@@ -236,6 +445,10 @@ bus_dispatch (DBusConnection *connection,
         {
           /* DBusConnection also handles some of these automatically, we leave
            * it to do so.
+           *
+           * FIXME: this means monitors won't get the opportunity to see
+           * non-signals with NULL destination, or their replies (which in
+           * practice are UnknownMethod errors)
            */
           result = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
           goto out;
@@ -261,28 +474,70 @@ bus_dispatch (DBusConnection *connection,
           BUS_SET_OOM (&error);
           goto out;
         }
+    }
+  else
+    {
+      /* For monitors' benefit: we don't want the sender to be able to
+       * trick the monitor by supplying a forged sender, and we also
+       * don't want the message to have no sender at all. */
+      if (!dbus_message_set_sender (message, ":not.active.yet"))
+        {
+          BUS_SET_OOM (&error);
+          goto out;
+        }
+    }
 
-      /* We need to refetch the service name here, because
-       * dbus_message_set_sender can cause the header to be
-       * reallocated, and thus the service_name pointer will become
-       * invalid.
-       */
-      service_name = dbus_message_get_destination (message);
+  /* We need to refetch the service name here, because
+   * dbus_message_set_sender can cause the header to be
+   * reallocated, and thus the service_name pointer will become
+   * invalid.
+   */
+  service_name = dbus_message_get_destination (message);
+
+  if (!bus_transaction_capture (transaction, connection, message))
+    {
+      BUS_SET_OOM (&error);
+      goto out;
     }
 
   if (service_name &&
       strcmp (service_name, DBUS_SERVICE_DBUS) == 0) /* to bus driver */
     {
-      if (!bus_context_check_security_policy (context, transaction,
-                                              connection, NULL, NULL, message, &error))
+      BusDeferredMessage *deferred_message = NULL;
+
+      switch (bus_context_check_security_policy (context, transaction,
+                                                 connection, NULL, NULL, message,
+                                                 NULL, &error,
+                                                 &deferred_message))
         {
+        case BUS_RESULT_TRUE:
+          break;
+        case BUS_RESULT_FALSE:
           _dbus_verbose ("Security policy rejected message\n");
           goto out;
+        case BUS_RESULT_LATER:
+        /* 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;
         }
 
       _dbus_verbose ("Giving message to %s\n", DBUS_SERVICE_DBUS);
-      if (!bus_driver_handle_message (connection, transaction, message, &error))
-        goto out;
+      switch (bus_driver_handle_message (connection, transaction, message, &error))
+        {
+        case BUS_RESULT_TRUE:
+          break;
+        case BUS_RESULT_FALSE:
+          goto out;
+        case BUS_RESULT_LATER:
+          bus_transaction_cancel_and_free (transaction);
+          transaction = NULL;
+          result = DBUS_HANDLER_RESULT_LATER;
+          goto out;
+        }
     }
   else if (!bus_connection_is_active (connection)) /* clients must talk to bus driver first */
     {
@@ -306,18 +561,31 @@ bus_dispatch (DBusConnection *connection,
       if (service == NULL && dbus_message_get_auto_start (message))
         {
           BusActivation *activation;
-          /* We can't do the security policy check here, since the addressed
-           * recipient service doesn't exist yet. We do it before sending the
-           * message after the service has been created.
-           */
+          BusDeferredMessage *deferred_message = NULL;
+
           activation = bus_connection_get_activation (connection);
 
-          if (!bus_activation_activate_service (activation, connection, transaction, TRUE,
-                                                message, service_name, &error))
+          /* This will do as much of a security policy check as it can.
+           * We can't do the full security policy check here, since the
+           * addressed recipient service doesn't exist yet. We do it before
+           * sending the message after the service has been created.
+           */
+          switch (bus_activation_activate_service (activation, connection, transaction, TRUE,
+                                                message, service_name, &error,
+                                                &deferred_message))
             {
+            case BUS_RESULT_FALSE:
               _DBUS_ASSERT_ERROR_IS_SET (&error);
               _dbus_verbose ("bus_activation_activate_service() failed: %s\n", error.name);
-              goto out;
+              break;
+            case BUS_RESULT_LATER:
+              bus_deferred_message_disable_sender(deferred_message);
+              bus_transaction_cancel_and_free (transaction);
+              transaction = NULL;
+              result = DBUS_HANDLER_RESULT_LATER;
+              break;
+            case BUS_RESULT_TRUE:
+              break;
             }
 
           goto out;
@@ -341,20 +609,26 @@ bus_dispatch (DBusConnection *connection,
    * 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, NULL, &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))
     {
-      if (!dbus_connection_get_is_connected (connection))
-        {
-          /* If we disconnected it, we won't bother to send it any error
-           * messages.
-           */
-          _dbus_verbose ("Not sending error to connection we disconnected\n");
-        }
-      else if (dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY))
+      /* Even if we disconnected it, pretend to send it any pending error
+       * messages so that monitors can observe them.
+       */
+      if (dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY))
         {
           bus_connection_send_oom_error (connection, message);
 
@@ -433,6 +707,8 @@ bus_dispatch_remove_connection (DBusConnection *connection)
 
 #include <stdio.h>
 
+#include "stats.h"
+
 /* This is used to know whether we need to block in order to finish
  * sending a message, or whether the initial dbus_connection_send()
  * already flushed the queue.
@@ -549,6 +825,7 @@ typedef struct
   const char *expected_service_name;
   dbus_bool_t failed;
   DBusConnection *skip_connection;
+  BusContext *context;
 } CheckServiceOwnerChangedData;
 
 static dbus_bool_t
@@ -570,9 +847,14 @@ check_service_owner_changed_foreach (DBusConnection *connection,
   message = pop_message_waiting_for_memory (connection);
   if (message == NULL)
     {
-      _dbus_warn ("Did not receive a message on %p, expecting %s\n",
-                  connection, "NameOwnerChanged");
-      goto out;
+      block_connection_until_message_from_bus (d->context, connection, "NameOwnerChanged");
+      message = pop_message_waiting_for_memory (connection);
+      if (message == NULL)
+        {
+          _dbus_warn ("Did not receive a message on %p, expecting %s\n",
+                      connection, "NameOwnerChanged");
+          goto out;
+        }
     }
   else if (!dbus_message_is_signal (message,
                                     DBUS_INTERFACE_DBUS,
@@ -685,6 +967,7 @@ kill_client_connection (BusContext     *context,
   socd.expected_service_name = base_service;
   socd.failed = FALSE;
   socd.skip_connection = NULL;
+  socd.context = context;
 
   bus_test_clients_foreach (check_service_owner_changed_foreach,
                             &socd);
@@ -837,8 +1120,6 @@ check_hello_message (BusContext     *context,
       return TRUE;
     }
 
-  dbus_connection_unref (connection);
-
   message = pop_message_waiting_for_memory (connection);
   if (message == NULL)
     {
@@ -915,6 +1196,8 @@ check_hello_message (BusContext     *context,
       socd.expected_service_name = name;
       socd.failed = FALSE;
       socd.skip_connection = connection; /* we haven't done AddMatch so won't get it ourselves */
+      socd.context = context;
+
       bus_test_clients_foreach (check_service_owner_changed_foreach,
                                 &socd);
 
@@ -927,9 +1210,14 @@ check_hello_message (BusContext     *context,
       message = pop_message_waiting_for_memory (connection);
       if (message == NULL)
         {
-          _dbus_warn ("Expecting %s, got nothing\n",
+          block_connection_until_message_from_bus (context, connection, "signal NameAcquired");
+          message = pop_message_waiting_for_memory (connection);
+          if (message == NULL)
+            {
+              _dbus_warn ("Expecting %s, got nothing\n",
                       "NameAcquired");
-          goto out;
+              goto out;
+            }
         }
       if (! dbus_message_is_signal (message, DBUS_INTERFACE_DBUS,
                                     "NameAcquired"))
@@ -986,6 +1274,8 @@ check_hello_message (BusContext     *context,
   if (name_message)
     dbus_message_unref (name_message);
 
+  dbus_connection_unref (connection);
+
   return retval;
 }
 
@@ -1159,6 +1449,12 @@ check_get_connection_unix_user (BusContext     *context,
         {
           ; /* good, this is a valid response */
         }
+#ifdef DBUS_WIN
+      else if (dbus_message_is_error (message, DBUS_ERROR_FAILED))
+        {
+          /* this is OK, Unix uids aren't meaningful on Windows */
+        }
+#endif
       else
         {
           warn_unexpected (connection, message, "not this error");
@@ -1228,7 +1524,9 @@ check_get_connection_unix_process_id (BusContext     *context,
   dbus_bool_t retval;
   DBusError error;
   const char *base_service_name;
+#ifdef DBUS_UNIX
   dbus_uint32_t pid;
+#endif
 
   retval = FALSE;
   dbus_error_init (&error);
@@ -1310,6 +1608,11 @@ check_get_connection_unix_process_id (BusContext     *context,
 #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || \
           defined(__linux__) || \
           defined(__OpenBSD__)
+          /* In principle NetBSD should also be in that list, but
+           * its implementation of PID-passing doesn't work
+           * over a socketpair() as used in the debug-pipe transport.
+           * We test this functionality in a more realistic situation
+           * in test/dbus-daemon.c. */
           warn_unexpected (connection, message, "not this error");
 
           goto out;
@@ -1393,20 +1696,21 @@ check_get_connection_unix_process_id (BusContext     *context,
  * but the correct thing may include OOM errors.
  */
 static dbus_bool_t
-check_add_match_all (BusContext     *context,
-                     DBusConnection *connection)
+check_add_match (BusContext     *context,
+                 DBusConnection *connection,
+                 const char     *rule)
 {
   DBusMessage *message;
   dbus_bool_t retval;
   dbus_uint32_t serial;
   DBusError error;
-  const char *empty = "";
 
   retval = FALSE;
   dbus_error_init (&error);
   message = NULL;
 
-  _dbus_verbose ("check_add_match_all for %p\n", connection);
+  _dbus_verbose ("check_add_match for connection %p, rule %s\n",
+                 connection, rule);
 
   message = dbus_message_new_method_call (DBUS_SERVICE_DBUS,
                                           DBUS_PATH_DBUS,
@@ -1416,8 +1720,7 @@ check_add_match_all (BusContext     *context,
   if (message == NULL)
     return TRUE;
 
-  /* empty string match rule matches everything */
-  if (!dbus_message_append_args (message, DBUS_TYPE_STRING, &empty,
+  if (!dbus_message_append_args (message, DBUS_TYPE_STRING, &rule,
                                  DBUS_TYPE_INVALID))
     {
       dbus_message_unref (message);
@@ -1521,6 +1824,132 @@ check_add_match_all (BusContext     *context,
   return retval;
 }
 
+#ifdef DBUS_ENABLE_STATS
+/* returns TRUE if the correct thing happens,
+ * but the correct thing may include OOM errors.
+ */
+static dbus_bool_t
+check_get_all_match_rules (BusContext     *context,
+                           DBusConnection *connection)
+{
+  DBusMessage *message;
+  dbus_bool_t retval;
+  dbus_uint32_t serial;
+  DBusError error;
+
+  retval = FALSE;
+  dbus_error_init (&error);
+  message = NULL;
+
+  _dbus_verbose ("check_get_all_match_rules for connection %p\n",
+                 connection);
+
+  message = dbus_message_new_method_call (DBUS_SERVICE_DBUS,
+                                          DBUS_PATH_DBUS,
+                                          BUS_INTERFACE_STATS,
+                                          "GetAllMatchRules");
+
+  if (message == NULL)
+    return TRUE;
+
+  if (!dbus_connection_send (connection, message, &serial))
+    {
+      dbus_message_unref (message);
+      return TRUE;
+    }
+
+  dbus_message_unref (message);
+  message = NULL;
+
+  dbus_connection_ref (connection); /* because we may get disconnected */
+
+  /* send our message */
+  bus_test_run_clients_loop (SEND_PENDING (connection));
+
+  if (!dbus_connection_get_is_connected (connection))
+    {
+      _dbus_verbose ("connection was disconnected\n");
+
+      dbus_connection_unref (connection);
+
+      return TRUE;
+    }
+
+  block_connection_until_message_from_bus (context, connection, "reply to AddMatch");
+
+  if (!dbus_connection_get_is_connected (connection))
+    {
+      _dbus_verbose ("connection was disconnected\n");
+
+      dbus_connection_unref (connection);
+
+      return TRUE;
+    }
+
+  dbus_connection_unref (connection);
+
+  message = pop_message_waiting_for_memory (connection);
+  if (message == NULL)
+    {
+      _dbus_warn ("Did not receive a reply to %s %d on %p\n",
+                  "AddMatch", serial, connection);
+      goto out;
+    }
+
+  verbose_message_received (connection, message);
+
+  if (!dbus_message_has_sender (message, DBUS_SERVICE_DBUS))
+    {
+      _dbus_warn ("Message has wrong sender %s\n",
+                  dbus_message_get_sender (message) ?
+                  dbus_message_get_sender (message) : "(none)");
+      goto out;
+    }
+
+  if (dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_ERROR)
+    {
+      if (dbus_message_is_error (message,
+                                 DBUS_ERROR_NO_MEMORY))
+        {
+          ; /* good, this is a valid response */
+        }
+      else
+        {
+          warn_unexpected (connection, message, "not this error");
+
+          goto out;
+        }
+    }
+  else
+    {
+      if (dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_METHOD_RETURN)
+        {
+          ; /* good, expected */
+          _dbus_assert (dbus_message_get_reply_serial (message) == serial);
+        }
+      else
+        {
+          warn_unexpected (connection, message, "method return for AddMatch");
+
+          goto out;
+        }
+    }
+
+  if (!check_no_leftovers (context))
+    goto out;
+
+  retval = TRUE;
+
+ out:
+  dbus_error_free (&error);
+
+  if (message)
+    dbus_message_unref (message);
+
+  return retval;
+}
+#endif
+
 /* returns TRUE if the correct thing happens,
  * but the correct thing may include OOM errors.
  */
@@ -1561,7 +1990,7 @@ check_hello_connection (BusContext *context)
     }
   else
     {
-      if (!check_add_match_all (context, connection))
+      if (!check_add_match (context, connection, ""))
         return FALSE;
 
       kill_client_connection (context, connection);
@@ -1849,6 +2278,8 @@ check_base_service_activated (BusContext     *context,
       socd.expected_service_name = base_service;
       socd.failed = FALSE;
       socd.skip_connection = connection;
+      socd.context = context;
+
       bus_test_clients_foreach (check_service_owner_changed_foreach,
                                 &socd);
 
@@ -1953,6 +2384,8 @@ check_service_activated (BusContext     *context,
       socd.skip_connection = connection;
       socd.failed = FALSE;
       socd.expected_service_name = service_name;
+      socd.context = context;
+
       bus_test_clients_foreach (check_service_owner_changed_foreach,
                                 &socd);
 
@@ -2090,6 +2523,8 @@ check_service_auto_activated (BusContext     *context,
       socd.expected_service_name = service_name;
       socd.failed = FALSE;
       socd.skip_connection = connection;
+      socd.context = context;
+
       bus_test_clients_foreach (check_service_owner_changed_foreach,
                                 &socd);
 
@@ -2139,6 +2574,8 @@ check_service_deactivated (BusContext     *context,
   socd.expected_service_name = activated_name;
   socd.failed = FALSE;
   socd.skip_connection = NULL;
+  socd.context = context;
+
   bus_test_clients_foreach (check_service_owner_changed_foreach,
                             &socd);
 
@@ -2149,6 +2586,8 @@ check_service_deactivated (BusContext     *context,
   socd.expected_service_name = base_service;
   socd.failed = FALSE;
   socd.skip_connection = NULL;
+  socd.context = context;
+
   bus_test_clients_foreach (check_service_owner_changed_foreach,
                             &socd);
 
@@ -2596,6 +3035,7 @@ check_existent_service_no_auto_start (BusContext     *context,
             socd.expected_service_name = base_service;
             socd.failed = FALSE;
             socd.skip_connection = NULL;
+            socd.context = context;
 
             bus_test_clients_foreach (check_service_owner_changed_foreach,
                                       &socd);
@@ -2664,7 +3104,6 @@ check_existent_service_no_auto_start (BusContext     *context,
   return retval;
 }
 
-#ifndef DBUS_WIN_FIXME
 /* returns TRUE if the correct thing happens,
  * but the correct thing may include OOM errors.
  */
@@ -2753,11 +3192,19 @@ check_segfault_service_no_auto_start (BusContext     *context,
           /* make sure this only happens with the launch helper */
           _dbus_assert (servicehelper != NULL);
         }
+#ifdef DBUS_WIN
+      else if (dbus_message_is_error (message,
+                                      DBUS_ERROR_SPAWN_CHILD_EXITED))
+        {
+          /* unhandled exceptions are normal exit codes */
+        }
+#else
       else if (dbus_message_is_error (message,
                                       DBUS_ERROR_SPAWN_CHILD_SIGNALED))
         {
           ; /* good, this is expected also */
         }
+#endif
       else
         {
           warn_unexpected (connection, message, "not this error");
@@ -2846,11 +3293,19 @@ check_segfault_service_auto_start (BusContext     *context,
         {
           ; /* good, this is a valid response */
         }
+#ifdef DBUS_WIN
+      else if (dbus_message_is_error (message,
+                                      DBUS_ERROR_SPAWN_CHILD_EXITED))
+        {
+          /* unhandled exceptions are normal exit codes */
+        }
+#else
       else if (dbus_message_is_error (message,
                                       DBUS_ERROR_SPAWN_CHILD_SIGNALED))
         {
           ; /* good, this is expected also */
         }
+#endif
       else
         {
           warn_unexpected (connection, message, "not this error");
@@ -2872,7 +3327,6 @@ check_segfault_service_auto_start (BusContext     *context,
 
   return retval;
 }
-#endif
 
 #define TEST_ECHO_MESSAGE "Test echo message"
 #define TEST_RUN_HELLO_FROM_SELF_MESSAGE "Test sending message to self"
@@ -3216,6 +3670,8 @@ check_existent_service_auto_start (BusContext     *context,
             socd.expected_service_name = base_service;
             socd.failed = FALSE;
             socd.skip_connection = NULL;
+            socd.context = context;
+
             bus_test_clients_foreach (check_service_owner_changed_foreach,
                                       &socd);
 
@@ -3904,6 +4360,8 @@ check_shell_service_success_auto_start (BusContext     *context,
             socd.expected_service_name = base_service;
             socd.failed = FALSE;
             socd.skip_connection = NULL;
+            socd.context = context;
+
             bus_test_clients_foreach (check_service_owner_changed_foreach,
                                       &socd);
 
@@ -4516,7 +4974,7 @@ bus_dispatch_test_conf (const DBusString *test_data_dir,
   if (!check_double_hello_message (context, foo))
     _dbus_assert_not_reached ("double hello message failed");
 
-  if (!check_add_match_all (context, foo))
+  if (!check_add_match (context, foo, ""))
     _dbus_assert_not_reached ("AddMatch message failed");
 
   bar = dbus_connection_open_private (TEST_DEBUG_PIPE, &error);
@@ -4531,7 +4989,7 @@ bus_dispatch_test_conf (const DBusString *test_data_dir,
   if (!check_hello_message (context, bar))
     _dbus_assert_not_reached ("hello message failed");
 
-  if (!check_add_match_all (context, bar))
+  if (!check_add_match (context, bar, ""))
     _dbus_assert_not_reached ("AddMatch message failed");
 
   baz = dbus_connection_open_private (TEST_DEBUG_PIPE, &error);
@@ -4546,16 +5004,23 @@ bus_dispatch_test_conf (const DBusString *test_data_dir,
   if (!check_hello_message (context, baz))
     _dbus_assert_not_reached ("hello message failed");
 
-  if (!check_add_match_all (context, baz))
+  if (!check_add_match (context, baz, ""))
     _dbus_assert_not_reached ("AddMatch message failed");
 
-#ifdef DBUS_WIN_FIXME
-  _dbus_warn("TODO: testing of GetConnectionUnixUser message skipped for now\n");
-  _dbus_warn("TODO: testing of GetConnectionUnixProcessID message skipped for now\n");
-#else
+  if (!check_add_match (context, baz, "interface='com.example'"))
+    _dbus_assert_not_reached ("AddMatch message failed");
+
+#ifdef DBUS_ENABLE_STATS
+  if (!check_get_all_match_rules (context, baz))
+    _dbus_assert_not_reached ("GetAllMatchRules message failed");
+#endif
+
   if (!check_get_connection_unix_user (context, baz))
     _dbus_assert_not_reached ("GetConnectionUnixUser message failed");
 
+#ifdef DBUS_WIN_FIXME
+  _dbus_verbose("TODO: testing of GetConnectionUnixProcessID message skipped for now\n");
+#else
   if (!check_get_connection_unix_process_id (context, baz))
     _dbus_assert_not_reached ("GetConnectionUnixProcessID message failed");
 #endif
@@ -4575,12 +5040,8 @@ bus_dispatch_test_conf (const DBusString *test_data_dir,
   check2_try_iterations (context, foo, "nonexistent_service_no_auto_start",
                          check_nonexistent_service_no_auto_start);
 
-#ifdef DBUS_WIN_FIXME
-  _dbus_warn("TODO: dispatch.c segfault_service_no_auto_start test\n");
-#else
   check2_try_iterations (context, foo, "segfault_service_no_auto_start",
                          check_segfault_service_no_auto_start);
-#endif
 
   check2_try_iterations (context, foo, "existent_service_no_auto_start",
                          check_existent_service_no_auto_start);
@@ -4588,14 +5049,9 @@ bus_dispatch_test_conf (const DBusString *test_data_dir,
   check2_try_iterations (context, foo, "nonexistent_service_auto_start",
                          check_nonexistent_service_auto_start);
 
-
-#ifdef DBUS_WIN_FIXME
-  _dbus_warn("TODO: dispatch.c segfault_service_auto_start test\n");
-#else
   /* only do the segfault test if we are not using the launcher */
   check2_try_iterations (context, foo, "segfault_service_auto_start",
                          check_segfault_service_auto_start);
-#endif
 
   /* only do the shell fail test if we are not using the launcher */
   check2_try_iterations (context, foo, "shell_fail_service_auto_start",
@@ -4665,7 +5121,7 @@ bus_dispatch_test_conf_fail (const DBusString *test_data_dir,
   if (!check_double_hello_message (context, foo))
     _dbus_assert_not_reached ("double hello message failed");
 
-  if (!check_add_match_all (context, foo))
+  if (!check_add_match (context, foo, ""))
     _dbus_assert_not_reached ("AddMatch message failed");
 
   /* this only tests the activation.c user check */
@@ -4689,18 +5145,139 @@ bus_dispatch_test_conf_fail (const DBusString *test_data_dir,
   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,
                               "valid-config-files/debug-allow-all.conf", FALSE))
     return FALSE;
 
-#ifdef DBUS_WIN
-  _dbus_warn("Info: Launch helper activation tests skipped because launch-helper is not supported yet\n");
-#else
+#ifndef DBUS_WIN
   /* run launch-helper activation tests */
   _dbus_verbose ("Launch helper activation tests\n");
   if (!bus_dispatch_test_conf (test_data_dir,
@@ -4745,7 +5322,7 @@ bus_dispatch_sha1_test (const DBusString *test_data_dir)
   if (!check_hello_message (context, foo))
     _dbus_assert_not_reached ("hello message failed");
 
-  if (!check_add_match_all (context, foo))
+  if (!check_add_match (context, foo, ""))
     _dbus_assert_not_reached ("addmatch message failed");
 
   if (!check_no_leftovers (context))
@@ -4773,7 +5350,8 @@ bus_unix_fds_passing_test(const DBusString *test_data_dir)
   DBusConnection *foo, *bar;
   DBusError error;
   DBusMessage *m;
-  int one[2], two[2], x, y, z;
+  DBusSocket one[2], two[2];
+  int x, y, z;
   char r;
 
   dbus_error_init (&error);
@@ -4794,7 +5372,7 @@ bus_unix_fds_passing_test(const DBusString *test_data_dir)
   if (!check_hello_message (context, foo))
     _dbus_assert_not_reached ("hello message failed");
 
-  if (!check_add_match_all (context, foo))
+  if (!check_add_match (context, foo, ""))
     _dbus_assert_not_reached ("AddMatch message failed");
 
   bar = dbus_connection_open_private (TEST_DEBUG_PIPE, &error);
@@ -4809,16 +5387,16 @@ bus_unix_fds_passing_test(const DBusString *test_data_dir)
   if (!check_hello_message (context, bar))
     _dbus_assert_not_reached ("hello message failed");
 
-  if (!check_add_match_all (context, bar))
+  if (!check_add_match (context, bar, ""))
     _dbus_assert_not_reached ("AddMatch message failed");
 
   if (!(m = dbus_message_new_signal("/", "a.b.c", "d")))
     _dbus_assert_not_reached ("could not alloc message");
 
-  if (!(_dbus_full_duplex_pipe(one, one+1, TRUE, &error)))
+  if (!(_dbus_socketpair (one, one+1, TRUE, &error)))
     _dbus_assert_not_reached("Failed to allocate pipe #1");
 
-  if (!(_dbus_full_duplex_pipe(two, two+1, TRUE, &error)))
+  if (!(_dbus_socketpair (two, two+1, TRUE, &error)))
     _dbus_assert_not_reached("Failed to allocate pipe #2");
 
   if (!dbus_message_append_args(m,
@@ -4828,9 +5406,9 @@ bus_unix_fds_passing_test(const DBusString *test_data_dir)
                                 DBUS_TYPE_INVALID))
     _dbus_assert_not_reached("Failed to attach fds.");
 
-  if (!_dbus_close(one[0], &error))
+  if (!_dbus_close_socket (one[0], &error))
     _dbus_assert_not_reached("Failed to close pipe #1 ");
-  if (!_dbus_close(two[0], &error))
+  if (!_dbus_close_socket (two[0], &error))
     _dbus_assert_not_reached("Failed to close pipe #2 ");
 
   if (!(dbus_connection_can_send_type(foo, DBUS_TYPE_UNIX_FD)))
@@ -4890,16 +5468,16 @@ bus_unix_fds_passing_test(const DBusString *test_data_dir)
   if (!_dbus_close(z, &error))
     _dbus_assert_not_reached("Failed to close pipe #2/other size 2nd fd ");
 
-  if (read(one[1], &r, 1) != 1 || r != 'X')
+  if (read(one[1].fd, &r, 1) != 1 || r != 'X')
     _dbus_assert_not_reached("Failed to read value from pipe.");
-  if (read(two[1], &r, 1) != 1 || r != 'Y')
+  if (read(two[1].fd, &r, 1) != 1 || r != 'Y')
     _dbus_assert_not_reached("Failed to read value from pipe.");
-  if (read(two[1], &r, 1) != 1 || r != 'Z')
+  if (read(two[1].fd, &r, 1) != 1 || r != 'Z')
     _dbus_assert_not_reached("Failed to read value from pipe.");
 
-  if (!_dbus_close(one[1], &error))
+  if (!_dbus_close_socket (one[1], &error))
     _dbus_assert_not_reached("Failed to close pipe #1 ");
-  if (!_dbus_close(two[1], &error))
+  if (!_dbus_close_socket (two[1], &error))
     _dbus_assert_not_reached("Failed to close pipe #2 ");
 
   _dbus_verbose ("Disconnecting foo\n");