bus/connection: don't check cmdline in session dbus-daemon
[platform/upstream/dbus.git] / bus / connection.c
index 93f9967..6b85ba3 100644 (file)
 #include "expirelist.h"
 #include "selinux.h"
 #include "apparmor.h"
+#include "check.h"
 #include <dbus/dbus-list.h>
 #include <dbus/dbus-hash.h>
 #include <dbus/dbus-timeout.h>
 #include <dbus/dbus-connection-internal.h>
+#include <dbus/dbus-internals.h>
+#include <dbus/dbus-message-internal.h>
+#ifdef DBUS_ENABLE_CYNARA
+#include <stdlib.h>
+#include <cynara-session.h>
+#include <stdio.h>
+#endif
 
 /* Trim executed commands to this length; we want to keep logs readable */
 #define MAX_LOG_COMMAND_LEN 50
@@ -97,6 +105,7 @@ typedef struct
   DBusMessage *oom_message;
   DBusPreallocatedSend *oom_preallocated;
   BusClientPolicy *policy;
+  DBusList *deferred_messages;  /**< Queue of messages deferred due to pending policy check */
 
   char *cached_loginfo_string;
   BusSELinuxID *selinux_id;
@@ -109,12 +118,16 @@ typedef struct
 #ifdef DBUS_ENABLE_STATS
   int peak_match_rules;
   int peak_bus_names;
+  int peak_pending_replies;
 #endif
   int n_pending_unix_fds;
   DBusTimeout *pending_unix_fds_timeout;
 
   /** non-NULL if and only if this is a monitor */
   DBusList *link_in_monitors;
+#ifdef DBUS_ENABLE_CYNARA
+  char *cynara_session_id;
+#endif
 } BusConnectionData;
 
 static dbus_bool_t bus_pending_reply_expired (BusExpireList *list,
@@ -128,12 +141,13 @@ static dbus_bool_t expire_incomplete_timeout (void *data);
 
 #define BUS_CONNECTION_DATA(connection) (dbus_connection_get_data ((connection), connection_data_slot))
 
-static DBusLoop*
-connection_get_loop (DBusConnection *connection)
+DBusLoop*
+bus_connection_get_loop (DBusConnection *connection)
 {
   BusConnectionData *d;
 
   d = BUS_CONNECTION_DATA (connection);
+  _dbus_assert(d != NULL);
 
   return bus_context_get_loop (d->connections->context);
 }
@@ -259,6 +273,8 @@ bus_connection_disconnected (DBusConnection *connection)
       bus_transaction_execute_and_free (transaction);
     }
 
+  bus_connection_clear_deferred_messages(connection);
+
   bus_dispatch_remove_connection (connection);
   
   /* no more watching */
