diff options
Diffstat (limited to 'test')
43 files changed, 4736 insertions, 648 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 e0ed3c87..9540a2c1 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -4,59 +4,48 @@ SUBDIRS= . name-test DIST_SUBDIRS=name-test -# CPPFLAGS for binaries that are normally dynamic +CLEANFILES = +EXTRA_DIST = + AM_CPPFLAGS = \ -I$(top_srcdir) \ $(DBUS_STATIC_BUILD_CPPFLAGS) \ + -DDBUS_COMPILATION \ $(GLIB_CFLAGS) \ - $(DBUS_GLIB_CFLAGS) \ $(NULL) # improve backtraces from test stuff AM_LDFLAGS = @R_DYNAMIC_LDFLAG@ -# CPPFLAGS for binaries that are always static -static_cppflags = \ - $(AM_CPPFLAGS) \ - -DDBUS_STATIC_BUILD \ - -DDBUS_COMPILATION \ - -DDBUS_TEST_USE_INTERNAL \ - $(NULL) - -noinst_LTLIBRARIES = libdbus-testutils-internal.la +noinst_LTLIBRARIES = libdbus-testutils.la -# 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) \ - $(DBUS_GLIB_LIBS) \ + +if DBUS_WITH_GLIB +libdbus_testutils_la_SOURCES += \ + test-utils-glib.c \ + test-utils-glib.h \ $(NULL) -testutils_shared_if_possible_cppflags = $(AM_CPPFLAGS) -testutils_shared_if_possible_libs = libdbus-testutils.la -else -testutils_shared_if_possible_cppflags = $(static_cppflags) -testutils_shared_if_possible_libs = libdbus-testutils-internal.la endif -libdbus_testutils_internal_la_CPPFLAGS = \ - $(static_cppflags) \ - $(NULL) -libdbus_testutils_internal_la_SOURCES = \ - test-utils.c \ - test-utils.h \ - $(NULL) -libdbus_testutils_internal_la_LIBADD = \ +libdbus_testutils_la_LIBADD = \ + $(top_builddir)/dbus/libdbus-1.la \ $(top_builddir)/dbus/libdbus-internal.la \ $(NULL) +TEST_EXTENSIONS = .sh + +LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) $(top_srcdir)/build-aux/tap-driver.sh +LOG_COMPILER = $(srcdir)/glib-tap-test.sh +SH_LOG_DRIVER = $(LOG_DRIVER) +SH_LOG_COMPILER = $(SHELL) +EXTRA_DIST += glib-tap-test.sh + +TESTS = + if DBUS_ENABLE_EMBEDDED_TESTS ## break-loader removed for now ## these binaries are used in tests but are not themselves tests @@ -72,154 +61,202 @@ TEST_BINARIES = \ ## These are conceptually part of directories that come earlier in SUBDIRS ## order, but we don't want to run them til we arrive in this directory, -## since they depend on stuff from this directory -TESTS = \ - ../bus/test-bus$(EXEEXT) \ - ../bus/test-bus-system$(EXEEXT) \ - ../dbus/test-dbus$(EXEEXT) \ - $(NULL) +## since they depend on stuff from this directory. We wrap them in a +## simple shell script to get TAP output. + +wrap_bus_tests = test-bus.sh +wrap_dbus_tests = test-dbus.sh if DBUS_UNIX -TESTS += ../bus/test-bus-launch-helper$(EXEEXT) +wrap_bus_tests += test-bus-launch-helper.sh +wrap_bus_tests += test-bus-system.sh endif +TESTS += $(wrap_bus_tests) $(wrap_dbus_tests) +CLEANFILES += $(wrap_bus_tests) $(wrap_dbus_tests) +EXTRA_DIST += tap-test.sh.in + +$(wrap_bus_tests): test-bus%.sh: ../bus/test-bus%$(EXEEXT) tap-test.sh.in Makefile + sed -e 's![@]RUN[@]!$<!' \ + < $(srcdir)/tap-test.sh.in > $@ + +$(wrap_dbus_tests): test-dbus%.sh: ../dbus/test-dbus%$(EXEEXT) tap-test.sh.in Makefile + sed -e 's![@]RUN[@]!$<!' \ + < $(srcdir)/tap-test.sh.in > $@ + else !DBUS_ENABLE_EMBEDDED_TESTS TEST_BINARIES= -TESTS= endif !DBUS_ENABLE_EMBEDDED_TESTS noinst_PROGRAMS= $(TEST_BINARIES) -test_service_CPPFLAGS = $(static_cppflags) -test_service_LDADD = libdbus-testutils-internal.la -test_names_CPPFLAGS = $(static_cppflags) -test_names_LDADD = libdbus-testutils-internal.la -## break_loader_CPPFLAGS = $(static_cppflags) +test_service_LDADD = libdbus-testutils.la +test_names_LDADD = libdbus-testutils.la ## break_loader_LDADD = $(top_builddir)/dbus/libdbus-internal.la -test_shell_service_CPPFLAGS = $(static_cppflags) -test_shell_service_LDADD = libdbus-testutils-internal.la +test_shell_service_LDADD = libdbus-testutils.la test_shell_SOURCES = shell-test.c -test_shell_CPPFLAGS = $(static_cppflags) -test_shell_LDADD = libdbus-testutils-internal.la +test_shell_LDADD = libdbus-testutils.la test_spawn_SOURCES = spawn-test.c -test_spawn_CPPFLAGS = $(static_cppflags) test_spawn_LDADD = $(top_builddir)/dbus/libdbus-internal.la test_printf_SOURCES = internals/printf.c -test_printf_CPPFLAGS = $(static_cppflags) test_printf_LDADD = $(top_builddir)/dbus/libdbus-internal.la test_refs_SOURCES = internals/refs.c -test_refs_CPPFLAGS = $(static_cppflags) -test_refs_LDADD = libdbus-testutils-internal.la $(GLIB_LIBS) +test_refs_LDADD = libdbus-testutils.la $(GLIB_LIBS) test_syslog_SOURCES = internals/syslog.c -test_syslog_CPPFLAGS = $(static_cppflags) -test_syslog_LDADD = libdbus-testutils-internal.la $(GLIB_LIBS) +test_syslog_LDADD = libdbus-testutils.la $(GLIB_LIBS) + +manual_dir_iter_SOURCES = manual-dir-iter.c +manual_dir_iter_LDADD = $(top_builddir)/dbus/libdbus-internal.la + +manual_paths_SOURCES = manual-paths.c +manual_paths_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 +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 = \ - manual-tcp \ - $(NULL) + manual-dir-iter \ + manual-tcp \ + $(NULL) + +if DBUS_WIN +installable_manual_tests += manual-paths +endif 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 \ - DBUS_TEST_DAEMON=$(DESTDIR)$(DBUS_DAEMONDIR)/dbus-daemon$(EXEEXT) \ - DBUS_TEST_HOMEDIR=@abs_top_builddir@/dbus \ - DBUS_TEST_SYSCONFDIR=$(DESTDIR)$(sysconfdir) - -TESTS_ENVIRONMENT = \ - XDG_RUNTIME_DIR=@abs_top_builddir@/test/XDG_RUNTIME_DIR \ - DBUS_FATAL_WARNINGS=1 \ - DBUS_TEST_DAEMON=@abs_top_builddir@/bus/dbus-daemon$(EXEEXT) \ - DBUS_TEST_DATA=@abs_top_builddir@/test/data \ - DBUS_TEST_HOMEDIR=@abs_top_builddir@/dbus \ + export XDG_RUNTIME_DIR=@abs_top_builddir@/test/XDG_RUNTIME_DIR; \ + export DBUS_TEST_DAEMON=$(DESTDIR)$(DBUS_DAEMONDIR)/dbus-daemon$(EXEEXT); \ + export DBUS_TEST_HOMEDIR=@abs_top_builddir@/dbus; \ + export DBUS_TEST_DATADIR=$(DESTDIR)$(datadir); \ + ${NULL} + +AM_TESTS_ENVIRONMENT = \ + export XDG_RUNTIME_DIR=@abs_top_builddir@/test/XDG_RUNTIME_DIR; \ + export DBUS_FATAL_WARNINGS=1; \ + export DBUS_TEST_DAEMON=@abs_top_builddir@/bus/dbus-daemon$(EXEEXT); \ + export DBUS_TEST_DATA=@abs_top_builddir@/test/data; \ + export DBUS_TEST_HOMEDIR=@abs_top_builddir@/dbus; \ $(NULL) manual_authz_SOURCES = manual-authz.c -manual_authz_CPPFLAGS = $(testutils_shared_if_possible_cppflags) manual_authz_LDADD = \ - $(testutils_shared_if_possible_libs) \ + libdbus-testutils.la \ $(GLIB_LIBS) \ $(NULL) test_corrupt_SOURCES = corrupt.c -test_corrupt_CPPFLAGS = $(testutils_shared_if_possible_cppflags) test_corrupt_LDADD = \ - $(testutils_shared_if_possible_libs) \ + libdbus-testutils.la \ $(GLIB_LIBS) \ $(NULL) test_loopback_SOURCES = loopback.c -test_loopback_CPPFLAGS = $(testutils_shared_if_possible_cppflags) test_loopback_LDADD = \ - $(testutils_shared_if_possible_libs) \ + libdbus-testutils.la \ $(GLIB_LIBS) \ $(NULL) test_relay_SOURCES = relay.c -test_relay_CPPFLAGS = $(testutils_shared_if_possible_cppflags) test_relay_LDADD = \ - $(testutils_shared_if_possible_libs) \ + libdbus-testutils.la \ $(GLIB_LIBS) \ $(NULL) test_dbus_daemon_SOURCES = dbus-daemon.c -test_dbus_daemon_CPPFLAGS = $(testutils_shared_if_possible_cppflags) test_dbus_daemon_LDADD = \ - $(testutils_shared_if_possible_libs) \ + libdbus-testutils.la \ $(GLIB_LIBS) \ $(NULL) test_dbus_daemon_eavesdrop_SOURCES = dbus-daemon-eavesdrop.c -test_dbus_daemon_eavesdrop_CPPFLAGS = $(testutils_shared_if_possible_cppflags) test_dbus_daemon_eavesdrop_LDADD = \ - $(testutils_shared_if_possible_libs) \ + libdbus-testutils.la \ + $(GLIB_LIBS) \ + $(NULL) + +test_sd_activation_SOURCES = \ + sd-activation.c \ + $(NULL) +test_sd_activation_LDADD = \ + libdbus-testutils.la \ $(GLIB_LIBS) \ $(NULL) test_marshal_SOURCES = marshal.c test_marshal_LDADD = \ - $(top_builddir)/dbus/libdbus-1.la \ + libdbus-testutils.la \ + $(GLIB_LIBS) \ + $(NULL) + +test_monitor_SOURCES = \ + monitor.c \ + $(NULL) +test_monitor_LDADD = \ + libdbus-testutils.la \ $(GLIB_LIBS) \ $(NULL) test_syntax_SOURCES = syntax.c test_syntax_LDADD = \ - $(top_builddir)/dbus/libdbus-1.la \ + libdbus-testutils.la \ + $(GLIB_LIBS) \ + $(NULL) + +test_uid_permissions_SOURCES = \ + uid-permissions.c \ + $(NULL) +test_uid_permissions_LDADD = \ + libdbus-testutils.la \ + $(GLIB_LIBS) \ + $(NULL) + +test_fdpass_SOURCES = \ + fdpass.c \ + $(NULL) +test_fdpass_LDADD = \ + libdbus-testutils.la \ $(GLIB_LIBS) \ $(NULL) @@ -229,6 +266,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 @@ -240,13 +280,13 @@ endif DBUS_ENABLE_MODULAR_TESTS # do a portable equivalent of setting LD_LIBRARY_PATH. installcheck-local: $(MAKE) check-TESTS TESTS='$$(installcheck_tests)' \ - TESTS_ENVIRONMENT='$$(installcheck_environment)' + AM_TESTS_ENVIRONMENT='$$(installcheck_environment)' if DBUS_ENABLE_INSTALLED_TESTS - test -n "$(DESTDIR)" || \ + test -n "$(DESTDIR)" || { \ $(installcheck_environment) \ $(srcdir)/dbus-test-runner \ $(testexecdir) \ - $(testexec_PROGRAMS) + $(testexec_PROGRAMS) } endif DBUS_ENABLE_INSTALLED_TESTS in_data = \ @@ -254,7 +294,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 \ @@ -323,9 +367,14 @@ 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 \ + data/valid-config-files/listen-unix-runtime.conf \ data/valid-config-files/many-rules.conf \ data/valid-config-files/system.d/test.conf \ data/valid-messages/array-of-array-of-uint32.message \ @@ -349,25 +398,82 @@ 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 $(datadir)/dbus-1/session.conf $(DESTDIR)$(testexecdir)/data/valid-config-files/session.conf + ln -nfs $(datadir)/dbus-1/system.conf $(DESTDIR)$(testexecdir)/data/valid-config-files/system.conf +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 = \ @@ -376,8 +482,28 @@ 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 'Output=TAP'; \ + echo 'Exec=env $(testexecdir)/$* --tap'; \ + ) > $@.tmp && mv $@.tmp $@ + +$(installable_test_meta_with_config): %_with_config.test: %$(EXEEXT) Makefile + $(AM_V_GEN) ( \ + echo '[Test]'; \ + echo 'Type=session'; \ + echo 'Output=TAP'; \ + echo 'Exec=env DBUS_TEST_DATA=$(testexecdir)/data $(testexecdir)/$* --tap'; \ + ) > $@.tmp && mv $@.tmp $@ diff --git a/test/corrupt.c b/test/corrupt.c index 1a7d4460..d106fcc5 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; @@ -110,7 +110,7 @@ test_connect (Fixture *f, while (f->server_conn == NULL) { - g_print ("."); + test_progress ('.'); test_main_context_iterate (f->ctx, TRUE); } @@ -139,7 +139,7 @@ test_message (Fixture *f, while (g_queue_is_empty (&f->client_messages)) { - g_print ("."); + test_progress ('.'); test_main_context_iterate (f->ctx, TRUE); } @@ -231,7 +231,7 @@ test_corrupt (Fixture *f, * rubbish, so it should disconnect */ while (g_queue_is_empty (&f->client_messages)) { - g_print ("."); + test_progress ('.'); test_main_context_iterate (f->ctx, TRUE); } @@ -320,7 +320,7 @@ test_byte_order (Fixture *f, * message, so it should disconnect */ while (g_queue_is_empty (&f->client_messages)) { - g_print ("."); + test_progress ('.'); test_main_context_iterate (f->ctx, TRUE); } @@ -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/listen-unix-runtime.conf b/test/data/valid-config-files/listen-unix-runtime.conf new file mode 100644 index 00000000..169de2cb --- /dev/null +++ b/test/data/valid-config-files/listen-unix-runtime.conf @@ -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> + <type>session</type> + <listen>unix:runtime=yes</listen> + <policy context="default"> + <allow send_destination="*" eavesdrop="true"/> + <allow eavesdrop="true"/> + <allow own="*"/> + </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..72bcd08c 100644 --- a/test/dbus-daemon.c +++ b/test/dbus-daemon.c @@ -2,6 +2,7 @@ * * 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 @@ -26,22 +27,47 @@ #include <config.h> -#include <glib.h> +#include <errno.h> +#include <string.h> #include <dbus/dbus.h> +#include <glib.h> +#include <glib/gstdio.h> + +#include "test-utils-glib.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 +83,30 @@ typedef struct { DBusConnection *right_conn; gboolean right_conn_echo; -} 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)); + gboolean wait_forever_called; - 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; -} + gchar *tmp_runtime_dir; + gchar *saved_runtime_dir; +} Fixture; 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) @@ -179,6 +124,7 @@ typedef struct { const char *bug_ref; guint min_messages; const char *config_file; + enum { SPECIFY_ADDRESS = 0, RELY_ON_DEFAULT } connect_mode; } Config; static void @@ -186,78 +132,57 @@ 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 (config != NULL && config->connect_mode == RELY_ON_DEFAULT) { - 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; - } + /* this is chosen to be something needing escaping */ + f->tmp_runtime_dir = g_dir_make_tmp ("dbus=daemon=test.XXXXXX", &f->ge); + g_assert_no_error (f->ge); - 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")); + /* we're relying on being single-threaded for this to be safe */ + f->saved_runtime_dir = g_strdup (g_getenv ("XDG_RUNTIME_DIR")); + g_setenv ("XDG_RUNTIME_DIR", f->tmp_runtime_dir, TRUE); } - 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 + + address = test_get_dbus_daemon (config ? config->config_file : NULL, + TEST_USER_ME, + &f->daemon_pid); + + if (address == NULL) { - arg = g_strdup ("--session"); + f->skip = TRUE; + return; } - dbus_daemon = g_strdup (g_getenv ("DBUS_TEST_DAEMON")); + f->left_conn = test_connect_to_bus (f->ctx, address); - if (dbus_daemon == NULL) - dbus_daemon = g_strdup ("dbus-daemon"); - - if (g_getenv ("DBUS_TEST_DAEMON_ADDRESS") != NULL) + if (config != NULL && config->connect_mode == RELY_ON_DEFAULT) { - address = g_strdup (g_getenv ("DBUS_TEST_DAEMON_ADDRESS")); + /* use the default bus for the echo service ("right"), to check that + * it ends up on the same bus as the client ("left") */ + f->right_conn = dbus_bus_get_private (DBUS_BUS_SESSION, &f->e); + test_assert_no_error (&f->e); + + if (!test_connection_setup (f->ctx, f->right_conn)) + g_error ("OOM"); } else { - address = spawn_dbus_daemon (dbus_daemon, arg, &f->daemon_pid); + f->right_conn = test_connect_to_bus (f->ctx, address); } - g_free (dbus_daemon); - g_free (arg); - - f->left_conn = connect_to_bus (f, address); - f->right_conn = connect_to_bus (f, 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 +258,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"); + + 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); - *message_p = dbus_pending_call_steal_reply (pc); - g_assert (*message_p != NULL); + /* 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 @@ -357,7 +346,8 @@ test_creds (Fixture *f, enum { SEEN_UNIX_USER = 1, SEEN_PID = 2, - SEEN_WINDOWS_SID = 4 + SEEN_WINDOWS_SID = 4, + SEEN_LINUX_SECURITY_LABEL = 8 } seen = 0; if (m == NULL) @@ -377,8 +367,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"); @@ -416,13 +406,34 @@ test_creds (Fixture *f, g_assert_cmpuint (dbus_message_iter_get_arg_type (&var_iter), ==, DBUS_TYPE_UINT32); dbus_message_iter_get_basic (&var_iter, &u32); - g_message ("%s of this process is %u", name, u32); + g_test_message ("%s of this process is %u", name, u32); g_assert_cmpuint (u32, ==, geteuid ()); seen |= SEEN_UNIX_USER; #else g_assert_not_reached (); #endif } + else if (g_strcmp0 (name, "WindowsSID") == 0) + { +#ifdef G_OS_WIN32 + gchar *sid; + char *self_sid; + + g_assert (!(seen & SEEN_WINDOWS_SID)); + g_assert_cmpuint (dbus_message_iter_get_arg_type (&var_iter), ==, + DBUS_TYPE_STRING); + dbus_message_iter_get_basic (&var_iter, &sid); + g_test_message ("%s of this process is %s", name, sid); + if (_dbus_getsid (&self_sid, 0)) + { + g_assert_cmpstr (self_sid, ==, sid); + LocalFree(self_sid); + } + seen |= SEEN_WINDOWS_SID; +#else + g_assert_not_reached (); +#endif + } else if (g_strcmp0 (name, "ProcessID") == 0) { guint32 u32; @@ -431,7 +442,7 @@ test_creds (Fixture *f, g_assert_cmpuint (dbus_message_iter_get_arg_type (&var_iter), ==, DBUS_TYPE_UINT32); dbus_message_iter_get_basic (&var_iter, &u32); - g_message ("%s of this process is %u", name, u32); + g_test_message ("%s of this process is %u", name, u32); #ifdef G_OS_UNIX g_assert_cmpuint (u32, ==, getpid ()); #elif defined(G_OS_WIN32) @@ -441,23 +452,111 @@ test_creds (Fixture *f, #endif seen |= SEEN_PID; } + else if (g_strcmp0 (name, "LinuxSecurityLabel") == 0) + { +#ifdef __linux__ + gchar *label; + int len; + DBusMessageIter ay_iter; + + g_assert (!(seen & SEEN_LINUX_SECURITY_LABEL)); + g_assert_cmpuint (dbus_message_iter_get_arg_type (&var_iter), ==, + DBUS_TYPE_ARRAY); + dbus_message_iter_recurse (&var_iter, &ay_iter); + g_assert_cmpuint (dbus_message_iter_get_arg_type (&ay_iter), ==, + DBUS_TYPE_BYTE); + dbus_message_iter_get_fixed_array (&ay_iter, &label, &len); + g_test_message ("%s of this process is %s", name, label); + g_assert_cmpuint (strlen (label) + 1, ==, len); + seen |= SEEN_LINUX_SECURITY_LABEL; +#else + g_assert_not_reached (); +#endif + } 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 #ifdef G_OS_WIN32 - /* FIXME: when implemented: g_assert (seen & SEEN_WINDOWS_SID); - */ #endif } 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_test_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 +586,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 +624,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 +659,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,36 +670,75 @@ 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; } + if (f->tmp_runtime_dir != NULL) + { + gchar *path; + + /* the socket may exist */ + path = g_strdup_printf ("%s/bus", f->tmp_runtime_dir); + g_assert (g_remove (path) == 0 || errno == ENOENT); + g_free (path); + /* there shouldn't be anything else in there */ + g_assert_cmpint (g_rmdir (f->tmp_runtime_dir), ==, 0); + + /* we're relying on being single-threaded for this to be safe */ + if (f->saved_runtime_dir != NULL) + g_setenv ("XDG_RUNTIME_DIR", f->saved_runtime_dir, TRUE); + else + g_unsetenv ("XDG_RUNTIME_DIR"); + g_free (f->saved_runtime_dir); + g_free (f->tmp_runtime_dir); + } + test_main_context_unref (f->ctx); } static Config limited_config = { - "34393", 10000, "valid-config-files/incoming-limit.conf" + "34393", 10000, "valid-config-files/incoming-limit.conf", + SPECIFY_ADDRESS +}; + +static Config finite_timeout_config = { + NULL, 1, "valid-config-files/finite-timeout.conf", + SPECIFY_ADDRESS }; +#ifdef DBUS_UNIX +static Config listen_unix_runtime_config = { + "61303", 1, "valid-config-files/listen-unix-runtime.conf", + RELY_ON_DEFAULT +}; +#endif + 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); +#ifdef DBUS_UNIX + /* We can't test this in loopback.c with the rest of unix:runtime=yes, + * because dbus_bus_get[_private] is the only way to use the default, + * and that blocks on a round-trip to the dbus-daemon */ + g_test_add ("/unix-runtime-is-default", Fixture, &listen_unix_runtime_config, + setup, test_echo, teardown); +#endif return g_test_run (); } diff --git a/test/fdpass.c b/test/fdpass.c new file mode 100644 index 00000000..a74ce814 --- /dev/null +++ b/test/fdpass.c @@ -0,0 +1,868 @@ +/* + * 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 <stdlib.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 should be + * less than the process's file descriptor limit. */ +#define SOME_MESSAGES 20 +/* To cover some situations on Linux we want this to be true. */ +_DBUS_STATIC_ASSERT (MAX_MESSAGE_UNIX_FDS * SOME_MESSAGES > 256); + +/* 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; + +#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); + abort (); +} + +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) + { + test_progress ('.'); + 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) + { + test_progress ('.'); + 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)) + { + test_progress ('*'); + 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) + { + test_progress ('.'); + 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) + { + test_progress ('.'); + 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; + unsigned 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)) + { + test_progress ('.'); + 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; + DBusSocket 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.fd)) + 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)) + { + test_progress ('.'); + 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 + unsigned 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) + { + test_progress ('.'); + 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)) + { + test_progress ('.'); + 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) + { + test_progress ('.'); + 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); + +#ifdef HAVE_GETRLIMIT + { + struct rlimit lim; + + if (getrlimit (RLIMIT_NOFILE, &lim) < 0) + g_error ("Failed to get RLIMIT_NOFILE limit: %s", g_strerror (errno)); + + if (lim.rlim_cur != RLIM_INFINITY && + /* only run if we have a fairly generous margin of error + * for stdout, stderr, duplicates, the D-Bus connection, etc. */ + lim.rlim_cur < 2 * MAX_MESSAGE_UNIX_FDS * SOME_MESSAGES) + { + g_message ("not enough RLIMIT_NOFILE to run this test"); + /* Autotools exit code for "all skipped" */ + return 77; + } + } +#endif + + 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/glib-tap-test.sh b/test/glib-tap-test.sh new file mode 100755 index 00000000..fcb73383 --- /dev/null +++ b/test/glib-tap-test.sh @@ -0,0 +1,13 @@ +#!/bin/sh +# Wrapper to make GTest tests output TAP syntax, because Automake's test +# drivers do not currently support passing the same command-line argument +# to each test executable. All GTest tests produce TAP output if invoked +# with the --tap option. +# +# Usage: "glib-tap-test.sh test-foo --verbose ..." is equivalent to +# "test-foo --tap --verbose ..." + +set -e +t="$1" +shift +exec "$t" --tap "$@" diff --git a/test/internals/printf.c b/test/internals/printf.c index db151518..2006f321 100644 --- a/test/internals/printf.c +++ b/test/internals/printf.c @@ -62,20 +62,32 @@ do_test (int minimum, #define X_TIMES_512 X_TIMES_256 X_TIMES_256 #define X_TIMES_1024 X_TIMES_512 X_TIMES_512 +/* This test outputs TAP syntax: http://testanything.org/ */ int main (int argc, char **argv) { char buf[] = X_TIMES_1024 X_TIMES_1024 X_TIMES_1024 X_TIMES_1024; int i; + int test_num = 0; do_test (1, "%d", 0); + printf ("ok %d\n", ++test_num); + do_test (7, "%d", 1234567); + printf ("ok %d\n", ++test_num); + do_test (3, "%f", 3.5); + printf ("ok %d\n", ++test_num); do_test (0, "%s", ""); + printf ("ok %d\n", ++test_num); + do_test (1024, "%s", X_TIMES_1024); + printf ("ok %d\n", ++test_num); + do_test (1025, "%s", X_TIMES_1024 "Y"); + printf ("ok %d\n", ++test_num); for (i = 4096; i > 0; i--) { @@ -83,6 +95,11 @@ main (int argc, do_test (i, "%s", buf); do_test (i + 3, "%s:%d", buf, 42); } + printf ("ok %d\n", ++test_num); + /* Tell the TAP driver that we have done all the tests we plan to do. + * This is how it can distinguish between an unexpected exit and + * successful completion. */ + printf ("1..%d\n", test_num); return 0; } 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..bf0542aa 100644 --- a/test/loopback.c +++ b/test/loopback.c @@ -2,6 +2,7 @@ * * Author: Simon McVittie <simon.mcvittie@collabora.co.uk> * Copyright © 2010-2012 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 @@ -27,12 +28,14 @@ #include <config.h> #include <glib.h> +#include <glib/gstdio.h> #include <dbus/dbus.h> +#include <errno.h> #include <string.h> -#include "test-utils.h" +#include "test-utils-glib.h" typedef struct { TestMainContext *ctx; @@ -44,6 +47,9 @@ typedef struct { GQueue server_messages; DBusConnection *client_conn; + + gchar *tmp_runtime_dir; + gchar *saved_runtime_dir; } Fixture; static void @@ -100,6 +106,56 @@ setup (Fixture *f, test_server_setup (f->ctx, f->server); } +#ifdef DBUS_UNIX +static void +setup_runtime (Fixture *f, + gconstpointer addr) +{ + char *listening_at; + GError *error = NULL; + + /* this is chosen to be something needing escaping */ + f->tmp_runtime_dir = g_dir_make_tmp ("dbus=daemon=test.XXXXXX", &error); + g_assert_no_error (error); + + /* we're relying on being single-threaded for this to be safe */ + f->saved_runtime_dir = g_strdup (g_getenv ("XDG_RUNTIME_DIR")); + g_setenv ("XDG_RUNTIME_DIR", f->tmp_runtime_dir, TRUE); + + setup (f, addr); + + listening_at = dbus_server_get_address (f->server); + g_test_message ("listening at %s", listening_at); + g_assert (g_str_has_prefix (listening_at, "unix:path=")); + g_assert (strstr (listening_at, "dbus%3ddaemon%3dtest.") != NULL); + g_assert (strstr (listening_at, "/bus,") != NULL || + g_str_has_suffix (listening_at, "/bus")); + + dbus_free (listening_at); +} + +static void +setup_no_runtime (Fixture *f, + gconstpointer addr) +{ + char *listening_at; + + /* we're relying on being single-threaded for this to be safe */ + f->saved_runtime_dir = g_strdup (g_getenv ("XDG_RUNTIME_DIR")); + g_unsetenv ("XDG_RUNTIME_DIR"); + + setup (f, addr); + + listening_at = dbus_server_get_address (f->server); + g_test_message ("listening at %s", listening_at); + /* we have fallen back to something in /tmp, either abstract or not */ + g_assert (g_str_has_prefix (listening_at, "unix:")); + g_assert (strstr (listening_at, "=/tmp/") != NULL); + + dbus_free (listening_at); +} +#endif + static void test_connect (Fixture *f, gconstpointer addr G_GNUC_UNUSED) @@ -114,7 +170,7 @@ test_connect (Fixture *f, while (f->server_conn == NULL) { - g_print ("."); + test_progress ('.'); test_main_context_iterate (f->ctx, TRUE); } } @@ -148,7 +204,7 @@ test_bad_guid (Fixture *f, while (f->server_conn == NULL) { - g_print ("."); + test_progress ('.'); test_main_context_iterate (f->ctx, TRUE); } @@ -156,7 +212,7 @@ test_bad_guid (Fixture *f, while (g_queue_is_empty (&f->server_messages)) { - g_print ("."); + test_progress ('.'); test_main_context_iterate (f->ctx, TRUE); } @@ -199,7 +255,7 @@ test_message (Fixture *f, while (g_queue_is_empty (&f->server_messages)) { - g_print ("."); + test_progress ('.'); test_main_context_iterate (f->ctx, TRUE); } @@ -251,12 +307,51 @@ teardown (Fixture *f, test_main_context_unref (f->ctx); } +#ifdef DBUS_UNIX +static void +teardown_no_runtime (Fixture *f, + gconstpointer addr) +{ + teardown (f, addr); + + /* we're relying on being single-threaded for this to be safe */ + if (f->saved_runtime_dir != NULL) + g_setenv ("XDG_RUNTIME_DIR", f->saved_runtime_dir, TRUE); + else + g_unsetenv ("XDG_RUNTIME_DIR"); + g_free (f->saved_runtime_dir); +} + +static void +teardown_runtime (Fixture *f, + gconstpointer addr) +{ + gchar *path; + + teardown (f, addr); + + /* the socket may exist */ + path = g_strdup_printf ("%s/bus", f->tmp_runtime_dir); + g_assert (g_remove (path) == 0 || errno == ENOENT); + g_free (path); + /* there shouldn't be anything else in there */ + g_assert_cmpint (g_rmdir (f->tmp_runtime_dir), ==, 0); + + /* we're relying on being single-threaded for this to be safe */ + if (f->saved_runtime_dir != NULL) + g_setenv ("XDG_RUNTIME_DIR", f->saved_runtime_dir, TRUE); + else + g_unsetenv ("XDG_RUNTIME_DIR"); + g_free (f->saved_runtime_dir); + g_free (f->tmp_runtime_dir); +} +#endif + 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); @@ -273,6 +368,13 @@ main (int argc, test_connect, teardown); g_test_add ("/message/unix", Fixture, "unix:tmpdir=/tmp", setup, test_message, teardown); + + g_test_add ("/connect/unix/runtime", Fixture, + "unix:runtime=yes;unix:tmpdir=/tmp", setup_runtime, test_connect, + teardown_runtime); + g_test_add ("/connect/unix/no-runtime", Fixture, + "unix:runtime=yes;unix:tmpdir=/tmp", setup_no_runtime, test_connect, + teardown_no_runtime); #endif g_test_add ("/message/bad-guid", Fixture, "tcp:host=127.0.0.1", setup, diff --git a/test/manual-authz.c b/test/manual-authz.c index f9e3688e..ee9bc52f 100644 --- a/test/manual-authz.c +++ b/test/manual-authz.c @@ -30,6 +30,7 @@ #include <dbus/dbus.h> +#include <stdlib.h> #ifdef G_OS_UNIX #include <unistd.h> #include <sys/types.h> @@ -57,6 +58,7 @@ static void oom (void) { g_error ("out of memory"); + abort (); } static void 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-paths.c b/test/manual-paths.c new file mode 100644 index 00000000..e392c5c3 --- /dev/null +++ b/test/manual-paths.c @@ -0,0 +1,73 @@ +/* + * Simple manual paths check + * + * syntax: manual-paths + * +*/ + +#include "config.h" +#include "dbus/dbus-list.h" +#include "dbus/dbus-internals.h" +#include "dbus/dbus-sysdeps.h" + +#include <stdio.h> + +static dbus_bool_t print_install_root() +{ + char runtime_prefix[1000]; + + if (!_dbus_get_install_root(runtime_prefix, sizeof(runtime_prefix))) + { + fprintf(stderr, "dbus_get_install_root() failed\n"); + return FALSE; + } + fprintf(stdout, "dbus_get_install_root() returned '%s'\n", runtime_prefix); + return TRUE; +} + +static dbus_bool_t print_service_dirs() +{ + DBusList *dirs; + DBusList *link; + dirs = NULL; + + if (!_dbus_get_standard_session_servicedirs (&dirs)) + _dbus_assert_not_reached ("couldn't get standard dirs"); + + while ((link = _dbus_list_pop_first_link (&dirs))) + { + printf ("default service dir: %s\n", (char *)link->data); + dbus_free (link->data); + _dbus_list_free_link (link); + } + dbus_free (dirs); + return TRUE; +} + +static dbus_bool_t print_replace_install_prefix(const char *s) +{ + const char *s2 = _dbus_replace_install_prefix(s); + if (!s2) + return FALSE; + + fprintf(stdout, "replaced '%s' by '%s'\n", s, s2); + return TRUE; +} + +int +main (int argc, char **argv) +{ + if (!print_install_root()) + return -1; + + if (!print_service_dirs()) + return -2; + + if (!print_replace_install_prefix(DBUS_BINDIR "/dbus-daemon")) + return -3; + + if (!print_replace_install_prefix("c:\\Windows\\System32\\testfile")) + return -4; + + return 0; +} 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..c099139a --- /dev/null +++ b/test/monitor.c @@ -0,0 +1,1523 @@ +/* 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_test_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_test_message ("\tfrom: %s", + not_null2 (dbus_message_get_sender (m), "(dbus-daemon)")); + g_test_message ("\tto: %s", + not_null2 (dbus_message_get_destination (m), "(broadcast)")); + g_test_message ("\tpath: %s", + not_null (dbus_message_get_path (m))); + g_test_message ("\tinterface: %s", + not_null (dbus_message_get_interface (m))); + g_test_message ("\tmember: %s", + not_null (dbus_message_get_member (m))); + g_test_message ("\tsignature: %s", + not_null (dbus_message_get_signature (m))); + g_test_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_test_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; + + dbus_connection_set_route_peer_messages (f->monitor, TRUE); + + 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) + { + if (g_queue_is_empty (&f->monitored)) + 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) + { + if (g_queue_is_empty (&f->monitored)) + 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) + { + if (g_queue_is_empty (&f->monitored)) + 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); + + /* regression test for + * https://bugs.freedesktop.org/show_bug.cgi?id=90952 */ + m = dbus_message_new_method_call (f->recipient_name, "/foo", + DBUS_INTERFACE_PEER, "Ping"); + 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", + DBUS_INTERFACE_PEER, "Ping", ""); + dbus_message_unref (m); + + m = g_queue_pop_head (&f->monitored); + assert_method_reply (m, f->recipient_name, f->sender_name, ""); + 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.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/Makefile.am b/test/name-test/Makefile.am index 8ed1e160..0922558f 100644 --- a/test/name-test/Makefile.am +++ b/test/name-test/Makefile.am @@ -1,25 +1,28 @@ -# Everything in this directory is statically-linked to libdbus-internal AM_CPPFLAGS = \ -I$(top_srcdir) \ + $(DBUS_STATIC_BUILD_CPPFLAGS) \ -DDBUS_COMPILATION \ - -DDBUS_STATIC_BUILD \ - -DDBUS_TEST_USE_INTERNAL \ $(NULL) # if assertions are enabled, improve backtraces AM_LDFLAGS = @R_DYNAMIC_LDFLAG@ +TEST_EXTENSIONS = .sh + +SH_LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) $(top_srcdir)/build-aux/tap-driver.sh +SH_LOG_COMPILER = $(SHELL) + ## note that TESTS has special meaning (stuff to use in make check) ## so if adding tests not to be run in make check, don't add them to ## TESTS if DBUS_ENABLE_EMBEDDED_TESTS -TESTS_ENVIRONMENT = \ - DBUS_TOP_BUILDDIR=@abs_top_builddir@ \ - DBUS_TOP_SRCDIR=@abs_top_srcdir@ \ - PYTHON=@PYTHON@ \ - DBUS_TEST_DATA=@abs_top_builddir@/test/data \ - DBUS_TEST_DAEMON=@abs_top_builddir@/bus/dbus-daemon$(EXEEXT) \ - XDG_RUNTIME_DIR=@abs_top_builddir@/test/XDG_RUNTIME_DIR \ +AM_TESTS_ENVIRONMENT = \ + export DBUS_TOP_BUILDDIR=@abs_top_builddir@; \ + export DBUS_TOP_SRCDIR=@abs_top_srcdir@; \ + export PYTHON=@PYTHON@; \ + export DBUS_TEST_DATA=@abs_top_builddir@/test/data; \ + export DBUS_TEST_DAEMON=@abs_top_builddir@/bus/dbus-daemon$(EXEEXT); \ + export XDG_RUNTIME_DIR=@abs_top_builddir@/test/XDG_RUNTIME_DIR; \ $(NULL) TESTS=run-test.sh run-test-systemserver.sh @@ -35,14 +38,14 @@ if DBUS_ENABLE_EMBEDDED_TESTS ## build even when not doing "make check" noinst_PROGRAMS=test-pending-call-dispatch test-pending-call-timeout test-threads-init test-ids test-shutdown test-privserver test-privserver-client test-autolaunch -test_pending_call_dispatch_LDADD=$(top_builddir)/dbus/libdbus-internal.la -test_pending_call_timeout_LDADD=$(top_builddir)/dbus/libdbus-internal.la -test_threads_init_LDADD=$(top_builddir)/dbus/libdbus-internal.la -test_ids_LDADD=$(top_builddir)/dbus/libdbus-internal.la +test_pending_call_dispatch_LDADD=$(top_builddir)/dbus/libdbus-1.la +test_pending_call_timeout_LDADD=$(top_builddir)/dbus/libdbus-1.la +test_threads_init_LDADD=$(top_builddir)/dbus/libdbus-1.la +test_ids_LDADD=$(top_builddir)/dbus/libdbus-1.la -test_shutdown_LDADD=../libdbus-testutils-internal.la -test_privserver_LDADD=../libdbus-testutils-internal.la -test_privserver_client_LDADD=../libdbus-testutils-internal.la -test_autolaunch_LDADD=../libdbus-testutils-internal.la +test_shutdown_LDADD=../libdbus-testutils.la +test_privserver_LDADD=../libdbus-testutils.la +test_privserver_client_LDADD=../libdbus-testutils.la +test_autolaunch_LDADD=../libdbus-testutils.la endif diff --git a/test/name-test/run-test-systemserver.sh b/test/name-test/run-test-systemserver.sh index 90c03723..9926cad6 100755 --- a/test/name-test/run-test-systemserver.sh +++ b/test/name-test/run-test-systemserver.sh @@ -1,14 +1,4 @@ #! /bin/sh -die() -{ - if ! test -z "$DBUS_SESSION_BUS_PID" ; then - echo "killing message bus "$DBUS_SESSION_BUS_PID >&2 - kill -9 $DBUS_SESSION_BUS_PID - fi - echo $SCRIPTNAME: $* >&2 - - exit 1 -} SCRIPTNAME=$0 MODE=$1 @@ -27,7 +17,7 @@ if test -z "$DBUS_TEST_NAME_IN_SYS_RUN_TEST"; then fi if test -n "$DBUS_TEST_MONITOR"; then - dbus-monitor --session & + dbus-monitor --session >&2 & fi XDG_RUNTIME_DIR="$DBUS_TOP_BUILDDIR"/test/XDG_RUNTIME_DIR @@ -35,22 +25,65 @@ test -d "$XDG_RUNTIME_DIR" || mkdir "$XDG_RUNTIME_DIR" chmod 0700 "$XDG_RUNTIME_DIR" export XDG_RUNTIME_DIR -echo "running test-expected-echo-fail" -${DBUS_TOP_BUILDDIR}/libtool --mode=execute $DEBUG $DBUS_TOP_BUILDDIR/tools/dbus-send --print-reply --dest=org.freedesktop.DBus.TestSuiteEchoService /org/freedesktop/TestSuite org.freedesktop.TestSuite.Echo string:hi >echo-error-output.tmp 2>&1 -if ! grep -q 'DBus.Error' echo-error-output.tmp; then - echo "Didn't get expected failure; output was:" - echo "=====" - cat echo-error-output.tmp - echo "=====" - exit 1 -fi +# Translate a command and exit status into TAP syntax. +# Usage: interpret_result $? description-of-test +# Uses global variable $test_num. +interpret_result () { + e="$1" + shift + case "$e" in + (0) + echo "ok $test_num $*" + ;; + (77) + echo "ok $test_num # SKIP $*" + ;; + (*) + echo "not ok $test_num $*" + ;; + esac + test_num=$(( $test_num + 1 )) +} -echo "running test echo signal" -if test "x$PYTHON" = "x:"; then - echo "Skipped test-echo-signal: Python interpreter not found" -elif ! $PYTHON $DBUS_TOP_SRCDIR/test/name-test/test-wait-for-echo.py; then - echo "Failed test-wait-for-echo" - exit 1 -fi +dbus_send_test () { + t="$1" + expected_exit="$2" + phrase="$3" + shift 3 + e=0 + echo "# running test $t" + "${DBUS_TOP_BUILDDIR}/libtool" --mode=execute $DEBUG "$DBUS_TOP_BUILDDIR/tools/dbus-send" "$@" > output.tmp 2>&1 || e=$? + if [ $e != $expected_exit ]; then + sed -e 's/^/# /' < output.tmp + interpret_result "1" "$t" "$@" "(expected exit status $expected_exit, got $e)" + return + fi + echo "# parsing results of test $t" + if ! grep -q "$phrase" output.tmp; then + sed -e 's/^/# /' < output.tmp + interpret_result "1" "$t" "$@" "(Did not see \"$phrase\" in output)" + return + fi + interpret_result "0" "$t" "$@" "(Saw \"$phrase\" in output as expected)" + rm -f output.tmp +} + +py_test () { + t="$1" + shift + if test "x$PYTHON" = "x:"; then + interpret_result 77 "$t" "(Python interpreter not found)" + else + e=0 + echo "# running test $t" + $PYTHON "$DBUS_TOP_SRCDIR/test/name-test/$t" "$@" >&2 || e=$? + interpret_result "$e" "$t" "$@" + fi +} + +test_num=1 +# TAP syntax: we plan to run 2 tests +echo "1..2" -exit 0 +dbus_send_test test-expected-echo-fail 1 DBus.Error --print-reply --dest=org.freedesktop.DBus.TestSuiteEchoService /org/freedesktop/TestSuite org.freedesktop.TestSuite.Echo string:hi +py_test test-wait-for-echo.py diff --git a/test/name-test/run-test.sh b/test/name-test/run-test.sh index 84379c4a..1e257a16 100755 --- a/test/name-test/run-test.sh +++ b/test/name-test/run-test.sh @@ -1,17 +1,5 @@ #! /bin/sh -die() -{ - if ! test -z "$DBUS_SESSION_BUS_PID" ; then - echo "killing message bus "$DBUS_SESSION_BUS_PID >&2 - kill -9 $DBUS_SESSION_BUS_PID - fi - echo $SCRIPTNAME: $* >&2 - - exit 1 -} - - SCRIPTNAME=$0 MODE=$1 @@ -27,7 +15,7 @@ if test -z "$DBUS_TEST_NAME_IN_RUN_TEST"; then fi if test -n "$DBUS_TEST_MONITOR"; then - dbus-monitor --session & + dbus-monitor --session >&2 & fi XDG_RUNTIME_DIR="$DBUS_TOP_BUILDDIR"/test/XDG_RUNTIME_DIR @@ -35,31 +23,60 @@ test -d "$XDG_RUNTIME_DIR" || mkdir "$XDG_RUNTIME_DIR" chmod 0700 "$XDG_RUNTIME_DIR" export XDG_RUNTIME_DIR -echo "running test-ids" -${DBUS_TOP_BUILDDIR}/libtool --mode=execute $DEBUG $DBUS_TOP_BUILDDIR/test/name-test/test-ids || die "test-ids failed" - -echo "running test-pending-call-dispatch" -${DBUS_TOP_BUILDDIR}/libtool --mode=execute $DEBUG $DBUS_TOP_BUILDDIR/test/name-test/test-pending-call-dispatch || die "test-pending-call-dispatch failed" - -echo "running test-pending-call-timeout" -${DBUS_TOP_BUILDDIR}/libtool --mode=execute $DEBUG $DBUS_TOP_BUILDDIR/test/name-test/test-pending-call-timeout || die "test-pending-call-timeout failed" - -echo "running test-threads-init" -${DBUS_TOP_BUILDDIR}/libtool --mode=execute $DEBUG $DBUS_TOP_BUILDDIR/test/name-test/test-threads-init || die "test-threads-init failed" - -echo "running test-privserver-client" -${DBUS_TOP_BUILDDIR}/libtool --mode=execute $DEBUG $DBUS_TOP_BUILDDIR/test/name-test/test-privserver-client || die "test-privserver-client failed" +# Translate a command and exit status into TAP syntax. +# Usage: interpret_result $? description-of-test +# Uses global variable $test_num. +interpret_result () { + e="$1" + shift + case "$e" in + (0) + echo "ok $test_num $*" + ;; + (77) + echo "ok $test_num # SKIP $*" + ;; + (*) + echo "not ok $test_num $*" + ;; + esac + test_num=$(( $test_num + 1 )) +} -echo "running test-shutdown" -${DBUS_TOP_BUILDDIR}/libtool --mode=execute $DEBUG $DBUS_TOP_BUILDDIR/test/name-test/test-shutdown || die "test-shutdown failed" +c_test () { + t="$1" + shift + e=0 + echo "# running test $t" + if ! "${DBUS_TOP_BUILDDIR}/libtool" --mode=execute $DEBUG "$DBUS_TOP_BUILDDIR/test/name-test/$t" "$@" >&2; then + e=$? + echo "# exit status $e" + fi + interpret_result "$e" "$t" "$@" +} -echo "running test activation forking" -if test "x$PYTHON" = "x:"; then - echo "Skipped test-activation-forking: Python interpreter not found" -elif ! $PYTHON $DBUS_TOP_SRCDIR/test/name-test/test-activation-forking.py; then - echo "Failed test-activation-forking" - exit 1 -fi +py_test () { + t="$1" + shift + if test "x$PYTHON" = "x:"; then + interpret_result 77 "$t" "(Python interpreter not found)" + else + e=0 + echo "# running test $t" + $PYTHON "$DBUS_TOP_SRCDIR/test/name-test/$t" "$@" >&2 || e=$? + interpret_result "$e" "$t" "$@" + fi +} -echo "running test-autolaunch" -${DBUS_TOP_BUILDDIR}/libtool --mode=execute $DEBUG $DBUS_TOP_BUILDDIR/test/name-test/test-autolaunch || die "test-autolaunch failed" +test_num=1 +# TAP test plan: we will run 8 tests +echo "1..8" + +c_test test-ids +c_test test-pending-call-dispatch +c_test test-pending-call-timeout +c_test test-threads-init +c_test test-privserver-client +c_test test-shutdown +py_test test-activation-forking.py +c_test test-autolaunch 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 984fde10..e275c167 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. @@ -155,7 +155,7 @@ test_connect (Fixture *f, while (f->left_server_conn == NULL) { - g_print ("."); + test_progress ('.'); test_main_context_iterate (f->ctx, TRUE); } @@ -168,7 +168,7 @@ test_connect (Fixture *f, while (f->right_server_conn == NULL) { - g_print ("."); + test_progress ('.'); test_main_context_iterate (f->ctx, TRUE); } @@ -210,7 +210,7 @@ test_relay (Fixture *f, while (g_queue_get_length (&f->messages) < 2) { - g_print ("."); + test_progress ('.'); test_main_context_iterate (f->ctx, TRUE); } @@ -317,8 +317,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..2a7366df --- /dev/null +++ b/test/sd-activation.c @@ -0,0 +1,349 @@ +/* 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); + + /* A malicious process tries to disrupt the activation. + * In a more realistic scenario this would be another parallel + * connection. */ + m = dbus_message_new_signal ("/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Activator", "ActivationFailure"); + if (!dbus_message_set_destination (m, "org.freedesktop.DBus")) + g_error ("OOM"); + + do + { + const char *unit = "dbus-com.example.SystemdActivatable2.service"; + const char *error_name = "com.example.Malice"; + const char *error_message = "I'm on yr bus, making yr activations fail"; + + 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); + + dbus_connection_send (f->caller, m, NULL); + dbus_message_unref (m); + + /* This is just to make sure that the malicious message has arrived and + * been processed by the dbus-daemon, i.e. @caller won the race + * with @activated. */ + take_well_known_name (f, f->caller, "com.example.Sync"); + + /* 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/shell-test.c b/test/shell-test.c index d1dc5b5b..61280d68 100644 --- a/test/shell-test.c +++ b/test/shell-test.c @@ -9,8 +9,13 @@ #include <dbus/dbus-string.h> #include <dbus/dbus-sysdeps.h> +static int test_num = 0; +static int num_failed = 0; + static dbus_bool_t -test_command_line (const char *arg1, ...) +test_command_line_internal (dbus_bool_t should_work, + const char *arg1, + va_list var_args) { int i, original_argc, shell_argc; char **shell_argv; @@ -18,10 +23,8 @@ test_command_line (const char *arg1, ...) char *command_line, *tmp; DBusString str; DBusList *list = NULL, *node; - va_list var_args; DBusError error; - va_start (var_args, arg1); _dbus_list_append (&list, (char *)arg1); do { @@ -30,7 +33,6 @@ test_command_line (const char *arg1, ...) break; _dbus_list_append (&list, tmp); } while (tmp); - va_end (var_args); original_argc = _dbus_list_get_length (&list); original_argv = dbus_new (char *, original_argc); @@ -46,23 +48,28 @@ test_command_line (const char *arg1, ...) _dbus_list_clear (&list); command_line = _dbus_string_get_data (&str); - printf ("\n\nTesting command line '%s'\n", command_line); + printf ("# Testing command line '%s'\n", command_line); dbus_error_init (&error); if (!_dbus_shell_parse_argv (command_line, &shell_argc, &shell_argv, &error)) { - fprintf (stderr, "Error parsing command line: %s\n", error.message ? error.message : ""); - return FALSE; + printf ("# Error%s parsing command line: %s\n", + should_work ? "" : " (as expected)", + error.message ? error.message : ""); + dbus_free (original_argv); + return !should_work; } else { if (shell_argc != original_argc) { - printf ("Number of arguments returned (%d) don't match original (%d)\n", + printf ("# Number of arguments returned (%d) don't match original (%d)\n", shell_argc, original_argc); + dbus_free (original_argv); + dbus_free_string_array (shell_argv); return FALSE; } - printf ("Number of arguments: %d\n", shell_argc); + printf ("# Number of arguments: %d\n", shell_argc); for (i = 0; i < shell_argc; i++) { char *unquoted; @@ -74,6 +81,8 @@ test_command_line (const char *arg1, ...) printf ("Position %d, returned argument (%s) does not match original (%s)\n", i, shell_argv[i], unquoted); dbus_free (unquoted); + dbus_free (original_argv); + dbus_free_string_array (shell_argv); return FALSE; } dbus_free (unquoted); @@ -83,27 +92,79 @@ test_command_line (const char *arg1, ...) dbus_free_string_array (shell_argv); } - + _dbus_string_free (&str); - + dbus_free (original_argv); + + if (!should_work) + { + printf ("# Expected an error\n"); + return FALSE; + } + return TRUE; } +static void +test_command_line (const char *arg1, ...) +{ + va_list var_args; + + va_start (var_args, arg1); + + if (test_command_line_internal (TRUE, arg1, var_args)) + { + printf ("ok %d\n", ++test_num); + } + else + { + printf ("not ok %d\n", ++test_num); + num_failed++; + } + + va_end (var_args); +} + +static void +test_command_line_fails (const char *arg1, ...) +{ + va_list var_args; + + va_start (var_args, arg1); + + if (test_command_line_internal (FALSE, arg1, var_args)) + { + printf ("ok %d\n", ++test_num); + } + else + { + printf ("not ok %d\n", ++test_num); + num_failed++; + } + + va_end (var_args); +} + +/* This test outputs TAP syntax: http://testanything.org/ */ int main (int argc, char **argv) { - if (!test_command_line ("command", "-s", "--force-shutdown", "\"a string\"", "123", NULL) - || !test_command_line ("command", "-s", NULL) - || !test_command_line ("/opt/gnome/bin/service-start", NULL) - || !test_command_line ("grep", "-l", "-r", "-i", "'whatever'", "files*.c", NULL) - || !test_command_line ("/home/boston/johnp/devel-local/dbus/test/test-segfault", NULL) - || !test_command_line ("ls", "-l", "-a", "--colors", _dbus_get_tmpdir(), NULL) - || !test_command_line ("rsync-to-server", NULL) - || !test_command_line ("test-segfault", "--no-segfault", NULL) - || !test_command_line ("evolution", "mailto:pepe@cuco.com", NULL) - || !test_command_line ("run", "\"a \n multiline\"", NULL) - || test_command_line ("ls", "\"a wrong string'", NULL) /* invalid command line */ ) - return -1; - - return 0; + test_command_line ("command", "-s", "--force-shutdown", "\"a string\"", "123", NULL); + test_command_line ("command", "-s", NULL); + test_command_line ("/opt/gnome/bin/service-start", NULL); + test_command_line ("grep", "-l", "-r", "-i", "'whatever'", "files*.c", NULL); + test_command_line ("/home/boston/johnp/devel-local/dbus/test/test-segfault", NULL); + test_command_line ("ls", "-l", "-a", "--colors", _dbus_get_tmpdir(), NULL); + test_command_line ("rsync-to-server", NULL); + test_command_line ("test-segfault", "--no-segfault", NULL); + test_command_line ("evolution", "mailto:pepe@cuco.com", NULL); + test_command_line ("run", "\"a \n multiline\"", NULL); + test_command_line_fails ("ls", "\"a wrong string'", NULL); + + /* Tell the TAP driver that we have done all the tests we plan to do. + * This is how it can distinguish between an unexpected exit and + * successful completion. */ + printf ("1..%d\n", test_num); + + return (num_failed != 0); } 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/tap-test.sh.in b/test/tap-test.sh.in new file mode 100644 index 00000000..743cdf39 --- /dev/null +++ b/test/tap-test.sh.in @@ -0,0 +1,32 @@ +#!/bin/sh + +# Wrapper to make an Automake-style test output TAP syntax: +# +# - arbitrary stdout/stderr is sent to stderr where it will not be +# interpreted as TAP +# - it is treated as a single test-case +# - exit 77 is a skip +# - exit 0 is a pass +# - anything else is a failure +# +# Usage: use sed to replace @RUN@ with the shell command-line to be run. + +set -e + +# we plan to do 1 test-case +echo "1..1" + +e=0 +@RUN@ >&2 || e=$? + +case "$e" in + (0) + echo "ok 1 @RUN@" + ;; + (77) + echo "ok 1 # SKIP @RUN@" + ;; + (*) + echo "not ok 1 @RUN@ (exit status $e)" + ;; +esac diff --git a/test/test-service.c b/test/test-service.c index 7181fa38..c0bd2c60 100644 --- a/test/test-service.c +++ b/test/test-service.c @@ -400,20 +400,18 @@ main (int argc, int result; DBusConnection *connection; const char *name; - dbus_bool_t do_fork; - +#ifndef DBUS_WIN + dbus_bool_t do_fork = FALSE; +#endif if (argc != 3) { name = "org.freedesktop.DBus.TestSuiteEchoService"; - do_fork = FALSE; } else { name = argv[1]; #ifndef DBUS_WIN do_fork = strcmp (argv[2], "fork") == 0; -#else - do_fork = FALSE; #endif } diff --git a/test/test-utils-glib.c b/test/test-utils-glib.c new file mode 100644 index 00000000..a40c30b7 --- /dev/null +++ b/test/test-utils-glib.c @@ -0,0 +1,452 @@ +/* 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> + +#ifdef G_OS_WIN +# define isatty(x) _isatty(x) +#endif + +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_test_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) + { + gchar *message = g_strdup_printf ("user '%s' does not exist", + DBUS_USER); + + g_test_skip (message); + g_free (message); + return NULL; + } + + break; + + case TEST_USER_OTHER: + pwd = getpwnam (DBUS_TEST_USER); + + if (pwd == NULL) + { + gchar *message = g_strdup_printf ("user '%s' does not exist", + DBUS_TEST_USER); + + g_test_skip (message); + g_free (message); + return NULL; + } + + break; + + default: + g_assert_not_reached (); + } +#else + g_test_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_test_message ("set DBUS_TEST_DATA to a directory containing %s", + config_file); + g_test_skip ("DBUS_TEST_DATA not set"); + return NULL; + } + + arg = g_strdup_printf ( + "--config-file=%s/%s", + g_getenv ("DBUS_TEST_DATA"), config_file); + } + else if (g_getenv ("DBUS_TEST_DATADIR") != NULL) + { + arg = g_strdup_printf ("--config-file=%s/dbus-1/session.conf", + g_getenv ("DBUS_TEST_DATADIR")); + } + 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_test_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_test_message ("not uid 0 (ruid=%ld euid=%ld suid=%ld)", + (unsigned long) ruid, (unsigned long) euid, (unsigned long) suid); + g_test_skip ("not uid 0"); + return NULL; + } + + pwd = getpwnam (username); + + if (pwd == NULL) + { + g_test_message ("getpwnam(\"%s\"): %s", username, g_strerror (errno)); + g_test_skip ("not uid 0"); + 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_test_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; +} + +#ifdef G_OS_UNIX +static void +wrap_abort (int signal) +{ + abort (); +} +#endif + +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); + + /* Get a core dump from the SIGALRM. */ + { + struct sigaction act = { }; + + act.sa_handler = wrap_abort; + + sigaction (SIGALRM, &act, NULL); + } +#endif +} + +void +test_progress (char symbol) +{ + if (g_test_verbose () && isatty (1)) + g_print ("%c", symbol); +} diff --git a/test/test-utils-glib.h b/test/test-utils-glib.h new file mode 100644 index 00000000..acacee0a --- /dev/null +++ b/test/test-utils-glib.h @@ -0,0 +1,94 @@ +/* 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); + +void test_progress (char symbol); + +#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_test_message ("SKIP: %s", s); +} +#endif + +#endif diff --git a/test/test-utils.c b/test/test-utils.c index 9a4f3584..cb6cf1fb 100644 --- a/test/test-utils.c +++ b/test/test-utils.c @@ -1,13 +1,6 @@ #include <config.h> #include "test-utils.h" -#ifndef DBUS_TEST_USE_INTERNAL -# include <dbus/dbus.h> -# include <dbus/dbus-glib-lowlevel.h> -#endif - -#ifdef DBUS_TEST_USE_INTERNAL - typedef struct { DBusLoop *loop; @@ -104,13 +97,10 @@ cdata_new (DBusLoop *loop, return cd; } -#endif /* DBUS_TEST_USE_INTERNAL */ - dbus_bool_t test_connection_setup (TestMainContext *ctx, DBusConnection *connection) { -#ifdef DBUS_TEST_USE_INTERNAL DBusLoop *loop = ctx; CData *cd; @@ -159,12 +149,6 @@ test_connection_setup (TestMainContext *ctx, dbus_connection_set_timeout_functions (connection, NULL, NULL, NULL, NULL, NULL); return FALSE; -#else /* !DBUS_TEST_USE_INTERNAL */ - - dbus_connection_setup_with_g_main (connection, ctx); - return TRUE; - -#endif /* !DBUS_TEST_USE_INTERNAL */ } static void @@ -195,8 +179,6 @@ test_connection_shutdown (TestMainContext *ctx, dbus_connection_set_dispatch_status_function (connection, NULL, NULL, NULL); } -#ifdef DBUS_TEST_USE_INTERNAL - typedef struct { DBusLoop *loop; @@ -278,13 +260,10 @@ remove_server_timeout (DBusTimeout *timeout, _dbus_loop_remove_timeout (context->loop, timeout); } -#endif /* DBUS_TEST_USE_INTERNAL */ - dbus_bool_t test_server_setup (TestMainContext *ctx, DBusServer *server) { -#ifdef DBUS_TEST_USE_INTERNAL DBusLoop *loop = ctx; ServerData *sd; @@ -323,13 +302,6 @@ test_server_setup (TestMainContext *ctx, test_server_shutdown (loop, server); return FALSE; - -#else /* !DBUS_TEST_USE_INTERNAL */ - - dbus_server_setup_with_g_main (server, ctx); - return TRUE; - -#endif /* !DBUS_TEST_USE_INTERNAL */ } void @@ -354,39 +326,32 @@ test_server_shutdown (TestMainContext *ctx, TestMainContext * test_main_context_get (void) { -#ifdef DBUS_TEST_USE_INTERNAL return _dbus_loop_new (); -#else - /* I suspect dbus-glib relies the default main context in some places */ - return g_main_context_ref (g_main_context_default ()); -#endif } TestMainContext * test_main_context_ref (TestMainContext *ctx) { -#ifdef DBUS_TEST_USE_INTERNAL return _dbus_loop_ref (ctx); -#else - return g_main_context_ref (ctx); -#endif } void test_main_context_unref (TestMainContext *ctx) { -#ifdef DBUS_TEST_USE_INTERNAL _dbus_loop_unref (ctx); -#else - g_main_context_unref (ctx); -#endif } void test_main_context_iterate (TestMainContext *ctx, dbus_bool_t may_block) { -#ifdef DBUS_TEST_USE_INTERNAL _dbus_loop_iterate (ctx, may_block); -#else - 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); + _dbus_assert (*message_p != NULL); } diff --git a/test/test-utils.h b/test/test-utils.h index 0d3f3690..39fae77b 100644 --- a/test/test-utils.h +++ b/test/test-utils.h @@ -6,18 +6,9 @@ #include <dbus/dbus.h> -#ifdef DBUS_TEST_USE_INTERNAL - -# include <dbus/dbus-mainloop.h> -# include <dbus/dbus-internals.h> - typedef DBusLoop TestMainContext; - -#else /* !DBUS_TEST_USE_INTERNAL */ - -# include <glib.h> - typedef GMainContext TestMainContext; - -#endif /* !DBUS_TEST_USE_INTERNAL */ +#include <dbus/dbus-mainloop.h> +#include <dbus/dbus-internals.h> +typedef DBusLoop TestMainContext; TestMainContext *test_main_context_get (void); TestMainContext *test_main_context_ref (TestMainContext *ctx); @@ -34,5 +25,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 (); +} |