/* Integration tests for monitor-mode D-Bus connections * * Copyright © 2010-2011 Nokia Corporation * Copyright © 2015 Collabora Ltd. * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation files * (the "Software"), to deal in the Software without restriction, * including without limitation the rights to use, copy, modify, merge, * publish, distribute, sublicense, and/or sell copies of the Software, * and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include #include #include "test-utils-glib.h" typedef struct { const char *config_file; const char * const *match_rules; gboolean care_about_our_names; } Config; typedef struct { const Config *config; TestMainContext *ctx; DBusError e; GError *ge; gchar *address; GPid daemon_pid; DBusConnection *monitor; DBusConnection *sender; DBusConnection *recipient; GQueue monitored; const char *monitor_name; const char *sender_name; const char *recipient_name; DBusConnection *systemd; const char *systemd_name; DBusMessage *systemd_message; DBusConnection *activated; const char *activated_name; DBusMessage *activated_message; } Fixture; static const char * const no_match_rules[] = { NULL }; static const char * const wildcard_match_rules[] = { "", NULL, FALSE }; static const char * const eavesdrop_match_rules[] = { "eavesdrop=true", NULL, FALSE }; static const char * const no_eavesdrop_match_rules[] = { "eavesdrop=false", NULL, FALSE }; static const char * const selective_match_rules[] = { "interface='com.example.Interesting'", "interface='com.example.Fun'", NULL, FALSE }; static Config forbidding_config = { "valid-config-files/forbidding.conf", NULL, FALSE }; static Config wildcard_config = { NULL, wildcard_match_rules, FALSE }; static Config selective_config = { NULL, selective_match_rules, FALSE }; static Config no_rules_config = { NULL, no_match_rules, FALSE }; static Config eavesdrop_config = { NULL, eavesdrop_match_rules, FALSE }; static Config no_eavesdrop_config = { NULL, no_eavesdrop_match_rules, FALSE }; static Config fake_systemd_config = { "valid-config-files/systemd-activation.conf", NULL, FALSE }; static Config side_effects_config = { NULL, NULL, TRUE }; static inline const char * not_null2 (const char *x, const char *fallback) { if (x == NULL) return fallback; return x; } static inline const char * not_null (const char *x) { return not_null2 (x, "(null)"); } #define log_message(m) _log_message (m, __FILE__, __LINE__) G_GNUC_UNUSED static void _log_message (DBusMessage *m, const char *file, int line) { g_message ("%s:%d: message type %d (%s)", file, line, dbus_message_get_type (m), dbus_message_type_to_string (dbus_message_get_type (m))); g_message ("\tfrom: %s", not_null2 (dbus_message_get_sender (m), "(dbus-daemon)")); g_message ("\tto: %s", not_null2 (dbus_message_get_destination (m), "(broadcast)")); g_message ("\tpath: %s", not_null (dbus_message_get_path (m))); g_message ("\tinterface: %s", not_null (dbus_message_get_interface (m))); g_message ("\tmember: %s", not_null (dbus_message_get_member (m))); g_message ("\tsignature: %s", not_null (dbus_message_get_signature (m))); g_message ("\terror name: %s", not_null (dbus_message_get_error_name (m))); if (strcmp ("s", dbus_message_get_signature (m)) == 0) { DBusError e = DBUS_ERROR_INIT; const char *s; dbus_message_get_args (m, &e, DBUS_TYPE_STRING, &s, DBUS_TYPE_INVALID); test_assert_no_error (&e); g_message ("\tstring payload: %s", s); } } /* these are macros so they get the right line number */ #define assert_hello(m) \ do { \ g_assert_cmpstr (dbus_message_type_to_string (dbus_message_get_type (m)), \ ==, dbus_message_type_to_string (DBUS_MESSAGE_TYPE_METHOD_CALL)); \ g_assert_cmpstr (dbus_message_get_destination (m), ==, DBUS_SERVICE_DBUS); \ g_assert_cmpstr (dbus_message_get_path (m), ==, DBUS_PATH_DBUS); \ g_assert_cmpstr (dbus_message_get_interface (m), ==, DBUS_INTERFACE_DBUS); \ g_assert_cmpstr (dbus_message_get_member (m), ==, "Hello"); \ g_assert_cmpstr (dbus_message_get_signature (m), ==, ""); \ g_assert_cmpint (dbus_message_get_serial (m), !=, 0); \ g_assert_cmpint (dbus_message_get_reply_serial (m), ==, 0); \ } while (0) #define assert_hello_reply(m) \ do { \ DBusError _e = DBUS_ERROR_INIT; \ const char *_s; \ \ g_assert_cmpstr (dbus_message_type_to_string (dbus_message_get_type (m)), \ ==, dbus_message_type_to_string (DBUS_MESSAGE_TYPE_METHOD_RETURN)); \ g_assert_cmpstr (dbus_message_get_sender (m), ==, DBUS_SERVICE_DBUS); \ g_assert_cmpstr (dbus_message_get_path (m), ==, NULL); \ g_assert_cmpstr (dbus_message_get_interface (m), ==, NULL); \ g_assert_cmpstr (dbus_message_get_member (m), ==, NULL); \ g_assert_cmpstr (dbus_message_get_signature (m), ==, "s"); \ g_assert_cmpint (dbus_message_get_serial (m), !=, 0); \ g_assert_cmpint (dbus_message_get_reply_serial (m), !=, 0); \ \ dbus_message_get_args (m, &_e, \ DBUS_TYPE_STRING, &_s, \ DBUS_TYPE_INVALID); \ test_assert_no_error (&_e); \ g_assert_cmpstr (dbus_message_get_destination (m), ==, _s); \ } while (0) #define assert_name_acquired(m) \ do { \ DBusError _e = DBUS_ERROR_INIT; \ const char *_s; \ \ g_assert_cmpstr (dbus_message_type_to_string (dbus_message_get_type (m)), \ ==, dbus_message_type_to_string (DBUS_MESSAGE_TYPE_SIGNAL)); \ g_assert_cmpstr (dbus_message_get_sender (m), ==, DBUS_SERVICE_DBUS); \ g_assert_cmpstr (dbus_message_get_path (m), ==, DBUS_PATH_DBUS); \ g_assert_cmpstr (dbus_message_get_interface (m), ==, DBUS_INTERFACE_DBUS); \ g_assert_cmpstr (dbus_message_get_member (m), ==, "NameAcquired"); \ g_assert_cmpstr (dbus_message_get_signature (m), ==, "s"); \ g_assert_cmpint (dbus_message_get_serial (m), !=, 0); \ g_assert_cmpint (dbus_message_get_reply_serial (m), ==, 0); \ \ dbus_message_get_args (m, &_e, \ DBUS_TYPE_STRING, &_s, \ DBUS_TYPE_INVALID); \ test_assert_no_error (&_e); \ g_assert_cmpstr (dbus_message_get_destination (m), ==, _s); \ } while (0) #define assert_method_call(m, sender, \ destination, path, iface, method, signature) \ do { \ g_assert_cmpstr (dbus_message_type_to_string (dbus_message_get_type (m)), \ ==, dbus_message_type_to_string (DBUS_MESSAGE_TYPE_METHOD_CALL)); \ g_assert_cmpstr (dbus_message_get_sender (m), ==, sender); \ g_assert_cmpstr (dbus_message_get_destination (m), ==, destination); \ g_assert_cmpstr (dbus_message_get_path (m), ==, path); \ g_assert_cmpstr (dbus_message_get_interface (m), ==, iface); \ g_assert_cmpstr (dbus_message_get_member (m), ==, method); \ g_assert_cmpstr (dbus_message_get_signature (m), ==, signature); \ g_assert_cmpint (dbus_message_get_serial (m), !=, 0); \ g_assert_cmpint (dbus_message_get_reply_serial (m), ==, 0); \ } while (0) #define assert_signal(m, \ sender, path, iface, member, signature, \ destination) \ do { \ g_assert_cmpstr (dbus_message_type_to_string (dbus_message_get_type (m)), \ ==, dbus_message_type_to_string (DBUS_MESSAGE_TYPE_SIGNAL)); \ g_assert_cmpstr (dbus_message_get_sender (m), ==, sender); \ g_assert_cmpstr (dbus_message_get_destination (m), ==, destination); \ g_assert_cmpstr (dbus_message_get_path (m), ==, path); \ g_assert_cmpstr (dbus_message_get_interface (m), ==, iface); \ g_assert_cmpstr (dbus_message_get_member (m), ==, member); \ g_assert_cmpstr (dbus_message_get_signature (m), ==, signature); \ g_assert_cmpint (dbus_message_get_serial (m), !=, 0); \ g_assert_cmpint (dbus_message_get_reply_serial (m), ==, 0); \ } while (0) #define assert_method_reply(m, sender, destination, signature) \ do { \ g_assert_cmpstr (dbus_message_type_to_string (dbus_message_get_type (m)), \ ==, dbus_message_type_to_string (DBUS_MESSAGE_TYPE_METHOD_RETURN)); \ g_assert_cmpstr (dbus_message_get_sender (m), ==, sender); \ g_assert_cmpstr (dbus_message_get_destination (m), ==, destination); \ g_assert_cmpstr (dbus_message_get_path (m), ==, NULL); \ g_assert_cmpstr (dbus_message_get_interface (m), ==, NULL); \ g_assert_cmpstr (dbus_message_get_member (m), ==, NULL); \ g_assert_cmpstr (dbus_message_get_signature (m), ==, signature); \ g_assert_cmpint (dbus_message_get_serial (m), !=, 0); \ g_assert_cmpint (dbus_message_get_reply_serial (m), !=, 0); \ } while (0) #define assert_error_reply(m, sender, destination, error_name) \ do { \ g_assert_cmpstr (dbus_message_type_to_string (dbus_message_get_type (m)), \ ==, dbus_message_type_to_string (DBUS_MESSAGE_TYPE_ERROR)); \ g_assert_cmpstr (dbus_message_get_sender (m), ==, sender); \ g_assert_cmpstr (dbus_message_get_destination (m), ==, destination); \ g_assert_cmpstr (dbus_message_get_error_name (m), ==, error_name); \ g_assert_cmpstr (dbus_message_get_path (m), ==, NULL); \ g_assert_cmpstr (dbus_message_get_interface (m), ==, NULL); \ g_assert_cmpstr (dbus_message_get_member (m), ==, NULL); \ g_assert_cmpstr (dbus_message_get_signature (m), ==, "s"); \ g_assert_cmpint (dbus_message_get_serial (m), !=, 0); \ g_assert_cmpint (dbus_message_get_reply_serial (m), !=, 0); \ } while (0) /* This is called after processing pending replies to our own method * calls, but before anything else. */ static DBusHandlerResult monitor_filter (DBusConnection *connection, DBusMessage *message, void *user_data) { Fixture *f = user_data; g_assert_cmpstr (dbus_message_get_interface (message), !=, "com.example.Tedious"); /* we are not interested in the monitor getting NameAcquired or NameLost * for most tests */ if (f->config == NULL || !f->config->care_about_our_names) { if (dbus_message_is_signal (message, DBUS_INTERFACE_DBUS, "NameAcquired") || dbus_message_is_signal (message, DBUS_INTERFACE_DBUS, "NameLost")) { DBusError e = DBUS_ERROR_INIT; const char *s; dbus_message_get_args (message, &e, DBUS_TYPE_STRING, &s, DBUS_TYPE_INVALID); test_assert_no_error (&e); if (strcmp (s, f->monitor_name) == 0) { /* ignore */ return DBUS_HANDLER_RESULT_HANDLED; } } } g_queue_push_tail (&f->monitored, dbus_message_ref (message)); return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult recipient_filter (DBusConnection *connection, DBusMessage *message, void *user_data) { g_assert_cmpstr (dbus_message_get_interface (message), !=, "com.example.CannotSend"); g_assert_cmpstr (dbus_message_get_interface (message), !=, "com.example.CannotReceive"); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } static DBusHandlerResult systemd_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")) { return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } g_assert (f->systemd_message == NULL); f->systemd_message = dbus_message_ref (message); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } static DBusHandlerResult activated_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")) { return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } g_assert (f->activated_message == NULL); f->activated_message = dbus_message_ref (message); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } static void setup (Fixture *f, gconstpointer context) { f->config = context; f->ctx = test_main_context_get (); f->ge = NULL; dbus_error_init (&f->e); f->address = test_get_dbus_daemon (f->config ? f->config->config_file : NULL, TEST_USER_ME, &f->daemon_pid); if (f->address == NULL) return; f->monitor = test_connect_to_bus (f->ctx, f->address); f->monitor_name = dbus_bus_get_unique_name (f->monitor); f->sender = test_connect_to_bus (f->ctx, f->address); f->sender_name = dbus_bus_get_unique_name (f->sender); f->recipient = test_connect_to_bus (f->ctx, f->address); f->recipient_name = dbus_bus_get_unique_name (f->recipient); 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)) g_error ("OOM"); } static void become_monitor (Fixture *f) { DBusMessage *m; DBusPendingCall *pc; dbus_bool_t ok; DBusMessageIter appender, array_appender; const char * const *match_rules; int i; dbus_uint32_t zero = 0; if (f->config != NULL && f->config->match_rules != NULL) match_rules = f->config->match_rules; else match_rules = wildcard_match_rules; 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"); for (i = 0; match_rules[i] != NULL; i++) { if (!dbus_message_iter_append_basic (&array_appender, DBUS_TYPE_STRING, &match_rules[i])) 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); ok = dbus_message_get_args (m, &f->e, DBUS_TYPE_INVALID); test_assert_no_error (&f->e); g_assert (ok); dbus_pending_call_unref (pc); dbus_message_unref (m); m = NULL; } /* * Test the side-effects of becoming a monitor. */ static void test_become_monitor (Fixture *f, gconstpointer context) { DBusMessage *m; int ret; dbus_bool_t got_unique = FALSE, got_a = FALSE, got_b = FALSE, got_c = FALSE; dbus_bool_t lost_unique = FALSE, lost_a = FALSE, lost_b = FALSE, lost_c = FALSE; if (f->address == NULL) return; ret = dbus_bus_request_name (f->monitor, "com.example.A", DBUS_NAME_FLAG_DO_NOT_QUEUE, &f->e); test_assert_no_error (&f->e); g_assert_cmpint (ret, ==, DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER); ret = dbus_bus_request_name (f->monitor, "com.example.B", DBUS_NAME_FLAG_DO_NOT_QUEUE, &f->e); test_assert_no_error (&f->e); g_assert_cmpint (ret, ==, DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER); ret = dbus_bus_request_name (f->monitor, "com.example.C", DBUS_NAME_FLAG_DO_NOT_QUEUE, &f->e); test_assert_no_error (&f->e); g_assert_cmpint (ret, ==, DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER); while (!got_unique || !got_a || !got_b || !got_c) { test_main_context_iterate (f->ctx, TRUE); while ((m = g_queue_pop_head (&f->monitored)) != NULL) { if (dbus_message_is_signal (m, DBUS_INTERFACE_DBUS, "NameAcquired")) { const char *name; dbus_bool_t ok = dbus_message_get_args (m, &f->e, DBUS_TYPE_STRING, &name, DBUS_TYPE_INVALID); g_assert_cmpstr (dbus_message_get_path (m), ==, DBUS_PATH_DBUS); test_assert_no_error (&f->e); g_assert (ok); if (g_str_equal (name, f->monitor_name)) { g_assert (!got_unique); got_unique = TRUE; } else if (g_str_equal (name, "com.example.A")) { g_assert (!got_a); got_a = TRUE; } else if (g_str_equal (name, "com.example.B")) { g_assert (!got_b); got_b = TRUE; } else { g_assert_cmpstr (name, ==, "com.example.C"); g_assert (!got_c); got_c = TRUE; } } else { g_error ("unexpected message %s.%s", dbus_message_get_interface (m), dbus_message_get_member (m)); } dbus_message_unref (m); } } become_monitor (f); while (!lost_unique || !lost_a || !lost_b || !lost_c) { test_main_context_iterate (f->ctx, TRUE); while ((m = g_queue_pop_head (&f->monitored)) != NULL) { if (dbus_message_is_signal (m, DBUS_INTERFACE_DBUS, "NameLost")) { const char *name; dbus_bool_t ok = dbus_message_get_args (m, &f->e, DBUS_TYPE_STRING, &name, DBUS_TYPE_INVALID); test_assert_no_error (&f->e); g_assert (ok); if (g_str_equal (name, f->monitor_name)) { g_assert (!lost_unique); lost_unique = TRUE; } else if (g_str_equal (name, "com.example.A")) { g_assert (!lost_a); lost_a = TRUE; } else if (g_str_equal (name, "com.example.B")) { g_assert (!lost_b); lost_b = TRUE; } else { g_assert_cmpstr (name, ==, "com.example.C"); g_assert (!lost_c); lost_c = TRUE; } } else { g_error ("unexpected message %s.%s", dbus_message_get_interface (m), dbus_message_get_member (m)); } dbus_message_unref (m); } } /* Calling methods is forbidden; we get disconnected. */ dbus_bus_add_match (f->monitor, "", &f->e); g_assert_cmpstr (f->e.name, ==, DBUS_ERROR_NO_REPLY); g_assert (!dbus_connection_get_is_connected (f->monitor)); while (TRUE) { test_main_context_iterate (f->ctx, TRUE); /* When we iterate all the connection's messages, we see ourselves * losing all our names, then we're disconnected. */ while ((m = g_queue_pop_head (&f->monitored)) != NULL) { if (dbus_message_is_signal (m, DBUS_INTERFACE_LOCAL, "Disconnected")) { dbus_message_unref (m); goto disconnected; } else { g_error ("unexpected message %s.%s", dbus_message_get_interface (m), dbus_message_get_member (m)); } dbus_message_unref (m); } } disconnected: g_assert (lost_a); g_assert (lost_b); g_assert (lost_c); } static void test_broadcast (Fixture *f, gconstpointer context) { DBusMessage *m; if (f->address == NULL) return; dbus_bus_add_match (f->recipient, "type='signal'", &f->e); test_assert_no_error (&f->e); become_monitor (f); m = dbus_message_new_signal ("/foo", "com.example.bar", "BroadcastSignal1"); dbus_connection_send (f->sender, m, NULL); dbus_message_unref (m); m = dbus_message_new_signal ("/foo", "com.example.bar", "BroadcastSignal2"); dbus_connection_send (f->sender, m, NULL); dbus_message_unref (m); m = dbus_message_new_signal ("/foo", "com.example.bar", "BroadcastSignal3"); dbus_connection_send (f->sender, m, NULL); dbus_message_unref (m); while (g_queue_get_length (&f->monitored) < 3) test_main_context_iterate (f->ctx, TRUE); m = g_queue_pop_head (&f->monitored); assert_signal (m, f->sender_name, "/foo", "com.example.bar", "BroadcastSignal1", "", NULL); dbus_message_unref (m); m = g_queue_pop_head (&f->monitored); assert_signal (m, f->sender_name, "/foo", "com.example.bar", "BroadcastSignal2", "", NULL); dbus_message_unref (m); m = g_queue_pop_head (&f->monitored); assert_signal (m, f->sender_name, "/foo", "com.example.bar", "BroadcastSignal3", "", NULL); dbus_message_unref (m); m = g_queue_pop_head (&f->monitored); g_assert (m == NULL); } static void test_forbidden_broadcast (Fixture *f, gconstpointer context) { DBusMessage *m; if (f->address == NULL) return; dbus_bus_add_match (f->recipient, "type='signal'", &f->e); test_assert_no_error (&f->e); become_monitor (f); m = dbus_message_new_signal ("/foo", "com.example.CannotSend", "BroadcastSignal1"); dbus_connection_send (f->sender, m, NULL); dbus_message_unref (m); m = dbus_message_new_signal ("/foo", "com.example.CannotReceive", "BroadcastSignal2"); dbus_connection_send (f->sender, m, NULL); dbus_message_unref (m); m = dbus_message_new_signal ("/foo", "com.example.CannotSend", "BroadcastSignal3"); dbus_connection_send (f->sender, m, NULL); dbus_message_unref (m); while (g_queue_get_length (&f->monitored) < 6) test_main_context_iterate (f->ctx, TRUE); m = g_queue_pop_head (&f->monitored); assert_signal (m, f->sender_name, "/foo", "com.example.CannotSend", "BroadcastSignal1", "", 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.CannotReceive", "BroadcastSignal2", "", 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.CannotSend", "BroadcastSignal3", "", 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); g_assert (m == NULL); } static void test_unicast_signal (Fixture *f, gconstpointer context) { DBusMessage *m; if (f->address == NULL) return; become_monitor (f); m = dbus_message_new_signal ("/foo", "com.example.bar", "UnicastSignal1"); 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.bar", "UnicastSignal2"); 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.bar", "UnicastSignal3"); 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) < 3) test_main_context_iterate (f->ctx, TRUE); m = g_queue_pop_head (&f->monitored); assert_signal (m, f->sender_name, "/foo", "com.example.bar", "UnicastSignal1", "", f->recipient_name); dbus_message_unref (m); m = g_queue_pop_head (&f->monitored); assert_signal (m, f->sender_name, "/foo", "com.example.bar", "UnicastSignal2", "", f->recipient_name); dbus_message_unref (m); m = g_queue_pop_head (&f->monitored); assert_signal (m, f->sender_name, "/foo", "com.example.bar", "UnicastSignal3", "", f->recipient_name); dbus_message_unref (m); m = g_queue_pop_head (&f->monitored); g_assert (m == NULL); } static void test_forbidden (Fixture *f, gconstpointer context) { DBusMessage *m; if (f->address == NULL) return; become_monitor (f); m = dbus_message_new_signal ("/foo", "com.example.CannotSend", "UnicastSignal1"); 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.CannotReceive", "UnicastSignal2"); 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.CannotSend", "UnicastSignal3"); 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) < 6) test_main_context_iterate (f->ctx, TRUE); m = g_queue_pop_head (&f->monitored); assert_signal (m, f->sender_name, "/foo", "com.example.CannotSend", "UnicastSignal1", "", 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.CannotReceive", "UnicastSignal2", "", 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.CannotSend", "UnicastSignal3", "", 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); g_assert (m == NULL); } static void test_method_call (Fixture *f, gconstpointer context) { DBusMessage *m; if (f->address == NULL) return; become_monitor (f); m = dbus_message_new_method_call (f->recipient_name, "/foo", "com.example.bar", "Call1"); 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_method_call (m, f->sender_name, f->recipient_name, "/foo", "com.example.bar", "Call1", ""); dbus_message_unref (m); m = g_queue_pop_head (&f->monitored); assert_error_reply (m, f->recipient_name, f->sender_name, DBUS_ERROR_UNKNOWN_METHOD); dbus_message_unref (m); m = g_queue_pop_head (&f->monitored); g_assert (m == NULL); } static void test_forbidden_method_call (Fixture *f, gconstpointer context) { DBusMessage *m; if (f->address == NULL) return; become_monitor (f); m = dbus_message_new_method_call (f->recipient_name, "/foo", "com.example.CannotSend", "Call1"); 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_method_call (m, f->sender_name, f->recipient_name, "/foo", "com.example.CannotSend", "Call1", ""); 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); g_assert (m == NULL); m = dbus_message_new_method_call (f->recipient_name, "/foo", "com.example.CannotReceive", "Call2"); 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_method_call (m, f->sender_name, f->recipient_name, "/foo", "com.example.CannotReceive", "Call2", ""); 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); g_assert (m == NULL); } static void test_dbus_daemon (Fixture *f, gconstpointer context) { DBusMessage *m; int res; if (f->address == NULL) return; become_monitor (f); res = dbus_bus_request_name (f->sender, "com.example.Sender", DBUS_NAME_FLAG_DO_NOT_QUEUE, &f->e); test_assert_no_error (&f->e); g_assert_cmpint (res, ==, DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER); res = dbus_bus_release_name (f->sender, "com.example.Sender", &f->e); test_assert_no_error (&f->e); g_assert_cmpint (res, ==, DBUS_RELEASE_NAME_REPLY_RELEASED); while (g_queue_get_length (&f->monitored) < 8) test_main_context_iterate (f->ctx, TRUE); m = g_queue_pop_head (&f->monitored); assert_method_call (m, f->sender_name, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, "RequestName", "su"); dbus_message_unref (m); m = g_queue_pop_head (&f->monitored); assert_signal (m, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, "NameOwnerChanged", "sss", NULL); dbus_message_unref (m); /* FIXME: should we get this? */ m = g_queue_pop_head (&f->monitored); assert_signal (m, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, "NameAcquired", "s", f->sender_name); dbus_message_unref (m); m = g_queue_pop_head (&f->monitored); assert_method_reply (m, DBUS_SERVICE_DBUS, f->sender_name, "u"); dbus_message_unref (m); m = g_queue_pop_head (&f->monitored); assert_method_call (m, f->sender_name, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, "ReleaseName", "s"); dbus_message_unref (m); /* FIXME: should we get this? */ m = g_queue_pop_head (&f->monitored); assert_signal (m, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, "NameLost", "s", f->sender_name); dbus_message_unref (m); m = g_queue_pop_head (&f->monitored); assert_signal (m, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, "NameOwnerChanged", "sss", NULL); dbus_message_unref (m); m = g_queue_pop_head (&f->monitored); assert_method_reply (m, DBUS_SERVICE_DBUS, f->sender_name, "u"); dbus_message_unref (m); m = g_queue_pop_head (&f->monitored); g_assert (m == NULL); } static void test_selective (Fixture *f, gconstpointer context) { DBusMessage *m; if (f->address == NULL) return; /* Match rules added before becoming a monitor should be cleared: * if they weren't, this test would get Interesting twice, then Tedious, * and only see Fun after that. */ dbus_bus_add_match (f->monitor, "eavesdrop='true',interface='com.example.Interesting'", &f->e); test_assert_no_error (&f->e); dbus_bus_add_match (f->monitor, "eavesdrop='true',interface='com.example.Tedious'", &f->e); test_assert_no_error (&f->e); become_monitor (f); m = dbus_message_new_signal ("/foo", "com.example.Interesting", "UnicastSignal1"); 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.Tedious", "UnicastSignal2"); 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.Fun", "UnicastSignal3"); 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); /* We get the interesting signal and the fun signal, but not the tedious * signal. */ m = g_queue_pop_head (&f->monitored); assert_signal (m, f->sender_name, "/foo", "com.example.Interesting", "UnicastSignal1", "", f->recipient_name); dbus_message_unref (m); m = g_queue_pop_head (&f->monitored); assert_signal (m, f->sender_name, "/foo", "com.example.Fun", "UnicastSignal3", "", f->recipient_name); dbus_message_unref (m); m = g_queue_pop_head (&f->monitored); g_assert (m == NULL); } static void expect_new_connection (Fixture *f) { DBusMessage *m; while (g_queue_get_length (&f->monitored) < 4) test_main_context_iterate (f->ctx, TRUE); m = g_queue_pop_head (&f->monitored); assert_hello (m); dbus_message_unref (m); m = g_queue_pop_head (&f->monitored); assert_hello_reply (m); dbus_message_unref (m); m = g_queue_pop_head (&f->monitored); assert_signal (m, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, "NameOwnerChanged", "sss", NULL); dbus_message_unref (m); m = g_queue_pop_head (&f->monitored); assert_name_acquired (m); dbus_message_unref (m); } 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 expect_take_well_known_name (Fixture *f, DBusConnection *connection, const char *name) { DBusMessage *m; const char *connection_name = dbus_bus_get_unique_name (connection); while (g_queue_get_length (&f->monitored) < 4) test_main_context_iterate (f->ctx, TRUE); m = g_queue_pop_head (&f->monitored); assert_method_call (m, connection_name, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, "RequestName", "su"); dbus_message_unref (m); m = g_queue_pop_head (&f->monitored); assert_signal (m, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, "NameOwnerChanged", "sss", NULL); dbus_message_unref (m); m = g_queue_pop_head (&f->monitored); assert_signal (m, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, "NameAcquired", "s", connection_name); dbus_message_unref (m); m = g_queue_pop_head (&f->monitored); assert_method_reply (m, DBUS_SERVICE_DBUS, connection_name, "u"); dbus_message_unref (m); } static void test_activation (Fixture *f, gconstpointer context) { DBusMessage *m; if (f->address == NULL) return; become_monitor (f); /* The sender sends a message to an activatable service. */ m = dbus_message_new_signal ("/foo", "com.example.bar", "UnicastSignal1"); if (!dbus_message_set_destination (m, "com.example.SystemdActivatable1")) g_error ("OOM"); dbus_connection_send (f->sender, m, NULL); dbus_message_unref (m); /* We observe the activation request, and the message that caused it, * before systemd has even joined the bus. */ 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, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, "org.freedesktop.systemd1.Activator", "ActivationRequest", "s", "org.freedesktop.systemd1"); dbus_message_unref (m); m = g_queue_pop_head (&f->monitored); assert_signal (m, f->sender_name, "/foo", "com.example.bar", "UnicastSignal1", "", "com.example.SystemdActivatable1"); dbus_message_unref (m); /* The fake systemd connects to the bus. */ f->systemd = test_connect_to_bus (f->ctx, f->address); if (!dbus_connection_add_filter (f->systemd, systemd_filter, f, NULL)) g_error ("OOM"); f->systemd_name = dbus_bus_get_unique_name (f->systemd); expect_new_connection (f); take_well_known_name (f, f->systemd, "org.freedesktop.systemd1"); expect_take_well_known_name (f, f->systemd, "org.freedesktop.systemd1"); /* It gets its activation request. */ while (f->systemd_message == NULL) test_main_context_iterate (f->ctx, TRUE); m = f->systemd_message; f->systemd_message = NULL; assert_signal (m, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, "org.freedesktop.systemd1.Activator", "ActivationRequest", "s", "org.freedesktop.systemd1"); dbus_message_unref (m); /* systemd starts the activatable service. */ f->activated = test_connect_to_bus (f->ctx, f->address); if (!dbus_connection_add_filter (f->activated, activated_filter, f, NULL)) g_error ("OOM"); f->activated_name = dbus_bus_get_unique_name (f->activated); expect_new_connection (f); take_well_known_name (f, f->activated, "com.example.SystemdActivatable1"); expect_take_well_known_name (f, f->activated, "com.example.SystemdActivatable1"); /* The message is delivered to the activatable service. */ while (f->activated_message == NULL) test_main_context_iterate (f->ctx, TRUE); m = f->activated_message; f->activated_message = NULL; assert_signal (m, f->sender_name, "/foo", "com.example.bar", "UnicastSignal1", "", "com.example.SystemdActivatable1"); dbus_message_unref (m); /* The sender sends a message to a different activatable service. */ m = dbus_message_new_signal ("/foo", "com.example.bar", "UnicastSignal2"); if (!dbus_message_set_destination (m, "com.example.SystemdActivatable2")) g_error ("OOM"); dbus_connection_send (f->sender, m, NULL); dbus_message_unref (m); /* This time systemd is already ready for it. */ while (g_queue_get_length (&f->monitored) < 2 || f->systemd_message == NULL) test_main_context_iterate (f->ctx, TRUE); m = f->systemd_message; f->systemd_message = NULL; assert_signal (m, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, "org.freedesktop.systemd1.Activator", "ActivationRequest", "s", "org.freedesktop.systemd1"); dbus_message_unref (m); /* The monitor sees the activation request and the signal that * prompted it.*/ m = g_queue_pop_head (&f->monitored); assert_signal (m, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, "org.freedesktop.systemd1.Activator", "ActivationRequest", "s", "org.freedesktop.systemd1"); dbus_message_unref (m); m = g_queue_pop_head (&f->monitored); assert_signal (m, f->sender_name, "/foo", "com.example.bar", "UnicastSignal2", "", "com.example.SystemdActivatable2"); dbus_message_unref (m); /* The activatable service takes its name. Here I'm faking it by using * an existing connection. */ take_well_known_name (f, f->activated, "com.example.SystemdActivatable2"); /* The message is delivered to the activatable service. * Implementation detail: the monitor sees this happen before it even * sees that the name request happened, which is pretty odd. */ while (f->activated_message == NULL) test_main_context_iterate (f->ctx, TRUE); m = f->activated_message; f->activated_message = NULL; assert_signal (m, f->sender_name, "/foo", "com.example.bar", "UnicastSignal2", "", "com.example.SystemdActivatable2"); dbus_message_unref (m); expect_take_well_known_name (f, f->activated, "com.example.SystemdActivatable2"); /* A third activation. */ m = dbus_message_new_signal ("/foo", "com.example.bar", "UnicastSignal3"); if (!dbus_message_set_destination (m, "com.example.SystemdActivatable3")) g_error ("OOM"); dbus_connection_send (f->sender, m, NULL); dbus_message_unref (m); /* Once again, we see the activation request and the reason. */ 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, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, "org.freedesktop.systemd1.Activator", "ActivationRequest", "s", "org.freedesktop.systemd1"); dbus_message_unref (m); m = g_queue_pop_head (&f->monitored); assert_signal (m, f->sender_name, "/foo", "com.example.bar", "UnicastSignal3", "", "com.example.SystemdActivatable3"); dbus_message_unref (m); /* systemd gets the request too. */ while (f->systemd_message == NULL) test_main_context_iterate (f->ctx, TRUE); m = f->systemd_message; f->systemd_message = NULL; assert_signal (m, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, "org.freedesktop.systemd1.Activator", "ActivationRequest", "s", "org.freedesktop.systemd1"); dbus_message_unref (m); /* This time activation fails */ m = dbus_message_new_signal ("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Activator", "ActivationFailure"); do { const char *unit = "dbus-com.example.SystemdActivatable3.service"; const char *error_name = "com.example.Nope"; const char *error_message = "Computer says no"; if (!dbus_message_append_args (m, DBUS_TYPE_STRING, &unit, DBUS_TYPE_STRING, &error_name, DBUS_TYPE_STRING, &error_message, DBUS_TYPE_INVALID)) g_error ("OOM"); } while (0); if (!dbus_message_set_destination (m, "org.freedesktop.DBus")) g_error ("OOM"); dbus_connection_send (f->systemd, m, NULL); dbus_message_unref (m); /* The monitor sees activation fail */ /* Once again, we see the activation request and the reason. */ while (g_queue_get_length (&f->monitored) < 1) test_main_context_iterate (f->ctx, TRUE); m = g_queue_pop_head (&f->monitored); assert_error_reply (m, DBUS_SERVICE_DBUS, f->sender_name, "com.example.Nope"); dbus_message_unref (m); } static void teardown (Fixture *f, gconstpointer context G_GNUC_UNUSED) { dbus_error_free (&f->e); g_clear_error (&f->ge); if (f->monitor != NULL) { dbus_connection_remove_filter (f->monitor, monitor_filter, f); dbus_connection_close (f->monitor); dbus_connection_unref (f->monitor); f->monitor = NULL; } if (f->sender != NULL) { dbus_connection_close (f->sender); dbus_connection_unref (f->sender); f->sender = NULL; } if (f->recipient != NULL) { dbus_connection_remove_filter (f->recipient, recipient_filter, f); dbus_connection_close (f->recipient); dbus_connection_unref (f->recipient); f->recipient = NULL; } if (f->systemd != NULL) { dbus_connection_remove_filter (f->systemd, systemd_filter, f); dbus_connection_close (f->systemd); dbus_connection_unref (f->systemd); f->systemd = NULL; } if (f->activated != NULL) { dbus_connection_remove_filter (f->activated, activated_filter, f); dbus_connection_close (f->activated); dbus_connection_unref (f->activated); f->activated = NULL; } test_kill_pid (f->daemon_pid); g_spawn_close_pid (f->daemon_pid); test_main_context_unref (f->ctx); g_queue_foreach (&f->monitored, (GFunc) dbus_message_unref, NULL); g_queue_clear (&f->monitored); g_free (f->address); } int main (int argc, char **argv) { test_init (&argc, &argv); g_test_add ("/monitor/become", Fixture, &side_effects_config, setup, test_become_monitor, teardown); g_test_add ("/monitor/broadcast", Fixture, NULL, setup, test_broadcast, teardown); g_test_add ("/monitor/forbidden-broadcast", Fixture, &forbidding_config, setup, test_forbidden_broadcast, teardown); g_test_add ("/monitor/unicast-signal", Fixture, NULL, setup, test_unicast_signal, teardown); g_test_add ("/monitor/forbidden", Fixture, &forbidding_config, setup, test_forbidden, teardown); g_test_add ("/monitor/method-call", Fixture, NULL, setup, test_method_call, teardown); g_test_add ("/monitor/forbidden-method", Fixture, &forbidding_config, setup, test_forbidden_method_call, teardown); g_test_add ("/monitor/dbus-daemon", Fixture, NULL, setup, test_dbus_daemon, teardown); g_test_add ("/monitor/selective", Fixture, &selective_config, setup, test_selective, teardown); g_test_add ("/monitor/wildcard", Fixture, &wildcard_config, setup, test_unicast_signal, teardown); g_test_add ("/monitor/no-rule", Fixture, &no_rules_config, setup, test_unicast_signal, teardown); g_test_add ("/monitor/eavesdrop", Fixture, &eavesdrop_config, setup, test_unicast_signal, teardown); g_test_add ("/monitor/no-eavesdrop", Fixture, &no_eavesdrop_config, setup, test_unicast_signal, teardown); g_test_add ("/monitor/activation", Fixture, &fake_systemd_config, setup, test_activation, teardown); return g_test_run (); }