diff options
Diffstat (limited to 'test')
33 files changed, 4015 insertions, 395 deletions
diff --git a/test/.gitignore b/test/.gitignore index 78dfbd30..4eaae4a4 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -29,8 +29,11 @@ test-relay test-dbus-daemon test-marshal manual-authz +manual-dir-iter +manual-tcp test-dbus-daemon-eavesdrop test-printf test-refs test-syntax test-syslog +internals/.dirstamp diff --git a/test/Makefile.am b/test/Makefile.am index cec5cdab..c2a55c9e 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -25,15 +25,23 @@ static_cppflags = \ noinst_LTLIBRARIES = libdbus-testutils-internal.la +libdbus_testutils_la_SOURCES = \ + test-utils.c \ + test-utils.h \ + $(NULL) + +if DBUS_WITH_GLIB +libdbus_testutils_la_SOURCES += \ + test-utils-glib.c \ + test-utils-glib.h \ + $(NULL) +endif + # You can link either libdbus-testutils, dbus-glib and libdbus-1, # or libdbus-testutils-internal and libdbus-internal - never both in the # same binary. if DBUS_WITH_DBUS_GLIB noinst_LTLIBRARIES += libdbus-testutils.la -libdbus_testutils_la_SOURCES = \ - test-utils.c \ - test-utils.h \ - $(NULL) libdbus_testutils_la_LIBADD = \ $(top_builddir)/dbus/libdbus-1.la \ $(GLIB_LIBS) \ @@ -50,8 +58,7 @@ libdbus_testutils_internal_la_CPPFLAGS = \ $(static_cppflags) \ $(NULL) libdbus_testutils_internal_la_SOURCES = \ - test-utils.c \ - test-utils.h \ + $(libdbus_testutils_la_SOURCES) \ $(NULL) libdbus_testutils_internal_la_LIBADD = \ $(top_builddir)/dbus/libdbus-internal.la \ @@ -75,12 +82,12 @@ TEST_BINARIES = \ ## since they depend on stuff from this directory TESTS = \ ../bus/test-bus$(EXEEXT) \ - ../bus/test-bus-system$(EXEEXT) \ ../dbus/test-dbus$(EXEEXT) \ $(NULL) if DBUS_UNIX TESTS += ../bus/test-bus-launch-helper$(EXEEXT) +TESTS += ../bus/test-bus-system$(EXEEXT) endif else !DBUS_ENABLE_EMBEDDED_TESTS @@ -119,36 +126,55 @@ test_syslog_SOURCES = internals/syslog.c test_syslog_CPPFLAGS = $(static_cppflags) test_syslog_LDADD = libdbus-testutils-internal.la $(GLIB_LIBS) +manual_dir_iter_SOURCES = manual-dir-iter.c +manual_dir_iter_CPPFLAGS = $(static_cppflags) +manual_dir_iter_LDADD = $(top_builddir)/dbus/libdbus-internal.la + +manual_tcp_SOURCES = manual-tcp.c +manual_tcp_CPPFLAGS = $(static_cppflags) +manual_tcp_LDADD = $(top_builddir)/dbus/libdbus-internal.la + EXTRA_DIST = dbus-test-runner -testexecdir = $(libdir)/dbus-1.0/test +testexecdir = $(libexecdir)/installed-tests/dbus +testmetadir = $(datadir)/installed-tests/dbus testexec_PROGRAMS = +testmeta_DATA = installable_tests = \ test-shell \ test-printf \ $(NULL) installable_manual_tests = \ - $(NULL) + manual-dir-iter \ + manual-tcp \ + $(NULL) if DBUS_WITH_GLIB installable_tests += \ test-corrupt \ test-dbus-daemon \ test-dbus-daemon-eavesdrop \ + test-fdpass \ + test-monitor \ test-loopback \ test-marshal \ test-refs \ test-relay \ + test-sd-activation \ test-syntax \ test-syslog \ + test-uid-permissions \ $(NULL) installable_manual_tests += \ manual-authz \ $(NULL) endif DBUS_WITH_GLIB +installable_test_meta = $(installable_tests:=.test) +installable_test_meta_with_config = $(installable_tests:=_with_config.test) + installcheck_tests = installcheck_environment = \ XDG_RUNTIME_DIR=@abs_top_builddir@/test/XDG_RUNTIME_DIR \ @@ -206,15 +232,55 @@ test_dbus_daemon_eavesdrop_LDADD = \ $(GLIB_LIBS) \ $(NULL) +test_sd_activation_SOURCES = \ + sd-activation.c \ + $(NULL) +test_sd_activation_CPPFLAGS = $(testutils_shared_if_possible_cppflags) +test_sd_activation_LDADD = \ + $(testutils_shared_if_possible_libs) \ + $(GLIB_LIBS) \ + $(NULL) + test_marshal_SOURCES = marshal.c +test_marshal_CPPFLAGS = $(testutils_shared_if_possible_cppflags) test_marshal_LDADD = \ - $(top_builddir)/dbus/libdbus-1.la \ + $(testutils_shared_if_possible_libs) \ + $(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_syntax_SOURCES = syntax.c +test_syntax_CPPFLAGS = $(testutils_shared_if_possible_cppflags) test_syntax_LDADD = \ - $(top_builddir)/dbus/libdbus-1.la \ + $(testutils_shared_if_possible_libs) \ + $(GLIB_LIBS) \ + $(NULL) + +test_uid_permissions_SOURCES = \ + uid-permissions.c \ + $(NULL) +test_uid_permissions_CPPFLAGS = $(testutils_shared_if_possible_cppflags) +test_uid_permissions_LDADD = \ + $(testutils_shared_if_possible_libs) \ + $(GLIB_LIBS) \ + $(NULL) + +test_fdpass_SOURCES = \ + fdpass.c \ + $(NULL) +test_fdpass_CPPFLAGS = \ + $(static_cppflags) \ + $(NULL) +test_fdpass_LDADD = \ + libdbus-testutils-internal.la \ $(GLIB_LIBS) \ $(NULL) @@ -224,6 +290,9 @@ installcheck_tests += $(installable_tests) if DBUS_ENABLE_INSTALLED_TESTS testexec_PROGRAMS += $(installable_tests) $(installable_manual_tests) + + testmeta_DATA += $(installable_test_meta) + testmeta_DATA += $(installable_test_meta_with_config) else !DBUS_ENABLE_INSTALLED_TESTS noinst_PROGRAMS += $(installable_tests) $(installable_manual_tests) endif !DBUS_ENABLE_INSTALLED_TESTS @@ -249,7 +318,11 @@ in_data = \ data/valid-config-files-system/debug-allow-all-pass.conf.in \ 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/multi-user.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 \ @@ -318,6 +391,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 \ @@ -344,25 +421,84 @@ EXTRA_DIST += $(static_data) ## copy tests to builddir so that generated tests and static tests ## are all in one place. -all-local: +all-local: copy-config-local uninstalled-config-local + @: + +copy-config-local: $(AM_V_at)$(MKDIR_P) data/valid-config-files/session.d - $(AM_V_at)set -e && \ + $(AM_V_GEN)set -e; \ if test $(srcdir) = . || test $(srcdir) -ef .; then \ echo '-- No need to copy test data as srcdir = builddir'; \ else \ for F in $(static_data); do \ - $(MKDIR_P) $${F%/*}; \ - rm -f $$F; \ - cp $(srcdir)/$$F $$F; \ + $(MKDIR_P) "$${F%/*}"; \ + rm -f "$$F"; \ + cp $(srcdir)/"$$F" "$$F"; \ done; \ fi +uninstalled-config-local: + $(AM_V_GEN)set -e; \ + for F in $(in_data); do \ + $(MKDIR_P) "$${F%/*}"; \ + sed \ + -e 's,[@]DBUS_TEST_DATA[@],@abs_builddir@/data,' \ + -e 's,[@]DBUS_TEST_EXEC[@],@abs_builddir@,' \ + -e 's,[@]EXEEXT[@],$(EXEEXT),' \ + -e 's,[@]TEST_LAUNCH_HELPER_BINARY[@],@abs_top_builddir@/bus/dbus-daemon-launch-helper-test$(EXEEXT),' \ + -e 's,[@]TEST_LISTEN[@],$(TEST_LISTEN),' \ + < $(srcdir)/"$$F" > "$${F%.in}"; \ + done + +installable-config-local: +if DBUS_ENABLE_INSTALLED_TESTS + $(AM_V_GEN)set -e; \ + for F in $(in_data); do \ + $(MKDIR_P) "installable/$${F%/*}"; \ + sed \ + -e 's,[@]DBUS_TEST_DATA[@],$(testexecdir)/data,' \ + -e 's,[@]DBUS_TEST_EXEC[@],$(testexecdir),' \ + -e 's,[@]EXEEXT[@],$(EXEEXT),' \ + -e 's,[@]TEST_LAUNCH_HELPER_BINARY[@],/bin/false,' \ + -e 's,[@]TEST_LISTEN[@],$(TEST_LISTEN),' \ + < $(srcdir)/"$$F" > "installable/$${F%.in}"; \ + done +else + @: +endif + + +install-data-local: install-config-local + @: + +install-config-local: installable-config-local +if DBUS_ENABLE_INSTALLED_TESTS + $(AM_V_GEN)set -e; \ + for F in $(static_data); do \ + install -d "$(DESTDIR)$(testexecdir)/$${F%/*}"; \ + install -m644 "$(srcdir)/$$F" "$(DESTDIR)$(testexecdir)/$$F"; \ + done; \ + for F in $(in_data); do \ + install -d "$(DESTDIR)$(testexecdir)/$${F%/*}"; \ + install -m644 "installable/$${F%.in}" "$(DESTDIR)$(testexecdir)/$${F%.in}"; \ + done + ln -nfs $(sysconfdir)/dbus-1/session.conf $(DESTDIR)$(testexecdir)/data/valid-config-files/session.conf + ln -nfs $(sysconfdir)/dbus-1/session.d $(DESTDIR)$(testexecdir)/data/valid-config-files/session.d + ln -nfs $(sysconfdir)/dbus-1/system.conf $(DESTDIR)$(testexecdir)/data/valid-config-files/system.conf + ln -nfs $(sysconfdir)/dbus-1/system.d $(DESTDIR)$(testexecdir)/data/valid-config-files/system.d +else + @: +endif + ## this doesn't clean most copied test data files when srcdir=builddir clean-local: $(AM_V_at)if test $(srcdir) = . || test $(srcdir) -ef .; then \ echo '-- No need to clean test data as srcdir = builddir'; \ else \ rm -f $(static_data); \ + for F in $(in_data); do \ + rm -f "$${F%.in}"; \ + done; \ fi imported_data = \ @@ -371,8 +507,26 @@ imported_data = \ $(NULL) noinst_DATA = $(imported_data) -CLEANFILES = $(noinst_DATA) XDG_RUNTIME_DIR +CLEANFILES = \ + $(noinst_DATA) \ + XDG_RUNTIME_DIR \ + installable \ + $(NULL) $(imported_data): data/valid-config-files/%.conf: $(top_builddir)/bus/%.conf $(AM_V_at)$(MKDIR_P) data/valid-config-files $(AM_V_GEN)cp $< $@ + +$(installable_test_meta): %.test: %$(EXEEXT) Makefile + $(AM_V_GEN) ( \ + echo '[Test]'; \ + echo 'Type=session'; \ + echo 'Exec=env DBUS_TEST_HOME=$$(pwd)/home.tmp $(testexecdir)/$*'; \ + ) > $@.tmp && mv $@.tmp $@ + +$(installable_test_meta_with_config): %_with_config.test: %$(EXEEXT) Makefile + $(AM_V_GEN) ( \ + echo '[Test]'; \ + echo 'Type=session'; \ + echo 'Exec=env DBUS_TEST_HOME=$$(pwd)/home.tmp DBUS_TEST_DATA=$(testexecdir)/data $(testexecdir)/$*'; \ + ) > $@.tmp && mv $@.tmp $@ diff --git a/test/corrupt.c b/test/corrupt.c index 1a7d4460..500cbeb7 100644 --- a/test/corrupt.c +++ b/test/corrupt.c @@ -31,7 +31,7 @@ #include <dbus/dbus.h> -#include "test-utils.h" +#include "test-utils-glib.h" typedef struct { DBusError e; @@ -379,8 +379,7 @@ int main (int argc, char **argv) { - g_test_init (&argc, &argv, NULL); - g_type_init (); + test_init (&argc, &argv); g_test_add ("/corrupt/tcp", Fixture, "tcp:host=127.0.0.1", setup, test_corrupt, teardown); 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 00000000..f15f0386 --- /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 00000000..dcedd734 --- /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 00000000..f6f0559c --- /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 00000000..aea93113 --- /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/.gitignore b/test/data/valid-config-files/.gitignore index a38e9d15..2a09552e 100644 --- a/test/data/valid-config-files/.gitignore +++ b/test/data/valid-config-files/.gitignore @@ -1,5 +1,7 @@ debug-allow-all.conf debug-allow-all-sha1.conf +incoming-limit.conf session.conf system.conf run-with-tmp-session-bus.conf +finite-timeout.conf diff --git a/test/data/valid-config-files/finite-timeout.conf.in b/test/data/valid-config-files/finite-timeout.conf.in new file mode 100644 index 00000000..7d26d715 --- /dev/null +++ b/test/data/valid-config-files/finite-timeout.conf.in @@ -0,0 +1,19 @@ +<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN" + "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> +<busconfig> + <!-- Our well-known bus type, don't change this --> + <type>session</type> + <listen>@TEST_LISTEN@</listen> + + <policy context="default"> + <!-- Allow everything to be sent --> + <allow send_destination="*" eavesdrop="true"/> + <!-- Allow everything to be received --> + <allow eavesdrop="true"/> + <!-- Allow anyone to own anything --> + <allow own="*"/> + </policy> + + <!-- Forcibly time out method calls after 100ms --> + <limit name="reply_timeout">100</limit> +</busconfig> 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 00000000..6a674f88 --- /dev/null +++ b/test/data/valid-config-files/forbidding.conf.in @@ -0,0 +1,18 @@ +<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN" + "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> +<busconfig> + <!-- Our well-known bus type, don't change this --> + <type>session</type> + <listen>@TEST_LISTEN@</listen> + + <policy context="default"> + <!-- Allow everything --> + <allow send_destination="*"/> + <allow receive_sender="*"/> + <allow own="*"/> + + <!-- Exception: some messages are forbidden --> + <deny send_interface="com.example.CannotSend"/> + <deny receive_interface="com.example.CannotReceive"/> + </policy> +</busconfig> diff --git a/test/data/valid-config-files/multi-user.conf.in b/test/data/valid-config-files/multi-user.conf.in new file mode 100644 index 00000000..37a7da67 --- /dev/null +++ b/test/data/valid-config-files/multi-user.conf.in @@ -0,0 +1,15 @@ +<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" + "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> +<busconfig> + <listen>@TEST_LISTEN@</listen> + + <policy context="default"> + <allow send_interface="*"/> + <allow receive_interface="*"/> + <allow own="*"/> + <allow user="*"/> + </policy> + + <!-- avoid allowing service activation since we are allowing everyone in --> + <servicehelper>/bin/false</servicehelper> +</busconfig> 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 00000000..bcd6416c --- /dev/null +++ b/test/data/valid-config-files/systemd-activation.conf.in @@ -0,0 +1,11 @@ +<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" + "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> +<busconfig> + <listen>@TEST_LISTEN@</listen> + <servicedir>@DBUS_TEST_DATA@/systemd-activation</servicedir> + <policy context="default"> + <allow send_destination="*"/> + <allow receive_sender="*"/> + <allow own="*"/> + </policy> +</busconfig> diff --git a/test/dbus-daemon-eavesdrop.c b/test/dbus-daemon-eavesdrop.c index a78d8888..41985787 100644 --- a/test/dbus-daemon-eavesdrop.c +++ b/test/dbus-daemon-eavesdrop.c @@ -27,21 +27,9 @@ #include <config.h> -#include <glib.h> - -#include <dbus/dbus.h> - #include <string.h> -#ifdef DBUS_WIN -# include <io.h> -# include <windows.h> -#else -# include <signal.h> -# include <unistd.h> -#endif - -#include "test-utils.h" +#include "test-utils-glib.h" #define SENDER_NAME "test.eavesdrop.sender" #define SENDER_PATH "/test/eavesdrop/sender" @@ -91,99 +79,6 @@ typedef struct { dbus_bool_t politelistener_got_stopper; } Fixture; -#define assert_no_error(e) _assert_no_error (e, __FILE__, __LINE__) -static void -_assert_no_error (const DBusError *e, - const char *file, - int line) -{ - if (G_UNLIKELY (dbus_error_is_set (e))) - g_error ("%s:%d: expected success but got error: %s: %s", - file, line, e->name, e->message); -} - -static gchar * -spawn_dbus_daemon (gchar *binary, - gchar *configuration, - GPid *daemon_pid) -{ - GError *error = NULL; - GString *address; - gint address_fd; - gchar *argv[] = { - binary, - configuration, - "--nofork", - "--print-address=1", /* stdout */ - NULL - }; - - g_spawn_async_with_pipes (NULL, /* working directory */ - argv, - NULL, /* envp */ - G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH, - NULL, /* child_setup */ - NULL, /* user data */ - daemon_pid, - NULL, /* child's stdin = /dev/null */ - &address_fd, - NULL, /* child's stderr = our stderr */ - &error); - g_assert_no_error (error); - - address = g_string_new (NULL); - - /* polling until the dbus-daemon writes out its address is a bit stupid, - * but at least it's simple, unlike dbus-launch... in principle we could - * use select() here, but life's too short */ - while (1) - { - gssize bytes; - gchar buf[4096]; - gchar *newline; - - bytes = read (address_fd, buf, sizeof (buf)); - - if (bytes > 0) - g_string_append_len (address, buf, bytes); - - newline = strchr (address->str, '\n'); - - if (newline != NULL) - { - if ((newline > address->str) && ('\r' == newline[-1])) - newline -= 1; - g_string_truncate (address, newline - address->str); - break; - } - - g_usleep (G_USEC_PER_SEC / 10); - } - - return g_string_free (address, FALSE); -} - -static DBusConnection * -connect_to_bus (Fixture *f, - const gchar *address) -{ - DBusConnection *conn; - DBusError error = DBUS_ERROR_INIT; - dbus_bool_t ok; - - conn = dbus_connection_open_private (address, &error); - assert_no_error (&error); - g_assert (conn != NULL); - - ok = dbus_bus_register (conn, &error); - assert_no_error (&error); - g_assert (ok); - g_assert (dbus_bus_get_unique_name (conn) != NULL); - - test_connection_setup (f->ctx, conn); - return conn; -} - /* send a unicast signal to <self> to ensure that no other connection * listening is the actual recipient for the signal */ static DBusHandlerResult @@ -338,9 +233,9 @@ add_receiver_filter (Fixture *f) DBusError e = DBUS_ERROR_INIT; dbus_bus_add_match (f->receiver, RECEIVER_RULE, &e); - assert_no_error (&e); + test_assert_no_error (&e); dbus_bus_add_match (f->receiver, STOPPER_RULE, &e); - assert_no_error (&e); + test_assert_no_error (&e); if (!dbus_connection_add_filter (f->receiver, signal_filter, f, NULL)) @@ -353,9 +248,9 @@ add_eavesdropper_filter (Fixture *f) DBusError e = DBUS_ERROR_INIT; dbus_bus_add_match (f->eavesdropper, EAVESDROPPER_RULE, &e); - assert_no_error (&e); + test_assert_no_error (&e); dbus_bus_add_match (f->eavesdropper, STOPPER_RULE, &e); - assert_no_error (&e); + test_assert_no_error (&e); if (!dbus_connection_add_filter (f->eavesdropper, signal_filter, f, NULL)) @@ -368,9 +263,9 @@ add_politelistener_filter (Fixture *f) DBusError e = DBUS_ERROR_INIT; dbus_bus_add_match (f->politelistener, POLITELISTENER_RULE, &e); - assert_no_error (&e); + test_assert_no_error (&e); dbus_bus_add_match (f->politelistener, STOPPER_RULE, &e); - assert_no_error (&e); + test_assert_no_error (&e); if (!dbus_connection_add_filter (f->politelistener, signal_filter, f, NULL)) @@ -381,8 +276,6 @@ static void setup (Fixture *f, gconstpointer context G_GNUC_UNUSED) { - gchar *dbus_daemon; - gchar *config; gchar *address; f->ctx = test_main_context_get (); @@ -390,45 +283,14 @@ setup (Fixture *f, f->ge = NULL; dbus_error_init (&f->e); - dbus_daemon = g_strdup (g_getenv ("DBUS_TEST_DAEMON")); - - if (dbus_daemon == NULL) - dbus_daemon = g_strdup ("dbus-daemon"); - - if (g_getenv ("DBUS_TEST_SYSCONFDIR") != NULL) - { - config = g_strdup_printf ("--config-file=%s/dbus-1/session.conf", - g_getenv ("DBUS_TEST_SYSCONFDIR")); - } - else if (g_getenv ("DBUS_TEST_DATA") != NULL) - { - config = g_strdup_printf ( - "--config-file=%s/valid-config-files/session.conf", - g_getenv ("DBUS_TEST_DATA")); - } - else - { - config = g_strdup ("--session"); - } - - if (g_getenv ("DBUS_TEST_DAEMON_ADDRESS") != NULL) - { - address = g_strdup (g_getenv ("DBUS_TEST_DAEMON_ADDRESS")); - } - else - { - address = spawn_dbus_daemon (dbus_daemon, config, &f->daemon_pid); - } - - g_free (dbus_daemon); - g_free (config); + address = test_get_dbus_daemon (NULL, TEST_USER_ME, &f->daemon_pid); - f->sender = connect_to_bus (f, address); + f->sender = test_connect_to_bus (f->ctx, address); dbus_bus_request_name (f->sender, SENDER_NAME, DBUS_NAME_FLAG_DO_NOT_QUEUE, &(f->e)); - f->receiver = connect_to_bus (f, address); - f->eavesdropper = connect_to_bus (f, address); - f->politelistener = connect_to_bus (f, address); + f->receiver = test_connect_to_bus (f->ctx, address); + f->eavesdropper = test_connect_to_bus (f->ctx, address); + f->politelistener = test_connect_to_bus (f->ctx, address); add_receiver_filter (f); add_politelistener_filter (f); add_eavesdropper_filter (f); @@ -541,12 +403,7 @@ teardown (Fixture *f, f->eavesdropper = NULL; } -#ifdef DBUS_WIN - TerminateProcess (f->daemon_pid, 1); -#else - kill (f->daemon_pid, SIGTERM); -#endif - + test_kill_pid (f->daemon_pid); g_spawn_close_pid (f->daemon_pid); test_main_context_unref (f->ctx); @@ -556,8 +413,7 @@ int main (int argc, char **argv) { - g_test_init (&argc, &argv, NULL); - g_test_bug_base ("https://bugs.freedesktop.org/show_bug.cgi?id="); + test_init (&argc, &argv); g_test_add ("/eavedrop/match_keyword/broadcast", Fixture, NULL, setup, test_eavesdrop_broadcast, teardown); diff --git a/test/dbus-daemon.c b/test/dbus-daemon.c index dc0f1317..6b0e9b8a 100644 --- a/test/dbus-daemon.c +++ b/test/dbus-daemon.c @@ -26,22 +26,37 @@ #include <config.h> -#include <glib.h> +#include "test-utils-glib.h" -#include <dbus/dbus.h> - -#include <string.h> - -#ifdef DBUS_WIN -# include <io.h> -# include <windows.h> -#else -# include <signal.h> +#ifdef DBUS_UNIX # include <unistd.h> # include <sys/types.h> #endif -#include "test-utils.h" +/* Platforms where we know that credentials-passing passes both the + * uid and the pid. Please keep these in alphabetical order. + * + * These platforms should #error in _dbus_read_credentials_socket() + * if we didn't detect their flavour of credentials-passing, since that + * would be a regression. + */ +#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || \ + defined(__linux__) || \ + defined(__NetBSD__) || \ + defined(__OpenBSD__) +# define UNIX_USER_SHOULD_WORK +# define PID_SHOULD_WORK +#endif + +/* Platforms where we know that credentials-passing passes the + * uid, but not necessarily the pid. Again, alphabetical order please. + * + * These platforms should also #error in _dbus_read_credentials_socket() + * if we didn't detect their flavour of credentials-passing. + */ +#if 0 /* defined(__your_platform_here__) */ +# define UNIX_USER_SHOULD_WORK +#endif typedef struct { gboolean skip; @@ -57,111 +72,27 @@ typedef struct { DBusConnection *right_conn; gboolean right_conn_echo; + gboolean wait_forever_called; } Fixture; -#define assert_no_error(e) _assert_no_error (e, __FILE__, __LINE__) -static void -_assert_no_error (const DBusError *e, - const char *file, - int line) -{ - if (G_UNLIKELY (dbus_error_is_set (e))) - g_error ("%s:%d: expected success but got error: %s: %s", - file, line, e->name, e->message); -} - -static gchar * -spawn_dbus_daemon (gchar *binary, - gchar *configuration, - GPid *daemon_pid) -{ - GError *error = NULL; - GString *address; - gint address_fd; - gchar *argv[] = { - binary, - configuration, - "--nofork", - "--print-address=1", /* stdout */ - NULL - }; - - g_spawn_async_with_pipes (NULL, /* working directory */ - argv, - NULL, /* envp */ - G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH, - NULL, /* child_setup */ - NULL, /* user data */ - daemon_pid, - NULL, /* child's stdin = /dev/null */ - &address_fd, - NULL, /* child's stderr = our stderr */ - &error); - g_assert_no_error (error); - - address = g_string_new (NULL); - - /* polling until the dbus-daemon writes out its address is a bit stupid, - * but at least it's simple, unlike dbus-launch... in principle we could - * use select() here, but life's too short */ - while (1) - { - gssize bytes; - gchar buf[4096]; - gchar *newline; - - bytes = read (address_fd, buf, sizeof (buf)); - - if (bytes > 0) - g_string_append_len (address, buf, bytes); - - newline = strchr (address->str, '\n'); - - if (newline != NULL) - { - if ((newline > address->str) && ('\r' == newline[-1])) - newline -= 1; - g_string_truncate (address, newline - address->str); - break; - } - - g_usleep (G_USEC_PER_SEC / 10); - } - - return g_string_free (address, FALSE); -} - -static DBusConnection * -connect_to_bus (Fixture *f, - const gchar *address) -{ - DBusConnection *conn; - DBusError error = DBUS_ERROR_INIT; - dbus_bool_t ok; - - conn = dbus_connection_open_private (address, &error); - assert_no_error (&error); - g_assert (conn != NULL); - - ok = dbus_bus_register (conn, &error); - assert_no_error (&error); - g_assert (ok); - g_assert (dbus_bus_get_unique_name (conn) != NULL); - - test_connection_setup (f->ctx, conn); - return conn; -} - static DBusHandlerResult echo_filter (DBusConnection *connection, DBusMessage *message, void *user_data) { + Fixture *f = user_data; DBusMessage *reply; if (dbus_message_get_type (message) != DBUS_MESSAGE_TYPE_METHOD_CALL) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + /* WaitForever() never replies, emulating a service that has got stuck */ + if (dbus_message_is_method_call (message, "com.example", "WaitForever")) + { + f->wait_forever_called = TRUE; + return DBUS_HANDLER_RESULT_HANDLED; + } + reply = dbus_message_new_method_return (message); if (reply == NULL) @@ -186,78 +117,31 @@ setup (Fixture *f, gconstpointer context) { const Config *config = context; - gchar *dbus_daemon; - gchar *arg; gchar *address; f->ctx = test_main_context_get (); f->ge = NULL; dbus_error_init (&f->e); - if (config != NULL && config->config_file != NULL) - { - if (g_getenv ("DBUS_TEST_DAEMON_ADDRESS") != NULL) - { - g_message ("SKIP: cannot use DBUS_TEST_DAEMON_ADDRESS for " - "unusally-configured dbus-daemon"); - f->skip = TRUE; - return; - } - - if (g_getenv ("DBUS_TEST_DATA") == NULL) - { - g_message ("SKIP: set DBUS_TEST_DATA to a directory containing %s", - config->config_file); - f->skip = TRUE; - return; - } + address = test_get_dbus_daemon (config ? config->config_file : NULL, + TEST_USER_ME, + &f->daemon_pid); - arg = g_strdup_printf ( - "--config-file=%s/%s", - g_getenv ("DBUS_TEST_DATA"), config->config_file); - } - else if (g_getenv ("DBUS_TEST_SYSCONFDIR") != NULL) - { - arg = g_strdup_printf ("--config-file=%s/dbus-1/session.conf", - g_getenv ("DBUS_TEST_SYSCONFDIR")); - } - else if (g_getenv ("DBUS_TEST_DATA") != NULL) - { - arg = g_strdup_printf ( - "--config-file=%s/valid-config-files/session.conf", - g_getenv ("DBUS_TEST_DATA")); - } - else - { - arg = g_strdup ("--session"); - } - - dbus_daemon = g_strdup (g_getenv ("DBUS_TEST_DAEMON")); - - if (dbus_daemon == NULL) - dbus_daemon = g_strdup ("dbus-daemon"); - - if (g_getenv ("DBUS_TEST_DAEMON_ADDRESS") != NULL) - { - address = g_strdup (g_getenv ("DBUS_TEST_DAEMON_ADDRESS")); - } - else + if (address == NULL) { - address = spawn_dbus_daemon (dbus_daemon, arg, &f->daemon_pid); + f->skip = TRUE; + return; } - g_free (dbus_daemon); - g_free (arg); - - f->left_conn = connect_to_bus (f, address); - f->right_conn = connect_to_bus (f, address); + f->left_conn = test_connect_to_bus (f->ctx, address); + f->right_conn = test_connect_to_bus (f->ctx, address); g_free (address); } static void add_echo_filter (Fixture *f) { - if (!dbus_connection_add_filter (f->right_conn, echo_filter, NULL, NULL)) + if (!dbus_connection_add_filter (f->right_conn, echo_filter, f, NULL)) g_error ("OOM"); f->right_conn_echo = TRUE; @@ -333,13 +217,77 @@ test_echo (Fixture *f, } static void -pending_call_store_reply (DBusPendingCall *pc, - void *data) +test_no_reply (Fixture *f, + gconstpointer context) { - DBusMessage **message_p = data; + const Config *config = context; + DBusMessage *m; + DBusPendingCall *pc; + DBusMessage *reply = NULL; + enum { TIMEOUT, DISCONNECT } mode; + gboolean ok; + + if (f->skip) + return; + + g_test_bug ("76112"); + + if (config != NULL && config->config_file != NULL) + mode = TIMEOUT; + else + mode = DISCONNECT; + + m = dbus_message_new_method_call ( + dbus_bus_get_unique_name (f->right_conn), "/", + "com.example", "WaitForever"); + + add_echo_filter (f); + + if (m == NULL) + g_error ("OOM"); - *message_p = dbus_pending_call_steal_reply (pc); - g_assert (*message_p != NULL); + if (!dbus_connection_send_with_reply (f->left_conn, m, &pc, + DBUS_TIMEOUT_INFINITE) || + pc == NULL) + g_error ("OOM"); + + if (dbus_pending_call_get_completed (pc)) + test_pending_call_store_reply (pc, &reply); + else if (!dbus_pending_call_set_notify (pc, test_pending_call_store_reply, + &reply, NULL)) + g_error ("OOM"); + + dbus_pending_call_unref (pc); + dbus_message_unref (m); + + if (mode == DISCONNECT) + { + while (!f->wait_forever_called) + test_main_context_iterate (f->ctx, TRUE); + + dbus_connection_remove_filter (f->right_conn, echo_filter, f); + dbus_connection_close (f->right_conn); + dbus_connection_unref (f->right_conn); + f->right_conn = NULL; + } + + while (reply == NULL) + test_main_context_iterate (f->ctx, TRUE); + + /* using inefficient string comparison for better assertion message */ + g_assert_cmpstr ( + dbus_message_type_to_string (dbus_message_get_type (reply)), ==, + dbus_message_type_to_string (DBUS_MESSAGE_TYPE_ERROR)); + ok = dbus_set_error_from_message (&f->e, reply); + g_assert (ok); + g_assert_cmpstr (f->e.name, ==, DBUS_ERROR_NO_REPLY); + + if (mode == DISCONNECT) + g_assert_cmpstr (f->e.message, ==, + "Message recipient disconnected from message bus without replying"); + else + g_assert_cmpstr (f->e.message, ==, + "Message did not receive a reply (timeout by message bus)"); } static void @@ -377,8 +325,8 @@ test_creds (Fixture *f, 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, + 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"); @@ -445,8 +393,11 @@ test_creds (Fixture *f, dbus_message_iter_next (&arr_iter); } -#ifdef G_OS_UNIX +#ifdef UNIX_USER_SHOULD_WORK g_assert (seen & SEEN_UNIX_USER); +#endif + +#ifdef PID_SHOULD_WORK g_assert (seen & SEEN_PID); #endif @@ -458,6 +409,72 @@ test_creds (Fixture *f, } static void +test_processid (Fixture *f, + gconstpointer context) +{ + const char *unique = dbus_bus_get_unique_name (f->left_conn); + DBusMessage *m = dbus_message_new_method_call (DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, "GetConnectionUnixProcessID"); + DBusPendingCall *pc; + DBusError error = DBUS_ERROR_INIT; + guint32 pid; + + if (m == NULL) + g_error ("OOM"); + + if (!dbus_message_append_args (m, + DBUS_TYPE_STRING, &unique, + DBUS_TYPE_INVALID)) + g_error ("OOM"); + + if (!dbus_connection_send_with_reply (f->left_conn, 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_message_get_args (m, &error, + DBUS_TYPE_UINT32, &pid, + DBUS_TYPE_INVALID)) + { + g_assert_cmpstr (dbus_message_get_signature (m), ==, "u"); + test_assert_no_error (&error); + + g_message ("GetConnectionUnixProcessID returned %u", pid); + +#ifdef G_OS_UNIX + g_assert_cmpuint (pid, ==, getpid ()); +#elif defined(G_OS_WIN32) + g_assert_cmpuint (pid, ==, GetCurrentProcessId ()); +#else + g_assert_not_reached (); +#endif + } + else + { + g_assert_cmpstr (error.name, ==, DBUS_ERROR_UNIX_PROCESS_ID_UNKNOWN); + +#ifdef PID_SHOULD_WORK + g_error ("Expected pid to be passed, but got %s: %s", + error.name, error.message); +#endif + + dbus_error_free (&error); + } +} + +static void test_canonical_path_uae (Fixture *f, gconstpointer context) { @@ -487,8 +504,8 @@ test_canonical_path_uae (Fixture *f, 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, + 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"); @@ -525,8 +542,8 @@ test_canonical_path_uae (Fixture *f, 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, + 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"); @@ -560,7 +577,7 @@ teardown (Fixture *f, { if (f->right_conn_echo) { - dbus_connection_remove_filter (f->right_conn, echo_filter, NULL); + dbus_connection_remove_filter (f->right_conn, echo_filter, f); f->right_conn_echo = FALSE; } @@ -571,12 +588,7 @@ teardown (Fixture *f, if (f->daemon_pid != 0) { -#ifdef DBUS_WIN - TerminateProcess (f->daemon_pid, 1); -#else - kill (f->daemon_pid, SIGTERM); -#endif - + test_kill_pid (f->daemon_pid); g_spawn_close_pid (f->daemon_pid); f->daemon_pid = 0; } @@ -588,17 +600,25 @@ static Config limited_config = { "34393", 10000, "valid-config-files/incoming-limit.conf" }; +static Config finite_timeout_config = { + NULL, 1, "valid-config-files/finite-timeout.conf" +}; + int main (int argc, char **argv) { - g_test_init (&argc, &argv, NULL); - g_test_bug_base ("https://bugs.freedesktop.org/show_bug.cgi?id="); + test_init (&argc, &argv); g_test_add ("/echo/session", Fixture, NULL, setup, test_echo, teardown); g_test_add ("/echo/limited", Fixture, &limited_config, setup, test_echo, teardown); + g_test_add ("/no-reply/disconnect", Fixture, NULL, + setup, test_no_reply, teardown); + g_test_add ("/no-reply/timeout", Fixture, &finite_timeout_config, + setup, test_no_reply, teardown); g_test_add ("/creds", Fixture, NULL, setup, test_creds, teardown); + g_test_add ("/processid", Fixture, NULL, setup, test_processid, teardown); g_test_add ("/canonical-path/uae", Fixture, NULL, setup, test_canonical_path_uae, teardown); diff --git a/test/fdpass.c b/test/fdpass.c new file mode 100644 index 00000000..fa958da8 --- /dev/null +++ b/test/fdpass.c @@ -0,0 +1,852 @@ +/* + * Copyright © 2010-2012 Nokia Corporation + * Copyright © 2014 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 <config.h> + +#include <dbus/dbus.h> +#include <dbus/dbus-internals.h> +#include <dbus/dbus-sysdeps.h> + +#include <glib.h> + +#include <string.h> + +#ifdef G_OS_UNIX +# include <dbus/dbus-sysdeps-unix.h> + +# include <errno.h> +# include <fcntl.h> +# ifdef HAVE_SYS_RESOURCE_H +# include <sys/resource.h> +# endif +# include <sys/stat.h> +# include <sys/time.h> +# include <sys/types.h> +# include <unistd.h> +#endif + +#include "test-utils-glib.h" + +/* Arbitrary; included here to avoid relying on the default */ +#define MAX_MESSAGE_UNIX_FDS 20 +/* This test won't work on Linux unless this is true. */ +_DBUS_STATIC_ASSERT (MAX_MESSAGE_UNIX_FDS <= 253); + +/* Arbitrary; included here to avoid relying on the default. */ +#define MAX_INCOMING_UNIX_FDS (MAX_MESSAGE_UNIX_FDS * 4) + +/* Arbitrary, except that MAX_MESSAGE_UNIX_FDS * SOME_MESSAGES must be + * less than the process's file descriptor limit. */ +#define SOME_MESSAGES 50 + +/* Linux won't allow more than 253 fds per sendmsg(). */ +#define TOO_MANY_FDS 255 +_DBUS_STATIC_ASSERT (MAX_MESSAGE_UNIX_FDS < TOO_MANY_FDS); + +/* As in test/relay.c, this is a miniature dbus-daemon: we relay messages + * from the client on the left to the client on the right. + * + * left socket left dispatch right socket right + * client ===========> server --------------> server ===========> client + * conn conn conn conn + */ + +typedef struct { + TestMainContext *ctx; + DBusError e; + + DBusServer *server; + + DBusConnection *left_client_conn; + DBusConnection *left_server_conn; + + DBusConnection *right_server_conn; + DBusConnection *right_client_conn; + /* queue of DBusMessage received by right_client_conn */ + GQueue messages; + + int fd_before; +} Fixture; + +#if !GLIB_CHECK_VERSION (2, 38, 0) +#define g_test_skip(s) my_test_skip (s) +static inline void my_test_skip (const gchar *s) +{ + g_message ("SKIP: %s", s); +} +#endif + +#ifdef HAVE_UNIX_FD_PASSING + +static void oom (const gchar *doing) G_GNUC_NORETURN; + +static void +oom (const gchar *doing) +{ + g_error ("out of memory (%s)", doing); +} + +static void +assert_no_error (const DBusError *e) +{ + if (G_UNLIKELY (dbus_error_is_set (e))) + g_error ("expected success but got error: %s: %s", e->name, e->message); +} + +static DBusHandlerResult +left_server_message_cb (DBusConnection *server_conn, + DBusMessage *message, + void *data) +{ + Fixture *f = data; + + g_assert (server_conn == f->left_server_conn); + g_assert (f->right_server_conn != NULL); + + dbus_connection_send (f->right_server_conn, message, NULL); + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult +right_client_message_cb (DBusConnection *client_conn, + DBusMessage *message, + void *data) +{ + Fixture *f = data; + + g_assert (client_conn == f->right_client_conn); + g_queue_push_tail (&f->messages, dbus_message_ref (message)); + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static void +new_conn_cb (DBusServer *server, + DBusConnection *server_conn, + void *data) +{ + Fixture *f = data; + + dbus_connection_set_max_message_unix_fds (server_conn, + MAX_MESSAGE_UNIX_FDS); + dbus_connection_set_max_received_unix_fds (server_conn, + MAX_INCOMING_UNIX_FDS); + + if (f->left_server_conn == NULL) + { + f->left_server_conn = dbus_connection_ref (server_conn); + + if (!dbus_connection_add_filter (server_conn, + left_server_message_cb, f, NULL)) + oom ("adding filter"); + } + else + { + g_assert (f->right_server_conn == NULL); + f->right_server_conn = dbus_connection_ref (server_conn); + } + + test_connection_setup (f->ctx, server_conn); +} + +static void +test_connect (Fixture *f, + gconstpointer data G_GNUC_UNUSED) +{ + char *address; + + g_assert (f->left_server_conn == NULL); + g_assert (f->right_server_conn == NULL); + + address = dbus_server_get_address (f->server); + g_assert (address != NULL); + + f->left_client_conn = dbus_connection_open_private (address, &f->e); + assert_no_error (&f->e); + g_assert (f->left_client_conn != NULL); + test_connection_setup (f->ctx, f->left_client_conn); + + /* The left client connection is allowed to behave abusively. */ + dbus_connection_set_max_message_unix_fds (f->left_client_conn, 1000); + dbus_connection_set_max_received_unix_fds (f->left_client_conn, 1000000); + + while (f->left_server_conn == NULL) + { + g_print ("."); + test_main_context_iterate (f->ctx, TRUE); + } + + f->right_client_conn = dbus_connection_open_private (address, &f->e); + assert_no_error (&f->e); + g_assert (f->right_client_conn != NULL); + test_connection_setup (f->ctx, f->right_client_conn); + + dbus_free (address); + + while (f->right_server_conn == NULL) + { + g_print ("."); + test_main_context_iterate (f->ctx, TRUE); + } + + if (!dbus_connection_add_filter (f->right_client_conn, + right_client_message_cb, f, NULL)) + oom ("adding filter"); + + /* The right client connection is allowed to queue all the messages. */ + dbus_connection_set_max_message_unix_fds (f->right_client_conn, 1000); + dbus_connection_set_max_received_unix_fds (f->right_client_conn, 1000000); + + while (!dbus_connection_get_is_authenticated (f->left_client_conn) || + !dbus_connection_get_is_authenticated (f->right_client_conn) || + !dbus_connection_get_is_authenticated (f->left_server_conn) || + !dbus_connection_get_is_authenticated (f->right_server_conn)) + { + g_printerr ("*"); + test_main_context_iterate (f->ctx, TRUE); + } + + if (!dbus_connection_can_send_type (f->left_client_conn, + DBUS_TYPE_UNIX_FD)) + g_error ("left client connection cannot send Unix fds"); + + if (!dbus_connection_can_send_type (f->left_server_conn, + DBUS_TYPE_UNIX_FD)) + g_error ("left server connection cannot send Unix fds"); + + if (!dbus_connection_can_send_type (f->right_client_conn, + DBUS_TYPE_UNIX_FD)) + g_error ("right client connection cannot send Unix fds"); + + if (!dbus_connection_can_send_type (f->right_server_conn, + DBUS_TYPE_UNIX_FD)) + g_error ("right server connection cannot send Unix fds"); +} +#endif + +static void +setup (Fixture *f, + gconstpointer data G_GNUC_UNUSED) +{ +#ifdef HAVE_UNIX_FD_PASSING + /* We assume that anything with fd-passing supports the unix: transport */ + + f->ctx = test_main_context_get (); + dbus_error_init (&f->e); + g_queue_init (&f->messages); + + f->server = dbus_server_listen ("unix:tmpdir=/tmp", &f->e); + assert_no_error (&f->e); + g_assert (f->server != NULL); + + dbus_server_set_new_connection_function (f->server, + new_conn_cb, f, NULL); + test_server_setup (f->ctx, f->server); + + f->fd_before = open ("/dev/null", O_RDONLY); + + /* this should succeed on any reasonable Unix */ + if (f->fd_before < 0) + g_error ("cannot open /dev/null for reading: %s", g_strerror (errno)); + + _dbus_fd_set_close_on_exec (f->fd_before); +#endif +} + +static void +test_relay (Fixture *f, + gconstpointer data) +{ +#ifdef HAVE_UNIX_FD_PASSING + /* We assume that any platform with working fd-passing is POSIX, + * and therefore has open() and fstat() */ + dbus_uint32_t serial; + DBusMessage *outgoing, *incoming; + int fd_after; + struct stat stat_before; + struct stat stat_after; + + test_connect (f, data); + + outgoing = dbus_message_new_signal ("/com/example/Hello", + "com.example.Hello", "Greeting"); + g_assert (outgoing != NULL); + + if (!dbus_message_append_args (outgoing, + DBUS_TYPE_UNIX_FD, &f->fd_before, + DBUS_TYPE_INVALID)) + oom ("appending fd"); + + if (!dbus_connection_send (f->left_client_conn, outgoing, &serial)) + oom ("sending message"); + + dbus_message_unref (outgoing); + + while (g_queue_get_length (&f->messages) < 1) + { + g_print ("."); + test_main_context_iterate (f->ctx, TRUE); + } + + g_assert_cmpuint (g_queue_get_length (&f->messages), ==, 1); + + incoming = g_queue_pop_head (&f->messages); + + g_assert (dbus_message_contains_unix_fds (incoming)); + g_assert_cmpstr (dbus_message_get_destination (incoming), ==, NULL); + g_assert_cmpstr (dbus_message_get_error_name (incoming), ==, NULL); + g_assert_cmpstr (dbus_message_get_interface (incoming), ==, + "com.example.Hello"); + g_assert_cmpstr (dbus_message_get_member (incoming), ==, "Greeting"); + g_assert_cmpstr (dbus_message_get_sender (incoming), ==, NULL); + g_assert_cmpstr (dbus_message_get_signature (incoming), ==, + DBUS_TYPE_UNIX_FD_AS_STRING); + g_assert_cmpstr (dbus_message_get_path (incoming), ==, "/com/example/Hello"); + g_assert_cmpuint (dbus_message_get_serial (incoming), ==, serial); + + if (!dbus_message_get_args (incoming, + &f->e, + DBUS_TYPE_UNIX_FD, &fd_after, + DBUS_TYPE_INVALID)) + g_error ("%s: %s", f->e.name, f->e.message); + + assert_no_error (&f->e); + + if (fstat (f->fd_before, &stat_before) < 0) + g_error ("%s", g_strerror (errno)); + + if (fstat (fd_after, &stat_after) < 0) + g_error ("%s", g_strerror (errno)); + + /* this seems like enough to say "it's the same file" */ + g_assert_cmpint (stat_before.st_dev, ==, stat_after.st_dev); + g_assert_cmpint (stat_before.st_ino, ==, stat_after.st_ino); + g_assert_cmpint (stat_before.st_rdev, ==, stat_after.st_rdev); + + dbus_message_unref (incoming); + + if (close (fd_after) < 0) + g_error ("%s", g_strerror (errno)); + + g_assert (dbus_connection_get_is_connected (f->right_client_conn)); + g_assert (dbus_connection_get_is_connected (f->right_server_conn)); + g_assert (dbus_connection_get_is_connected (f->left_client_conn)); + g_assert (dbus_connection_get_is_connected (f->left_server_conn)); +#else + g_test_skip ("fd-passing not supported on this platform"); +#endif +} + +static void +test_limit (Fixture *f, + gconstpointer data) +{ +#ifdef HAVE_UNIX_FD_PASSING + dbus_uint32_t serial; + DBusMessage *outgoing, *incoming; + int i; + + test_connect (f, data); + + outgoing = dbus_message_new_signal ("/com/example/Hello", + "com.example.Hello", "Greeting"); + g_assert (outgoing != NULL); + + for (i = 0; i < MAX_MESSAGE_UNIX_FDS; i++) + { + if (!dbus_message_append_args (outgoing, + DBUS_TYPE_UNIX_FD, &f->fd_before, + DBUS_TYPE_INVALID)) + oom ("appending fd"); + } + + if (!dbus_connection_send (f->left_client_conn, outgoing, &serial)) + oom ("sending message"); + + dbus_message_unref (outgoing); + + while (g_queue_get_length (&f->messages) < 1) + { + g_print ("."); + test_main_context_iterate (f->ctx, TRUE); + } + + g_assert_cmpuint (g_queue_get_length (&f->messages), ==, 1); + + incoming = g_queue_pop_head (&f->messages); + + g_assert (dbus_message_contains_unix_fds (incoming)); + g_assert_cmpstr (dbus_message_get_destination (incoming), ==, NULL); + g_assert_cmpstr (dbus_message_get_error_name (incoming), ==, NULL); + g_assert_cmpstr (dbus_message_get_interface (incoming), ==, + "com.example.Hello"); + g_assert_cmpstr (dbus_message_get_member (incoming), ==, "Greeting"); + g_assert_cmpstr (dbus_message_get_sender (incoming), ==, NULL); + g_assert_cmpstr (dbus_message_get_path (incoming), ==, "/com/example/Hello"); + g_assert_cmpuint (dbus_message_get_serial (incoming), ==, serial); + + dbus_message_unref (incoming); + + g_assert (dbus_connection_get_is_connected (f->right_client_conn)); + g_assert (dbus_connection_get_is_connected (f->right_server_conn)); + g_assert (dbus_connection_get_is_connected (f->left_client_conn)); + g_assert (dbus_connection_get_is_connected (f->left_server_conn)); +#else + g_test_skip ("fd-passing not supported on this platform"); +#endif +} + +static void +test_too_many (Fixture *f, + gconstpointer data) +{ +#ifdef HAVE_UNIX_FD_PASSING + DBusMessage *outgoing; + int i; + + test_connect (f, data); + + outgoing = dbus_message_new_signal ("/com/example/Hello", + "com.example.Hello", "Greeting"); + g_assert (outgoing != NULL); + + for (i = 0; i < MAX_MESSAGE_UNIX_FDS + GPOINTER_TO_UINT (data); i++) + { + if (!dbus_message_append_args (outgoing, + DBUS_TYPE_UNIX_FD, &f->fd_before, + DBUS_TYPE_INVALID)) + oom ("appending fd"); + } + + if (!dbus_connection_send (f->left_client_conn, outgoing, NULL)) + oom ("sending message"); + + dbus_message_unref (outgoing); + + /* The sender is unceremoniously disconnected. */ + while (dbus_connection_get_is_connected (f->left_client_conn) || + dbus_connection_get_is_connected (f->left_server_conn)) + { + g_print ("."); + test_main_context_iterate (f->ctx, TRUE); + } + + /* The message didn't get through without its fds. */ + g_assert_cmpuint (g_queue_get_length (&f->messages), ==, 0); + + /* The intended victim is unaffected by the left connection's + * misbehaviour. */ + g_assert (dbus_connection_get_is_connected (f->right_client_conn)); + g_assert (dbus_connection_get_is_connected (f->right_server_conn)); +#else + g_test_skip ("fd-passing not supported on this platform"); +#endif +} + +static void +test_too_many_split (Fixture *f, + gconstpointer data) +{ +#ifdef HAVE_UNIX_FD_PASSING + DBusMessage *outgoing; + int i; + int left_client_socket; + char *payload; + int payload_len; + DBusString buffer; + int fds[TOO_MANY_FDS]; + int done; + + /* This test deliberately pushes up against OS limits, so skip it + * if we don't have enough fds. 4 times the maximum per message + * ought to be enough: that will cover the message, the dup'd fds + * we actually send, the copy that we potentially receive, and some + * spare capacity for everything else. */ +#ifdef HAVE_GETRLIMIT + struct rlimit lim; + + if (getrlimit (RLIMIT_NOFILE, &lim) == 0) + { + if (lim.rlim_cur != RLIM_INFINITY && + lim.rlim_cur < 4 * TOO_MANY_FDS) + { + g_test_skip ("not enough RLIMIT_NOFILE"); + return; + } + } +#endif + + test_connect (f, data); + + outgoing = dbus_message_new_signal ("/com/example/Hello", + "com.example.Hello", "Greeting"); + g_assert (outgoing != NULL); + + /* TOO_MANY_FDS fds are far too many: in particular, Linux doesn't allow + * sending this many in a single sendmsg(). libdbus never splits + * a message between two sendmsg() calls if it can help it, and + * in particular it always sends all the fds with the first sendmsg(), + * but malicious senders might not be so considerate. */ + for (i = 0; i < TOO_MANY_FDS; i++) + { + if (!dbus_message_append_args (outgoing, + DBUS_TYPE_UNIX_FD, &f->fd_before, + DBUS_TYPE_INVALID)) + oom ("appending fd"); + } + + /* This probably shouldn't work for messages with fds, but it does, + * which is convenient for this sort of trickery. */ + if (!dbus_message_marshal (outgoing, &payload, &payload_len)) + oom ("marshalling message"); + + _dbus_string_init_const_len (&buffer, payload, payload_len); + + for (i = 0; i < TOO_MANY_FDS; i++) + { + fds[i] = dup (f->fd_before); + + if (fds[i] < 0) + g_error ("could not dup fd: %s", g_strerror (errno)); + } + + /* This is blatant cheating, and the API documentation specifically + * tells you not use this function in this way. Never do this + * in application code. */ + if (!dbus_connection_get_socket (f->left_client_conn, &left_client_socket)) + g_error ("'unix:' DBusConnection should have had a socket"); + + /* Just to be sure that we're at a message boundary. */ + dbus_connection_flush (f->left_client_conn); + + /* We have too many fds for one sendmsg(), so send the first half + * (rounding down if odd) with the first byte... */ + done = _dbus_write_socket_with_unix_fds (left_client_socket, &buffer, 0, 1, + &fds[0], TOO_MANY_FDS / 2); + + if (done < 0) + g_error ("could not send first byte and first batch of fds: %s", + g_strerror (errno)); + + /* ... and the second half (rounding up if odd) with the rest of + * the message */ + done = _dbus_write_socket_with_unix_fds (left_client_socket, &buffer, 1, + payload_len - 1, &fds[TOO_MANY_FDS / 2], + TOO_MANY_FDS - (TOO_MANY_FDS / 2)); + + if (done < 0) + { + g_error ("could not send rest of message and rest of fds: %s", + g_strerror (errno)); + } + else if (done < payload_len - 1) + { + /* For simplicity, assume the socket buffer is big enough for the + * whole message, which should be < 2 KiB. If this fails on some + * OS, redo this test code to use a proper loop like the real + * libdbus does. */ + g_error ("short write in sendmsg(), fix this test: %d/%d", + done, payload_len - 1); + } + + dbus_free (payload); + + for (i = 0; i < TOO_MANY_FDS; i++) + close (fds[i]); + + dbus_message_unref (outgoing); + + /* The sender is unceremoniously disconnected. */ + while (dbus_connection_get_is_connected (f->left_client_conn) || + dbus_connection_get_is_connected (f->left_server_conn)) + { + g_print ("."); + test_main_context_iterate (f->ctx, TRUE); + } + + /* The message didn't get through without its fds. */ + g_assert_cmpuint (g_queue_get_length (&f->messages), ==, 0); + + /* The intended victim is unaffected by the left connection's + * misbehaviour. */ + g_assert (dbus_connection_get_is_connected (f->right_client_conn)); + g_assert (dbus_connection_get_is_connected (f->right_server_conn)); +#else + g_test_skip ("fd-passing not supported on this platform"); +#endif +} + +static void +test_flood (Fixture *f, + gconstpointer data) +{ +#ifdef HAVE_UNIX_FD_PASSING + int i, j; + DBusMessage *outgoing[SOME_MESSAGES]; + dbus_uint32_t serial; + + test_connect (f, data); + + for (j = 0; j < SOME_MESSAGES; j++) + { + outgoing[j] = dbus_message_new_signal ("/com/example/Hello", + "com.example.Hello", "Greeting"); + g_assert (outgoing[j] != NULL); + + for (i = 0; i < GPOINTER_TO_UINT (data); i++) + { + if (!dbus_message_append_args (outgoing[j], + DBUS_TYPE_UNIX_FD, &f->fd_before, + DBUS_TYPE_INVALID)) + oom ("appending fd"); + } + } + + /* This is in its own loop so we do it as fast as possible */ + for (j = 0; j < SOME_MESSAGES; j++) + { + if (!dbus_connection_send (f->left_client_conn, outgoing[j], &serial)) + oom ("sending message"); + } + + for (j = 0; j < SOME_MESSAGES; j++) + { + dbus_message_unref (outgoing[j]); + } + + while (g_queue_get_length (&f->messages) < SOME_MESSAGES) + { + g_print ("."); + test_main_context_iterate (f->ctx, TRUE); + } + + g_assert_cmpuint (g_queue_get_length (&f->messages), ==, SOME_MESSAGES); + + for (j = 0; j < SOME_MESSAGES; j++) + { + DBusMessage *incoming; + + incoming = g_queue_pop_head (&f->messages); + + g_assert (dbus_message_contains_unix_fds (incoming)); + g_assert_cmpstr (dbus_message_get_destination (incoming), ==, NULL); + g_assert_cmpstr (dbus_message_get_error_name (incoming), ==, NULL); + g_assert_cmpstr (dbus_message_get_interface (incoming), ==, + "com.example.Hello"); + g_assert_cmpstr (dbus_message_get_member (incoming), ==, "Greeting"); + g_assert_cmpstr (dbus_message_get_sender (incoming), ==, NULL); + g_assert_cmpstr (dbus_message_get_path (incoming), ==, "/com/example/Hello"); + + dbus_message_unref (incoming); + } + + g_assert (dbus_connection_get_is_connected (f->right_client_conn)); + g_assert (dbus_connection_get_is_connected (f->right_server_conn)); + g_assert (dbus_connection_get_is_connected (f->left_client_conn)); + g_assert (dbus_connection_get_is_connected (f->left_server_conn)); +#else + g_test_skip ("fd-passing not supported on this platform"); +#endif +} + +static void +test_odd_limit (Fixture *f, + gconstpointer data) +{ +#ifdef HAVE_UNIX_FD_PASSING + DBusMessage *outgoing; + int i; + + test_connect (f, data); + dbus_connection_set_max_message_unix_fds (f->left_server_conn, 7); + dbus_connection_set_max_message_unix_fds (f->right_server_conn, 7); + + outgoing = dbus_message_new_signal ("/com/example/Hello", + "com.example.Hello", "Greeting"); + g_assert (outgoing != NULL); + + for (i = 0; i < 7 + GPOINTER_TO_INT (data); i++) + { + if (!dbus_message_append_args (outgoing, + DBUS_TYPE_UNIX_FD, &f->fd_before, + DBUS_TYPE_INVALID)) + oom ("appending fd"); + } + + if (!dbus_connection_send (f->left_client_conn, outgoing, NULL)) + oom ("sending message"); + + dbus_message_unref (outgoing); + + if (GPOINTER_TO_INT (data) > 0) + { + /* The sender is unceremoniously disconnected. */ + while (dbus_connection_get_is_connected (f->left_client_conn) || + dbus_connection_get_is_connected (f->left_server_conn)) + { + g_print ("."); + test_main_context_iterate (f->ctx, TRUE); + } + + /* The message didn't get through without its fds. */ + g_assert_cmpuint (g_queue_get_length (&f->messages), ==, 0); + + /* The intended victim is unaffected by the left connection's + * misbehaviour. */ + g_assert (dbus_connection_get_is_connected (f->right_client_conn)); + g_assert (dbus_connection_get_is_connected (f->right_server_conn)); + } + else + { + DBusMessage *incoming; + + /* We're at or under the limit. The message gets through intact. */ + while (g_queue_get_length (&f->messages) < 1) + { + g_print ("."); + test_main_context_iterate (f->ctx, TRUE); + } + + g_assert_cmpuint (g_queue_get_length (&f->messages), ==, 1); + + incoming = g_queue_pop_head (&f->messages); + + g_assert (dbus_message_contains_unix_fds (incoming)); + g_assert_cmpstr (dbus_message_get_destination (incoming), ==, NULL); + g_assert_cmpstr (dbus_message_get_error_name (incoming), ==, NULL); + g_assert_cmpstr (dbus_message_get_interface (incoming), ==, + "com.example.Hello"); + g_assert_cmpstr (dbus_message_get_member (incoming), ==, "Greeting"); + g_assert_cmpstr (dbus_message_get_sender (incoming), ==, NULL); + g_assert_cmpstr (dbus_message_get_path (incoming), ==, + "/com/example/Hello"); + + dbus_message_unref (incoming); + + g_assert (dbus_connection_get_is_connected (f->right_client_conn)); + g_assert (dbus_connection_get_is_connected (f->right_server_conn)); + g_assert (dbus_connection_get_is_connected (f->left_client_conn)); + g_assert (dbus_connection_get_is_connected (f->left_server_conn)); + } +#else + g_test_skip ("fd-passing not supported on this platform"); +#endif +} + +static void +teardown (Fixture *f, + gconstpointer data G_GNUC_UNUSED) +{ +#ifdef HAVE_UNIX_FD_PASSING + if (f->left_client_conn != NULL) + { + dbus_connection_close (f->left_client_conn); + dbus_connection_unref (f->left_client_conn); + f->left_client_conn = NULL; + } + + if (f->right_client_conn != NULL) + { + dbus_connection_close (f->right_client_conn); + dbus_connection_unref (f->right_client_conn); + f->right_client_conn = NULL; + } + + if (f->left_server_conn != NULL) + { + dbus_connection_close (f->left_server_conn); + dbus_connection_unref (f->left_server_conn); + f->left_server_conn = NULL; + } + + if (f->right_server_conn != NULL) + { + dbus_connection_close (f->right_server_conn); + dbus_connection_unref (f->right_server_conn); + f->right_server_conn = NULL; + } + + if (f->server != NULL) + { + dbus_server_disconnect (f->server); + dbus_server_unref (f->server); + f->server = NULL; + } + + test_main_context_unref (f->ctx); + + if (f->fd_before >= 0 && close (f->fd_before) < 0) + g_error ("%s", g_strerror (errno)); +#endif +} + +int +main (int argc, + char **argv) +{ + test_init (&argc, &argv); + + g_test_add ("/relay", Fixture, NULL, setup, + test_relay, teardown); + g_test_add ("/limit", Fixture, NULL, setup, + test_limit, teardown); + + g_test_add ("/too-many/plus1", Fixture, GUINT_TO_POINTER (1), setup, + test_too_many, teardown); + g_test_add ("/too-many/plus2", Fixture, GUINT_TO_POINTER (2), setup, + test_too_many, teardown); + g_test_add ("/too-many/plus17", Fixture, GUINT_TO_POINTER (17), setup, + test_too_many, teardown); + + g_test_add ("/too-many/split", Fixture, NULL, setup, + test_too_many_split, teardown); + + g_test_add ("/flood/1", Fixture, GUINT_TO_POINTER (1), + setup, test_flood, teardown); +#if MAX_MESSAGE_UNIX_FDS >= 2 + g_test_add ("/flood/half-limit", Fixture, + GUINT_TO_POINTER (MAX_MESSAGE_UNIX_FDS / 2), + setup, test_flood, teardown); +#endif +#if MAX_MESSAGE_UNIX_FDS >= 4 + g_test_add ("/flood/over-half-limit", Fixture, + GUINT_TO_POINTER (3 * MAX_MESSAGE_UNIX_FDS / 4), + setup, test_flood, teardown); +#endif + g_test_add ("/flood/limit", Fixture, + GUINT_TO_POINTER (MAX_MESSAGE_UNIX_FDS), setup, test_flood, teardown); + + g_test_add ("/odd-limit/minus1", Fixture, GINT_TO_POINTER (-1), setup, + test_odd_limit, teardown); + g_test_add ("/odd-limit/at", Fixture, GINT_TO_POINTER (0), setup, + test_odd_limit, teardown); + g_test_add ("/odd-limit/plus1", Fixture, GINT_TO_POINTER (+1), setup, + test_odd_limit, teardown); + g_test_add ("/odd-limit/plus2", Fixture, GINT_TO_POINTER (+2), setup, + test_odd_limit, teardown); + + return g_test_run (); +} diff --git a/test/internals/refs.c b/test/internals/refs.c index 202dc043..7eae44e1 100644 --- a/test/internals/refs.c +++ b/test/internals/refs.c @@ -35,7 +35,7 @@ #include <dbus/dbus-message-internal.h> #include <dbus/dbus-pending-call-internal.h> #include <dbus/dbus-server-protected.h> -#include "test-utils.h" +#include "test-utils-glib.h" static void assert_no_error (const DBusError *e) @@ -585,14 +585,7 @@ int main (int argc, char **argv) { - /* In GLib >= 2.24, < 2.31 this acts like g_thread_init() but avoids - * the deprecation of that function. In GLib >= 2.32 this is not - * necessary at all. - */ - g_type_init (); - - g_test_init (&argc, &argv, NULL); - g_test_bug_base ("https://bugs.freedesktop.org/show_bug.cgi?id="); + test_init (&argc, &argv); g_test_add ("/refs/connection", Fixture, NULL, setup_connection, test_connection, teardown); diff --git a/test/internals/syslog.c b/test/internals/syslog.c index 7e0eae79..805c5784 100644 --- a/test/internals/syslog.c +++ b/test/internals/syslog.c @@ -33,6 +33,8 @@ #include <dbus/dbus.h> #include <dbus/dbus-sysdeps.h> +#include "test-utils-glib.h" + typedef struct { int dummy; } Fixture; @@ -68,16 +70,18 @@ test_syslog (Fixture *f, { _dbus_init_system_log (FALSE); _dbus_system_log (DBUS_SYSTEM_LOG_INFO, MESSAGE "%d", 42); + _dbus_system_log (DBUS_SYSTEM_LOG_WARNING, MESSAGE "%d", 45); _dbus_system_log (DBUS_SYSTEM_LOG_SECURITY, MESSAGE "%d", 666); exit (0); } g_test_trap_assert_passed (); - g_test_trap_assert_stderr ("*" MESSAGE "42\n*" MESSAGE "666\n*"); + g_test_trap_assert_stderr ("*" MESSAGE "42\n*" MESSAGE "45\n*" MESSAGE "666\n*"); #endif /* manual test (this is the best we can do on Windows) */ _dbus_init_system_log (FALSE); _dbus_system_log (DBUS_SYSTEM_LOG_INFO, MESSAGE "%d", 42); + _dbus_system_log (DBUS_SYSTEM_LOG_WARNING, MESSAGE "%d", 45); _dbus_system_log (DBUS_SYSTEM_LOG_SECURITY, MESSAGE "%d", 666); } @@ -91,8 +95,7 @@ int main (int argc, char **argv) { - g_test_init (&argc, &argv, NULL); - g_test_bug_base ("https://bugs.freedesktop.org/show_bug.cgi?id="); + test_init (&argc, &argv); g_test_add ("/syslog", Fixture, NULL, setup, test_syslog, teardown); diff --git a/test/loopback.c b/test/loopback.c index 7526d8d2..eeb0d65d 100644 --- a/test/loopback.c +++ b/test/loopback.c @@ -32,7 +32,7 @@ #include <string.h> -#include "test-utils.h" +#include "test-utils-glib.h" typedef struct { TestMainContext *ctx; @@ -255,8 +255,7 @@ int main (int argc, char **argv) { - g_test_init (&argc, &argv, NULL); - g_test_bug_base ("https://bugs.freedesktop.org/show_bug.cgi?id="); + test_init (&argc, &argv); g_test_add ("/connect/tcp", Fixture, "tcp:host=127.0.0.1", setup, test_connect, teardown); diff --git a/test/manual-dir-iter.c b/test/manual-dir-iter.c new file mode 100644 index 00000000..21ac0e95 --- /dev/null +++ b/test/manual-dir-iter.c @@ -0,0 +1,92 @@ +#include <config.h> +#include "test-utils.h" + +#include "dbus/dbus-macros.h" +#include "dbus/dbus-sysdeps.h" + +static void oom (const char *doing) _DBUS_GNUC_NORETURN; +static void die (const char *message) _DBUS_GNUC_NORETURN; + +void +oom (const char *doing) +{ + fprintf (stderr, "*** manual-dir-iter: OOM while %s\n", doing); + exit (1); +} + +void +die (const char *message) +{ + fprintf (stderr, "*** manual-dir-iter: %s\n", message); + exit (1); +} + +static void +debug (const char *message) +{ + fprintf (stdout, "+++ manual-dir-iter: %s\n", message); +} + +int +main (int argc, + char **argv) +{ + DBusString filename; + DBusString dirname; + DBusError tmp_error; + DBusDirIter *dir; + + if (argc != 2) + die ("syntax: manual-dir-iter <path>"); + + dbus_error_init (&tmp_error); + + if (!_dbus_string_init (&filename)) + oom ("init filename"); + + if (!_dbus_string_init (&dirname)) + oom ("init dirname"); + + _dbus_string_append (&dirname, argv[1]); + dir = _dbus_directory_open (&dirname, &tmp_error); + + if (dir == NULL) + { + fprintf (stderr, "could not open directory: %s: %s\n", + tmp_error.name, tmp_error.message); + exit(1); + } + + while (_dbus_directory_get_next_file (dir, &filename, &tmp_error)) + { + DBusString full_path; + if (!_dbus_string_init (&full_path)) + { + oom ("init full_path"); + } + + if (!_dbus_string_copy (&dirname, 0, &full_path, 0)) + { + oom ("copying full_path to dirname"); + } + + if (!_dbus_concat_dir_and_file (&full_path, &filename)) + { + oom ("concat full_path"); + } + debug (_dbus_string_get_const_data (&filename)); + _dbus_string_free (&full_path); + } + + if (dbus_error_is_set (&tmp_error)) + die (tmp_error.message); + + _dbus_string_free (&filename); + + if (dir) + _dbus_directory_close (dir); + + _dbus_verbose ("*** Test dir name exiting\n"); + + return 0; +} diff --git a/test/manual-tcp.c b/test/manual-tcp.c new file mode 100644 index 00000000..64691c9d --- /dev/null +++ b/test/manual-tcp.c @@ -0,0 +1,46 @@ +/* + * Simple manual tcp check + * + * supports: + * - server listening check + * + * syntax: manual-tcp [<ipv4>|<ipv6>] + * +*/ + +#include "config.h" +#include "dbus/dbus-server-socket.h" + +#include <stdio.h> + +int +main (int argc, char **argv) +{ + DBusServer *server; + DBusError error; + int result = 0; + int i; + + char *family = NULL; + + if (argc == 2) + family = argv[1]; + + for (i = 0; i < 1000; i++) + { + dbus_error_init (&error); + server = _dbus_server_new_for_tcp_socket ("localhost", "localhost", "0", family, &error, FALSE); + if (server == NULL) + { + printf("%d: %s %s\n",i, error.name, error.message); + dbus_error_free(&error); + result = -1; + } + else { + printf("%d: %s \n",i, dbus_server_get_address(server)); + dbus_server_disconnect(server); + dbus_server_unref(server); + } + } + return result; +} diff --git a/test/marshal.c b/test/marshal.c index d74e7671..3353ec00 100644 --- a/test/marshal.c +++ b/test/marshal.c @@ -31,6 +31,8 @@ #include <dbus/dbus.h> +#include "test-utils-glib.h" + typedef struct { DBusError e; } Fixture; @@ -248,7 +250,7 @@ main (int argc, char *aligned_le_blob; char *aligned_be_blob; - g_test_init (&argc, &argv, NULL); + test_init (&argc, &argv); /* We have to pass in a buffer that's at least "default aligned", * i.e. on GNU systems to 8 or 16. The linker may have only given diff --git a/test/monitor.c b/test/monitor.c new file mode 100644 index 00000000..32b7b5dd --- /dev/null +++ b/test/monitor.c @@ -0,0 +1,1496 @@ +/* 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 <config.h> + +#include <string.h> + +#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 (); +} diff --git a/test/name-test/test-activation-forking.py b/test/name-test/test-activation-forking.py index 0d820754..f98537eb 100644 --- a/test/name-test/test-activation-forking.py +++ b/test/name-test/test-activation-forking.py @@ -3,7 +3,7 @@ import os,sys try: - import gobject + from gi.repository import GObject import dbus import dbus.mainloop.glib except: @@ -11,7 +11,7 @@ except: sys.exit(0) dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) -loop = gobject.MainLoop() +loop = GObject.MainLoop() exitcode = 0 @@ -54,7 +54,7 @@ def check_counter(): if counter == 0: print "Failed to get NameOwnerChanged for TestSuiteForkingEchoService" sys.exit(1) -gobject.timeout_add(15000, check_counter) +GObject.timeout_add(15000, check_counter) loop.run() sys.exit(0) diff --git a/test/name-test/test-threads-init.c b/test/name-test/test-threads-init.c index 580ffe14..a517e2a2 100644 --- a/test/name-test/test-threads-init.c +++ b/test/name-test/test-threads-init.c @@ -149,16 +149,6 @@ main (int argc, char *argv[]) &dispatch_cond1, &io_path_cond1); - /* Since 1.7 it is no longer the case that mutex1 != mutex2, because - * initializing global locks automatically initializes locks - * in general. However, it is true that the mutex is not the dummy - * implementation, which is what we really wanted to check here. */ - _dbus_assert (mutex1 != (DBusMutex *) 0xABCDEF); - _dbus_assert (dispatch_mutex1 != (DBusMutex *) 0xABCDEF); - _dbus_assert (dispatch_cond1 != (DBusCondVar *) 0xABCDEF2); - _dbus_assert (io_path_mutex1 != (DBusMutex *) 0xABCDEF); - _dbus_assert (io_path_cond1 != (DBusCondVar *) 0xABCDEF2); - _run_iteration (conn); _dbus_connection_test_get_locks (conn, &mutex2, &dispatch_mutex2, diff --git a/test/name-test/test-wait-for-echo.py b/test/name-test/test-wait-for-echo.py index bd09e459..49ecbb46 100755 --- a/test/name-test/test-wait-for-echo.py +++ b/test/name-test/test-wait-for-echo.py @@ -3,15 +3,15 @@ import os,sys try: - import gobject import dbus import dbus.mainloop.glib + from gi.repository import GObject except: print "Failed import, aborting test" sys.exit(0) dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) -loop = gobject.MainLoop() +loop = GObject.MainLoop() exitcode = 0 @@ -21,7 +21,7 @@ def handle_noreceipt(): exitcode = 1 loop.quit() -gobject.timeout_add(7000, handle_noreceipt) +GObject.timeout_add(7000, handle_noreceipt) bus = dbus.SessionBus() diff --git a/test/relay.c b/test/relay.c index ecfe4c82..a6ccb412 100644 --- a/test/relay.c +++ b/test/relay.c @@ -30,7 +30,7 @@ #include <dbus/dbus.h> -#include "test-utils.h" +#include "test-utils-glib.h" /* This is basically a miniature dbus-daemon. We relay messages from the client * on the left to the client on the right. @@ -313,8 +313,7 @@ int main (int argc, char **argv) { - g_test_init (&argc, &argv, NULL); - g_test_bug_base ("https://bugs.freedesktop.org/show_bug.cgi?id="); + test_init (&argc, &argv); g_test_add ("/connect", Fixture, NULL, setup, test_connect, teardown); diff --git a/test/sd-activation.c b/test/sd-activation.c new file mode 100644 index 00000000..b9ea323f --- /dev/null +++ b/test/sd-activation.c @@ -0,0 +1,318 @@ +/* Unit tests for systemd activation. + * + * 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 <config.h> + +#include <string.h> + +#include "test-utils-glib.h" + +typedef struct { + TestMainContext *ctx; + DBusError e; + GError *ge; + + gchar *address; + GPid daemon_pid; + + DBusConnection *caller; + const char *caller_name; + DBusConnection *systemd; + const char *systemd_name; + DBusMessage *systemd_message; + DBusConnection *activated; + const char *activated_name; + DBusMessage *activated_message; +} Fixture; + +/* this is a macro so it gets the right line number */ +#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) + +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 G_GNUC_UNUSED) +{ + f->ctx = test_main_context_get (); + + f->ge = NULL; + dbus_error_init (&f->e); + + f->address = test_get_dbus_daemon ( + "valid-config-files/systemd-activation.conf", + TEST_USER_ME, &f->daemon_pid); + + if (f->address == NULL) + return; + + f->caller = test_connect_to_bus (f->ctx, f->address); + f->caller_name = dbus_bus_get_unique_name (f->caller); +} + +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 +test_activation (Fixture *f, + gconstpointer context) +{ + DBusMessage *m; + + if (f->address == NULL) + return; + + /* 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->caller, m, NULL); + 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); + 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); + 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->caller_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->caller, m, NULL); + dbus_message_unref (m); + + /* This time systemd is already ready for it. */ + 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); + + /* The activatable service takes its name. Here I'm faking it by using + * an existing connection; in real life it would be yet another + * connection. */ + take_well_known_name (f, f->activated, "com.example.SystemdActivatable2"); + + /* 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->caller_name, "/foo", + "com.example.bar", "UnicastSignal2", "", + "com.example.SystemdActivatable2"); + dbus_message_unref (m); + + /* 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->caller, m, NULL); + dbus_message_unref (m); + + 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); +} + +static void +teardown (Fixture *f, + gconstpointer context G_GNUC_UNUSED) +{ + dbus_error_free (&f->e); + g_clear_error (&f->ge); + + if (f->caller != NULL) + { + dbus_connection_close (f->caller); + dbus_connection_unref (f->caller); + f->caller = 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_free (f->address); +} + +int +main (int argc, + char **argv) +{ + test_init (&argc, &argv); + + g_test_add ("/sd-activation", Fixture, NULL, + setup, test_activation, teardown); + + return g_test_run (); +} diff --git a/test/syntax.c b/test/syntax.c index e26b3643..bf960c9e 100644 --- a/test/syntax.c +++ b/test/syntax.c @@ -30,6 +30,8 @@ #include <dbus/dbus.h> +#include "test-utils-glib.h" + typedef struct { DBusError e; } Fixture; @@ -269,7 +271,7 @@ int main (int argc, char **argv) { - g_test_init (&argc, &argv, NULL); + test_init (&argc, &argv); g_test_add ("/syntax/path", Fixture, &paths, setup, test_syntax, teardown); g_test_add ("/syntax/interface", Fixture, &interfaces, diff --git a/test/test-utils-glib.c b/test/test-utils-glib.c new file mode 100644 index 00000000..6cffcb21 --- /dev/null +++ b/test/test-utils-glib.c @@ -0,0 +1,413 @@ +/* Utility functions for tests that rely on GLib + * + * Copyright © 2010-2011 Nokia Corporation + * Copyright © 2013-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 <config.h> +#include "test-utils-glib.h" + +#include <string.h> + +#ifdef DBUS_WIN +# include <io.h> +# include <windows.h> +#else +# include <errno.h> +# include <signal.h> +# include <unistd.h> +# include <sys/types.h> +# include <pwd.h> +#endif + +#include <glib.h> +#include <glib/gstdio.h> + +#include <dbus/dbus.h> + +void +_test_assert_no_error (const DBusError *e, + const char *file, + int line) +{ + if (G_UNLIKELY (dbus_error_is_set (e))) + g_error ("%s:%d: expected success but got error: %s: %s", + file, line, e->name, e->message); +} + +#ifdef DBUS_UNIX +static void +child_setup (gpointer user_data) +{ + const struct passwd *pwd = user_data; + uid_t uid = geteuid (); + + if (pwd == NULL || (pwd->pw_uid == uid && getuid () == uid)) + return; + + if (uid != 0) + g_error ("not currently euid 0: %lu", (unsigned long) uid); + + if (setuid (pwd->pw_uid) != 0) + g_error ("could not setuid (%lu): %s", + (unsigned long) pwd->pw_uid, g_strerror (errno)); + + uid = getuid (); + + if (uid != pwd->pw_uid) + g_error ("after successful setuid (%lu) my uid is %ld", + (unsigned long) pwd->pw_uid, (unsigned long) uid); + + uid = geteuid (); + + if (uid != pwd->pw_uid) + g_error ("after successful setuid (%lu) my euid is %ld", + (unsigned long) pwd->pw_uid, (unsigned long) uid); +} +#endif + +static gchar * +spawn_dbus_daemon (const gchar *binary, + const gchar *configuration, + TestUser user, + GPid *daemon_pid) +{ + GError *error = NULL; + GString *address; + gint address_fd; + const gchar *const argv[] = { + binary, + configuration, + "--nofork", + "--print-address=1", /* stdout */ +#ifdef DBUS_UNIX + "--systemd-activation", +#endif + NULL + }; +#ifdef DBUS_UNIX + const struct passwd *pwd = NULL; +#endif + + if (user != TEST_USER_ME) + { +#ifdef DBUS_UNIX + if (getuid () != 0) + { + g_message ("SKIP: cannot use alternative uid when not uid 0"); + return NULL; + } + + switch (user) + { + case TEST_USER_ROOT: + break; + + case TEST_USER_MESSAGEBUS: + pwd = getpwnam (DBUS_USER); + + if (pwd == NULL) + { + g_message ("SKIP: user '%s' does not exist", DBUS_USER); + return NULL; + } + + break; + + case TEST_USER_OTHER: + pwd = getpwnam (DBUS_TEST_USER); + + if (pwd == NULL) + { + g_message ("SKIP: user '%s' does not exist", DBUS_TEST_USER); + return NULL; + } + + break; + + default: + g_assert_not_reached (); + } +#else + g_message ("SKIP: cannot use alternative uid on Windows"); + return NULL; +#endif + } + + g_spawn_async_with_pipes (NULL, /* working directory */ + (gchar **) argv, /* g_s_a_w_p() is not const-correct :-( */ + NULL, /* envp */ + G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH, +#ifdef DBUS_UNIX + child_setup, (gpointer) pwd, +#else + NULL, NULL, +#endif + daemon_pid, + NULL, /* child's stdin = /dev/null */ + &address_fd, + NULL, /* child's stderr = our stderr */ + &error); + g_assert_no_error (error); + + address = g_string_new (NULL); + + /* polling until the dbus-daemon writes out its address is a bit stupid, + * but at least it's simple, unlike dbus-launch... in principle we could + * use select() here, but life's too short */ + while (1) + { + gssize bytes; + gchar buf[4096]; + gchar *newline; + + bytes = read (address_fd, buf, sizeof (buf)); + + if (bytes > 0) + g_string_append_len (address, buf, bytes); + + newline = strchr (address->str, '\n'); + + if (newline != NULL) + { + if ((newline > address->str) && ('\r' == newline[-1])) + newline -= 1; + g_string_truncate (address, newline - address->str); + break; + } + + g_usleep (G_USEC_PER_SEC / 10); + } + + g_close (address_fd, NULL); + + return g_string_free (address, FALSE); +} + +gchar * +test_get_dbus_daemon (const gchar *config_file, + TestUser user, + GPid *daemon_pid) +{ + gchar *dbus_daemon; + gchar *arg; + gchar *address; + + if (config_file != NULL) + { + + if (g_getenv ("DBUS_TEST_DATA") == NULL) + { + g_message ("SKIP: set DBUS_TEST_DATA to a directory containing %s", + config_file); + return NULL; + } + + arg = g_strdup_printf ( + "--config-file=%s/%s", + g_getenv ("DBUS_TEST_DATA"), config_file); + } + else if (g_getenv ("DBUS_TEST_SYSCONFDIR") != NULL) + { + arg = g_strdup_printf ("--config-file=%s/dbus-1/session.conf", + g_getenv ("DBUS_TEST_SYSCONFDIR")); + } + else if (g_getenv ("DBUS_TEST_DATA") != NULL) + { + arg = g_strdup_printf ( + "--config-file=%s/valid-config-files/session.conf", + g_getenv ("DBUS_TEST_DATA")); + } + else + { + arg = g_strdup ("--session"); + } + + dbus_daemon = g_strdup (g_getenv ("DBUS_TEST_DAEMON")); + + if (dbus_daemon == NULL) + dbus_daemon = g_strdup ("dbus-daemon"); + + if (g_getenv ("DBUS_TEST_DAEMON_ADDRESS") != NULL) + { + if (config_file != NULL || user != TEST_USER_ME) + { + g_message ("SKIP: cannot use DBUS_TEST_DAEMON_ADDRESS for " + "unusally-configured dbus-daemon"); + address = NULL; + } + else + { + address = g_strdup (g_getenv ("DBUS_TEST_DAEMON_ADDRESS")); + } + } + else + { + address = spawn_dbus_daemon (dbus_daemon, arg, user, daemon_pid); + } + + g_free (dbus_daemon); + g_free (arg); + return address; +} + +DBusConnection * +test_connect_to_bus (TestMainContext *ctx, + const gchar *address) +{ + DBusConnection *conn; + DBusError error = DBUS_ERROR_INIT; + dbus_bool_t ok; + + conn = dbus_connection_open_private (address, &error); + test_assert_no_error (&error); + g_assert (conn != NULL); + + ok = dbus_bus_register (conn, &error); + test_assert_no_error (&error); + g_assert (ok); + g_assert (dbus_bus_get_unique_name (conn) != NULL); + + test_connection_setup (ctx, conn); + return conn; +} + +DBusConnection * +test_connect_to_bus_as_user (TestMainContext *ctx, + const char *address, + TestUser user) +{ + /* For now we only do tests like this on Linux, because I don't know how + * safe this use of setresuid() is on other platforms */ +#if defined(HAVE_GETRESUID) && defined(HAVE_SETRESUID) && defined(__linux__) + uid_t ruid, euid, suid; + const struct passwd *pwd; + DBusConnection *conn; + const char *username; + + switch (user) + { + case TEST_USER_ME: + return test_connect_to_bus (ctx, address); + + case TEST_USER_ROOT: + username = "root"; + break; + + case TEST_USER_MESSAGEBUS: + username = DBUS_USER; + break; + + case TEST_USER_OTHER: + username = DBUS_TEST_USER; + break; + + default: + g_return_val_if_reached (NULL); + } + + if (getresuid (&ruid, &euid, &suid) != 0) + g_error ("getresuid: %s", g_strerror (errno)); + + if (ruid != 0 || euid != 0 || suid != 0) + { + g_message ("SKIP: not uid 0 (ruid=%ld euid=%ld suid=%ld)", + (unsigned long) ruid, (unsigned long) euid, (unsigned long) suid); + return NULL; + } + + pwd = getpwnam (username); + + if (pwd == NULL) + { + g_message ("SKIP: getpwnam(\"%s\"): %s", username, g_strerror (errno)); + return NULL; + } + + /* Impersonate the desired user while we connect to the bus. + * This should work, because we're root. */ + if (setresuid (pwd->pw_uid, pwd->pw_uid, 0) != 0) + g_error ("setresuid(%ld, (same), 0): %s", + (unsigned long) pwd->pw_uid, g_strerror (errno)); + + conn = test_connect_to_bus (ctx, address); + + /* go back to our saved uid */ + if (setresuid (0, 0, 0) != 0) + g_error ("setresuid(0, 0, 0): %s", g_strerror (errno)); + + return conn; + +#else + + switch (user) + { + case TEST_USER_ME: + return test_connect_to_bus (ctx, address); + + default: + g_message ("SKIP: setresuid() not available, or unsure about " + "credentials-passing semantics on this platform"); + return NULL; + } + +#endif +} + +void +test_kill_pid (GPid pid) +{ +#ifdef DBUS_WIN + if (pid != NULL) + TerminateProcess (pid, 1); +#else + if (pid > 0) + kill (pid, SIGTERM); +#endif +} + +static gboolean +time_out (gpointer data) +{ + g_error ("timed out"); + return FALSE; +} + +void +test_init (int *argcp, char ***argvp) +{ + g_test_init (argcp, argvp, NULL); + g_test_bug_base ("https://bugs.freedesktop.org/show_bug.cgi?id="); + + /* Prevent tests from hanging forever. This is intended to be long enough + * that any reasonable regression test on any reasonable hardware would + * have finished. */ +#define TIMEOUT 60 + + g_timeout_add_seconds (TIMEOUT, time_out, NULL); +#ifdef G_OS_UNIX + /* The GLib main loop might not be running (we don't use it in every + * test). Die with SIGALRM shortly after if necessary. */ + alarm (TIMEOUT + 10); +#endif +} diff --git a/test/test-utils-glib.h b/test/test-utils-glib.h new file mode 100644 index 00000000..fd14fd1f --- /dev/null +++ b/test/test-utils-glib.h @@ -0,0 +1,84 @@ +/* Utility functions for tests that rely on GLib + * + * Copyright © 2010-2011 Nokia Corporation + * Copyright © 2013-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. + */ + +#ifndef TEST_UTILS_GLIB_H +#define TEST_UTILS_GLIB_H + +#include <dbus/dbus.h> + +#include <glib.h> + +#include "test-utils.h" + +/* + * Multi-user support for regression tests run with root privileges in + * a continuous integration system. + * + * A developer would normally run the tests as their own uid. Tests run + * as TEST_USER_ME are run, and the others are skipped. + * + * In a CI system that has access to root privileges, most tests should still + * be run as an arbitrary non-root user, as above. + * + * Certain tests can usefully be run again, as root. When this is done, + * tests using TEST_USER_ROOT, TEST_USER_MESSAGEBUS and/or TEST_USER_OTHER + * can exercise situations that only arise when there's more than one uid. + */ +typedef enum { + /* Whatever user happens to be running the regression test; + * such tests also work on Windows */ + TEST_USER_ME, + /* Must be uid 0 on Unix; the test is skipped on Windows */ + TEST_USER_ROOT, + /* The user who would normally run the system bus. This is the DBUS_USER + * from configure.ac, usually 'messagebus' but perhaps 'dbus' or + * '_dbus'. */ + TEST_USER_MESSAGEBUS, + /* An unprivileged user who is neither root nor DBUS_USER. + * This is DBUS_TEST_USER from configure.ac, usually 'nobody'. */ + TEST_USER_OTHER +} TestUser; + +#define test_assert_no_error(e) _test_assert_no_error (e, __FILE__, __LINE__) +void _test_assert_no_error (const DBusError *e, + const char *file, + int line); + +gchar *test_get_dbus_daemon (const gchar *config_file, + TestUser user, + GPid *daemon_pid); + +DBusConnection *test_connect_to_bus (TestMainContext *ctx, + const gchar *address); +DBusConnection *test_connect_to_bus_as_user (TestMainContext *ctx, + const char *address, + TestUser user); + +void test_kill_pid (GPid pid); + +void test_init (int *argcp, char ***argvp); + +#endif diff --git a/test/test-utils.c b/test/test-utils.c index 9a4f3584..9686a9a8 100644 --- a/test/test-utils.c +++ b/test/test-utils.c @@ -7,6 +7,14 @@ #endif #ifdef DBUS_TEST_USE_INTERNAL +# define test_assert(x) _dbus_assert(x) +#elif defined(g_assert_true) +# define test_assert(x) g_assert_true(x) +#else +# define test_assert(x) g_assert(x) +#endif + +#ifdef DBUS_TEST_USE_INTERNAL typedef struct { @@ -390,3 +398,13 @@ void test_main_context_iterate (TestMainContext *ctx, g_main_context_iteration (ctx, may_block); #endif } + +void +test_pending_call_store_reply (DBusPendingCall *pc, + void *data) +{ + DBusMessage **message_p = data; + + *message_p = dbus_pending_call_steal_reply (pc); + test_assert (*message_p != NULL); +} diff --git a/test/test-utils.h b/test/test-utils.h index 0d3f3690..b526e927 100644 --- a/test/test-utils.h +++ b/test/test-utils.h @@ -34,5 +34,7 @@ dbus_bool_t test_server_setup (TestMainContext *ctx, DBusServer *server); void test_server_shutdown (TestMainContext *ctx, DBusServer *server); +void test_pending_call_store_reply (DBusPendingCall *pc, + void *data); #endif diff --git a/test/uid-permissions.c b/test/uid-permissions.c new file mode 100644 index 00000000..2c62185a --- /dev/null +++ b/test/uid-permissions.c @@ -0,0 +1,199 @@ +/* Integration tests for the dbus-daemon's uid-based hardening + * + * Author: Simon McVittie <simon.mcvittie@collabora.co.uk> + * 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 <config.h> + +#include "test-utils-glib.h" + +typedef struct { + gboolean skip; + + TestMainContext *ctx; + + DBusError e; + GError *ge; + + GPid daemon_pid; + + DBusConnection *conn; +} Fixture; + +typedef struct { + const char *config_file; + TestUser user; + gboolean expect_success; +} Config; + +static void +setup (Fixture *f, + gconstpointer context) +{ + const Config *config = context; + gchar *address; + + f->ctx = test_main_context_get (); + f->ge = NULL; + dbus_error_init (&f->e); + + address = test_get_dbus_daemon (config ? config->config_file : NULL, + TEST_USER_MESSAGEBUS, + &f->daemon_pid); + + if (address == NULL) + { + f->skip = TRUE; + return; + } + + f->conn = test_connect_to_bus_as_user (f->ctx, address, + config ? config->user : TEST_USER_ME); + + if (f->conn == NULL) + f->skip = TRUE; + + g_free (address); +} + +static void +test_uae (Fixture *f, + gconstpointer context) +{ + const Config *config = context; + DBusMessage *m; + DBusPendingCall *pc; + DBusMessageIter args_iter; + DBusMessageIter arr_iter; + + if (f->skip) + return; + + m = dbus_message_new_method_call (DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, "UpdateActivationEnvironment"); + + if (m == NULL) + g_error ("OOM"); + + dbus_message_iter_init_append (m, &args_iter); + + /* Append an empty a{ss} (string => string dictionary). */ + if (!dbus_message_iter_open_container (&args_iter, DBUS_TYPE_ARRAY, + "{ss}", &arr_iter) || + !dbus_message_iter_close_container (&args_iter, &arr_iter)) + g_error ("OOM"); + + if (!dbus_connection_send_with_reply (f->conn, 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 (config->expect_success) + { + /* it succeeds */ + g_assert_cmpint (dbus_message_get_type (m), ==, + DBUS_MESSAGE_TYPE_METHOD_RETURN); + } + else + { + /* it fails, yielding an error message with one string argument */ + g_assert_cmpint (dbus_message_get_type (m), ==, DBUS_MESSAGE_TYPE_ERROR); + g_assert_cmpstr (dbus_message_get_error_name (m), ==, + DBUS_ERROR_ACCESS_DENIED); + g_assert_cmpstr (dbus_message_get_signature (m), ==, "s"); + } + + 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->conn != NULL) + { + dbus_connection_close (f->conn); + dbus_connection_unref (f->conn); + f->conn = NULL; + } + + 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); +} + +static Config root_ok_config = { + "valid-config-files/multi-user.conf", + TEST_USER_ROOT, + TRUE +}; + +static Config messagebus_ok_config = { + "valid-config-files/multi-user.conf", + TEST_USER_MESSAGEBUS, + TRUE +}; + +static Config other_fail_config = { + "valid-config-files/multi-user.conf", + TEST_USER_OTHER, + FALSE +}; + +int +main (int argc, + char **argv) +{ + test_init (&argc, &argv); + + g_test_add ("/uid-permissions/uae/root", Fixture, &root_ok_config, + setup, test_uae, teardown); + g_test_add ("/uid-permissions/uae/messagebus", Fixture, &messagebus_ok_config, + setup, test_uae, teardown); + g_test_add ("/uid-permissions/uae/other", Fixture, &other_fail_config, + setup, test_uae, teardown); + + return g_test_run (); +} |