summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon McVittie <simon.mcvittie@collabora.co.uk>2015-01-23 19:11:31 +0000
committerSimon McVittie <simon.mcvittie@collabora.co.uk>2015-02-04 17:15:08 +0000
commit00af6389be46d65afcce8cdfd060f278aaaa9466 (patch)
treef5f32aadd98ef4e46b97453a01fa582600acba4b
parent4a0f1849be319b1b2b7a6d415b57e5544ec191d6 (diff)
Add support for morphing a D-Bus connection into a "monitor"
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>
-rw-r--r--bus/connection.c102
-rw-r--r--bus/connection.h5
-rw-r--r--bus/dispatch.c55
-rw-r--r--bus/driver.c35
-rw-r--r--dbus/dbus-shared.h3
5 files changed, 199 insertions, 1 deletions
diff --git a/bus/connection.c b/bus/connection.c
index f278e619c..1d9bfb2b4 100644
--- a/bus/connection.c
+++ b/bus/connection.c
@@ -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;
+}
diff --git a/bus/connection.h b/bus/connection.h
index 6fbcd38dc..f8d616517 100644
--- a/bus/connection.h
+++ b/bus/connection.h
@@ -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);
diff --git a/bus/dispatch.c b/bus/dispatch.c
index 8f322f8c1..97fe37129 100644
--- a/bus/dispatch.c
+++ b/bus/dispatch.c
@@ -47,6 +47,13 @@
* 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
diff --git a/bus/driver.c b/bus/driver.c
index 6e8a6dac4..ac29b9f13 100644
--- a/bus/driver.c
+++ b/bus/driver.c
@@ -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
diff --git a/dbus/dbus-shared.h b/dbus/dbus-shared.h
index 6a5767041..51c3da8fa 100644
--- a/dbus/dbus-shared.h
+++ b/dbus/dbus-shared.h
@@ -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 */