@@ -352,7 +368,7 @@ add_connection_watch (DBusWatch      *watch,
 {
   DBusConnection *connection = data;
 
-  return _dbus_loop_add_watch (connection_get_loop (connection), watch);
+  return _dbus_loop_add_watch (bus_connection_get_loop (connection), watch);
 }
 
 static void
@@ -361,7 +377,7 @@ remove_connection_watch (DBusWatch      *watch,
 {
   DBusConnection *connection = data;
   
-  _dbus_loop_remove_watch (connection_get_loop (connection), watch);
+  _dbus_loop_remove_watch (bus_connection_get_loop (connection), watch);
 }
 
 static void
@@ -370,7 +386,7 @@ toggle_connection_watch (DBusWatch      *watch,
 {
   DBusConnection *connection = data;
 
-  _dbus_loop_toggle_watch (connection_get_loop (connection), watch);
+  _dbus_loop_toggle_watch (bus_connection_get_loop (connection), watch);
 }
 
 static dbus_bool_t
@@ -379,7 +395,7 @@ add_connection_timeout (DBusTimeout    *timeout,
 {
   DBusConnection *connection = data;
   
-  return _dbus_loop_add_timeout (connection_get_loop (connection), timeout);
+  return _dbus_loop_add_timeout (bus_connection_get_loop (connection), timeout);
 }
 
 static void
@@ -388,7 +404,7 @@ remove_connection_timeout (DBusTimeout    *timeout,
 {
   DBusConnection *connection = data;
   
-  _dbus_loop_remove_timeout (connection_get_loop (connection), timeout);
+  _dbus_loop_remove_timeout (bus_connection_get_loop (connection), timeout);
 }
 
 static void
@@ -439,9 +455,6 @@ free_connection_data (void *data)
   if (d->policy)
     bus_client_policy_unref (d->policy);
 
-  if (d->selinux_id)
-    bus_selinux_id_unref (d->selinux_id);
-
   if (d->apparmor_confinement)
     bus_apparmor_confinement_unref (d->apparmor_confinement);
   
@@ -449,6 +462,10 @@ free_connection_data (void *data)
   
   dbus_free (d->name);
   
+#ifdef DBUS_ENABLE_CYNARA
+  free (d->cynara_session_id);
+#endif
+
   dbus_free (d);
 }
 
@@ -475,7 +492,7 @@ bus_connections_new (BusContext *context)
   if (connections->expire_timeout == NULL)
     goto failed_3;
 
-  _dbus_timeout_set_enabled (connections->expire_timeout, FALSE);
+  _dbus_timeout_disable (connections->expire_timeout);
 
   connections->pending_replies = bus_expire_list_new (bus_context_get_loop (context),
                                                       bus_context_get_reply_timeout (context),
@@ -574,6 +591,13 @@ bus_connections_unref (BusConnections *connections)
     }
 }
 
+static dbus_bool_t
+is_context_type_session (BusConnectionData *d)
+{
+  const char *context_type = bus_context_get_type (d->connections->context);
+  return context_type && !strcmp (context_type, "session");
+}
+
 /* Used for logging */
 static dbus_bool_t
 cache_peer_loginfo_string (BusConnectionData *d, 
@@ -582,7 +606,7 @@ cache_peer_loginfo_string (BusConnectionData *d,
   DBusString loginfo_buf;
   unsigned long uid;
   unsigned long pid;
-  char *windows_sid;
+  char *windows_sid = NULL, *security_label = NULL;
   dbus_bool_t prev_added;
 
   if (!_dbus_string_init (&loginfo_buf))
@@ -607,20 +631,63 @@ cache_peer_loginfo_string (BusConnectionData *d,
       if (!_dbus_string_append_printf (&loginfo_buf, "pid=%ld comm=\"", pid))
         goto oom;
       /* Ignore errors here; we may not have permissions to read the
-       * proc file. */
-      _dbus_command_for_pid (pid, &loginfo_buf, MAX_LOG_COMMAND_LEN, NULL);
+       * proc file.
+       * Don't even try it for the session daemon, to avoid cluttering logs with security error logs for
+       * accessing the proc file.
+       */
+      if (!is_context_type_session(d))
+        {
+          _dbus_command_for_pid (pid, &loginfo_buf, MAX_LOG_COMMAND_LEN, NULL);
+        }
+      else
+        {
+          if (!_dbus_string_append (&loginfo_buf, "<not-read>"))   /* for session daemon just say that we didn't try */
+            goto oom;
+        }
       if (!_dbus_string_append_byte (&loginfo_buf, '"'))
         goto oom;
+      else
+        prev_added = TRUE;
     }
 
   if (dbus_connection_get_windows_user (connection, &windows_sid))
     {
       dbus_bool_t did_append;
+
+      if (prev_added)
+        {
+          if (!_dbus_string_append_byte (&loginfo_buf, ' '))
+            goto oom;
+        }
+
       did_append = _dbus_string_append_printf (&loginfo_buf,
-                                               "sid=\"%s\" ", windows_sid);
+                                               "sid=\"%s\"", windows_sid);
       dbus_free (windows_sid);
+      windows_sid = NULL;
       if (!did_append)
         goto oom;
+      else
+        prev_added = TRUE;
+    }
+
+  if (_dbus_connection_get_linux_security_label (connection, &security_label))
+    {
+      dbus_bool_t did_append;
+
+      if (prev_added)
+        {
+          if (!_dbus_string_append_byte (&loginfo_buf, ' '))
+            goto oom;
+        }
+
+      did_append = _dbus_string_append_printf (&loginfo_buf,
+                                               "label=\"%s\"", security_label);
+      dbus_free (security_label);
+      security_label = NULL;
+      if (!did_append)
+        goto oom;
+      else
+        prev_added = TRUE;
     }
 
   if (!_dbus_string_steal_data (&loginfo_buf, &(d->cached_loginfo_string)))
@@ -631,6 +698,11 @@ cache_peer_loginfo_string (BusConnectionData *d,
   return TRUE;
 oom:
    _dbus_string_free (&loginfo_buf);
+   if (security_label != NULL)
+     dbus_free (security_label);
+   if (windows_sid != NULL)
+     dbus_free (windows_sid);
+
    return FALSE;
 }
 
@@ -638,9 +710,12 @@ static void
 check_pending_fds_cb (DBusConnection *connection)
 {
   BusConnectionData *d = BUS_CONNECTION_DATA (connection);
-  int n_pending_unix_fds_old = d->n_pending_unix_fds;
+  int n_pending_unix_fds_old;
   int n_pending_unix_fds_new;
 
+  _dbus_assert(d != NULL);
+
+  n_pending_unix_fds_old = d->n_pending_unix_fds;
   n_pending_unix_fds_new = _dbus_connection_get_pending_fds_count (connection);
 
   _dbus_verbose ("Pending fds count changed on connection %p: %d -> %d\n",
@@ -648,14 +723,13 @@ check_pending_fds_cb (DBusConnection *connection)
 
   if (n_pending_unix_fds_old == 0 && n_pending_unix_fds_new > 0)
     {
-      _dbus_timeout_set_interval (d->pending_unix_fds_timeout,
+      _dbus_timeout_restart (d->pending_unix_fds_timeout,
               bus_context_get_pending_fd_timeout (d->connections->context));
-      _dbus_timeout_set_enabled (d->pending_unix_fds_timeout, TRUE);
     }
 
   if (n_pending_unix_fds_old > 0 && n_pending_unix_fds_new == 0)
     {
-      _dbus_timeout_set_enabled (d->pending_unix_fds_timeout, FALSE);
+      _dbus_timeout_disable (d->pending_unix_fds_timeout);
     }
 
 
@@ -666,6 +740,18 @@ static dbus_bool_t
 pending_unix_fds_timeout_cb (void *data)
 {
   DBusConnection *connection = data;
+  BusConnectionData *d = BUS_CONNECTION_DATA (connection);
+  int limit;
+
+  _dbus_assert (d != NULL);
+  limit = bus_context_get_pending_fd_timeout (d->connections->context);
+  bus_context_log (d->connections->context, DBUS_SYSTEM_LOG_WARNING,
+      "Connection \"%s\" (%s) has had Unix fds pending for too long, "
+      "closing it (pending_fd_timeout=%d ms)",
+      d->name != NULL ? d->name : "(null)",
+      bus_connection_get_loginfo (connection),
+      limit);
+
   dbus_connection_close (connection);
   return TRUE;
 }
@@ -675,15 +761,13 @@ bus_connections_setup_connection (BusConnections *connections,
                                   DBusConnection *connection)
 {
 
-  BusConnectionData *d;
-  dbus_bool_t retval;
+  BusConnectionData *d = NULL;
   DBusError error;
 
-  
   d = dbus_new0 (BusConnectionData, 1);
   
   if (d == NULL)
-    return FALSE;
+    goto oom;
 
   d->connections = connections;
   d->connection = connection;
@@ -697,39 +781,35 @@ bus_connections_setup_connection (BusConnections *connections,
                                  connection_data_slot,
                                  d, free_connection_data))
     {
+      /* We have to free d explicitly, because this is the only code
+       * path where it's non-NULL but dbus_connection_set_data() hasn't
+       * taken responsibility for freeing it. */
       dbus_free (d);
-      return FALSE;
+      d = NULL;
+      goto oom;
     }
 
   dbus_connection_set_route_peer_messages (connection, TRUE);
-  
-  retval = FALSE;
 
   dbus_error_init (&error);
   d->selinux_id = bus_selinux_init_connection_id (connection,
                                                   &error);
   if (dbus_error_is_set (&error))
     {
-      /* This is a bit bogus because we pretend all errors
-       * are OOM; this is done because we know that in bus.c
-       * an OOM error disconnects the connection, which is
-       * the same thing we want on any other error.
-       */
+      bus_context_log (connections->context, DBUS_SYSTEM_LOG_WARNING,
+                       "Unable to set up new connection: %s", error.message);
       dbus_error_free (&error);
-      goto out;
+      goto error;
     }
 
   d->apparmor_confinement = bus_apparmor_init_connection_confinement (connection,
                                                                       &error);
   if (dbus_error_is_set (&error))
     {
-      /* This is a bit bogus because we pretend all errors
-       * are OOM; this is done because we know that in bus.c
-       * an OOM error disconnects the connection, which is
-       * the same thing we want on any other error.
-       */
+      bus_context_log (connections->context, DBUS_SYSTEM_LOG_WARNING,
+                       "Unable to set up new connection: %s", error.message);
       dbus_error_free (&error);
-      goto out;
+      goto error;
     }
 
   if (!dbus_connection_set_watch_functions (connection,
@@ -738,14 +818,14 @@ bus_connections_setup_connection (BusConnections *connections,
                                             toggle_connection_watch,
                                             connection,
                                             NULL))
-    goto out;
+    goto oom;
   
   if (!dbus_connection_set_timeout_functions (connection,
                                               add_connection_timeout,
                                               remove_connection_timeout,
                                               NULL,
                                               connection, NULL))
-    goto out;
+    goto oom;
 
   /* For now we don't need to set a Windows user function because
    * there are no policies in the config file controlling what
@@ -763,18 +843,18 @@ bus_connections_setup_connection (BusConnections *connections,
 
   d->link_in_connection_list = _dbus_list_alloc_link (connection);
   if (d->link_in_connection_list == NULL)
-    goto out;
+    goto oom;
   
   /* Setup the connection with the dispatcher */
   if (!bus_dispatch_add_connection (connection))
-    goto out;
+    goto oom;
 
   if (dbus_connection_get_dispatch_status (connection) != DBUS_DISPATCH_COMPLETE)
     {
       if (!_dbus_loop_queue_dispatch (bus_context_get_loop (connections->context), connection))
         {
           bus_dispatch_remove_connection (connection);
-          goto out;
+          goto oom;
         }
     }
 
@@ -783,12 +863,12 @@ bus_connections_setup_connection (BusConnections *connections,
                                                    pending_unix_fds_timeout_cb,
                                                    connection, NULL);
   if (d->pending_unix_fds_timeout == NULL)
-    goto out;
+    goto oom;
 
-  _dbus_timeout_set_enabled (d->pending_unix_fds_timeout, FALSE);
+  _dbus_timeout_disable (d->pending_unix_fds_timeout);
   if (!_dbus_loop_add_timeout (bus_context_get_loop (connections->context),
                                d->pending_unix_fds_timeout))
-    goto out;
+    goto oom;
 
   _dbus_connection_set_pending_fds_function (connection,
           (DBusPendingFdsChangeFunction) check_pending_fds_cb,
@@ -811,13 +891,15 @@ bus_connections_setup_connection (BusConnections *connections,
    * stop accept()ing any more, to avert a DoS. See fd.o #80919 */
   bus_context_check_all_watches (d->connections->context);
   
-  retval = TRUE;
+  return TRUE;
 
- out:
-  if (!retval)
+oom:
+  bus_context_log (connections->context, DBUS_SYSTEM_LOG_WARNING,
+                   "No memory to set up new connection");
+  /* fall through */
+error:
+  if (d != NULL)
     {
-      if (d->selinux_id)
-        bus_selinux_id_unref (d->selinux_id);
       d->selinux_id = NULL;
 
       if (d->apparmor_confinement)
@@ -868,7 +950,7 @@ bus_connections_setup_connection (BusConnections *connections,
       /* "d" has now been freed */
     }
   
-  return retval;
+  return FALSE;
 }
 
 void
@@ -1012,6 +1094,7 @@ bus_connection_get_loginfo (DBusConnection        *connection)
   BusConnectionData *d;
     
   d = BUS_CONNECTION_DATA (connection);
+  _dbus_assert(d != NULL);
 
   if (!bus_connection_is_active (connection))
     return "inactive";
@@ -1031,6 +1114,27 @@ bus_connection_get_policy (DBusConnection *connection)
   return d->policy;
 }
 
+#ifdef DBUS_ENABLE_CYNARA
+const char *bus_connection_get_cynara_session_id (DBusConnection *connection)
+{
+  BusConnectionData *d = BUS_CONNECTION_DATA (connection);
+  _dbus_assert (d != NULL);
+
+  if (d->cynara_session_id == NULL)
+    {
+      unsigned long pid;
+      if (dbus_connection_get_unix_process_id(connection, &pid))
+        d->cynara_session_id = cynara_session_from_pid(pid);
+
+      /* If client exits as soon as async call, cynara_session_from_pid() returns null.
+         cynara_session_from_pid checks /proc/pid to verify process */
+      if (d->cynara_session_id == NULL)
+        asprintf (&d->cynara_session_id, "/proc/%ld", pid);
+    }
+  return d->cynara_session_id;
+}
+#endif
+
 static dbus_bool_t
 foreach_active (BusConnections               *connections,
                 BusConnectionForeachFunction  function,
@@ -1223,6 +1327,19 @@ bus_connection_get_selinux_id (DBusConnection *connection)
   return d->selinux_id;
 }
 
+BusAppArmorConfinement*
+bus_connection_dup_apparmor_confinement (DBusConnection *connection)
+{
+  BusConnectionData *d;
+
+  d = BUS_CONNECTION_DATA (connection);
+
+  _dbus_assert (d != NULL);
+
+  bus_apparmor_confinement_ref (d->apparmor_confinement);
+  return d->apparmor_confinement;
+}
+
 /**
  * Checks whether the connection is registered with the message bus.
  *
@@ -1235,8 +1352,9 @@ bus_connection_is_active (DBusConnection *connection)
   BusConnectionData *d;
 
   d = BUS_CONNECTION_DATA (connection);
+  _dbus_assert(d != NULL);
   
-  return d != NULL && d->name != NULL;
+  return d->name != NULL;
 }
 
 dbus_bool_t
@@ -1401,6 +1519,45 @@ bus_connection_get_n_match_rules (DBusConnection *connection)
   return d->n_match_rules;
 }
 
+dbus_bool_t
+bus_connection_is_service_owner_by_prefix (DBusConnection *connection,
+                                           const char *name_prefix)
+{
+  BusConnectionData *d;
+  DBusList *link;
+
+  d = BUS_CONNECTION_DATA (connection);
+  _dbus_assert (d != NULL);
+
+  link = _dbus_list_get_first_link (&d->services_owned);
+  while (link != NULL)
+    {
+      BusService *service = link->data;
+      DBusString str;
+
+      _dbus_string_init_const (&str, bus_service_get_name (service));
+
+      if (_dbus_string_starts_with_words_c_str (&str, name_prefix, '.'))
+        return TRUE;
+
+      link = _dbus_list_get_next_link (&d->services_owned, link);
+    }
+
+  return FALSE;
+}
+
+const DBusList *
+bus_connection_get_owned_services_list (DBusConnection *connection)
+{
+  BusConnectionData *d;
+
+  d = BUS_CONNECTION_DATA (connection);
+
+  _dbus_assert (d != NULL);
+
+  return d->services_owned;
+}
+
 void
 bus_connection_add_owned_service_link (DBusConnection *connection,
                                        DBusList       *link)
@@ -1613,13 +1770,23 @@ bus_connection_get_name (DBusConnection *connection)
 dbus_bool_t
 bus_connections_check_limits (BusConnections  *connections,
                               DBusConnection  *requesting_completion,
+                              const char     **limit_name_out,
+                              int             *limit_out,
                               DBusError       *error)
 {
   unsigned long uid;
+  int limit;
 
-  if (connections->n_completed >=
-      bus_context_get_max_completed_connections (connections->context))
+  limit = bus_context_get_max_completed_connections (connections->context);
+
+  if (connections->n_completed >= limit)
     {
+      if (limit_name_out != NULL)
+        *limit_name_out = "max_completed_connections";
+
+      if (limit_out != NULL)
+        *limit_out = limit;
+
       dbus_set_error (error, DBUS_ERROR_LIMITS_EXCEEDED,
                       "The maximum number of active connections has been reached");
       return FALSE;
@@ -1627,9 +1794,16 @@ bus_connections_check_limits (BusConnections  *connections,
   
   if (dbus_connection_get_unix_user (requesting_completion, &uid))
     {
-      if (get_connections_for_uid (connections, uid) >=
-          bus_context_get_max_connections_per_user (connections->context))
+      limit = bus_context_get_max_connections_per_user (connections->context);
+
+      if (get_connections_for_uid (connections, uid) >= limit)
         {
+          if (limit_name_out != NULL)
+            *limit_name_out = "max_connections_per_user";
+
+          if (limit_out != NULL)
+            *limit_out = limit;
+
           dbus_set_error (error, DBUS_ERROR_LIMITS_EXCEEDED,
                           "The maximum number of active connections for UID %lu has been reached",
                           uid);
@@ -1849,6 +2023,7 @@ bus_connections_expect_reply (BusConnections  *connections,
   DBusList *link;
   CancelPendingReplyData *cprd;
   int count;
+  int limit;
 
   _dbus_assert (will_get_reply != NULL);
   _dbus_assert (will_send_reply != NULL);
@@ -1879,15 +2054,35 @@ bus_connections_expect_reply (BusConnections  *connections,
       if (pending->will_get_reply == will_get_reply)
         ++count;
     }
-  
-  if (count >=
-      bus_context_get_max_replies_per_connection (connections->context))
+
+  limit = bus_context_get_max_replies_per_connection (connections->context);
+
+  if (count >= limit)
     {
+      bus_context_log (connections->context, DBUS_SYSTEM_LOG_WARNING,
+                       "The maximum number of pending replies for "
+                       "\"%s\" (%s) has been reached "
+                       "(max_replies_per_connection=%d)",
+                       bus_connection_get_name (will_get_reply),
+                       bus_connection_get_loginfo (will_get_reply),
+                       limit);
+
       dbus_set_error (error, DBUS_ERROR_LIMITS_EXCEEDED,
                      "The maximum number of pending replies per connection has been reached");
       return FALSE;
     }
 
+#ifdef DBUS_ENABLE_STATS
+  {
+    BusConnectionData *d;
+
+    d = BUS_CONNECTION_DATA (will_get_reply);
+    _dbus_assert (d != NULL);
+
+    update_peak(&d->peak_pending_replies, count);
+  }
+#endif
+
   pending = dbus_new0 (BusPendingReply, 1);
   if (pending == NULL)
     {
@@ -2162,6 +2357,7 @@ bus_transaction_get_context (BusTransaction  *transaction)
 dbus_bool_t
 bus_transaction_capture (BusTransaction *transaction,
                          DBusConnection *sender,
+                         DBusConnection *addressed_recipient,
                          DBusMessage    *message)
 {
   BusConnections *connections;
@@ -2181,8 +2377,8 @@ bus_transaction_capture (BusTransaction *transaction,
    * There's little point, since there is up to 1 per process. */
   _dbus_assert (mm != NULL);
 
-  if (!bus_matchmaker_get_recipients (mm, connections, sender, NULL, message,
-        &recipients))
+  if (!bus_matchmaker_get_recipients (mm, connections, sender,
+        addressed_recipient, message, &recipients))
     goto out;
 
   for (link = _dbus_list_get_first_link (&recipients);
@@ -2191,7 +2387,7 @@ bus_transaction_capture (BusTransaction *transaction,
     {
       DBusConnection *recipient = link->data;
 
-      if (!bus_transaction_send (transaction, recipient, message))
+      if (!bus_transaction_send (transaction, recipient, message, FALSE))
         goto out;
     }
 
@@ -2204,6 +2400,7 @@ out:
 
 dbus_bool_t
 bus_transaction_capture_error_reply (BusTransaction  *transaction,
+                                     DBusConnection  *addressed_recipient,
                                      const DBusError *error,
                                      DBusMessage     *in_reply_to)
 {
@@ -2230,7 +2427,7 @@ bus_transaction_capture_error_reply (BusTransaction  *transaction,
   if (!dbus_message_set_sender (reply, DBUS_SERVICE_DBUS))
     goto out;
 
-  ret = bus_transaction_capture (transaction, NULL, reply);
+  ret = bus_transaction_capture (transaction, NULL, addressed_recipient, reply);
 
 out:
   dbus_message_unref (reply);
@@ -2243,6 +2440,7 @@ bus_transaction_send_from_driver (BusTransaction *transaction,
                                   DBusMessage    *message)
 {
   DBusError error = DBUS_ERROR_INIT;
+  BusDeferredMessage *deferred_message = NULL;
 
   /* We have to set the sender to the driver, and have
    * to check security policy since it was not done in
@@ -2272,7 +2470,7 @@ bus_transaction_send_from_driver (BusTransaction *transaction,
   /* Capture it for monitors, even if the real recipient's receive policy
    * does not allow it to receive this message from us (which would be odd).
    */
-  if (!bus_transaction_capture (transaction, NULL, message))
+  if (!bus_transaction_capture (transaction, NULL, connection, message))
     return FALSE;
 
   /* If security policy doesn't allow the message, we would silently
@@ -2280,11 +2478,17 @@ bus_transaction_send_from_driver (BusTransaction *transaction,
    * if we're actively capturing messages, it's nice to log that we
    * tried to send it and did not allow ourselves to do so.
    */
-  if (!bus_context_check_security_policy (bus_transaction_get_context (transaction),
-                                          transaction,
-                                          NULL, connection, connection, message, &error))
-    {
-      if (!bus_transaction_capture_error_reply (transaction, &error, message))
+  switch (bus_context_check_security_policy (bus_transaction_get_context (transaction),
+                                             transaction,
+                                             NULL, connection, connection,
+                                             message, NULL, &error,
+                                             &deferred_message))
+    {
+    case BUS_RESULT_TRUE:
+      break;
+    case BUS_RESULT_FALSE:
+      if (!bus_transaction_capture_error_reply (transaction, connection,
+                                                &error, message))
         {
           bus_context_log (transaction->context, DBUS_SYSTEM_LOG_WARNING,
                            "message from dbus-daemon rejected but not enough "
@@ -2295,15 +2499,21 @@ bus_transaction_send_from_driver (BusTransaction *transaction,
        * message (see reasoning above) */
       dbus_error_free (&error);
       return TRUE;
+      break;
+    case BUS_RESULT_LATER:
+      if (!bus_deferred_message_queue_at_recipient(deferred_message, transaction, FALSE, FALSE))
+          return FALSE;
+      return TRUE; /* pretend to have sent it */
     }
 
-  return bus_transaction_send (transaction, connection, message);
+  return bus_transaction_send (transaction, connection, message, FALSE);
 }
 
 dbus_bool_t
 bus_transaction_send (BusTransaction *transaction,
                       DBusConnection *connection,
-                      DBusMessage    *message)
+                      DBusMessage    *message,
+                      dbus_bool_t     deferred_dispatch)
 {
   MessageToSend *to_send;
   BusConnectionData *d;
@@ -2329,7 +2539,28 @@ bus_transaction_send (BusTransaction *transaction,
   
   d = BUS_CONNECTION_DATA (connection);
   _dbus_assert (d != NULL);
-  
+
+  if (!deferred_dispatch && d->deferred_messages != NULL)
+    {
+      BusDeferredMessage *deferred_message;
+      dbus_bool_t success;
+      /* sender and addressed recipient are not required at this point as we only need to send message
+       * to a single recipient without performing policy check. */
+      deferred_message = bus_deferred_message_new (message,
+                                                   NULL,
+                                                   NULL,
+                                                   connection,
+                                                   BUS_RESULT_TRUE);
+      if (deferred_message == NULL)
+        return FALSE;
+
+      success = bus_deferred_message_queue_at_recipient(deferred_message, transaction,
+          FALSE, FALSE);
+      bus_deferred_message_unref(deferred_message);
+
+      return success;
+    }
+
   to_send = dbus_new (MessageToSend, 1);
   if (to_send == NULL)
     {
@@ -2581,6 +2812,126 @@ bus_transaction_add_cancel_hook (BusTransaction               *transaction,
   return TRUE;
 }
 
+void
+bus_connection_dispatch_deferred (DBusConnection *connection)
+{
+  BusDeferredMessage *message;
+
+  _dbus_return_if_fail (connection != NULL);
+
+  while ((message = bus_connection_pop_deferred_message(connection)) != NULL)
+    {
+      bus_deferred_message_dispatch(message);
+      bus_deferred_message_unref(message);
+    }
+}
+
+dbus_bool_t
+bus_connection_has_deferred_messages (DBusConnection *connection)
+{
+  BusConnectionData *d = BUS_CONNECTION_DATA(connection);
+
+  _dbus_assert (d != NULL);
+
+  return d->deferred_messages != NULL ? TRUE : FALSE;
+}
+
+dbus_bool_t
+bus_connection_queue_deferred_message (DBusConnection *connection,
+                                       BusDeferredMessage *message,
+                                       dbus_bool_t prepend)
+{
+  BusConnectionData *d = BUS_CONNECTION_DATA(connection);
+  dbus_bool_t success;
+
+  _dbus_assert (d != NULL);
+
+  if (prepend)
+    success = _dbus_list_prepend(&d->deferred_messages, message);
+  else
+    success = _dbus_list_append(&d->deferred_messages, message);
+
+  if (success)
+    {
+      bus_deferred_message_ref(message);
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+BusDeferredMessage *
+bus_connection_pop_deferred_message (DBusConnection *connection)
+{
+  DBusList *link;
+  BusDeferredMessage *message;
+  BusConnectionData *d = BUS_CONNECTION_DATA(connection);
+
+  _dbus_assert (d != NULL);
+
+  link =_dbus_list_get_first_link(&d->deferred_messages);
+  if (link != NULL)
+    {
+      message = link->data;
+      if (!bus_deferred_message_waits_for_check(message))
+        {
+          _dbus_list_remove_link(&d->deferred_messages, link);
+          return message;
+        }
+    }
+
+  return NULL;
+}
+
+dbus_bool_t
+bus_connection_putback_deferred_message (DBusConnection *connection, BusDeferredMessage *message)
+{
+  BusConnectionData *d = BUS_CONNECTION_DATA(connection);
+
+  _dbus_assert (d != NULL);
+
+  if (_dbus_list_prepend(&d->deferred_messages, message))
+    {
+      return TRUE;
+    }
+  return FALSE;
+}
+
+void
+bus_connection_clear_deferred_messages (DBusConnection *connection)
+{
+  BusConnectionData *d = BUS_CONNECTION_DATA(connection);
+  DBusList *link;
+  DBusList *next;
+  BusDeferredMessage *message;
+
+  _dbus_assert (d != NULL);
+
+  link =_dbus_list_get_first_link(&d->deferred_messages);
+  while (link != NULL)
+    {
+      next = _dbus_list_get_next_link (&d->deferred_messages, link);
+      message = link->data;
+
+      bus_deferred_message_unref(message);
+      _dbus_list_remove_link(&d->deferred_messages, link);
+
+      link = next;
+    }
+}
+
+void
+bus_connection_remove_deferred_message (DBusConnection *connection,
+                                        BusDeferredMessage *message)
+{
+  BusConnectionData *d = BUS_CONNECTION_DATA(connection);
+
+  _dbus_assert (d != NULL);
+
+  if (_dbus_list_remove(&d->deferred_messages, message))
+    bus_deferred_message_unref(message);
+}
+
 int
 bus_connections_get_n_active (BusConnections *connections)
 {
@@ -2636,6 +2987,8 @@ bus_connection_get_peak_match_rules (DBusConnection *connection)
   BusConnectionData *d;
 
   d = BUS_CONNECTION_DATA (connection);
+  _dbus_assert(d != NULL);
+
   return d->peak_match_rules;
 }
 
@@ -2645,8 +2998,44 @@ bus_connection_get_peak_bus_names (DBusConnection *connection)
   BusConnectionData *d;
 
   d = BUS_CONNECTION_DATA (connection);
+  _dbus_assert(d != NULL);
+
   return d->peak_bus_names;
 }
+
+int bus_connection_get_n_pending_replies (DBusConnection *connection)
+{
+  BusConnectionData *d;
+  DBusList *link;
+  BusPendingReply *pending;
+  int count = 0;
+
+  d = BUS_CONNECTION_DATA (connection);
+  _dbus_assert(d != NULL);
+
+  link = bus_expire_list_get_first_link(d->connections->pending_replies);
+  while (link != NULL)
+    {
+      pending = link->data;
+      link = bus_expire_list_get_next_link(d->connections->pending_replies,
+        link);
+      if (pending->will_get_reply == connection)
+        ++count;
+    }
+
+  return count;
+}
+
+int
+bus_connection_get_peak_pending_replies (DBusConnection *connection)
+{
+  BusConnectionData *d;
+
+  d = BUS_CONNECTION_DATA (connection);
+  _dbus_assert (d != NULL);
+
+  return d->peak_pending_replies;
+}
 #endif /* DBUS_ENABLE_STATS */
 
 dbus_bool_t
@@ -2655,8 +3044,9 @@ bus_connection_is_monitor (DBusConnection *connection)
   BusConnectionData *d;
 
   d = BUS_CONNECTION_DATA (connection);
+  _dbus_assert(d != NULL);
 
-  return d != NULL && d->link_in_monitors != NULL;
+  return d->link_in_monitors != NULL;
 }
 
 static dbus_bool_t