Add support for morphing a D-Bus connection into a "monitor"
authorSimon McVittie <simon.mcvittie@collabora.co.uk>
Fri, 23 Jan 2015 19:11:31 +0000 (19:11 +0000)
committerSimon McVittie <simon.mcvittie@collabora.co.uk>
Wed, 4 Feb 2015 17:15:08 +0000 (17:15 +0000)
This is a special connection that is not allowed to send anything,
and loses all its well-known names.

In future commits, it will get a new set of match rules and the
ability to eavesdrop on messages before the rest of the bus daemon
has had a chance to process them.

Bug: https://bugs.freedesktop.org/show_bug.cgi?id=46787
Reviewed-by: Philip Withnall <philip.withnall@collabora.co.uk>
bus/connection.c
bus/connection.h
bus/dispatch.c
bus/driver.c
dbus/dbus-shared.h

index f278e61..1d9bfb2 100644 (file)
@@ -64,6 +64,10 @@ 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;
+
 #ifdef DBUS_ENABLE_STATS
   int total_match_rules;
   int peak_match_rules;
@@ -105,6 +109,9 @@ typedef struct
 #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;
 } BusConnectionData;
 
 static dbus_bool_t bus_pending_reply_expired (BusExpireList *list,
@@ -283,6 +290,12 @@ bus_connection_disconnected (DBusConnection *connection)
   
   bus_connection_remove_transactions (connection);
 
+  if (d->link_in_monitors != NULL)
+    {
+      _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)
@@ -513,7 +526,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)
         {
@@ -2493,3 +2509,87 @@ bus_connection_get_peak_bus_names (DBusConnection *connection)
   return d->peak_bus_names;
 }
 #endif /* DBUS_ENABLE_STATS */
+
+dbus_bool_t
+bus_connection_is_monitor (DBusConnection *connection)
+{
+  BusConnectionData *d;
+
+  d = BUS_CONNECTION_DATA (connection);
+
+  return d != NULL && d->link_in_monitors != NULL;
+}
+
+dbus_bool_t
+bus_connection_be_monitor (DBusConnection *connection,
+                           BusTransaction *transaction,
+                           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;
+    }
+
+  /* release all its names */
+  if (!_dbus_list_copy (&d->services_owned, &tmp))
+    {
+      _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))
+        {
+          _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;
+}
index 6fbcd38..f8d6165 100644 (file)
@@ -116,6 +116,11 @@ dbus_bool_t      bus_connection_get_unix_groups  (DBusConnection       *connecti
                                                   DBusError            *error);
 BusClientPolicy* bus_connection_get_policy  (DBusConnection       *connection);
 
+dbus_bool_t bus_connection_is_monitor (DBusConnection *connection);
+dbus_bool_t bus_connection_be_monitor (DBusConnection *connection,
+                                       BusTransaction *transaction,
+                                       DBusError      *error);
+
 /* transaction API so we can send or not send a block of messages as a whole */
 
 typedef void (* BusTransactionCancelFunction) (void *data);
index 8f322f8..97fe371 100644 (file)
  * 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,
@@ -200,6 +207,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
index 6e8a6da..ac29b9f 100644 (file)
@@ -1788,6 +1788,35 @@ bus_driver_handle_get_id (DBusConnection *connection,
   return FALSE;
 }
 
+static dbus_bool_t
+bus_driver_handle_become_monitor (DBusConnection *connection,
+                                  BusTransaction *transaction,
+                                  DBusMessage    *message,
+                                  DBusError      *error)
+{
+  _DBUS_ASSERT_ERROR_IS_CLEAR (error);
+
+  if (!bus_driver_check_message_is_for_us (message, error))
+    return FALSE;
+
+  if (!bus_driver_check_caller_is_privileged (connection, transaction,
+                                              message, error))
+    return FALSE;
+
+  /* Send the ack before we remove the rule, since the ack is undone
+   * on transaction cancel, but becoming a monitor isn't.
+   */
+  if (!send_ack_reply (connection, transaction, message, error))
+    return FALSE;
+
+  /* FIXME: use the array of filters from the message */
+
+  if (!bus_connection_be_monitor (connection, transaction, error))
+    return FALSE;
+
+  return TRUE;
+}
+
 typedef struct
 {
   const char *name;
@@ -1889,6 +1918,11 @@ static const MessageHandler introspectable_message_handlers[] = {
   { NULL, NULL, NULL, NULL }
 };
 
+static const MessageHandler monitoring_message_handlers[] = {
+  { "BecomeMonitor", "asu", "", bus_driver_handle_become_monitor },
+  { NULL, NULL, NULL, NULL }
+};
+
 #ifdef DBUS_ENABLE_STATS
 static const MessageHandler stats_message_handlers[] = {
   { "GetStats", "", "a{sv}", bus_stats_handle_get_stats },
@@ -1920,6 +1954,7 @@ static InterfaceHandler interface_handlers[] = {
     "      <arg type=\"s\"/>\n"
     "    </signal>\n" },
   { DBUS_INTERFACE_INTROSPECTABLE, introspectable_message_handlers, NULL },
+  { DBUS_INTERFACE_MONITORING, monitoring_message_handlers, NULL },
 #ifdef DBUS_ENABLE_STATS
   { BUS_INTERFACE_STATS, stats_message_handlers, NULL },
 #endif
index 6a57670..51c3da8 100644 (file)
@@ -86,6 +86,9 @@ typedef enum
  */
 /** The interface exported by the object with #DBUS_SERVICE_DBUS and #DBUS_PATH_DBUS */
 #define DBUS_INTERFACE_DBUS           "org.freedesktop.DBus"
+/** The monitoring interface exported by the dbus-daemon */
+#define DBUS_INTERFACE_MONITORING     "org.freedesktop.DBus.Monitoring"
+
 /** The interface supported by introspectable objects */
 #define DBUS_INTERFACE_INTROSPECTABLE "org.freedesktop.DBus.Introspectable"
 /** The interface supported by objects with properties */