bus/connection: don't check cmdline in session dbus-daemon
[platform/upstream/dbus.git] / bus / connection.c
index f278e61..6b85ba3 100644 (file)
 #include "signals.h"
 #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
@@ -64,6 +73,11 @@ struct BusConnections
   int stamp;                   /**< Incrementing number */
   BusExpireList *pending_replies; /**< List of pending replies */
 
+  /** List of all monitoring connections, a subset of completed.
+   * Each member is a #DBusConnection. */
+  DBusList *monitors;
+  BusMatchmaker *monitor_matchmaker;
+
 #ifdef DBUS_ENABLE_STATS
   int total_match_rules;
   int peak_match_rules;
@@ -91,9 +105,11 @@ 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;
+  BusAppArmorConfinement *apparmor_confinement;
 
   long connection_tv_sec;  /**< Time when we connected (seconds component) */
   long connection_tv_usec; /**< Time when we connected (microsec component) */
@@ -102,9 +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,
@@ -118,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);
 }
@@ -249,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 */
@@ -283,6 +309,17 @@ bus_connection_disconnected (DBusConnection *connection)
   
   bus_connection_remove_transactions (connection);
 
+  if (d->link_in_monitors != NULL)
+    {
+      BusMatchmaker *mm = d->connections->monitor_matchmaker;
+
+      if (mm != NULL)
+        bus_matchmaker_disconnected (mm, connection);
+
+      _dbus_list_remove_link (&d->connections->monitors, d->link_in_monitors);
+      d->link_in_monitors = NULL;
+    }
+
   if (d->link_in_connection_list != NULL)
     {
       if (d->name != NULL)
@@ -331,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
@@ -340,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
@@ -349,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
@@ -358,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
@@ -367,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
@@ -418,13 +455,17 @@ 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);
   
   dbus_free (d->cached_loginfo_string);
   
   dbus_free (d->name);
   
+#ifdef DBUS_ENABLE_CYNARA
+  free (d->cynara_session_id);
+#endif
+
   dbus_free (d);
 }
 
@@ -451,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),
@@ -513,7 +554,10 @@ bus_connections_unref (BusConnections *connections)
         }
 
       _dbus_assert (connections->n_incomplete == 0);
-      
+
+      /* drop all monitors */
+      _dbus_list_clear (&connections->monitors);
+
       /* drop all real connections */
       while (connections->completed != NULL)
         {
@@ -537,13 +581,23 @@ bus_connections_unref (BusConnections *connections)
       _dbus_timeout_unref (connections->expire_timeout);
       
       _dbus_hash_table_unref (connections->completed_by_user);
-      
+
+      if (connections->monitor_matchmaker != NULL)
+        bus_matchmaker_unref (connections->monitor_matchmaker);
+
       dbus_free (connections);
 
       dbus_connection_free_data_slot (&connection_data_slot);
     }
 }
 
+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, 
@@ -552,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))
@@ -577,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)))
@@ -601,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;
 }
 
@@ -608,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",
@@ -618,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);
     }
 
 
@@ -636,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;
 }
@@ -645,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;
@@ -667,26 +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 error;
+    }
+
+  d->apparmor_confinement = bus_apparmor_init_connection_confinement (connection,
+                                                                      &error);
+  if (dbus_error_is_set (&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,
@@ -695,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
@@ -720,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;
         }
     }
 
@@ -740,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,
@@ -768,14 +891,20 @@ 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)
+        bus_apparmor_confinement_unref (d->apparmor_confinement);
+      d->apparmor_confinement = NULL;
       
       if (!dbus_connection_set_watch_functions (connection,
                                                 NULL, NULL, NULL,
@@ -821,7 +950,7 @@ bus_connections_setup_connection (BusConnections *connections,
       /* "d" has now been freed */
     }
   
-  return retval;
+  return FALSE;
 }
 
 void
@@ -965,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";
@@ -984,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,
@@ -1176,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.
  *
@@ -1188,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
@@ -1256,6 +1421,10 @@ bus_connection_send_oom_error (DBusConnection *connection,
   _dbus_assert (d != NULL);  
   _dbus_assert (d->oom_message != NULL);
 
+  bus_context_log (d->connections->context, DBUS_SYSTEM_LOG_WARNING,
+                   "dbus-daemon transaction failed (OOM), sending error to "
+                   "sender %s", bus_connection_get_loginfo (connection));
+
   /* should always succeed since we set it to a placeholder earlier */
   if (!dbus_message_set_reply_serial (d->oom_message,
                                       dbus_message_get_serial (in_reply_to)))
@@ -1350,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)
@@ -1562,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;
+
+  limit = bus_context_get_max_completed_connections (connections->context);
 
-  if (connections->n_completed >=
-      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;
@@ -1576,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);
@@ -1798,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);
@@ -1828,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)
     {
@@ -2104,11 +2350,98 @@ bus_transaction_get_context (BusTransaction  *transaction)
   return transaction->context;
 }
 
