From 6e62ddf54a6696c3e0b95a248cd28cb1e3cbfe0f Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Fri, 23 Jan 2015 19:21:06 +0000 Subject: [PATCH 07/10] Add a regression test for becoming a monitor This includes most of the situations I could think of: * method call on dbus-daemon and response * NameOwnerChanged * NameAcquired, NameLost (although I'm not 100% sure these should get captured, since they're redundant with NameOwnerChanged) * unicast message is allowed through * unicast message is rejected by no-sending or no-receiving policy * broadcast is allowed through * broadcast is rejected by no-sending policy (the error reply is also captured) * broadcast is rejected by no-receiving policy (there is no error reply) * message causing service activation, and the message telling systemd to do the actual activation * systemd reporting that activation failed Because some of the code here is systemd-specific, I wrote a test-case for systemd activation, which mostly addresses #57952 as a bonus. It does not cover: * sending a message to dbus-daemon, then provoking a reply, then dbus-daemon does not allow itself to send the reply due to its own security policy This is such an obscure corner case that I don't think it can be tested without using a second uid. --- configure.ac | 2 + test/Makefile.am | 19 + .../com.example.SystemdActivatable1.service | 4 + .../com.example.SystemdActivatable2.service | 4 + .../com.example.SystemdActivatable3.service | 4 + .../org.freedesktop.systemd1.service | 3 + test/data/valid-config-files/forbidding.conf.in | 20 + .../valid-config-files/systemd-activation.conf.in | 12 + test/monitor.c | 1447 ++++++++++++++++++++ test/test-utils-glib.c | 3 + 10 files changed, 1518 insertions(+) create mode 100644 test/data/systemd-activation/com.example.SystemdActivatable1.service create mode 100644 test/data/systemd-activation/com.example.SystemdActivatable2.service create mode 100644 test/data/systemd-activation/com.example.SystemdActivatable3.service create mode 100644 test/data/systemd-activation/org.freedesktop.systemd1.service create mode 100644 test/data/valid-config-files/forbidding.conf.in create mode 100644 test/data/valid-config-files/systemd-activation.conf.in create mode 100644 test/monitor.c diff --git a/configure.ac b/configure.ac index 70d31b7..524f4d2 100644 --- a/configure.ac +++ b/configure.ac @@ -1785,7 +1785,9 @@ dbus-1-uninstalled.pc test/data/valid-config-files/debug-allow-all.conf test/data/valid-config-files/debug-allow-all-sha1.conf test/data/valid-config-files/finite-timeout.conf +test/data/valid-config-files/forbidding.conf test/data/valid-config-files/incoming-limit.conf +test/data/valid-config-files/systemd-activation.conf test/data/valid-config-files-system/debug-allow-all-pass.conf test/data/valid-config-files-system/debug-allow-all-fail.conf test/data/valid-service-files/org.freedesktop.DBus.TestSuite.PrivServer.service diff --git a/test/Makefile.am b/test/Makefile.am index a8327d0..4085b8f 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -149,6 +149,7 @@ installable_tests += \ test-corrupt \ test-dbus-daemon \ test-dbus-daemon-eavesdrop \ + test-monitor \ test-loopback \ test-marshal \ test-refs \ @@ -163,12 +164,15 @@ endif DBUS_WITH_GLIB installcheck_tests = installcheck_environment = \ + XDG_DATA_DIRS=${datadir}:/usr/local/share:/usr/share \ XDG_RUNTIME_DIR=@abs_top_builddir@/test/XDG_RUNTIME_DIR \ DBUS_TEST_DAEMON=$(DESTDIR)$(DBUS_DAEMONDIR)/dbus-daemon$(EXEEXT) \ DBUS_TEST_HOMEDIR=@abs_top_builddir@/dbus \ DBUS_TEST_SYSCONFDIR=$(DESTDIR)$(sysconfdir) TESTS_ENVIRONMENT = \ + XDG_DATA_HOME=@abs_top_builddir@/test/data \ + XDG_DATA_DIRS=@abs_top_srcdir@/test/data \ XDG_RUNTIME_DIR=@abs_top_builddir@/test/XDG_RUNTIME_DIR \ DBUS_FATAL_WARNINGS=1 \ DBUS_TEST_DAEMON=@abs_top_builddir@/bus/dbus-daemon$(EXEEXT) \ @@ -222,6 +226,15 @@ test_dbus_daemon_eavesdrop_LDADD = \ $(GLIB_LIBS) \ $(NULL) +test_monitor_SOURCES = \ + monitor.c \ + $(NULL) +test_monitor_CPPFLAGS = $(testutils_shared_if_possible_cppflags) +test_monitor_LDADD = \ + $(testutils_shared_if_possible_libs) \ + $(GLIB_LIBS) \ + $(NULL) + test_marshal_SOURCES = marshal.c test_marshal_LDADD = \ $(top_builddir)/dbus/libdbus-1.la \ @@ -266,7 +279,9 @@ in_data = \ data/valid-config-files/debug-allow-all-sha1.conf.in \ data/valid-config-files/debug-allow-all.conf.in \ data/valid-config-files/finite-timeout.conf.in \ + data/valid-config-files/forbidding.conf.in \ data/valid-config-files/incoming-limit.conf.in \ + data/valid-config-files/systemd-activation.conf.in \ data/invalid-service-files-system/org.freedesktop.DBus.TestSuiteNoExec.service.in \ data/invalid-service-files-system/org.freedesktop.DBus.TestSuiteNoService.service.in \ data/invalid-service-files-system/org.freedesktop.DBus.TestSuiteNoUser.service.in \ @@ -335,6 +350,10 @@ static_data = \ data/sha-1/bit-messages.sha1 \ data/sha-1/byte-hashes.sha1 \ data/sha-1/byte-messages.sha1 \ + data/systemd-activation/com.example.SystemdActivatable1.service \ + data/systemd-activation/com.example.SystemdActivatable2.service \ + data/systemd-activation/com.example.SystemdActivatable3.service \ + data/systemd-activation/org.freedesktop.systemd1.service \ data/valid-config-files/basic.conf \ data/valid-config-files/basic.d/basic.conf \ data/valid-config-files/entities.conf \ diff --git a/test/data/systemd-activation/com.example.SystemdActivatable1.service b/test/data/systemd-activation/com.example.SystemdActivatable1.service new file mode 100644 index 0000000..f15f038 --- /dev/null +++ b/test/data/systemd-activation/com.example.SystemdActivatable1.service @@ -0,0 +1,4 @@ +[D-BUS Service] +Name=com.example.SystemdActivatable1 +Exec=/bin/false 1 +SystemdService=dbus-com.example.SystemdActivatable1.service diff --git a/test/data/systemd-activation/com.example.SystemdActivatable2.service b/test/data/systemd-activation/com.example.SystemdActivatable2.service new file mode 100644 index 0000000..dcedd73 --- /dev/null +++ b/test/data/systemd-activation/com.example.SystemdActivatable2.service @@ -0,0 +1,4 @@ +[D-BUS Service] +Name=com.example.SystemdActivatable2 +Exec=/bin/false 2 +SystemdService=dbus-com.example.SystemdActivatable2.service diff --git a/test/data/systemd-activation/com.example.SystemdActivatable3.service b/test/data/systemd-activation/com.example.SystemdActivatable3.service new file mode 100644 index 0000000..f6f0559 --- /dev/null +++ b/test/data/systemd-activation/com.example.SystemdActivatable3.service @@ -0,0 +1,4 @@ +[D-BUS Service] +Name=com.example.SystemdActivatable3 +Exec=/bin/false 3 +SystemdService=dbus-com.example.SystemdActivatable3.service diff --git a/test/data/systemd-activation/org.freedesktop.systemd1.service b/test/data/systemd-activation/org.freedesktop.systemd1.service new file mode 100644 index 0000000..aea9311 --- /dev/null +++ b/test/data/systemd-activation/org.freedesktop.systemd1.service @@ -0,0 +1,3 @@ +[D-BUS Service] +Name=org.freedesktop.systemd1 +Exec=/bin/false diff --git a/test/data/valid-config-files/forbidding.conf.in b/test/data/valid-config-files/forbidding.conf.in new file mode 100644 index 0000000..ae47c28 --- /dev/null +++ b/test/data/valid-config-files/forbidding.conf.in @@ -0,0 +1,20 @@ + + + + session + @TEST_LISTEN@ + + + + + + + + + + + + + + diff --git a/test/data/valid-config-files/systemd-activation.conf.in b/test/data/valid-config-files/systemd-activation.conf.in new file mode 100644 index 0000000..9c0871c --- /dev/null +++ b/test/data/valid-config-files/systemd-activation.conf.in @@ -0,0 +1,12 @@ + + + @TEST_LISTEN@ + @DBUS_TEST_DATA@/systemd-activation + + + + + + + diff --git a/test/monitor.c b/test/monitor.c new file mode 100644 index 0000000..c152f79 --- /dev/null +++ b/test/monitor.c @@ -0,0 +1,1447 @@ +/* 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, + &f->daemon_pid); + + 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 +pending_call_store_reply (DBusPendingCall *pc, + void *data) +{ + DBusMessage **message_p = data; + + *message_p = dbus_pending_call_steal_reply (pc); + g_assert (*message_p != NULL); +} + +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)) + pending_call_store_reply (pc, &m); + else if (!dbus_pending_call_set_notify (pc, 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; + + 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; + + 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; + + 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) < 3) + 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); + + /* FIXME: dbus-daemon does not fake a denial message for the monitor's + * benefit; should it? */ + + 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_signal (m, f->sender_name, "/foo", "com.example.CannotSend", + "BroadcastSignal3", "", NULL); + 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; + + 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; + + 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; + + 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; + + 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; + + 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; + + 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; + + 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) +{ + g_test_init (&argc, &argv, NULL); + g_test_bug_base ("https://bugs.freedesktop.org/show_bug.cgi?id="); + + 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 (); +} diff --git a/test/test-utils-glib.c b/test/test-utils-glib.c index 10eedf6..62de0c6 100644 --- a/test/test-utils-glib.c +++ b/test/test-utils-glib.c @@ -39,6 +39,9 @@ spawn_dbus_daemon (gchar *binary, configuration, "--nofork", "--print-address=1", /* stdout */ +#ifdef DBUS_UNIX + "--systemd-activation", +#endif NULL }; -- 2.1.4