bus/policy: separate prefix rules in default context
[platform/upstream/dbus.git] / test / monitor.c
index 7fef934..df5a718 100644 (file)
@@ -48,8 +48,10 @@ typedef struct {
     DBusConnection *monitor;
     DBusConnection *sender;
     DBusConnection *recipient;
+    gboolean recipient_enqueue_filter_added;
 
     GQueue monitored;
+    GQueue received;
 
     const char *monitor_name;
     const char *sender_name;
@@ -92,6 +94,11 @@ static const char * const selective_match_rules[] = {
     FALSE
 };
 
+static const char * const well_known_destination_match_rules[] = {
+    "destination='com.example.Recipient'",
+    NULL
+};
+
 static Config forbidding_config = {
     "valid-config-files/forbidding.conf",
     NULL,
@@ -110,6 +117,12 @@ static Config selective_config = {
     FALSE
 };
 
+static Config well_known_destination_config = {
+    NULL,
+    well_known_destination_match_rules,
+    FALSE
+};
+
 static Config no_rules_config = {
     NULL,
     no_match_rules,
@@ -360,7 +373,7 @@ monitor_filter (DBusConnection *connection,
 }
 
 static DBusHandlerResult
-recipient_filter (DBusConnection *connection,
+recipient_check_filter (DBusConnection *connection,
     DBusMessage *message,
     void *user_data)
 {
@@ -373,6 +386,27 @@ recipient_filter (DBusConnection *connection,
 }
 
 static DBusHandlerResult
+recipient_enqueue_filter (DBusConnection *connection,
+    DBusMessage *message,
+    void *user_data)
+{
+  Fixture *f = user_data;
+
+  if (dbus_message_is_signal (message, DBUS_INTERFACE_DBUS,
+        "NameAcquired") ||
+      dbus_message_is_signal (message, DBUS_INTERFACE_DBUS,
+        "NameLost") ||
+      dbus_message_is_signal (message, DBUS_INTERFACE_DBUS,
+        "NameOwnerChanged"))
+    {
+      return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+    }
+
+  g_queue_push_tail (&f->received, dbus_message_ref (message));
+  return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+static DBusHandlerResult
 systemd_filter (DBusConnection *connection,
     DBusMessage *message,
     void *user_data)
@@ -415,6 +449,19 @@ activated_filter (DBusConnection *connection,
 }
 
 static void
+take_well_known_name (Fixture *f,
+    DBusConnection *connection,
+    const char *name)
+{
+  int ret;
+
+  ret = dbus_bus_request_name (connection, name,
+      DBUS_NAME_FLAG_DO_NOT_QUEUE, &f->e);
+  test_assert_no_error (&f->e);
+  g_assert_cmpint (ret, ==, DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER);
+}
+
+static void
 setup (Fixture *f,
     gconstpointer context)
 {
@@ -426,7 +473,7 @@ setup (Fixture *f,
   dbus_error_init (&f->e);
 
   f->address = test_get_dbus_daemon (f->config ? f->config->config_file : NULL,
-      TEST_USER_ME, &f->daemon_pid);
+      TEST_USER_ME, NULL, &f->daemon_pid);
 
   if (f->address == NULL)
     return;
@@ -441,12 +488,14 @@ setup (Fixture *f,
   if (!dbus_connection_add_filter (f->monitor, monitor_filter, f, NULL))
     g_error ("OOM");
 
-  if (!dbus_connection_add_filter (f->recipient, recipient_filter, f, NULL))
+  if (!dbus_connection_add_filter (f->recipient, recipient_check_filter,
+        f, NULL))
     g_error ("OOM");
 }
 
 static void
-become_monitor (Fixture *f)
+become_monitor (Fixture *f,
+    const Config *config)
 {
   DBusMessage *m;
   DBusPendingCall *pc;
@@ -458,8 +507,11 @@ become_monitor (Fixture *f)
 
   dbus_connection_set_route_peer_messages (f->monitor, TRUE);
 
-  if (f->config != NULL && f->config->match_rules != NULL)
-    match_rules = f->config->match_rules;
+  if (config == NULL)
+    config = f->config;
+
+  if (config != NULL && config->match_rules != NULL)
+    match_rules = config->match_rules;
   else
     match_rules = wildcard_match_rules;
 
@@ -514,6 +566,201 @@ become_monitor (Fixture *f)
 }
 
 /*
+ * Test what happens if the method call arguments are invalid.
+ */
+static void
+test_invalid (Fixture *f,
+    gconstpointer context)
+{
+  DBusMessage *m;
+  DBusPendingCall *pc;
+  dbus_bool_t ok;
+  DBusMessageIter appender, array_appender;
+  dbus_uint32_t zero = 0;
+  dbus_uint32_t invalid_flags = G_MAXUINT32;
+  const char *s;
+
+  if (f->address == NULL)
+    return;
+
+  dbus_connection_set_route_peer_messages (f->monitor, TRUE);
+
+  /* Try to become a monitor but specify nonzero flags - not allowed */
+
+  m = dbus_message_new_method_call (DBUS_SERVICE_DBUS,
+      DBUS_PATH_DBUS, DBUS_INTERFACE_MONITORING, "BecomeMonitor");
+
+  if (m == NULL)
+    g_error ("OOM");
+
+  dbus_message_iter_init_append (m, &appender);
+
+  if (!dbus_message_iter_open_container (&appender, DBUS_TYPE_ARRAY, "s",
+        &array_appender))
+    g_error ("OOM");
+
+  if (!dbus_message_iter_close_container (&appender, &array_appender) ||
+      !dbus_message_iter_append_basic (&appender, DBUS_TYPE_UINT32,
+        &invalid_flags))
+    g_error ("OOM");
+
+  if (!dbus_connection_send_with_reply (f->monitor, m, &pc,
+        DBUS_TIMEOUT_USE_DEFAULT) ||
+      pc == NULL)
+    g_error ("OOM");
+
+  dbus_message_unref (m);
+  m = NULL;
+
+  if (dbus_pending_call_get_completed (pc))
+    test_pending_call_store_reply (pc, &m);
+  else if (!dbus_pending_call_set_notify (pc, test_pending_call_store_reply,
+        &m, NULL))
+    g_error ("OOM");
+
+  while (m == NULL)
+    test_main_context_iterate (f->ctx, TRUE);
+
+  g_assert_cmpint (dbus_message_get_type (m), ==, DBUS_MESSAGE_TYPE_ERROR);
+  g_assert_cmpstr (dbus_message_get_error_name (m), ==,
+      DBUS_ERROR_INVALID_ARGS);
+
+  /* Try to become a monitor but use the wrong object path - not allowed
+   * (security hardening against inappropriate XML policy rules) */
+
+  dbus_pending_call_unref (pc);
+  dbus_message_unref (m);
+
+  m = dbus_message_new_method_call (DBUS_SERVICE_DBUS,
+      "/", DBUS_INTERFACE_MONITORING, "BecomeMonitor");
+
+  if (m == NULL)
+    g_error ("OOM");
+
+  dbus_message_iter_init_append (m, &appender);
+
+  if (!dbus_message_iter_open_container (&appender, DBUS_TYPE_ARRAY, "s",
+        &array_appender))
+    g_error ("OOM");
+
+  if (!dbus_message_iter_close_container (&appender, &array_appender) ||
+      !dbus_message_iter_append_basic (&appender, DBUS_TYPE_UINT32, &zero))
+    g_error ("OOM");
+
+  if (!dbus_connection_send_with_reply (f->monitor, m, &pc,
+        DBUS_TIMEOUT_USE_DEFAULT) ||
+      pc == NULL)
+    g_error ("OOM");
+
+  dbus_message_unref (m);
+  m = NULL;
+
+  if (dbus_pending_call_get_completed (pc))
+    test_pending_call_store_reply (pc, &m);
+  else if (!dbus_pending_call_set_notify (pc, test_pending_call_store_reply,
+        &m, NULL))
+    g_error ("OOM");
+
+  while (m == NULL)
+    test_main_context_iterate (f->ctx, TRUE);
+
+  g_assert_cmpint (dbus_message_get_type (m), ==, DBUS_MESSAGE_TYPE_ERROR);
+  g_assert_cmpstr (dbus_message_get_error_name (m), ==,
+      DBUS_ERROR_UNKNOWN_INTERFACE);
+
+  /* Try to become a monitor but specify a bad match rule -
+   * also not allowed */
+
+  dbus_pending_call_unref (pc);
+  dbus_message_unref (m);
+
+  m = dbus_message_new_method_call (DBUS_SERVICE_DBUS,
+      DBUS_PATH_DBUS, DBUS_INTERFACE_MONITORING, "BecomeMonitor");
+
+  if (m == NULL)
+    g_error ("OOM");
+
+  dbus_message_iter_init_append (m, &appender);
+
+  if (!dbus_message_iter_open_container (&appender, DBUS_TYPE_ARRAY, "s",
+        &array_appender))
+    g_error ("OOM");
+
+  /* Syntactically incorrect match rule taken from #92298 - was probably
+   * intended to be path='/modules/...'
+   */
+  s = "interface='org.kde.walletd',member='/modules/kwalletd/org.kde.KWallet/walletOpened'";
+
+  if (!dbus_message_iter_append_basic (&array_appender, DBUS_TYPE_STRING,
+        &s) ||
+      !dbus_message_iter_close_container (&appender, &array_appender) ||
+      !dbus_message_iter_append_basic (&appender, DBUS_TYPE_UINT32, &zero) ||
+      !dbus_connection_send_with_reply (f->monitor, m, &pc,
+        DBUS_TIMEOUT_USE_DEFAULT) ||
+      pc == NULL)
+    g_error ("OOM");
+
+  dbus_message_unref (m);
+  m = NULL;
+
+  if (dbus_pending_call_get_completed (pc))
+    test_pending_call_store_reply (pc, &m);
+  else if (!dbus_pending_call_set_notify (pc, test_pending_call_store_reply,
+        &m, NULL))
+    g_error ("OOM");
+
+  while (m == NULL)
+    test_main_context_iterate (f->ctx, TRUE);
+
+  g_assert_cmpint (dbus_message_get_type (m), ==, DBUS_MESSAGE_TYPE_ERROR);
+  g_assert_cmpstr (dbus_message_get_error_name (m), ==,
+      DBUS_ERROR_MATCH_RULE_INVALID);
+
+  dbus_pending_call_unref (pc);
+  dbus_message_unref (m);
+
+  /* We did not become a monitor, so we can still call methods. */
+
+  pc = NULL;
+  m = dbus_message_new_method_call (DBUS_SERVICE_DBUS,
+      DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, "GetId");
+
+  if (m == NULL)
+    g_error ("OOM");
+
+  if (!dbus_connection_send_with_reply (f->monitor, m, &pc,
+        DBUS_TIMEOUT_USE_DEFAULT) ||
+      pc == NULL)
+    g_error ("OOM");
+
+  dbus_message_unref (m);
+  m = NULL;
+
+  if (dbus_pending_call_get_completed (pc))
+    test_pending_call_store_reply (pc, &m);
+  else if (!dbus_pending_call_set_notify (pc, test_pending_call_store_reply,
+        &m, NULL))
+    g_error ("OOM");
+
+  while (m == NULL)
+    test_main_context_iterate (f->ctx, TRUE);
+
+  if (dbus_set_error_from_message (&f->e, m))
+    g_error ("%s: %s", f->e.name, f->e.message);
+
+  ok = dbus_message_get_args (m, &f->e,
+      DBUS_TYPE_STRING, &s,
+      DBUS_TYPE_INVALID);
+  test_assert_no_error (&f->e);
+  g_assert (ok);
+  g_assert_cmpstr (s, !=, NULL);
+  g_assert_cmpstr (s, !=, "");
+
+  dbus_pending_call_unref (pc);
+  dbus_message_unref (m);
+}
+
+/*
  * Test the side-effects of becoming a monitor.
  */
 static void
@@ -597,7 +844,7 @@ test_become_monitor (Fixture *f,
         }
     }
 
-  become_monitor (f);
+  become_monitor (f, NULL);
 
   while (!lost_unique || !lost_a || !lost_b || !lost_c)
     {
@@ -699,7 +946,7 @@ test_broadcast (Fixture *f,
   dbus_bus_add_match (f->recipient, "type='signal'", &f->e);
   test_assert_no_error (&f->e);
 
-  become_monitor (f);
+  become_monitor (f, NULL);
 
   m = dbus_message_new_signal ("/foo", "com.example.bar", "BroadcastSignal1");
   dbus_connection_send (f->sender, m, NULL);
@@ -747,7 +994,12 @@ test_forbidden_broadcast (Fixture *f,
   dbus_bus_add_match (f->recipient, "type='signal'", &f->e);
   test_assert_no_error (&f->e);
 
-  become_monitor (f);
+  if (!dbus_connection_add_filter (f->recipient, recipient_enqueue_filter,
+        f, NULL))
+    g_error ("OOM");
+  f->recipient_enqueue_filter_added = TRUE;
+
+  become_monitor (f, NULL);
 
   m = dbus_message_new_signal ("/foo", "com.example.CannotSend",
       "BroadcastSignal1");
@@ -764,7 +1016,28 @@ test_forbidden_broadcast (Fixture *f,
   dbus_connection_send (f->sender, m, NULL);
   dbus_message_unref (m);
 
-  while (g_queue_get_length (&f->monitored) < 6)
+  m = dbus_message_new_signal ("/foo", "com.example.CannotBroadcast",
+      "CannotBroadcast");
+  dbus_connection_send (f->sender, m, NULL);
+  dbus_message_unref (m);
+
+  m = dbus_message_new_signal ("/foo", "com.example.CannotBroadcast2",
+      "CannotBroadcast2");
+  dbus_connection_send (f->sender, m, NULL);
+  dbus_message_unref (m);
+
+  /* these two will go through: we use them as an indirect way to assert that
+   * the recipient has not received anything earlier */
+  m = dbus_message_new_signal ("/foo", "com.example.CannotUnicast",
+      "CannotUnicast");
+  dbus_connection_send (f->sender, m, NULL);
+  dbus_message_unref (m);
+  m = dbus_message_new_signal ("/foo", "com.example.CannotUnicast2",
+      "CannotUnicast2");
+  dbus_connection_send (f->sender, m, NULL);
+  dbus_message_unref (m);
+
+  while (g_queue_get_length (&f->monitored) < 12)
     test_main_context_iterate (f->ctx, TRUE);
 
   m = g_queue_pop_head (&f->monitored);
@@ -798,6 +1071,55 @@ test_forbidden_broadcast (Fixture *f,
   dbus_message_unref (m);
 
   m = g_queue_pop_head (&f->monitored);
+  assert_signal (m, f->sender_name, "/foo", "com.example.CannotBroadcast",
+      "CannotBroadcast", "", NULL);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_error_reply (m, DBUS_SERVICE_DBUS, f->sender_name,
+      DBUS_ERROR_ACCESS_DENIED);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_signal (m, f->sender_name, "/foo", "com.example.CannotBroadcast2",
+      "CannotBroadcast2", "", NULL);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_error_reply (m, DBUS_SERVICE_DBUS, f->sender_name,
+      DBUS_ERROR_ACCESS_DENIED);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_signal (m, f->sender_name, "/foo", "com.example.CannotUnicast",
+      "CannotUnicast", "", NULL);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_signal (m, f->sender_name, "/foo", "com.example.CannotUnicast2",
+      "CannotUnicast2", "", NULL);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  g_assert (m == NULL);
+
+  /* the intended recipient only received the ones that were on the interface
+   * where broadcasts are allowed */
+
+  while (g_queue_get_length (&f->received) < 2)
+    test_main_context_iterate (f->ctx, TRUE);
+
+  m = g_queue_pop_head (&f->received);
+  assert_signal (m, f->sender_name, "/foo", "com.example.CannotUnicast",
+      "CannotUnicast", "", NULL);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->received);
+  assert_signal (m, f->sender_name, "/foo", "com.example.CannotUnicast2",
+      "CannotUnicast2", "", NULL);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->received);
   g_assert (m == NULL);
 }
 
@@ -810,7 +1132,7 @@ test_unicast_signal (Fixture *f,
   if (f->address == NULL)
     return;
 
-  become_monitor (f);
+  become_monitor (f, NULL);
 
   m = dbus_message_new_signal ("/foo", "com.example.bar", "UnicastSignal1");
   if (!dbus_message_set_destination (m, f->recipient_name))
@@ -861,7 +1183,12 @@ test_forbidden (Fixture *f,
   if (f->address == NULL)
     return;
 
-  become_monitor (f);
+  if (!dbus_connection_add_filter (f->recipient, recipient_enqueue_filter,
+        f, NULL))
+    g_error ("OOM");
+  f->recipient_enqueue_filter_added = TRUE;
+
+  become_monitor (f, NULL);
 
   m = dbus_message_new_signal ("/foo", "com.example.CannotSend",
       "UnicastSignal1");
@@ -884,7 +1211,36 @@ test_forbidden (Fixture *f,
   dbus_connection_send (f->sender, m, NULL);
   dbus_message_unref (m);
 
-  while (g_queue_get_length (&f->monitored) < 6)
+  m = dbus_message_new_signal ("/foo", "com.example.CannotUnicast",
+      "CannotUnicast");
+  if (!dbus_message_set_destination (m, f->recipient_name))
+    g_error ("OOM");
+  dbus_connection_send (f->sender, m, NULL);
+  dbus_message_unref (m);
+
+  m = dbus_message_new_signal ("/foo", "com.example.CannotUnicast2",
+      "CannotUnicast2");
+  if (!dbus_message_set_destination (m, f->recipient_name))
+    g_error ("OOM");
+  dbus_connection_send (f->sender, m, NULL);
+  dbus_message_unref (m);
+
+  /* these two will go through: we use them as an indirect way to assert that
+   * the recipient has not received anything earlier */
+  m = dbus_message_new_signal ("/foo", "com.example.CannotBroadcast",
+      "CannotBroadcast");
+  if (!dbus_message_set_destination (m, f->recipient_name))
+    g_error ("OOM");
+  dbus_connection_send (f->sender, m, NULL);
+  dbus_message_unref (m);
+  m = dbus_message_new_signal ("/foo", "com.example.CannotBroadcast2",
+      "CannotBroadcast2");
+  if (!dbus_message_set_destination (m, f->recipient_name))
+    g_error ("OOM");
+  dbus_connection_send (f->sender, m, NULL);
+  dbus_message_unref (m);
+
+  while (g_queue_get_length (&f->monitored) < 12)
     test_main_context_iterate (f->ctx, TRUE);
 
   m = g_queue_pop_head (&f->monitored);
@@ -918,6 +1274,55 @@ test_forbidden (Fixture *f,
   dbus_message_unref (m);
 
   m = g_queue_pop_head (&f->monitored);
+  assert_signal (m, f->sender_name, "/foo", "com.example.CannotUnicast",
+      "CannotUnicast", "", f->recipient_name);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_error_reply (m, DBUS_SERVICE_DBUS, f->sender_name,
+      DBUS_ERROR_ACCESS_DENIED);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_signal (m, f->sender_name, "/foo", "com.example.CannotUnicast2",
+      "CannotUnicast2", "", f->recipient_name);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_error_reply (m, DBUS_SERVICE_DBUS, f->sender_name,
+      DBUS_ERROR_ACCESS_DENIED);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_signal (m, f->sender_name, "/foo", "com.example.CannotBroadcast",
+      "CannotBroadcast", "", f->recipient_name);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_signal (m, f->sender_name, "/foo", "com.example.CannotBroadcast2",
+      "CannotBroadcast2", "", f->recipient_name);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  g_assert (m == NULL);
+
+  /* the intended recipient only received the ones that were on the interface
+   * where unicasts are allowed */
+
+  while (g_queue_get_length (&f->received) < 2)
+    test_main_context_iterate (f->ctx, TRUE);
+
+  m = g_queue_pop_head (&f->received);
+  assert_signal (m, f->sender_name, "/foo", "com.example.CannotBroadcast",
+      "CannotBroadcast", "", f->recipient_name);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->received);
+  assert_signal (m, f->sender_name, "/foo", "com.example.CannotBroadcast2",
+      "CannotBroadcast2", "", f->recipient_name);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->received);
   g_assert (m == NULL);
 }
 
@@ -930,7 +1335,7 @@ test_method_call (Fixture *f,
   if (f->address == NULL)
     return;
 
-  become_monitor (f);
+  become_monitor (f, NULL);
 
   /* regression test for
    * https://bugs.freedesktop.org/show_bug.cgi?id=90952 */
@@ -985,7 +1390,7 @@ test_forbidden_method_call (Fixture *f,
   if (f->address == NULL)
     return;
 
-  become_monitor (f);
+  become_monitor (f, NULL);
 
   m = dbus_message_new_method_call (f->recipient_name, "/foo",
       "com.example.CannotSend", "Call1");
@@ -1040,7 +1445,7 @@ test_dbus_daemon (Fixture *f,
   if (f->address == NULL)
     return;
 
-  become_monitor (f);
+  become_monitor (f, NULL);
 
   res = dbus_bus_request_name (f->sender, "com.example.Sender",
       DBUS_NAME_FLAG_DO_NOT_QUEUE, &f->e);
@@ -1117,7 +1522,7 @@ test_selective (Fixture *f,
       "eavesdrop='true',interface='com.example.Tedious'", &f->e);
   test_assert_no_error (&f->e);
 
-  become_monitor (f);
+  become_monitor (f, NULL);
 
   m = dbus_message_new_signal ("/foo", "com.example.Interesting",
       "UnicastSignal1");
@@ -1160,6 +1565,129 @@ test_selective (Fixture *f,
   g_assert (m == NULL);
 }
 
+static void
+test_well_known_destination (Fixture *f,
+    gconstpointer context)
+{
+  DBusMessage *m;
+
+  if (f->address == NULL)
+    return;
+
+  take_well_known_name (f, f->recipient, "com.example.Recipient");
+  /* we don't expect_take_well_known_name here because the
+   * monitor isn't up yet */
+
+  become_monitor (f, NULL);
+
+  /* The sender sends a message to itself. It will not be observed. */
+  m = dbus_message_new_signal ("/foo", "com.example.bar", "Unobserved");
+  if (!dbus_message_set_destination (m, f->sender_name))
+    g_error ("OOM");
+  dbus_connection_send (f->sender, m, NULL);
+  dbus_message_unref (m);
+
+  /* The sender sends a message to the recipient by well-known name.
+   * It will be observed. */
+  m = dbus_message_new_signal ("/foo", "com.example.bar", "Observed1");
+  if (!dbus_message_set_destination (m, "com.example.Recipient"))
+    g_error ("OOM");
+  dbus_connection_send (f->sender, m, NULL);
+  dbus_message_unref (m);
+
+  /* The sender sends a message to the recipient by unique name.
+   * It will still be observed. */
+  m = dbus_message_new_signal ("/foo", "com.example.bar", "Observed2");
+  if (!dbus_message_set_destination (m, f->recipient_name))
+    g_error ("OOM");
+  dbus_connection_send (f->sender, m, NULL);
+  dbus_message_unref (m);
+
+  while (g_queue_get_length (&f->monitored) < 2)
+    test_main_context_iterate (f->ctx, TRUE);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_signal (m, f->sender_name, "/foo", "com.example.bar",
+      "Observed1", "", "com.example.Recipient");
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_signal (m, f->sender_name, "/foo", "com.example.bar",
+      "Observed2", "", f->recipient_name);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  g_assert (m == NULL);
+}
+
+static void
+test_unique_destination (Fixture *f,
+    gconstpointer context)
+{
+  DBusMessage *m;
+  Config config = {
+    NULL,
+    NULL, /* match rules */
+    FALSE
+  };
+  const gchar *match_rules[2] = { NULL, NULL };
+  gchar *rule;
+
+  if (f->address == NULL)
+    return;
+
+  take_well_known_name (f, f->recipient, "com.example.Recipient");
+  /* we don't expect_take_well_known_name here because the
+   * monitor isn't up yet */
+
+  rule = g_strdup_printf ("destination='%s'", f->recipient_name);
+  /* free it later */
+  g_test_queue_free (rule);
+  match_rules[0] = rule;
+  config.match_rules = match_rules;
+
+  become_monitor (f, &config);
+
+  /* The sender sends a message to itself. It will not be observed. */
+  m = dbus_message_new_signal ("/foo", "com.example.bar", "Unobserved");
+  if (!dbus_message_set_destination (m, f->sender_name))
+    g_error ("OOM");
+  dbus_connection_send (f->sender, m, NULL);
+  dbus_message_unref (m);
+
+  /* The sender sends a message to the recipient by well-known name.
+   * It will be observed. */
+  m = dbus_message_new_signal ("/foo", "com.example.bar", "Observed1");
+  if (!dbus_message_set_destination (m, "com.example.Recipient"))
+    g_error ("OOM");
+  dbus_connection_send (f->sender, m, NULL);
+  dbus_message_unref (m);
+
+  /* The sender sends a message to the recipient by unique name.
+   * It will still be observed. */
+  m = dbus_message_new_signal ("/foo", "com.example.bar", "Observed2");
+  if (!dbus_message_set_destination (m, f->recipient_name))
+    g_error ("OOM");
+  dbus_connection_send (f->sender, m, NULL);
+  dbus_message_unref (m);
+
+  while (g_queue_get_length (&f->monitored) < 2)
+    test_main_context_iterate (f->ctx, TRUE);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_signal (m, f->sender_name, "/foo", "com.example.bar",
+      "Observed1", "", "com.example.Recipient");
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_signal (m, f->sender_name, "/foo", "com.example.bar",
+      "Observed2", "", f->recipient_name);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  g_assert (m == NULL);
+}
+
 #ifdef DBUS_UNIX
 /* currently only used for the systemd activation test */
 static void
@@ -1190,20 +1718,6 @@ expect_new_connection (Fixture *f)
 
 /* currently only used for the systemd activation test */
 static void
-take_well_known_name (Fixture *f,
-    DBusConnection *connection,
-    const char *name)
-{
-  int ret;
-
-  ret = dbus_bus_request_name (connection, name,
-      DBUS_NAME_FLAG_DO_NOT_QUEUE, &f->e);
-  test_assert_no_error (&f->e);
-  g_assert_cmpint (ret, ==, DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER);
-}
-
-/* currently only used for the systemd activation test */
-static void
 expect_take_well_known_name (Fixture *f,
     DBusConnection *connection,
     const char *name)
@@ -1243,7 +1757,7 @@ test_activation (Fixture *f,
   if (f->address == NULL)
     return;
 
-  become_monitor (f);
+  become_monitor (f, NULL);
 
   /* The sender sends a message to an activatable service. */
   m = dbus_message_new_signal ("/foo", "com.example.bar", "UnicastSignal1");
@@ -1458,7 +1972,10 @@ teardown (Fixture *f,
 
   if (f->recipient != NULL)
     {
-      dbus_connection_remove_filter (f->recipient, recipient_filter, f);
+      dbus_connection_remove_filter (f->recipient, recipient_check_filter, f);
+      if (f->recipient_enqueue_filter_added)
+        dbus_connection_remove_filter (f->recipient, recipient_enqueue_filter,
+            f);
       dbus_connection_close (f->recipient);
       dbus_connection_unref (f->recipient);
       f->recipient = NULL;
@@ -1480,14 +1997,21 @@ teardown (Fixture *f,
       f->activated = NULL;
     }
 
-  test_kill_pid (f->daemon_pid);
-  g_spawn_close_pid (f->daemon_pid);
+  if (f->daemon_pid != 0)
+    {
+      test_kill_pid (f->daemon_pid);
+      g_spawn_close_pid (f->daemon_pid);
+      f->daemon_pid = 0;
+    }
 
   test_main_context_unref (f->ctx);
 
   g_queue_foreach (&f->monitored, (GFunc) dbus_message_unref, NULL);
   g_queue_clear (&f->monitored);
 
+  g_queue_foreach (&f->received, (GFunc) dbus_message_unref, NULL);
+  g_queue_clear (&f->received);
+
   g_free (f->address);
 }
 
@@ -1497,6 +2021,8 @@ main (int argc,
 {
   test_init (&argc, &argv);
 
+  g_test_add ("/monitor/invalid", Fixture, NULL,
+      setup, test_invalid, teardown);
   g_test_add ("/monitor/become", Fixture, &side_effects_config,
       setup, test_become_monitor, teardown);
   g_test_add ("/monitor/broadcast", Fixture, NULL,
@@ -1515,6 +2041,12 @@ main (int argc,
       setup, test_dbus_daemon, teardown);
   g_test_add ("/monitor/selective", Fixture, &selective_config,
       setup, test_selective, teardown);
+  g_test_add ("/monitor/well-known-destination",
+      Fixture, &well_known_destination_config,
+      setup, test_well_known_destination, teardown);
+  g_test_add ("/monitor/unique-destination",
+      Fixture, NULL,
+      setup, test_unique_destination, teardown);
   g_test_add ("/monitor/wildcard", Fixture, &wildcard_config,
       setup, test_unicast_signal, teardown);
   g_test_add ("/monitor/no-rule", Fixture, &no_rules_config,