+/**
+ * Reserve enough memory to capture the given message if the
+ * transaction goes through.
+ */
+dbus_bool_t
+bus_transaction_capture (BusTransaction *transaction,
+                         DBusConnection *sender,
+                         DBusConnection *addressed_recipient,
+                         DBusMessage    *message)
+{
+  BusConnections *connections;
+  BusMatchmaker *mm;
+  DBusList *link;
+  DBusList *recipients = NULL;
+  dbus_bool_t ret = FALSE;
+
+  connections = bus_context_get_connections (transaction->context);
+
+  /* shortcut: don't compose the message unless someone wants it */
+  if (connections->monitors == NULL)
+    return TRUE;
+
+  mm = connections->monitor_matchmaker;
+  /* This is non-null if there has ever been a monitor - we don't GC it.
+   * There's little point, since there is up to 1 per process. */
+  _dbus_assert (mm != NULL);
+
+  if (!bus_matchmaker_get_recipients (mm, connections, sender,
+        addressed_recipient, message, &recipients))
+    goto out;
+
+  for (link = _dbus_list_get_first_link (&recipients);
+      link != NULL;
+      link = _dbus_list_get_next_link (&recipients, link))
+    {
+      DBusConnection *recipient = link->data;
+
+      if (!bus_transaction_send (transaction, recipient, message, FALSE))
+        goto out;
+    }
+
+  ret = TRUE;
+
+out:
+  _dbus_list_clear (&recipients);
+  return ret;
+}
+
+dbus_bool_t
+bus_transaction_capture_error_reply (BusTransaction  *transaction,
+                                     DBusConnection  *addressed_recipient,
+                                     const DBusError *error,
+                                     DBusMessage     *in_reply_to)
+{
+  BusConnections *connections;
+  DBusMessage *reply;
+  dbus_bool_t ret = FALSE;
+
+  _dbus_assert (error != NULL);
+  _DBUS_ASSERT_ERROR_IS_SET (error);
+
+  connections = bus_context_get_connections (transaction->context);
+
+  /* shortcut: don't compose the message unless someone wants it */
+  if (connections->monitors == NULL)
+    return TRUE;
+
+  reply = dbus_message_new_error (in_reply_to,
+                                  error->name,
+                                  error->message);
+
+  if (reply == NULL)
+    return FALSE;
+
+  if (!dbus_message_set_sender (reply, DBUS_SERVICE_DBUS))
+    goto out;
+
+  ret = bus_transaction_capture (transaction, NULL, addressed_recipient, reply);
+
+out:
+  dbus_message_unref (reply);
+  return ret;
+}
+
 dbus_bool_t
 bus_transaction_send_from_driver (BusTransaction *transaction,
                                   DBusConnection *connection,
                                   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
    * dispatch.c
@@ -2133,22 +2466,54 @@ bus_transaction_send_from_driver (BusTransaction *transaction,
   
   /* bus driver never wants a reply */
   dbus_message_set_no_reply (message, TRUE);
-  
-  /* If security policy doesn't allow the message, we silently
-   * eat it; the driver doesn't care about getting a reply.
+
+  /* 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_context_check_security_policy (bus_transaction_get_context (transaction),
-                                          transaction,
-                                          NULL, connection, connection, message, NULL))
-    return TRUE;
+  if (!bus_transaction_capture (transaction, NULL, connection, message))
+    return FALSE;
 
-  return bus_transaction_send (transaction, connection, message);
+  /* If security policy doesn't allow the message, we would silently
+   * eat it; the driver doesn't care about getting a reply. However,
+   * 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.
+   */
+  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 "
+                           "memory to capture it");
+        }
+
+      /* This is not fatal to the transaction so silently eat the disallowed
+       * 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, 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;
@@ -2174,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)
     {
@@ -2426,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)
 {
@@ -2481,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;
 }
 
@@ -2490,6 +2998,179 @@ 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
+bus_connection_is_monitor (DBusConnection *connection)
+{
+  BusConnectionData *d;
+
+  d = BUS_CONNECTION_DATA (connection);
+  _dbus_assert(d != NULL);
+
+  return d->link_in_monitors != NULL;
+}
+
+static dbus_bool_t
+bcd_add_monitor_rules (BusConnectionData  *d,
+                       DBusConnection     *connection,
+                       DBusList          **rules)
+{
+  BusMatchmaker *mm = d->connections->monitor_matchmaker;
+  DBusList *iter;
+
+  if (mm == NULL)
+    {
+      mm = bus_matchmaker_new ();
+
+      if (mm == NULL)
+        return FALSE;
+
+      d->connections->monitor_matchmaker = mm;
+    }
+
+  for (iter = _dbus_list_get_first_link (rules);
+      iter != NULL;
+      iter = _dbus_list_get_next_link (rules, iter))
+    {
+      if (!bus_matchmaker_add_rule (mm, iter->data))
+        {
+          bus_matchmaker_disconnected (mm, connection);
+          return FALSE;
+        }
+    }
+
+  return TRUE;
+}
+
+static void
+bcd_drop_monitor_rules (BusConnectionData *d,
+                        DBusConnection *connection)
+{
+  BusMatchmaker *mm = d->connections->monitor_matchmaker;
+
+  if (mm != NULL)
+    bus_matchmaker_disconnected (mm, connection);
+}
+
+dbus_bool_t
+bus_connection_be_monitor (DBusConnection  *connection,
+                           BusTransaction  *transaction,
+                           DBusList       **rules,
+                           DBusError       *error)
+{
+  BusConnectionData *d;
+  DBusList *link;
+  DBusList *tmp;
+  DBusList *iter;
+
+  d = BUS_CONNECTION_DATA (connection);
+  _dbus_assert (d != NULL);
+
+  link = _dbus_list_alloc_link (connection);
+
+  if (link == NULL)
+    {
+      BUS_SET_OOM (error);
+      return FALSE;
+    }
+
+  if (!bcd_add_monitor_rules (d, connection, rules))
+    {
+      _dbus_list_free_link (link);
+      BUS_SET_OOM (error);
+      return FALSE;
+    }
+
+  /* release all its names */
+  if (!_dbus_list_copy (&d->services_owned, &tmp))
+    {
+      bcd_drop_monitor_rules (d, connection);
+      _dbus_list_free_link (link);
+      BUS_SET_OOM (error);
+      return FALSE;
+    }
+
+  for (iter = _dbus_list_get_first_link (&tmp);
+      iter != NULL;
+      iter = _dbus_list_get_next_link (&tmp, iter))
+    {
+      BusService *service = iter->data;
+
+      /* This call is transactional: if there isn't enough memory to
+       * do everything, then the service gets all its names back when
+       * the transaction is cancelled due to OOM. */
+      if (!bus_service_remove_owner (service, connection, transaction, error))
+        {
+          bcd_drop_monitor_rules (d, connection);
+          _dbus_list_free_link (link);
+          _dbus_list_clear (&tmp);
+          return FALSE;
+        }
+    }
+
+  /* We have now done everything that can fail, so there is no problem
+   * with doing the irrevocable stuff. */
+
+  _dbus_list_clear (&tmp);
+
+  bus_context_log (transaction->context, DBUS_SYSTEM_LOG_INFO,
+                   "Connection %s (%s) became a monitor.", d->name,
+                   d->cached_loginfo_string);
+
+  if (d->n_match_rules > 0)
+    {
+      BusMatchmaker *mm;
+
+      mm = bus_context_get_matchmaker (d->connections->context);
+      bus_matchmaker_disconnected (mm, connection);
+    }
+
+  /* flag it as a monitor */
+  d->link_in_monitors = link;
+  _dbus_list_append_link (&d->connections->monitors, link);
+
+  /* it isn't allowed to reply, and it is no longer relevant whether it
+   * receives replies */
+  bus_connection_drop_pending_replies (d->connections, connection);
+
+  return TRUE;
+}