diff options
Diffstat (limited to 'bus')
55 files changed, 32403 insertions, 0 deletions
diff --git a/bus/Makefile.am b/bus/Makefile.am new file mode 100644 index 00000000..ad49e6dd --- /dev/null +++ b/bus/Makefile.am @@ -0,0 +1,234 @@ + +configdir=$(sysconfdir)/dbus-1 + +INCLUDES=-I$(top_srcdir) $(DBUS_BUS_CFLAGS) @PIE_CFLAGS@ \ + -DDBUS_SYSTEM_CONFIG_FILE=\""$(configdir)/system.conf"\" \ + -DDAEMON_NAME=\"dbus-daemon\" -DDBUS_COMPILATION + +EFENCE= + +CONFIG_IN_FILES= \ + session.conf.in \ + system.conf.in + +config_DATA= \ + session.conf \ + system.conf + +if DBUS_USE_LIBXML +XML_SOURCES=config-loader-libxml.c +endif +if DBUS_USE_EXPAT +XML_SOURCES=config-loader-expat.c +endif + +if DBUS_BUS_ENABLE_KQUEUE +DIR_WATCH_SOURCE=dir-watch-kqueue.c +else +if DBUS_BUS_ENABLE_INOTIFY +DIR_WATCH_SOURCE=dir-watch-inotify.c +else +if DBUS_BUS_ENABLE_DNOTIFY_ON_LINUX +DIR_WATCH_SOURCE=dir-watch-dnotify.c +else +DIR_WATCH_SOURCE=dir-watch-default.c +endif +endif +endif + +BUS_SOURCES= \ + activation.c \ + activation.h \ + activation-exit-codes.h \ + bus.c \ + bus.h \ + config-parser.c \ + config-parser.h \ + config-parser-common.c \ + config-parser-common.h \ + connection.c \ + connection.h \ + desktop-file.c \ + desktop-file.h \ + $(DIR_WATCH_SOURCE) \ + dir-watch.h \ + dispatch.c \ + dispatch.h \ + driver.c \ + driver.h \ + expirelist.c \ + expirelist.h \ + policy.c \ + policy.h \ + selinux.h \ + selinux.c \ + services.c \ + services.h \ + signals.c \ + signals.h \ + test.c \ + test.h \ + utils.c \ + utils.h \ + $(XML_SOURCES) + +dbus_daemon_SOURCES= \ + $(BUS_SOURCES) \ + main.c + +dbus_daemon_LDADD= \ + $(EFENCE) \ + $(top_builddir)/dbus/libdbus-convenience.la \ + $(DBUS_BUS_LIBS) + +dbus_daemon_LDFLAGS=@R_DYNAMIC_LDFLAG@ @SECTION_LDFLAGS@ @PIE_LDFLAGS@ + +LAUNCH_HELPER_SOURCES= \ + $(XML_SOURCES) \ + config-parser-common.c \ + config-parser-common.h \ + config-parser-trivial.c \ + config-parser-trivial.h \ + desktop-file.c \ + desktop-file.h \ + utils.c \ + utils.h \ + activation-exit-codes.h \ + activation-helper.h \ + activation-helper.c + +## This is the installed launch helper with the setuid checks +dbus_daemon_launch_helper_SOURCES= \ + activation-helper-bin.c \ + $(LAUNCH_HELPER_SOURCES) + +dbus_daemon_launch_helper_LDADD= \ + $(top_builddir)/dbus/libdbus-convenience.la \ + $(DBUS_LAUNCHER_LIBS) + +dbus_daemon_launch_helper_LDFLAGS=@R_DYNAMIC_LDFLAG@ @SECTION_LDFLAGS@ + +## we build another binary so we can do the launch testing without root privs. +## DO NOT INSTALL THIS FILE +dbus_daemon_launch_helper_test_SOURCES= \ + activation-helper-bin.c \ + $(LAUNCH_HELPER_SOURCES) + +dbus_daemon_launch_helper_test_LDADD= \ + $(top_builddir)/dbus/libdbus-convenience.la \ + $(DBUS_LAUNCHER_LIBS) + +dbus_daemon_launch_helper_test_LDFLAGS=@R_DYNAMIC_LDFLAG@ @SECTION_LDFLAGS@ +dbus_daemon_launch_helper_test_CPPFLAGS= \ + -DACTIVATION_LAUNCHER_TEST + +## we build yet another binary so we can do the OOM tests +## DO NOT INSTALL THIS FILE +bus_test_launch_helper_SOURCES= \ + test-launch-helper.c \ + $(LAUNCH_HELPER_SOURCES) + +bus_test_launch_helper_LDADD= \ + $(top_builddir)/dbus/libdbus-convenience.la \ + $(DBUS_LAUNCHER_LIBS) + +bus_test_launch_helper_LDFLAGS=@R_DYNAMIC_LDFLAG@ @SECTION_LDFLAGS@ +bus_test_launch_helper_CPPFLAGS= \ + -DACTIVATION_LAUNCHER_TEST \ + -DACTIVATION_LAUNCHER_DO_OOM + +## 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_BUILD_TESTS +TESTS_ENVIRONMENT=DBUS_TEST_DATA=$(top_builddir)/test/data DBUS_TEST_HOMEDIR=$(top_builddir)/dbus DBUS_FATAL_WARNINGS=1 DBUS_BLOCK_ON_ABORT=1 +TESTS=bus-test bus-test-system bus-test-launch-helper +else +TESTS= +endif + +## we use noinst_PROGRAMS not check_PROGRAMS so that we build +## even when not doing "make check" +noinst_PROGRAMS=$(TESTS) dbus-daemon dbus-daemon-launch-helper-test dbus-daemon-launch-helper + +bus_test_system_SOURCES= \ + $(XML_SOURCES) \ + config-parser-common.c \ + config-parser-common.h \ + config-parser-trivial.c \ + config-parser-trivial.h \ + utils.c \ + utils.h \ + test-system.c + +bus_test_system_LDADD=$(top_builddir)/dbus/libdbus-convenience.la $(DBUS_BUS_LIBS) +bus_test_system_LDFLAGS=@R_DYNAMIC_LDFLAG@ + +bus_test_SOURCES= \ + $(BUS_SOURCES) \ + test-main.c + +bus_test_LDADD=$(top_builddir)/dbus/libdbus-convenience.la $(DBUS_BUS_LIBS) +bus_test_LDFLAGS=@R_DYNAMIC_LDFLAG@ + +## mop up the gcov files +clean-local: + /bin/rm *.bb *.bbg *.da *.gcov || true + +uninstall-hook: + rm -f $(DESTDIR)$(DBUS_DAEMONDIR)/dbus-daemon + rm -f $(DESTDIR)$(libexecdir)/dbus-daemon-launch-helper + +install-data-hook: + if test '!' -d $(DESTDIR)$(DBUS_DAEMONDIR); then \ + $(mkinstalldirs) $(DESTDIR)$(DBUS_DAEMONDIR); \ + chmod 755 $(DESTDIR)$(DBUS_DAEMONDIR); \ + fi + $(INSTALL_PROGRAM) dbus-daemon $(DESTDIR)$(DBUS_DAEMONDIR) + $(mkinstalldirs) $(DESTDIR)$(localstatedir)/run/dbus + $(mkinstalldirs) $(DESTDIR)$(configdir)/system.d + $(mkinstalldirs) $(DESTDIR)$(configdir)/session.d + $(mkinstalldirs) $(DESTDIR)$(datadir)/dbus-1/services + $(mkinstalldirs) $(DESTDIR)$(datadir)/dbus-1/system-services + $(mkinstalldirs) $(DESTDIR)$(libexecdir)/dbus-1 + $(INSTALL_PROGRAM) dbus-daemon-launch-helper $(DESTDIR)$(libexecdir) + if test `id -u` -eq 0; then \ + chown root:$(DBUS_USER) $(DESTDIR)$(libexecdir)/dbus-daemon-launch-helper; \ + chmod 4750 $(DESTDIR)$(libexecdir)/dbus-daemon-launch-helper; \ + else \ + echo "Not installing $(DESTDIR)$(libexecdir)/dbus-daemon-launch-helper binary setuid!"; \ + echo "You'll need to manually set permissions to root:$(DBUS_USER) and permissions 4750"; \ + fi + +#### Init scripts fun +SCRIPT_IN_FILES=messagebus.in \ + rc.messagebus.in + +## Red Hat start +if DBUS_INIT_SCRIPTS_RED_HAT + +initddir=$(sysconfdir)/rc.d/init.d + +initd_SCRIPTS= \ + messagebus + +endif + ## Red Hat end + +## Slackware start +if DBUS_INIT_SCRIPTS_SLACKWARE + +initddir=$(sysconfdir)/rc.d/ + +initd_SCRIPTS= \ + rc.messagebus + +endif +## Slackware end + +MAN_IN_FILES=dbus-daemon.1.in +man_MANS = dbus-daemon.1 + +#### Extra dist + +EXTRA_DIST=$(CONFIG_IN_FILES) $(SCRIPT_IN_FILES) $(man_MANS) $(MAN_IN_FILES) diff --git a/bus/Makefile.in b/bus/Makefile.in new file mode 100644 index 00000000..13a599af --- /dev/null +++ b/bus/Makefile.in @@ -0,0 +1,1458 @@ +# Makefile.in generated by automake 1.11.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, +# Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + + +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +@DBUS_BUILD_TESTS_TRUE@TESTS = bus-test$(EXEEXT) \ +@DBUS_BUILD_TESTS_TRUE@ bus-test-system$(EXEEXT) \ +@DBUS_BUILD_TESTS_TRUE@ bus-test-launch-helper$(EXEEXT) +noinst_PROGRAMS = $(am__EXEEXT_1) dbus-daemon$(EXEEXT) \ + dbus-daemon-launch-helper-test$(EXEEXT) \ + dbus-daemon-launch-helper$(EXEEXT) +subdir = bus +DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in \ + $(srcdir)/dbus-daemon.1.in $(srcdir)/messagebus.in \ + $(srcdir)/rc.messagebus.in $(srcdir)/session.conf.in \ + $(srcdir)/system.conf.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/acinclude.m4 \ + $(top_srcdir)/configure.in +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = system.conf session.conf messagebus rc.messagebus \ + dbus-daemon.1 +CONFIG_CLEAN_VPATH_FILES = +@DBUS_BUILD_TESTS_TRUE@am__EXEEXT_1 = bus-test$(EXEEXT) \ +@DBUS_BUILD_TESTS_TRUE@ bus-test-system$(EXEEXT) \ +@DBUS_BUILD_TESTS_TRUE@ bus-test-launch-helper$(EXEEXT) +PROGRAMS = $(noinst_PROGRAMS) +am__bus_test_SOURCES_DIST = activation.c activation.h \ + activation-exit-codes.h bus.c bus.h config-parser.c \ + config-parser.h config-parser-common.c config-parser-common.h \ + connection.c connection.h desktop-file.c desktop-file.h \ + dir-watch-default.c dir-watch-dnotify.c dir-watch-inotify.c \ + dir-watch-kqueue.c dir-watch.h dispatch.c dispatch.h driver.c \ + driver.h expirelist.c expirelist.h policy.c policy.h selinux.h \ + selinux.c services.c services.h signals.c signals.h test.c \ + test.h utils.c utils.h config-loader-expat.c \ + config-loader-libxml.c test-main.c +@DBUS_BUS_ENABLE_DNOTIFY_ON_LINUX_FALSE@@DBUS_BUS_ENABLE_INOTIFY_FALSE@@DBUS_BUS_ENABLE_KQUEUE_FALSE@am__objects_1 = dir-watch-default.$(OBJEXT) +@DBUS_BUS_ENABLE_DNOTIFY_ON_LINUX_TRUE@@DBUS_BUS_ENABLE_INOTIFY_FALSE@@DBUS_BUS_ENABLE_KQUEUE_FALSE@am__objects_1 = dir-watch-dnotify.$(OBJEXT) +@DBUS_BUS_ENABLE_INOTIFY_TRUE@@DBUS_BUS_ENABLE_KQUEUE_FALSE@am__objects_1 = dir-watch-inotify.$(OBJEXT) +@DBUS_BUS_ENABLE_KQUEUE_TRUE@am__objects_1 = \ +@DBUS_BUS_ENABLE_KQUEUE_TRUE@ dir-watch-kqueue.$(OBJEXT) +@DBUS_USE_EXPAT_FALSE@@DBUS_USE_LIBXML_TRUE@am__objects_2 = config-loader-libxml.$(OBJEXT) +@DBUS_USE_EXPAT_TRUE@am__objects_2 = config-loader-expat.$(OBJEXT) +am__objects_3 = activation.$(OBJEXT) bus.$(OBJEXT) \ + config-parser.$(OBJEXT) config-parser-common.$(OBJEXT) \ + connection.$(OBJEXT) desktop-file.$(OBJEXT) $(am__objects_1) \ + dispatch.$(OBJEXT) driver.$(OBJEXT) expirelist.$(OBJEXT) \ + policy.$(OBJEXT) selinux.$(OBJEXT) services.$(OBJEXT) \ + signals.$(OBJEXT) test.$(OBJEXT) utils.$(OBJEXT) \ + $(am__objects_2) +am_bus_test_OBJECTS = $(am__objects_3) test-main.$(OBJEXT) +bus_test_OBJECTS = $(am_bus_test_OBJECTS) +am__DEPENDENCIES_1 = +bus_test_DEPENDENCIES = $(top_builddir)/dbus/libdbus-convenience.la \ + $(am__DEPENDENCIES_1) +AM_V_lt = $(am__v_lt_$(V)) +am__v_lt_ = $(am__v_lt_$(AM_DEFAULT_VERBOSITY)) +am__v_lt_0 = --silent +bus_test_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(bus_test_LDFLAGS) $(LDFLAGS) -o $@ +am__bus_test_launch_helper_SOURCES_DIST = test-launch-helper.c \ + config-loader-expat.c config-loader-libxml.c \ + config-parser-common.c config-parser-common.h \ + config-parser-trivial.c config-parser-trivial.h desktop-file.c \ + desktop-file.h utils.c utils.h activation-exit-codes.h \ + activation-helper.h activation-helper.c +@DBUS_USE_EXPAT_FALSE@@DBUS_USE_LIBXML_TRUE@am__objects_4 = bus_test_launch_helper-config-loader-libxml.$(OBJEXT) +@DBUS_USE_EXPAT_TRUE@am__objects_4 = bus_test_launch_helper-config-loader-expat.$(OBJEXT) +am__objects_5 = $(am__objects_4) \ + bus_test_launch_helper-config-parser-common.$(OBJEXT) \ + bus_test_launch_helper-config-parser-trivial.$(OBJEXT) \ + bus_test_launch_helper-desktop-file.$(OBJEXT) \ + bus_test_launch_helper-utils.$(OBJEXT) \ + bus_test_launch_helper-activation-helper.$(OBJEXT) +am_bus_test_launch_helper_OBJECTS = \ + bus_test_launch_helper-test-launch-helper.$(OBJEXT) \ + $(am__objects_5) +bus_test_launch_helper_OBJECTS = $(am_bus_test_launch_helper_OBJECTS) +bus_test_launch_helper_DEPENDENCIES = \ + $(top_builddir)/dbus/libdbus-convenience.la \ + $(am__DEPENDENCIES_1) +bus_test_launch_helper_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(bus_test_launch_helper_LDFLAGS) \ + $(LDFLAGS) -o $@ +am__bus_test_system_SOURCES_DIST = config-loader-expat.c \ + config-loader-libxml.c config-parser-common.c \ + config-parser-common.h config-parser-trivial.c \ + config-parser-trivial.h utils.c utils.h test-system.c +am_bus_test_system_OBJECTS = $(am__objects_2) \ + config-parser-common.$(OBJEXT) config-parser-trivial.$(OBJEXT) \ + utils.$(OBJEXT) test-system.$(OBJEXT) +bus_test_system_OBJECTS = $(am_bus_test_system_OBJECTS) +bus_test_system_DEPENDENCIES = \ + $(top_builddir)/dbus/libdbus-convenience.la \ + $(am__DEPENDENCIES_1) +bus_test_system_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(bus_test_system_LDFLAGS) $(LDFLAGS) \ + -o $@ +am__dbus_daemon_SOURCES_DIST = activation.c activation.h \ + activation-exit-codes.h bus.c bus.h config-parser.c \ + config-parser.h config-parser-common.c config-parser-common.h \ + connection.c connection.h desktop-file.c desktop-file.h \ + dir-watch-default.c dir-watch-dnotify.c dir-watch-inotify.c \ + dir-watch-kqueue.c dir-watch.h dispatch.c dispatch.h driver.c \ + driver.h expirelist.c expirelist.h policy.c policy.h selinux.h \ + selinux.c services.c services.h signals.c signals.h test.c \ + test.h utils.c utils.h config-loader-expat.c \ + config-loader-libxml.c main.c +am_dbus_daemon_OBJECTS = $(am__objects_3) main.$(OBJEXT) +dbus_daemon_OBJECTS = $(am_dbus_daemon_OBJECTS) +dbus_daemon_DEPENDENCIES = $(am__DEPENDENCIES_1) \ + $(top_builddir)/dbus/libdbus-convenience.la \ + $(am__DEPENDENCIES_1) +dbus_daemon_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(dbus_daemon_LDFLAGS) $(LDFLAGS) -o $@ +am__dbus_daemon_launch_helper_SOURCES_DIST = activation-helper-bin.c \ + config-loader-expat.c config-loader-libxml.c \ + config-parser-common.c config-parser-common.h \ + config-parser-trivial.c config-parser-trivial.h desktop-file.c \ + desktop-file.h utils.c utils.h activation-exit-codes.h \ + activation-helper.h activation-helper.c +am__objects_6 = $(am__objects_2) config-parser-common.$(OBJEXT) \ + config-parser-trivial.$(OBJEXT) desktop-file.$(OBJEXT) \ + utils.$(OBJEXT) activation-helper.$(OBJEXT) +am_dbus_daemon_launch_helper_OBJECTS = \ + activation-helper-bin.$(OBJEXT) $(am__objects_6) +dbus_daemon_launch_helper_OBJECTS = \ + $(am_dbus_daemon_launch_helper_OBJECTS) +dbus_daemon_launch_helper_DEPENDENCIES = \ + $(top_builddir)/dbus/libdbus-convenience.la \ + $(am__DEPENDENCIES_1) +dbus_daemon_launch_helper_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(dbus_daemon_launch_helper_LDFLAGS) \ + $(LDFLAGS) -o $@ +am__dbus_daemon_launch_helper_test_SOURCES_DIST = \ + activation-helper-bin.c config-loader-expat.c \ + config-loader-libxml.c config-parser-common.c \ + config-parser-common.h config-parser-trivial.c \ + config-parser-trivial.h desktop-file.c desktop-file.h utils.c \ + utils.h activation-exit-codes.h activation-helper.h \ + activation-helper.c +@DBUS_USE_EXPAT_FALSE@@DBUS_USE_LIBXML_TRUE@am__objects_7 = dbus_daemon_launch_helper_test-config-loader-libxml.$(OBJEXT) +@DBUS_USE_EXPAT_TRUE@am__objects_7 = dbus_daemon_launch_helper_test-config-loader-expat.$(OBJEXT) +am__objects_8 = $(am__objects_7) \ + dbus_daemon_launch_helper_test-config-parser-common.$(OBJEXT) \ + dbus_daemon_launch_helper_test-config-parser-trivial.$(OBJEXT) \ + dbus_daemon_launch_helper_test-desktop-file.$(OBJEXT) \ + dbus_daemon_launch_helper_test-utils.$(OBJEXT) \ + dbus_daemon_launch_helper_test-activation-helper.$(OBJEXT) +am_dbus_daemon_launch_helper_test_OBJECTS = dbus_daemon_launch_helper_test-activation-helper-bin.$(OBJEXT) \ + $(am__objects_8) +dbus_daemon_launch_helper_test_OBJECTS = \ + $(am_dbus_daemon_launch_helper_test_OBJECTS) +dbus_daemon_launch_helper_test_DEPENDENCIES = \ + $(top_builddir)/dbus/libdbus-convenience.la \ + $(am__DEPENDENCIES_1) +dbus_daemon_launch_helper_test_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) \ + $(dbus_daemon_launch_helper_test_LDFLAGS) $(LDFLAGS) -o $@ +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__installdirs = "$(DESTDIR)$(initddir)" "$(DESTDIR)$(man1dir)" \ + "$(DESTDIR)$(configdir)" +SCRIPTS = $(initd_SCRIPTS) +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_$(V)) +am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY)) +am__v_CC_0 = @echo " CC " $@; +AM_V_at = $(am__v_at_$(V)) +am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY)) +am__v_at_0 = @ +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_$(V)) +am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY)) +am__v_CCLD_0 = @echo " CCLD " $@; +AM_V_GEN = $(am__v_GEN_$(V)) +am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY)) +am__v_GEN_0 = @echo " GEN " $@; +SOURCES = $(bus_test_SOURCES) $(bus_test_launch_helper_SOURCES) \ + $(bus_test_system_SOURCES) $(dbus_daemon_SOURCES) \ + $(dbus_daemon_launch_helper_SOURCES) \ + $(dbus_daemon_launch_helper_test_SOURCES) +DIST_SOURCES = $(am__bus_test_SOURCES_DIST) \ + $(am__bus_test_launch_helper_SOURCES_DIST) \ + $(am__bus_test_system_SOURCES_DIST) \ + $(am__dbus_daemon_SOURCES_DIST) \ + $(am__dbus_daemon_launch_helper_SOURCES_DIST) \ + $(am__dbus_daemon_launch_helper_test_SOURCES_DIST) +man1dir = $(mandir)/man1 +NROFF = nroff +MANS = $(man_MANS) +DATA = $(config_DATA) +ETAGS = etags +CTAGS = ctags +am__tty_colors = \ +red=; grn=; lgn=; blu=; std= +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DBUS_BINDIR = @DBUS_BINDIR@ +DBUS_BUS_CFLAGS = @DBUS_BUS_CFLAGS@ +DBUS_BUS_LIBS = @DBUS_BUS_LIBS@ +DBUS_CLIENT_CFLAGS = @DBUS_CLIENT_CFLAGS@ +DBUS_CLIENT_LIBS = @DBUS_CLIENT_LIBS@ +DBUS_CONSOLE_AUTH_DIR = @DBUS_CONSOLE_AUTH_DIR@ +DBUS_CONSOLE_OWNER_FILE = @DBUS_CONSOLE_OWNER_FILE@ +DBUS_DAEMONDIR = @DBUS_DAEMONDIR@ +DBUS_DATADIR = @DBUS_DATADIR@ +DBUS_HAVE_INT64 = @DBUS_HAVE_INT64@ +DBUS_INT16_TYPE = @DBUS_INT16_TYPE@ +DBUS_INT32_TYPE = @DBUS_INT32_TYPE@ +DBUS_INT64_CONSTANT = @DBUS_INT64_CONSTANT@ +DBUS_INT64_TYPE = @DBUS_INT64_TYPE@ +DBUS_LAUNCHER_CFLAGS = @DBUS_LAUNCHER_CFLAGS@ +DBUS_LAUNCHER_LIBS = @DBUS_LAUNCHER_LIBS@ +DBUS_LIBEXECDIR = @DBUS_LIBEXECDIR@ +DBUS_MAJOR_VERSION = @DBUS_MAJOR_VERSION@ +DBUS_MICRO_VERSION = @DBUS_MICRO_VERSION@ +DBUS_MINOR_VERSION = @DBUS_MINOR_VERSION@ +DBUS_PATH_OR_ABSTRACT = @DBUS_PATH_OR_ABSTRACT@ +DBUS_SESSION_SOCKET_DIR = @DBUS_SESSION_SOCKET_DIR@ +DBUS_SYSTEM_BUS_DEFAULT_ADDRESS = @DBUS_SYSTEM_BUS_DEFAULT_ADDRESS@ +DBUS_SYSTEM_PID_FILE = @DBUS_SYSTEM_PID_FILE@ +DBUS_SYSTEM_SOCKET = @DBUS_SYSTEM_SOCKET@ +DBUS_TEST_CFLAGS = @DBUS_TEST_CFLAGS@ +DBUS_TEST_LIBS = @DBUS_TEST_LIBS@ +DBUS_UINT64_CONSTANT = @DBUS_UINT64_CONSTANT@ +DBUS_USER = @DBUS_USER@ +DBUS_VERSION = @DBUS_VERSION@ +DBUS_X_CFLAGS = @DBUS_X_CFLAGS@ +DBUS_X_LIBS = @DBUS_X_LIBS@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DOXYGEN = @DOXYGEN@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +EXPANDED_BINDIR = @EXPANDED_BINDIR@ +EXPANDED_DATADIR = @EXPANDED_DATADIR@ +EXPANDED_LIBDIR = @EXPANDED_LIBDIR@ +EXPANDED_LIBEXECDIR = @EXPANDED_LIBEXECDIR@ +EXPANDED_LOCALSTATEDIR = @EXPANDED_LOCALSTATEDIR@ +EXPANDED_SYSCONFDIR = @EXPANDED_SYSCONFDIR@ +FGREP = @FGREP@ +GETTEXT_PACKAGE = @GETTEXT_PACKAGE@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIBXML_CFLAGS = @LIBXML_CFLAGS@ +LIBXML_LIBS = @LIBXML_LIBS@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +LT_AGE = @LT_AGE@ +LT_CURRENT = @LT_CURRENT@ +LT_REVISION = @LT_REVISION@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PIC_CFLAGS = @PIC_CFLAGS@ +PIC_LDFLAGS = @PIC_LDFLAGS@ +PIE_CFLAGS = @PIE_CFLAGS@ +PIE_LDFLAGS = @PIE_LDFLAGS@ +PKG_CONFIG = @PKG_CONFIG@ +RANLIB = @RANLIB@ +R_DYNAMIC_LDFLAG = @R_DYNAMIC_LDFLAG@ +SECTION_FLAGS = @SECTION_FLAGS@ +SECTION_LDFLAGS = @SECTION_LDFLAGS@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +TEST_BUS_BINARY = @TEST_BUS_BINARY@ +TEST_EXIT_BINARY = @TEST_EXIT_BINARY@ +TEST_INVALID_SERVICE_DIR = @TEST_INVALID_SERVICE_DIR@ +TEST_INVALID_SERVICE_SYSTEM_DIR = @TEST_INVALID_SERVICE_SYSTEM_DIR@ +TEST_LAUNCH_HELPER_BINARY = @TEST_LAUNCH_HELPER_BINARY@ +TEST_PRIVSERVER_BINARY = @TEST_PRIVSERVER_BINARY@ +TEST_SEGFAULT_BINARY = @TEST_SEGFAULT_BINARY@ +TEST_SERVICE_BINARY = @TEST_SERVICE_BINARY@ +TEST_SHELL_SERVICE_BINARY = @TEST_SHELL_SERVICE_BINARY@ +TEST_SLEEP_FOREVER_BINARY = @TEST_SLEEP_FOREVER_BINARY@ +TEST_SOCKET_DIR = @TEST_SOCKET_DIR@ +TEST_VALID_SERVICE_DIR = @TEST_VALID_SERVICE_DIR@ +TEST_VALID_SERVICE_SYSTEM_DIR = @TEST_VALID_SERVICE_SYSTEM_DIR@ +VERSION = @VERSION@ +XMKMF = @XMKMF@ +XMLTO = @XMLTO@ +X_CFLAGS = @X_CFLAGS@ +X_EXTRA_LIBS = @X_EXTRA_LIBS@ +X_LIBS = @X_LIBS@ +X_PRE_LIBS = @X_PRE_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +lt_ECHO = @lt_ECHO@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +configdir = $(sysconfdir)/dbus-1 +INCLUDES = -I$(top_srcdir) $(DBUS_BUS_CFLAGS) @PIE_CFLAGS@ \ + -DDBUS_SYSTEM_CONFIG_FILE=\""$(configdir)/system.conf"\" \ + -DDAEMON_NAME=\"dbus-daemon\" -DDBUS_COMPILATION + +EFENCE = +CONFIG_IN_FILES = \ + session.conf.in \ + system.conf.in + +config_DATA = \ + session.conf \ + system.conf + +@DBUS_USE_EXPAT_TRUE@XML_SOURCES = config-loader-expat.c +@DBUS_USE_LIBXML_TRUE@XML_SOURCES = config-loader-libxml.c +@DBUS_BUS_ENABLE_DNOTIFY_ON_LINUX_FALSE@@DBUS_BUS_ENABLE_INOTIFY_FALSE@@DBUS_BUS_ENABLE_KQUEUE_FALSE@DIR_WATCH_SOURCE = dir-watch-default.c +@DBUS_BUS_ENABLE_DNOTIFY_ON_LINUX_TRUE@@DBUS_BUS_ENABLE_INOTIFY_FALSE@@DBUS_BUS_ENABLE_KQUEUE_FALSE@DIR_WATCH_SOURCE = dir-watch-dnotify.c +@DBUS_BUS_ENABLE_INOTIFY_TRUE@@DBUS_BUS_ENABLE_KQUEUE_FALSE@DIR_WATCH_SOURCE = dir-watch-inotify.c +@DBUS_BUS_ENABLE_KQUEUE_TRUE@DIR_WATCH_SOURCE = dir-watch-kqueue.c +BUS_SOURCES = \ + activation.c \ + activation.h \ + activation-exit-codes.h \ + bus.c \ + bus.h \ + config-parser.c \ + config-parser.h \ + config-parser-common.c \ + config-parser-common.h \ + connection.c \ + connection.h \ + desktop-file.c \ + desktop-file.h \ + $(DIR_WATCH_SOURCE) \ + dir-watch.h \ + dispatch.c \ + dispatch.h \ + driver.c \ + driver.h \ + expirelist.c \ + expirelist.h \ + policy.c \ + policy.h \ + selinux.h \ + selinux.c \ + services.c \ + services.h \ + signals.c \ + signals.h \ + test.c \ + test.h \ + utils.c \ + utils.h \ + $(XML_SOURCES) + +dbus_daemon_SOURCES = \ + $(BUS_SOURCES) \ + main.c + +dbus_daemon_LDADD = \ + $(EFENCE) \ + $(top_builddir)/dbus/libdbus-convenience.la \ + $(DBUS_BUS_LIBS) + +dbus_daemon_LDFLAGS = @R_DYNAMIC_LDFLAG@ @SECTION_LDFLAGS@ @PIE_LDFLAGS@ +LAUNCH_HELPER_SOURCES = \ + $(XML_SOURCES) \ + config-parser-common.c \ + config-parser-common.h \ + config-parser-trivial.c \ + config-parser-trivial.h \ + desktop-file.c \ + desktop-file.h \ + utils.c \ + utils.h \ + activation-exit-codes.h \ + activation-helper.h \ + activation-helper.c + +dbus_daemon_launch_helper_SOURCES = \ + activation-helper-bin.c \ + $(LAUNCH_HELPER_SOURCES) + +dbus_daemon_launch_helper_LDADD = \ + $(top_builddir)/dbus/libdbus-convenience.la \ + $(DBUS_LAUNCHER_LIBS) + +dbus_daemon_launch_helper_LDFLAGS = @R_DYNAMIC_LDFLAG@ @SECTION_LDFLAGS@ +dbus_daemon_launch_helper_test_SOURCES = \ + activation-helper-bin.c \ + $(LAUNCH_HELPER_SOURCES) + +dbus_daemon_launch_helper_test_LDADD = \ + $(top_builddir)/dbus/libdbus-convenience.la \ + $(DBUS_LAUNCHER_LIBS) + +dbus_daemon_launch_helper_test_LDFLAGS = @R_DYNAMIC_LDFLAG@ @SECTION_LDFLAGS@ +dbus_daemon_launch_helper_test_CPPFLAGS = \ + -DACTIVATION_LAUNCHER_TEST + +bus_test_launch_helper_SOURCES = \ + test-launch-helper.c \ + $(LAUNCH_HELPER_SOURCES) + +bus_test_launch_helper_LDADD = \ + $(top_builddir)/dbus/libdbus-convenience.la \ + $(DBUS_LAUNCHER_LIBS) + +bus_test_launch_helper_LDFLAGS = @R_DYNAMIC_LDFLAG@ @SECTION_LDFLAGS@ +bus_test_launch_helper_CPPFLAGS = \ + -DACTIVATION_LAUNCHER_TEST \ + -DACTIVATION_LAUNCHER_DO_OOM + +@DBUS_BUILD_TESTS_TRUE@TESTS_ENVIRONMENT = DBUS_TEST_DATA=$(top_builddir)/test/data DBUS_TEST_HOMEDIR=$(top_builddir)/dbus DBUS_FATAL_WARNINGS=1 DBUS_BLOCK_ON_ABORT=1 +bus_test_system_SOURCES = \ + $(XML_SOURCES) \ + config-parser-common.c \ + config-parser-common.h \ + config-parser-trivial.c \ + config-parser-trivial.h \ + utils.c \ + utils.h \ + test-system.c + +bus_test_system_LDADD = $(top_builddir)/dbus/libdbus-convenience.la $(DBUS_BUS_LIBS) +bus_test_system_LDFLAGS = @R_DYNAMIC_LDFLAG@ +bus_test_SOURCES = \ + $(BUS_SOURCES) \ + test-main.c + +bus_test_LDADD = $(top_builddir)/dbus/libdbus-convenience.la $(DBUS_BUS_LIBS) +bus_test_LDFLAGS = @R_DYNAMIC_LDFLAG@ + +#### Init scripts fun +SCRIPT_IN_FILES = messagebus.in \ + rc.messagebus.in + +@DBUS_INIT_SCRIPTS_RED_HAT_TRUE@initddir = $(sysconfdir)/rc.d/init.d +@DBUS_INIT_SCRIPTS_SLACKWARE_TRUE@initddir = $(sysconfdir)/rc.d/ +@DBUS_INIT_SCRIPTS_RED_HAT_TRUE@initd_SCRIPTS = \ +@DBUS_INIT_SCRIPTS_RED_HAT_TRUE@ messagebus + +@DBUS_INIT_SCRIPTS_SLACKWARE_TRUE@initd_SCRIPTS = \ +@DBUS_INIT_SCRIPTS_SLACKWARE_TRUE@ rc.messagebus + +MAN_IN_FILES = dbus-daemon.1.in +man_MANS = dbus-daemon.1 + +#### Extra dist +EXTRA_DIST = $(CONFIG_IN_FILES) $(SCRIPT_IN_FILES) $(man_MANS) $(MAN_IN_FILES) +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu bus/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu bus/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +system.conf: $(top_builddir)/config.status $(srcdir)/system.conf.in + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ +session.conf: $(top_builddir)/config.status $(srcdir)/session.conf.in + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ +messagebus: $(top_builddir)/config.status $(srcdir)/messagebus.in + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ +rc.messagebus: $(top_builddir)/config.status $(srcdir)/rc.messagebus.in + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ +dbus-daemon.1: $(top_builddir)/config.status $(srcdir)/dbus-daemon.1.in + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ + +clean-noinstPROGRAMS: + @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +bus-test$(EXEEXT): $(bus_test_OBJECTS) $(bus_test_DEPENDENCIES) + @rm -f bus-test$(EXEEXT) + $(AM_V_CCLD)$(bus_test_LINK) $(bus_test_OBJECTS) $(bus_test_LDADD) $(LIBS) +bus-test-launch-helper$(EXEEXT): $(bus_test_launch_helper_OBJECTS) $(bus_test_launch_helper_DEPENDENCIES) + @rm -f bus-test-launch-helper$(EXEEXT) + $(AM_V_CCLD)$(bus_test_launch_helper_LINK) $(bus_test_launch_helper_OBJECTS) $(bus_test_launch_helper_LDADD) $(LIBS) +bus-test-system$(EXEEXT): $(bus_test_system_OBJECTS) $(bus_test_system_DEPENDENCIES) + @rm -f bus-test-system$(EXEEXT) + $(AM_V_CCLD)$(bus_test_system_LINK) $(bus_test_system_OBJECTS) $(bus_test_system_LDADD) $(LIBS) +dbus-daemon$(EXEEXT): $(dbus_daemon_OBJECTS) $(dbus_daemon_DEPENDENCIES) + @rm -f dbus-daemon$(EXEEXT) + $(AM_V_CCLD)$(dbus_daemon_LINK) $(dbus_daemon_OBJECTS) $(dbus_daemon_LDADD) $(LIBS) +dbus-daemon-launch-helper$(EXEEXT): $(dbus_daemon_launch_helper_OBJECTS) $(dbus_daemon_launch_helper_DEPENDENCIES) + @rm -f dbus-daemon-launch-helper$(EXEEXT) + $(AM_V_CCLD)$(dbus_daemon_launch_helper_LINK) $(dbus_daemon_launch_helper_OBJECTS) $(dbus_daemon_launch_helper_LDADD) $(LIBS) +dbus-daemon-launch-helper-test$(EXEEXT): $(dbus_daemon_launch_helper_test_OBJECTS) $(dbus_daemon_launch_helper_test_DEPENDENCIES) + @rm -f dbus-daemon-launch-helper-test$(EXEEXT) + $(AM_V_CCLD)$(dbus_daemon_launch_helper_test_LINK) $(dbus_daemon_launch_helper_test_OBJECTS) $(dbus_daemon_launch_helper_test_LDADD) $(LIBS) +install-initdSCRIPTS: $(initd_SCRIPTS) + @$(NORMAL_INSTALL) + test -z "$(initddir)" || $(MKDIR_P) "$(DESTDIR)$(initddir)" + @list='$(initd_SCRIPTS)'; test -n "$(initddir)" || list=; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + if test -f "$$d$$p"; then echo "$$d$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n' \ + -e 'h;s|.*|.|' \ + -e 'p;x;s,.*/,,;$(transform)' | sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1; } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) { files[d] = files[d] " " $$1; \ + if (++n[d] == $(am__install_max)) { \ + print "f", d, files[d]; n[d] = 0; files[d] = "" } } \ + else { print "f", d "/" $$4, $$1 } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_SCRIPT) $$files '$(DESTDIR)$(initddir)$$dir'"; \ + $(INSTALL_SCRIPT) $$files "$(DESTDIR)$(initddir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-initdSCRIPTS: + @$(NORMAL_UNINSTALL) + @list='$(initd_SCRIPTS)'; test -n "$(initddir)" || exit 0; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 's,.*/,,;$(transform)'`; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(initddir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(initddir)" && rm -f $$files + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/activation-helper-bin.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/activation-helper.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/activation.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bus.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bus_test_launch_helper-activation-helper.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bus_test_launch_helper-config-loader-expat.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bus_test_launch_helper-config-loader-libxml.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bus_test_launch_helper-config-parser-common.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bus_test_launch_helper-config-parser-trivial.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bus_test_launch_helper-desktop-file.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bus_test_launch_helper-test-launch-helper.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bus_test_launch_helper-utils.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/config-loader-expat.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/config-loader-libxml.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/config-parser-common.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/config-parser-trivial.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/config-parser.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/connection.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dbus_daemon_launch_helper_test-activation-helper-bin.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dbus_daemon_launch_helper_test-activation-helper.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dbus_daemon_launch_helper_test-config-loader-expat.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dbus_daemon_launch_helper_test-config-loader-libxml.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dbus_daemon_launch_helper_test-config-parser-common.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dbus_daemon_launch_helper_test-config-parser-trivial.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dbus_daemon_launch_helper_test-desktop-file.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dbus_daemon_launch_helper_test-utils.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/desktop-file.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dir-watch-default.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dir-watch-dnotify.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dir-watch-inotify.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dir-watch-kqueue.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dispatch.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/driver.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/expirelist.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/main.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/policy.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/selinux.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/services.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/signals.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-main.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-system.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/utils.Po@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LTCOMPILE) -c -o $@ $< + +bus_test_launch_helper-test-launch-helper.o: test-launch-helper.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(bus_test_launch_helper_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT bus_test_launch_helper-test-launch-helper.o -MD -MP -MF $(DEPDIR)/bus_test_launch_helper-test-launch-helper.Tpo -c -o bus_test_launch_helper-test-launch-helper.o `test -f 'test-launch-helper.c' || echo '$(srcdir)/'`test-launch-helper.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/bus_test_launch_helper-test-launch-helper.Tpo $(DEPDIR)/bus_test_launch_helper-test-launch-helper.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='test-launch-helper.c' object='bus_test_launch_helper-test-launch-helper.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(bus_test_launch_helper_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o bus_test_launch_helper-test-launch-helper.o `test -f 'test-launch-helper.c' || echo '$(srcdir)/'`test-launch-helper.c + +bus_test_launch_helper-test-launch-helper.obj: test-launch-helper.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(bus_test_launch_helper_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT bus_test_launch_helper-test-launch-helper.obj -MD -MP -MF $(DEPDIR)/bus_test_launch_helper-test-launch-helper.Tpo -c -o bus_test_launch_helper-test-launch-helper.obj `if test -f 'test-launch-helper.c'; then $(CYGPATH_W) 'test-launch-helper.c'; else $(CYGPATH_W) '$(srcdir)/test-launch-helper.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/bus_test_launch_helper-test-launch-helper.Tpo $(DEPDIR)/bus_test_launch_helper-test-launch-helper.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='test-launch-helper.c' object='bus_test_launch_helper-test-launch-helper.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(bus_test_launch_helper_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o bus_test_launch_helper-test-launch-helper.obj `if test -f 'test-launch-helper.c'; then $(CYGPATH_W) 'test-launch-helper.c'; else $(CYGPATH_W) '$(srcdir)/test-launch-helper.c'; fi` + +bus_test_launch_helper-config-loader-expat.o: config-loader-expat.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(bus_test_launch_helper_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT bus_test_launch_helper-config-loader-expat.o -MD -MP -MF $(DEPDIR)/bus_test_launch_helper-config-loader-expat.Tpo -c -o bus_test_launch_helper-config-loader-expat.o `test -f 'config-loader-expat.c' || echo '$(srcdir)/'`config-loader-expat.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/bus_test_launch_helper-config-loader-expat.Tpo $(DEPDIR)/bus_test_launch_helper-config-loader-expat.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='config-loader-expat.c' object='bus_test_launch_helper-config-loader-expat.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(bus_test_launch_helper_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o bus_test_launch_helper-config-loader-expat.o `test -f 'config-loader-expat.c' || echo '$(srcdir)/'`config-loader-expat.c + +bus_test_launch_helper-config-loader-expat.obj: config-loader-expat.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(bus_test_launch_helper_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT bus_test_launch_helper-config-loader-expat.obj -MD -MP -MF $(DEPDIR)/bus_test_launch_helper-config-loader-expat.Tpo -c -o bus_test_launch_helper-config-loader-expat.obj `if test -f 'config-loader-expat.c'; then $(CYGPATH_W) 'config-loader-expat.c'; else $(CYGPATH_W) '$(srcdir)/config-loader-expat.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/bus_test_launch_helper-config-loader-expat.Tpo $(DEPDIR)/bus_test_launch_helper-config-loader-expat.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='config-loader-expat.c' object='bus_test_launch_helper-config-loader-expat.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(bus_test_launch_helper_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o bus_test_launch_helper-config-loader-expat.obj `if test -f 'config-loader-expat.c'; then $(CYGPATH_W) 'config-loader-expat.c'; else $(CYGPATH_W) '$(srcdir)/config-loader-expat.c'; fi` + +bus_test_launch_helper-config-loader-libxml.o: config-loader-libxml.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(bus_test_launch_helper_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT bus_test_launch_helper-config-loader-libxml.o -MD -MP -MF $(DEPDIR)/bus_test_launch_helper-config-loader-libxml.Tpo -c -o bus_test_launch_helper-config-loader-libxml.o `test -f 'config-loader-libxml.c' || echo '$(srcdir)/'`config-loader-libxml.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/bus_test_launch_helper-config-loader-libxml.Tpo $(DEPDIR)/bus_test_launch_helper-config-loader-libxml.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='config-loader-libxml.c' object='bus_test_launch_helper-config-loader-libxml.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(bus_test_launch_helper_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o bus_test_launch_helper-config-loader-libxml.o `test -f 'config-loader-libxml.c' || echo '$(srcdir)/'`config-loader-libxml.c + +bus_test_launch_helper-config-loader-libxml.obj: config-loader-libxml.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(bus_test_launch_helper_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT bus_test_launch_helper-config-loader-libxml.obj -MD -MP -MF $(DEPDIR)/bus_test_launch_helper-config-loader-libxml.Tpo -c -o bus_test_launch_helper-config-loader-libxml.obj `if test -f 'config-loader-libxml.c'; then $(CYGPATH_W) 'config-loader-libxml.c'; else $(CYGPATH_W) '$(srcdir)/config-loader-libxml.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/bus_test_launch_helper-config-loader-libxml.Tpo $(DEPDIR)/bus_test_launch_helper-config-loader-libxml.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='config-loader-libxml.c' object='bus_test_launch_helper-config-loader-libxml.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(bus_test_launch_helper_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o bus_test_launch_helper-config-loader-libxml.obj `if test -f 'config-loader-libxml.c'; then $(CYGPATH_W) 'config-loader-libxml.c'; else $(CYGPATH_W) '$(srcdir)/config-loader-libxml.c'; fi` + +bus_test_launch_helper-config-parser-common.o: config-parser-common.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(bus_test_launch_helper_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT bus_test_launch_helper-config-parser-common.o -MD -MP -MF $(DEPDIR)/bus_test_launch_helper-config-parser-common.Tpo -c -o bus_test_launch_helper-config-parser-common.o `test -f 'config-parser-common.c' || echo '$(srcdir)/'`config-parser-common.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/bus_test_launch_helper-config-parser-common.Tpo $(DEPDIR)/bus_test_launch_helper-config-parser-common.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='config-parser-common.c' object='bus_test_launch_helper-config-parser-common.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(bus_test_launch_helper_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o bus_test_launch_helper-config-parser-common.o `test -f 'config-parser-common.c' || echo '$(srcdir)/'`config-parser-common.c + +bus_test_launch_helper-config-parser-common.obj: config-parser-common.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(bus_test_launch_helper_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT bus_test_launch_helper-config-parser-common.obj -MD -MP -MF $(DEPDIR)/bus_test_launch_helper-config-parser-common.Tpo -c -o bus_test_launch_helper-config-parser-common.obj `if test -f 'config-parser-common.c'; then $(CYGPATH_W) 'config-parser-common.c'; else $(CYGPATH_W) '$(srcdir)/config-parser-common.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/bus_test_launch_helper-config-parser-common.Tpo $(DEPDIR)/bus_test_launch_helper-config-parser-common.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='config-parser-common.c' object='bus_test_launch_helper-config-parser-common.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(bus_test_launch_helper_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o bus_test_launch_helper-config-parser-common.obj `if test -f 'config-parser-common.c'; then $(CYGPATH_W) 'config-parser-common.c'; else $(CYGPATH_W) '$(srcdir)/config-parser-common.c'; fi` + +bus_test_launch_helper-config-parser-trivial.o: config-parser-trivial.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(bus_test_launch_helper_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT bus_test_launch_helper-config-parser-trivial.o -MD -MP -MF $(DEPDIR)/bus_test_launch_helper-config-parser-trivial.Tpo -c -o bus_test_launch_helper-config-parser-trivial.o `test -f 'config-parser-trivial.c' || echo '$(srcdir)/'`config-parser-trivial.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/bus_test_launch_helper-config-parser-trivial.Tpo $(DEPDIR)/bus_test_launch_helper-config-parser-trivial.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='config-parser-trivial.c' object='bus_test_launch_helper-config-parser-trivial.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(bus_test_launch_helper_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o bus_test_launch_helper-config-parser-trivial.o `test -f 'config-parser-trivial.c' || echo '$(srcdir)/'`config-parser-trivial.c + +bus_test_launch_helper-config-parser-trivial.obj: config-parser-trivial.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(bus_test_launch_helper_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT bus_test_launch_helper-config-parser-trivial.obj -MD -MP -MF $(DEPDIR)/bus_test_launch_helper-config-parser-trivial.Tpo -c -o bus_test_launch_helper-config-parser-trivial.obj `if test -f 'config-parser-trivial.c'; then $(CYGPATH_W) 'config-parser-trivial.c'; else $(CYGPATH_W) '$(srcdir)/config-parser-trivial.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/bus_test_launch_helper-config-parser-trivial.Tpo $(DEPDIR)/bus_test_launch_helper-config-parser-trivial.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='config-parser-trivial.c' object='bus_test_launch_helper-config-parser-trivial.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(bus_test_launch_helper_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o bus_test_launch_helper-config-parser-trivial.obj `if test -f 'config-parser-trivial.c'; then $(CYGPATH_W) 'config-parser-trivial.c'; else $(CYGPATH_W) '$(srcdir)/config-parser-trivial.c'; fi` + +bus_test_launch_helper-desktop-file.o: desktop-file.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(bus_test_launch_helper_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT bus_test_launch_helper-desktop-file.o -MD -MP -MF $(DEPDIR)/bus_test_launch_helper-desktop-file.Tpo -c -o bus_test_launch_helper-desktop-file.o `test -f 'desktop-file.c' || echo '$(srcdir)/'`desktop-file.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/bus_test_launch_helper-desktop-file.Tpo $(DEPDIR)/bus_test_launch_helper-desktop-file.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='desktop-file.c' object='bus_test_launch_helper-desktop-file.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(bus_test_launch_helper_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o bus_test_launch_helper-desktop-file.o `test -f 'desktop-file.c' || echo '$(srcdir)/'`desktop-file.c + +bus_test_launch_helper-desktop-file.obj: desktop-file.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(bus_test_launch_helper_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT bus_test_launch_helper-desktop-file.obj -MD -MP -MF $(DEPDIR)/bus_test_launch_helper-desktop-file.Tpo -c -o bus_test_launch_helper-desktop-file.obj `if test -f 'desktop-file.c'; then $(CYGPATH_W) 'desktop-file.c'; else $(CYGPATH_W) '$(srcdir)/desktop-file.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/bus_test_launch_helper-desktop-file.Tpo $(DEPDIR)/bus_test_launch_helper-desktop-file.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='desktop-file.c' object='bus_test_launch_helper-desktop-file.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(bus_test_launch_helper_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o bus_test_launch_helper-desktop-file.obj `if test -f 'desktop-file.c'; then $(CYGPATH_W) 'desktop-file.c'; else $(CYGPATH_W) '$(srcdir)/desktop-file.c'; fi` + +bus_test_launch_helper-utils.o: utils.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(bus_test_launch_helper_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT bus_test_launch_helper-utils.o -MD -MP -MF $(DEPDIR)/bus_test_launch_helper-utils.Tpo -c -o bus_test_launch_helper-utils.o `test -f 'utils.c' || echo '$(srcdir)/'`utils.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/bus_test_launch_helper-utils.Tpo $(DEPDIR)/bus_test_launch_helper-utils.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='utils.c' object='bus_test_launch_helper-utils.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(bus_test_launch_helper_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o bus_test_launch_helper-utils.o `test -f 'utils.c' || echo '$(srcdir)/'`utils.c + +bus_test_launch_helper-utils.obj: utils.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(bus_test_launch_helper_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT bus_test_launch_helper-utils.obj -MD -MP -MF $(DEPDIR)/bus_test_launch_helper-utils.Tpo -c -o bus_test_launch_helper-utils.obj `if test -f 'utils.c'; then $(CYGPATH_W) 'utils.c'; else $(CYGPATH_W) '$(srcdir)/utils.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/bus_test_launch_helper-utils.Tpo $(DEPDIR)/bus_test_launch_helper-utils.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='utils.c' object='bus_test_launch_helper-utils.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(bus_test_launch_helper_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o bus_test_launch_helper-utils.obj `if test -f 'utils.c'; then $(CYGPATH_W) 'utils.c'; else $(CYGPATH_W) '$(srcdir)/utils.c'; fi` + +bus_test_launch_helper-activation-helper.o: activation-helper.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(bus_test_launch_helper_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT bus_test_launch_helper-activation-helper.o -MD -MP -MF $(DEPDIR)/bus_test_launch_helper-activation-helper.Tpo -c -o bus_test_launch_helper-activation-helper.o `test -f 'activation-helper.c' || echo '$(srcdir)/'`activation-helper.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/bus_test_launch_helper-activation-helper.Tpo $(DEPDIR)/bus_test_launch_helper-activation-helper.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='activation-helper.c' object='bus_test_launch_helper-activation-helper.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(bus_test_launch_helper_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o bus_test_launch_helper-activation-helper.o `test -f 'activation-helper.c' || echo '$(srcdir)/'`activation-helper.c + +bus_test_launch_helper-activation-helper.obj: activation-helper.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(bus_test_launch_helper_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT bus_test_launch_helper-activation-helper.obj -MD -MP -MF $(DEPDIR)/bus_test_launch_helper-activation-helper.Tpo -c -o bus_test_launch_helper-activation-helper.obj `if test -f 'activation-helper.c'; then $(CYGPATH_W) 'activation-helper.c'; else $(CYGPATH_W) '$(srcdir)/activation-helper.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/bus_test_launch_helper-activation-helper.Tpo $(DEPDIR)/bus_test_launch_helper-activation-helper.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='activation-helper.c' object='bus_test_launch_helper-activation-helper.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(bus_test_launch_helper_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o bus_test_launch_helper-activation-helper.obj `if test -f 'activation-helper.c'; then $(CYGPATH_W) 'activation-helper.c'; else $(CYGPATH_W) '$(srcdir)/activation-helper.c'; fi` + +dbus_daemon_launch_helper_test-activation-helper-bin.o: activation-helper-bin.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dbus_daemon_launch_helper_test_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT dbus_daemon_launch_helper_test-activation-helper-bin.o -MD -MP -MF $(DEPDIR)/dbus_daemon_launch_helper_test-activation-helper-bin.Tpo -c -o dbus_daemon_launch_helper_test-activation-helper-bin.o `test -f 'activation-helper-bin.c' || echo '$(srcdir)/'`activation-helper-bin.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dbus_daemon_launch_helper_test-activation-helper-bin.Tpo $(DEPDIR)/dbus_daemon_launch_helper_test-activation-helper-bin.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='activation-helper-bin.c' object='dbus_daemon_launch_helper_test-activation-helper-bin.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dbus_daemon_launch_helper_test_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o dbus_daemon_launch_helper_test-activation-helper-bin.o `test -f 'activation-helper-bin.c' || echo '$(srcdir)/'`activation-helper-bin.c + +dbus_daemon_launch_helper_test-activation-helper-bin.obj: activation-helper-bin.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dbus_daemon_launch_helper_test_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT dbus_daemon_launch_helper_test-activation-helper-bin.obj -MD -MP -MF $(DEPDIR)/dbus_daemon_launch_helper_test-activation-helper-bin.Tpo -c -o dbus_daemon_launch_helper_test-activation-helper-bin.obj `if test -f 'activation-helper-bin.c'; then $(CYGPATH_W) 'activation-helper-bin.c'; else $(CYGPATH_W) '$(srcdir)/activation-helper-bin.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dbus_daemon_launch_helper_test-activation-helper-bin.Tpo $(DEPDIR)/dbus_daemon_launch_helper_test-activation-helper-bin.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='activation-helper-bin.c' object='dbus_daemon_launch_helper_test-activation-helper-bin.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dbus_daemon_launch_helper_test_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o dbus_daemon_launch_helper_test-activation-helper-bin.obj `if test -f 'activation-helper-bin.c'; then $(CYGPATH_W) 'activation-helper-bin.c'; else $(CYGPATH_W) '$(srcdir)/activation-helper-bin.c'; fi` + +dbus_daemon_launch_helper_test-config-loader-expat.o: config-loader-expat.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dbus_daemon_launch_helper_test_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT dbus_daemon_launch_helper_test-config-loader-expat.o -MD -MP -MF $(DEPDIR)/dbus_daemon_launch_helper_test-config-loader-expat.Tpo -c -o dbus_daemon_launch_helper_test-config-loader-expat.o `test -f 'config-loader-expat.c' || echo '$(srcdir)/'`config-loader-expat.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dbus_daemon_launch_helper_test-config-loader-expat.Tpo $(DEPDIR)/dbus_daemon_launch_helper_test-config-loader-expat.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='config-loader-expat.c' object='dbus_daemon_launch_helper_test-config-loader-expat.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dbus_daemon_launch_helper_test_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o dbus_daemon_launch_helper_test-config-loader-expat.o `test -f 'config-loader-expat.c' || echo '$(srcdir)/'`config-loader-expat.c + +dbus_daemon_launch_helper_test-config-loader-expat.obj: config-loader-expat.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dbus_daemon_launch_helper_test_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT dbus_daemon_launch_helper_test-config-loader-expat.obj -MD -MP -MF $(DEPDIR)/dbus_daemon_launch_helper_test-config-loader-expat.Tpo -c -o dbus_daemon_launch_helper_test-config-loader-expat.obj `if test -f 'config-loader-expat.c'; then $(CYGPATH_W) 'config-loader-expat.c'; else $(CYGPATH_W) '$(srcdir)/config-loader-expat.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dbus_daemon_launch_helper_test-config-loader-expat.Tpo $(DEPDIR)/dbus_daemon_launch_helper_test-config-loader-expat.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='config-loader-expat.c' object='dbus_daemon_launch_helper_test-config-loader-expat.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dbus_daemon_launch_helper_test_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o dbus_daemon_launch_helper_test-config-loader-expat.obj `if test -f 'config-loader-expat.c'; then $(CYGPATH_W) 'config-loader-expat.c'; else $(CYGPATH_W) '$(srcdir)/config-loader-expat.c'; fi` + +dbus_daemon_launch_helper_test-config-loader-libxml.o: config-loader-libxml.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dbus_daemon_launch_helper_test_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT dbus_daemon_launch_helper_test-config-loader-libxml.o -MD -MP -MF $(DEPDIR)/dbus_daemon_launch_helper_test-config-loader-libxml.Tpo -c -o dbus_daemon_launch_helper_test-config-loader-libxml.o `test -f 'config-loader-libxml.c' || echo '$(srcdir)/'`config-loader-libxml.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dbus_daemon_launch_helper_test-config-loader-libxml.Tpo $(DEPDIR)/dbus_daemon_launch_helper_test-config-loader-libxml.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='config-loader-libxml.c' object='dbus_daemon_launch_helper_test-config-loader-libxml.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dbus_daemon_launch_helper_test_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o dbus_daemon_launch_helper_test-config-loader-libxml.o `test -f 'config-loader-libxml.c' || echo '$(srcdir)/'`config-loader-libxml.c + +dbus_daemon_launch_helper_test-config-loader-libxml.obj: config-loader-libxml.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dbus_daemon_launch_helper_test_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT dbus_daemon_launch_helper_test-config-loader-libxml.obj -MD -MP -MF $(DEPDIR)/dbus_daemon_launch_helper_test-config-loader-libxml.Tpo -c -o dbus_daemon_launch_helper_test-config-loader-libxml.obj `if test -f 'config-loader-libxml.c'; then $(CYGPATH_W) 'config-loader-libxml.c'; else $(CYGPATH_W) '$(srcdir)/config-loader-libxml.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dbus_daemon_launch_helper_test-config-loader-libxml.Tpo $(DEPDIR)/dbus_daemon_launch_helper_test-config-loader-libxml.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='config-loader-libxml.c' object='dbus_daemon_launch_helper_test-config-loader-libxml.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dbus_daemon_launch_helper_test_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o dbus_daemon_launch_helper_test-config-loader-libxml.obj `if test -f 'config-loader-libxml.c'; then $(CYGPATH_W) 'config-loader-libxml.c'; else $(CYGPATH_W) '$(srcdir)/config-loader-libxml.c'; fi` + +dbus_daemon_launch_helper_test-config-parser-common.o: config-parser-common.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dbus_daemon_launch_helper_test_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT dbus_daemon_launch_helper_test-config-parser-common.o -MD -MP -MF $(DEPDIR)/dbus_daemon_launch_helper_test-config-parser-common.Tpo -c -o dbus_daemon_launch_helper_test-config-parser-common.o `test -f 'config-parser-common.c' || echo '$(srcdir)/'`config-parser-common.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dbus_daemon_launch_helper_test-config-parser-common.Tpo $(DEPDIR)/dbus_daemon_launch_helper_test-config-parser-common.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='config-parser-common.c' object='dbus_daemon_launch_helper_test-config-parser-common.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dbus_daemon_launch_helper_test_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o dbus_daemon_launch_helper_test-config-parser-common.o `test -f 'config-parser-common.c' || echo '$(srcdir)/'`config-parser-common.c + +dbus_daemon_launch_helper_test-config-parser-common.obj: config-parser-common.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dbus_daemon_launch_helper_test_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT dbus_daemon_launch_helper_test-config-parser-common.obj -MD -MP -MF $(DEPDIR)/dbus_daemon_launch_helper_test-config-parser-common.Tpo -c -o dbus_daemon_launch_helper_test-config-parser-common.obj `if test -f 'config-parser-common.c'; then $(CYGPATH_W) 'config-parser-common.c'; else $(CYGPATH_W) '$(srcdir)/config-parser-common.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dbus_daemon_launch_helper_test-config-parser-common.Tpo $(DEPDIR)/dbus_daemon_launch_helper_test-config-parser-common.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='config-parser-common.c' object='dbus_daemon_launch_helper_test-config-parser-common.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dbus_daemon_launch_helper_test_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o dbus_daemon_launch_helper_test-config-parser-common.obj `if test -f 'config-parser-common.c'; then $(CYGPATH_W) 'config-parser-common.c'; else $(CYGPATH_W) '$(srcdir)/config-parser-common.c'; fi` + +dbus_daemon_launch_helper_test-config-parser-trivial.o: config-parser-trivial.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dbus_daemon_launch_helper_test_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT dbus_daemon_launch_helper_test-config-parser-trivial.o -MD -MP -MF $(DEPDIR)/dbus_daemon_launch_helper_test-config-parser-trivial.Tpo -c -o dbus_daemon_launch_helper_test-config-parser-trivial.o `test -f 'config-parser-trivial.c' || echo '$(srcdir)/'`config-parser-trivial.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dbus_daemon_launch_helper_test-config-parser-trivial.Tpo $(DEPDIR)/dbus_daemon_launch_helper_test-config-parser-trivial.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='config-parser-trivial.c' object='dbus_daemon_launch_helper_test-config-parser-trivial.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dbus_daemon_launch_helper_test_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o dbus_daemon_launch_helper_test-config-parser-trivial.o `test -f 'config-parser-trivial.c' || echo '$(srcdir)/'`config-parser-trivial.c + +dbus_daemon_launch_helper_test-config-parser-trivial.obj: config-parser-trivial.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dbus_daemon_launch_helper_test_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT dbus_daemon_launch_helper_test-config-parser-trivial.obj -MD -MP -MF $(DEPDIR)/dbus_daemon_launch_helper_test-config-parser-trivial.Tpo -c -o dbus_daemon_launch_helper_test-config-parser-trivial.obj `if test -f 'config-parser-trivial.c'; then $(CYGPATH_W) 'config-parser-trivial.c'; else $(CYGPATH_W) '$(srcdir)/config-parser-trivial.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dbus_daemon_launch_helper_test-config-parser-trivial.Tpo $(DEPDIR)/dbus_daemon_launch_helper_test-config-parser-trivial.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='config-parser-trivial.c' object='dbus_daemon_launch_helper_test-config-parser-trivial.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dbus_daemon_launch_helper_test_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o dbus_daemon_launch_helper_test-config-parser-trivial.obj `if test -f 'config-parser-trivial.c'; then $(CYGPATH_W) 'config-parser-trivial.c'; else $(CYGPATH_W) '$(srcdir)/config-parser-trivial.c'; fi` + +dbus_daemon_launch_helper_test-desktop-file.o: desktop-file.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dbus_daemon_launch_helper_test_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT dbus_daemon_launch_helper_test-desktop-file.o -MD -MP -MF $(DEPDIR)/dbus_daemon_launch_helper_test-desktop-file.Tpo -c -o dbus_daemon_launch_helper_test-desktop-file.o `test -f 'desktop-file.c' || echo '$(srcdir)/'`desktop-file.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dbus_daemon_launch_helper_test-desktop-file.Tpo $(DEPDIR)/dbus_daemon_launch_helper_test-desktop-file.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='desktop-file.c' object='dbus_daemon_launch_helper_test-desktop-file.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dbus_daemon_launch_helper_test_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o dbus_daemon_launch_helper_test-desktop-file.o `test -f 'desktop-file.c' || echo '$(srcdir)/'`desktop-file.c + +dbus_daemon_launch_helper_test-desktop-file.obj: desktop-file.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dbus_daemon_launch_helper_test_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT dbus_daemon_launch_helper_test-desktop-file.obj -MD -MP -MF $(DEPDIR)/dbus_daemon_launch_helper_test-desktop-file.Tpo -c -o dbus_daemon_launch_helper_test-desktop-file.obj `if test -f 'desktop-file.c'; then $(CYGPATH_W) 'desktop-file.c'; else $(CYGPATH_W) '$(srcdir)/desktop-file.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dbus_daemon_launch_helper_test-desktop-file.Tpo $(DEPDIR)/dbus_daemon_launch_helper_test-desktop-file.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='desktop-file.c' object='dbus_daemon_launch_helper_test-desktop-file.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dbus_daemon_launch_helper_test_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o dbus_daemon_launch_helper_test-desktop-file.obj `if test -f 'desktop-file.c'; then $(CYGPATH_W) 'desktop-file.c'; else $(CYGPATH_W) '$(srcdir)/desktop-file.c'; fi` + +dbus_daemon_launch_helper_test-utils.o: utils.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dbus_daemon_launch_helper_test_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT dbus_daemon_launch_helper_test-utils.o -MD -MP -MF $(DEPDIR)/dbus_daemon_launch_helper_test-utils.Tpo -c -o dbus_daemon_launch_helper_test-utils.o `test -f 'utils.c' || echo '$(srcdir)/'`utils.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dbus_daemon_launch_helper_test-utils.Tpo $(DEPDIR)/dbus_daemon_launch_helper_test-utils.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='utils.c' object='dbus_daemon_launch_helper_test-utils.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dbus_daemon_launch_helper_test_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o dbus_daemon_launch_helper_test-utils.o `test -f 'utils.c' || echo '$(srcdir)/'`utils.c + +dbus_daemon_launch_helper_test-utils.obj: utils.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dbus_daemon_launch_helper_test_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT dbus_daemon_launch_helper_test-utils.obj -MD -MP -MF $(DEPDIR)/dbus_daemon_launch_helper_test-utils.Tpo -c -o dbus_daemon_launch_helper_test-utils.obj `if test -f 'utils.c'; then $(CYGPATH_W) 'utils.c'; else $(CYGPATH_W) '$(srcdir)/utils.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dbus_daemon_launch_helper_test-utils.Tpo $(DEPDIR)/dbus_daemon_launch_helper_test-utils.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='utils.c' object='dbus_daemon_launch_helper_test-utils.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dbus_daemon_launch_helper_test_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o dbus_daemon_launch_helper_test-utils.obj `if test -f 'utils.c'; then $(CYGPATH_W) 'utils.c'; else $(CYGPATH_W) '$(srcdir)/utils.c'; fi` + +dbus_daemon_launch_helper_test-activation-helper.o: activation-helper.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dbus_daemon_launch_helper_test_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT dbus_daemon_launch_helper_test-activation-helper.o -MD -MP -MF $(DEPDIR)/dbus_daemon_launch_helper_test-activation-helper.Tpo -c -o dbus_daemon_launch_helper_test-activation-helper.o `test -f 'activation-helper.c' || echo '$(srcdir)/'`activation-helper.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dbus_daemon_launch_helper_test-activation-helper.Tpo $(DEPDIR)/dbus_daemon_launch_helper_test-activation-helper.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='activation-helper.c' object='dbus_daemon_launch_helper_test-activation-helper.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dbus_daemon_launch_helper_test_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o dbus_daemon_launch_helper_test-activation-helper.o `test -f 'activation-helper.c' || echo '$(srcdir)/'`activation-helper.c + +dbus_daemon_launch_helper_test-activation-helper.obj: activation-helper.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dbus_daemon_launch_helper_test_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT dbus_daemon_launch_helper_test-activation-helper.obj -MD -MP -MF $(DEPDIR)/dbus_daemon_launch_helper_test-activation-helper.Tpo -c -o dbus_daemon_launch_helper_test-activation-helper.obj `if test -f 'activation-helper.c'; then $(CYGPATH_W) 'activation-helper.c'; else $(CYGPATH_W) '$(srcdir)/activation-helper.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dbus_daemon_launch_helper_test-activation-helper.Tpo $(DEPDIR)/dbus_daemon_launch_helper_test-activation-helper.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='activation-helper.c' object='dbus_daemon_launch_helper_test-activation-helper.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dbus_daemon_launch_helper_test_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o dbus_daemon_launch_helper_test-activation-helper.obj `if test -f 'activation-helper.c'; then $(CYGPATH_W) 'activation-helper.c'; else $(CYGPATH_W) '$(srcdir)/activation-helper.c'; fi` + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs +install-man1: $(man_MANS) + @$(NORMAL_INSTALL) + test -z "$(man1dir)" || $(MKDIR_P) "$(DESTDIR)$(man1dir)" + @list=''; test -n "$(man1dir)" || exit 0; \ + { for i in $$list; do echo "$$i"; done; \ + l2='$(man_MANS)'; for i in $$l2; do echo "$$i"; done | \ + sed -n '/\.1[a-z]*$$/p'; \ + } | while read p; do \ + if test -f $$p; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; echo "$$p"; \ + done | \ + sed -e 'n;s,.*/,,;p;h;s,.*\.,,;s,^[^1][0-9a-z]*$$,1,;x' \ + -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,' | \ + sed 'N;N;s,\n, ,g' | { \ + list=; while read file base inst; do \ + if test "$$base" = "$$inst"; then list="$$list $$file"; else \ + echo " $(INSTALL_DATA) '$$file' '$(DESTDIR)$(man1dir)/$$inst'"; \ + $(INSTALL_DATA) "$$file" "$(DESTDIR)$(man1dir)/$$inst" || exit $$?; \ + fi; \ + done; \ + for i in $$list; do echo "$$i"; done | $(am__base_list) | \ + while read files; do \ + test -z "$$files" || { \ + echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(man1dir)'"; \ + $(INSTALL_DATA) $$files "$(DESTDIR)$(man1dir)" || exit $$?; }; \ + done; } + +uninstall-man1: + @$(NORMAL_UNINSTALL) + @list=''; test -n "$(man1dir)" || exit 0; \ + files=`{ for i in $$list; do echo "$$i"; done; \ + l2='$(man_MANS)'; for i in $$l2; do echo "$$i"; done | \ + sed -n '/\.1[a-z]*$$/p'; \ + } | sed -e 's,.*/,,;h;s,.*\.,,;s,^[^1][0-9a-z]*$$,1,;x' \ + -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,'`; \ + test -z "$$files" || { \ + echo " ( cd '$(DESTDIR)$(man1dir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(man1dir)" && rm -f $$files; } +install-configDATA: $(config_DATA) + @$(NORMAL_INSTALL) + test -z "$(configdir)" || $(MKDIR_P) "$(DESTDIR)$(configdir)" + @list='$(config_DATA)'; test -n "$(configdir)" || list=; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(configdir)'"; \ + $(INSTALL_DATA) $$files "$(DESTDIR)$(configdir)" || exit $$?; \ + done + +uninstall-configDATA: + @$(NORMAL_UNINSTALL) + @list='$(config_DATA)'; test -n "$(configdir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + test -n "$$files" || exit 0; \ + echo " ( cd '$(DESTDIR)$(configdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(configdir)" && rm -f $$files + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + set x; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: CTAGS +CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +check-TESTS: $(TESTS) + @failed=0; all=0; xfail=0; xpass=0; skip=0; \ + srcdir=$(srcdir); export srcdir; \ + list=' $(TESTS) '; \ + $(am__tty_colors); \ + if test -n "$$list"; then \ + for tst in $$list; do \ + if test -f ./$$tst; then dir=./; \ + elif test -f $$tst; then dir=; \ + else dir="$(srcdir)/"; fi; \ + if $(TESTS_ENVIRONMENT) $${dir}$$tst; then \ + all=`expr $$all + 1`; \ + case " $(XFAIL_TESTS) " in \ + *[\ \ ]$$tst[\ \ ]*) \ + xpass=`expr $$xpass + 1`; \ + failed=`expr $$failed + 1`; \ + col=$$red; res=XPASS; \ + ;; \ + *) \ + col=$$grn; res=PASS; \ + ;; \ + esac; \ + elif test $$? -ne 77; then \ + all=`expr $$all + 1`; \ + case " $(XFAIL_TESTS) " in \ + *[\ \ ]$$tst[\ \ ]*) \ + xfail=`expr $$xfail + 1`; \ + col=$$lgn; res=XFAIL; \ + ;; \ + *) \ + failed=`expr $$failed + 1`; \ + col=$$red; res=FAIL; \ + ;; \ + esac; \ + else \ + skip=`expr $$skip + 1`; \ + col=$$blu; res=SKIP; \ + fi; \ + echo "$${col}$$res$${std}: $$tst"; \ + done; \ + if test "$$all" -eq 1; then \ + tests="test"; \ + All=""; \ + else \ + tests="tests"; \ + All="All "; \ + fi; \ + if test "$$failed" -eq 0; then \ + if test "$$xfail" -eq 0; then \ + banner="$$All$$all $$tests passed"; \ + else \ + if test "$$xfail" -eq 1; then failures=failure; else failures=failures; fi; \ + banner="$$All$$all $$tests behaved as expected ($$xfail expected $$failures)"; \ + fi; \ + else \ + if test "$$xpass" -eq 0; then \ + banner="$$failed of $$all $$tests failed"; \ + else \ + if test "$$xpass" -eq 1; then passes=pass; else passes=passes; fi; \ + banner="$$failed of $$all $$tests did not behave as expected ($$xpass unexpected $$passes)"; \ + fi; \ + fi; \ + dashes="$$banner"; \ + skipped=""; \ + if test "$$skip" -ne 0; then \ + if test "$$skip" -eq 1; then \ + skipped="($$skip test was not run)"; \ + else \ + skipped="($$skip tests were not run)"; \ + fi; \ + test `echo "$$skipped" | wc -c` -le `echo "$$banner" | wc -c` || \ + dashes="$$skipped"; \ + fi; \ + report=""; \ + if test "$$failed" -ne 0 && test -n "$(PACKAGE_BUGREPORT)"; then \ + report="Please report to $(PACKAGE_BUGREPORT)"; \ + test `echo "$$report" | wc -c` -le `echo "$$banner" | wc -c` || \ + dashes="$$report"; \ + fi; \ + dashes=`echo "$$dashes" | sed s/./=/g`; \ + if test "$$failed" -eq 0; then \ + echo "$$grn$$dashes"; \ + else \ + echo "$$red$$dashes"; \ + fi; \ + echo "$$banner"; \ + test -z "$$skipped" || echo "$$skipped"; \ + test -z "$$report" || echo "$$report"; \ + echo "$$dashes$$std"; \ + test "$$failed" -eq 0; \ + else :; fi + +distdir: $(DISTFILES) + @list='$(MANS)'; if test -n "$$list"; then \ + list=`for p in $$list; do \ + if test -f $$p; then d=; else d="$(srcdir)/"; fi; \ + if test -f "$$d$$p"; then echo "$$d$$p"; else :; fi; done`; \ + if test -n "$$list" && \ + grep 'ab help2man is required to generate this page' $$list >/dev/null; then \ + echo "error: found man pages containing the \`missing help2man' replacement text:" >&2; \ + grep -l 'ab help2man is required to generate this page' $$list | sed 's/^/ /' >&2; \ + echo " to fix them, install help2man, remove and regenerate the man pages;" >&2; \ + echo " typically \`make maintainer-clean' will remove them" >&2; \ + exit 1; \ + else :; fi; \ + else :; fi + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am + $(MAKE) $(AM_MAKEFLAGS) check-TESTS +check: check-am +all-am: Makefile $(PROGRAMS) $(SCRIPTS) $(MANS) $(DATA) +installdirs: + for dir in "$(DESTDIR)$(initddir)" "$(DESTDIR)$(man1dir)" "$(DESTDIR)$(configdir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-local clean-noinstPROGRAMS \ + mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-configDATA install-initdSCRIPTS install-man + @$(NORMAL_INSTALL) + $(MAKE) $(AM_MAKEFLAGS) install-data-hook +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: install-man1 + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-configDATA uninstall-initdSCRIPTS \ + uninstall-man + @$(NORMAL_INSTALL) + $(MAKE) $(AM_MAKEFLAGS) uninstall-hook +uninstall-man: uninstall-man1 + +.MAKE: check-am install-am install-data-am install-strip uninstall-am + +.PHONY: CTAGS GTAGS all all-am check check-TESTS check-am clean \ + clean-generic clean-libtool clean-local clean-noinstPROGRAMS \ + ctags distclean distclean-compile distclean-generic \ + distclean-libtool distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-configDATA \ + install-data install-data-am install-data-hook install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am \ + install-initdSCRIPTS install-man install-man1 install-pdf \ + install-pdf-am install-ps install-ps-am install-strip \ + installcheck installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags uninstall uninstall-am uninstall-configDATA \ + uninstall-hook uninstall-initdSCRIPTS uninstall-man \ + uninstall-man1 + + +clean-local: + /bin/rm *.bb *.bbg *.da *.gcov || true + +uninstall-hook: + rm -f $(DESTDIR)$(DBUS_DAEMONDIR)/dbus-daemon + rm -f $(DESTDIR)$(libexecdir)/dbus-daemon-launch-helper + +install-data-hook: + if test '!' -d $(DESTDIR)$(DBUS_DAEMONDIR); then \ + $(mkinstalldirs) $(DESTDIR)$(DBUS_DAEMONDIR); \ + chmod 755 $(DESTDIR)$(DBUS_DAEMONDIR); \ + fi + $(INSTALL_PROGRAM) dbus-daemon $(DESTDIR)$(DBUS_DAEMONDIR) + $(mkinstalldirs) $(DESTDIR)$(localstatedir)/run/dbus + $(mkinstalldirs) $(DESTDIR)$(configdir)/system.d + $(mkinstalldirs) $(DESTDIR)$(configdir)/session.d + $(mkinstalldirs) $(DESTDIR)$(datadir)/dbus-1/services + $(mkinstalldirs) $(DESTDIR)$(datadir)/dbus-1/system-services + $(mkinstalldirs) $(DESTDIR)$(libexecdir)/dbus-1 + $(INSTALL_PROGRAM) dbus-daemon-launch-helper $(DESTDIR)$(libexecdir) + if test `id -u` -eq 0; then \ + chown root:$(DBUS_USER) $(DESTDIR)$(libexecdir)/dbus-daemon-launch-helper; \ + chmod 4750 $(DESTDIR)$(libexecdir)/dbus-daemon-launch-helper; \ + else \ + echo "Not installing $(DESTDIR)$(libexecdir)/dbus-daemon-launch-helper binary setuid!"; \ + echo "You'll need to manually set permissions to root:$(DBUS_USER) and permissions 4750"; \ + fi + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/bus/activation-exit-codes.h b/bus/activation-exit-codes.h new file mode 100644 index 00000000..bbb98dca --- /dev/null +++ b/bus/activation-exit-codes.h @@ -0,0 +1,45 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* activation-exit-codes.h Return values for the launch helper which is set + * in the helper and read in dbus-spawn. + * + * Copyright (C) 2007 Red Hat, Inc. + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef BUS_ACTIVATION_EXIT_CODES_H +#define BUS_ACTIVATION_EXIT_CODES_H + +/** Return codes from the launch helper - not public API. However, + * presumably if some third party did write their own launch helper, + * they would have to rely on these, or at least always return + * 1 for GENERIC_FAILURE. + */ +#define BUS_SPAWN_EXIT_CODE_GENERIC_FAILURE 1 +#define BUS_SPAWN_EXIT_CODE_NO_MEMORY 2 +#define BUS_SPAWN_EXIT_CODE_CONFIG_INVALID 3 +#define BUS_SPAWN_EXIT_CODE_SETUP_FAILED 4 +#define BUS_SPAWN_EXIT_CODE_NAME_INVALID 5 +#define BUS_SPAWN_EXIT_CODE_SERVICE_NOT_FOUND 6 +#define BUS_SPAWN_EXIT_CODE_PERMISSIONS_INVALID 7 +#define BUS_SPAWN_EXIT_CODE_FILE_INVALID 8 +#define BUS_SPAWN_EXIT_CODE_EXEC_FAILED 9 +#define BUS_SPAWN_EXIT_CODE_INVALID_ARGS 10 +#define BUS_SPAWN_EXIT_CODE_CHILD_SIGNALED 11 + +#endif /* BUS_ACTIVATION_EXIT_CODES_H */ diff --git a/bus/activation-helper-bin.c b/bus/activation-helper-bin.c new file mode 100644 index 00000000..a360acc7 --- /dev/null +++ b/bus/activation-helper-bin.c @@ -0,0 +1,100 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* activation-helper-bin.c Setuid helper for launching programs as a custom + * user. This file is security sensitive. + * + * Copyright (C) 2007 Red Hat, Inc. + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> + +#include "utils.h" +#include "activation-helper.h" +#include "activation-exit-codes.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +static int +convert_error_to_exit_code (DBusError *error) +{ + if (dbus_error_has_name (error, DBUS_ERROR_NO_MEMORY)) + return BUS_SPAWN_EXIT_CODE_NO_MEMORY; + + if (dbus_error_has_name (error, DBUS_ERROR_SPAWN_CONFIG_INVALID)) + return BUS_SPAWN_EXIT_CODE_CONFIG_INVALID; + + if (dbus_error_has_name (error, DBUS_ERROR_SPAWN_SETUP_FAILED)) + return BUS_SPAWN_EXIT_CODE_SETUP_FAILED; + + if (dbus_error_has_name (error, DBUS_ERROR_SPAWN_SERVICE_INVALID)) + return BUS_SPAWN_EXIT_CODE_SERVICE_NOT_FOUND; + + if (dbus_error_has_name (error, DBUS_ERROR_SPAWN_PERMISSIONS_INVALID)) + return BUS_SPAWN_EXIT_CODE_PERMISSIONS_INVALID; + + if (dbus_error_has_name (error, DBUS_ERROR_SPAWN_FILE_INVALID)) + return BUS_SPAWN_EXIT_CODE_FILE_INVALID; + + if (dbus_error_has_name (error, DBUS_ERROR_SPAWN_EXEC_FAILED)) + return BUS_SPAWN_EXIT_CODE_EXEC_FAILED; + + if (dbus_error_has_name (error, DBUS_ERROR_INVALID_ARGS)) + return BUS_SPAWN_EXIT_CODE_INVALID_ARGS; + + if (dbus_error_has_name (error, DBUS_ERROR_SPAWN_CHILD_SIGNALED)) + return BUS_SPAWN_EXIT_CODE_CHILD_SIGNALED; + + /* should we assert? */ + fprintf(stderr, "%s: %s\n", error->name, error->message); + + return BUS_SPAWN_EXIT_CODE_SETUP_FAILED; +} + +int +main (int argc, char **argv) +{ + DBusError error; + int retval; + + /* default is all okay */ + retval = 0; + + /* have we used a help option or not specified the correct arguments? */ + if (argc != 2 || + strcmp (argv[1], "--help") == 0 || + strcmp (argv[1], "-h") == 0 || + strcmp (argv[1], "-?") == 0) + { + fprintf (stderr, "dbus-daemon-activation-helper service.to.activate\n"); + exit (0); + } + + dbus_error_init (&error); + if (!run_launch_helper (argv[1], &error)) + { + /* convert error to an exit code */ + retval = convert_error_to_exit_code (&error); + dbus_error_free (&error); + } + + return retval; +} + diff --git a/bus/activation-helper.c b/bus/activation-helper.c new file mode 100644 index 00000000..baba8f04 --- /dev/null +++ b/bus/activation-helper.c @@ -0,0 +1,563 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* activation-helper.c Setuid helper for launching programs as a custom + * user. This file is security sensitive. + * + * Copyright (C) 2007 Red Hat, Inc. + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> + +#include "bus.h" +#include "driver.h" +#include "utils.h" +#include "desktop-file.h" +#include "config-parser-trivial.h" +#include "activation-helper.h" +#include "activation-exit-codes.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/types.h> +#include <pwd.h> +#include <grp.h> + +#include <dbus/dbus-shell.h> +#include <dbus/dbus-marshal-validate.h> + +static BusDesktopFile * +desktop_file_for_name (BusConfigParser *parser, + const char *name, + DBusError *error) +{ + BusDesktopFile *desktop_file; + DBusList **service_dirs; + DBusList *link; + DBusError tmp_error; + DBusString full_path; + DBusString filename; + const char *dir; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + desktop_file = NULL; + + if (!_dbus_string_init (&filename)) + { + BUS_SET_OOM (error); + goto out_all; + } + + if (!_dbus_string_init (&full_path)) + { + BUS_SET_OOM (error); + goto out_filename; + } + + if (!_dbus_string_append (&filename, name) || + !_dbus_string_append (&filename, ".service")) + { + BUS_SET_OOM (error); + goto out; + } + + service_dirs = bus_config_parser_get_service_dirs (parser); + for (link = _dbus_list_get_first_link (service_dirs); + link != NULL; + link = _dbus_list_get_next_link (service_dirs, link)) + { + dir = link->data; + _dbus_verbose ("Looking at '%s'\n", dir); + + dbus_error_init (&tmp_error); + + /* clear the path from last time */ + _dbus_string_set_length (&full_path, 0); + + /* build the full path */ + if (!_dbus_string_append (&full_path, dir) || + !_dbus_concat_dir_and_file (&full_path, &filename)) + { + BUS_SET_OOM (error); + goto out; + } + + _dbus_verbose ("Trying to load file '%s'\n", _dbus_string_get_data (&full_path)); + desktop_file = bus_desktop_file_load (&full_path, &tmp_error); + if (desktop_file == NULL) + { + _DBUS_ASSERT_ERROR_IS_SET (&tmp_error); + _dbus_verbose ("Could not load %s: %s: %s\n", + _dbus_string_get_const_data (&full_path), + tmp_error.name, tmp_error.message); + + /* we may have failed if the file is not found; this is not fatal */ + if (dbus_error_has_name (&tmp_error, DBUS_ERROR_NO_MEMORY)) + { + dbus_move_error (&tmp_error, error); + /* we only bail out on OOM */ + goto out; + } + dbus_error_free (&tmp_error); + } + + /* did we find the desktop file we want? */ + if (desktop_file != NULL) + break; + } + + /* Didn't find desktop file; set error */ + if (desktop_file == NULL) + { + dbus_set_error (error, DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND, + "The name %s was not provided by any .service files", + name); + } + +out: + _dbus_string_free (&full_path); +out_filename: + _dbus_string_free (&filename); +out_all: + return desktop_file; +} + +/* Cleares the environment, except for DBUS_VERBOSE and DBUS_STARTER_x */ +static dbus_bool_t +clear_environment (DBusError *error) +{ + const char *debug_env = NULL; + const char *starter_env = NULL; + +#ifdef DBUS_ENABLE_VERBOSE_MODE + /* are we debugging */ + debug_env = _dbus_getenv ("DBUS_VERBOSE"); +#endif + + /* we save the starter */ + starter_env = _dbus_getenv ("DBUS_STARTER_ADDRESS"); + +#ifndef ACTIVATION_LAUNCHER_TEST + /* totally clear the environment */ + if (!_dbus_clearenv ()) + { + dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED, + "could not clear environment\n"); + return FALSE; + } +#endif + +#ifdef DBUS_ENABLE_VERBOSE_MODE + /* restore the debugging environment setting if set */ + if (debug_env) + _dbus_setenv ("DBUS_VERBOSE", debug_env); +#endif + + /* restore the starter */ + if (starter_env) + _dbus_setenv ("DBUS_STARTER_ADDRESS", starter_env); + + /* set the type, which must be system if we got this far */ + _dbus_setenv ("DBUS_STARTER_BUS_TYPE", "system"); + + return TRUE; +} + +static dbus_bool_t +check_permissions (const char *dbus_user, DBusError *error) +{ + uid_t uid, euid; + struct passwd *pw; + + pw = NULL; + uid = 0; + euid = 0; + +#ifndef ACTIVATION_LAUNCHER_TEST + /* bail out unless the dbus user is invoking the helper */ + pw = getpwnam(dbus_user); + if (!pw) + { + dbus_set_error (error, DBUS_ERROR_SPAWN_PERMISSIONS_INVALID, + "cannot find user '%s'", dbus_user); + return FALSE; + } + uid = getuid(); + if (pw->pw_uid != uid) + { + dbus_set_error (error, DBUS_ERROR_SPAWN_PERMISSIONS_INVALID, + "not invoked from user '%s'", dbus_user); + return FALSE; + } + + /* bail out unless we are setuid to user root */ + euid = geteuid(); + if (euid != 0) + { + dbus_set_error (error, DBUS_ERROR_SPAWN_PERMISSIONS_INVALID, + "not setuid root"); + return FALSE; + } +#endif + + return TRUE; +} + +static dbus_bool_t +check_service_name (BusDesktopFile *desktop_file, + const char *service_name, + DBusError *error) +{ + char *name_tmp; + dbus_bool_t retval; + + retval = FALSE; + + /* try to get Name */ + if (!bus_desktop_file_get_string (desktop_file, + DBUS_SERVICE_SECTION, + DBUS_SERVICE_NAME, + &name_tmp, + error)) + goto failed; + + /* verify that the name is the same as the file service name */ + if (strcmp (service_name, name_tmp) != 0) + { + dbus_set_error (error, DBUS_ERROR_SPAWN_FILE_INVALID, + "Service '%s' does not match expected value", name_tmp); + goto failed_free; + } + + retval = TRUE; + +failed_free: + /* we don't return the name, so free it here */ + dbus_free (name_tmp); +failed: + return retval; +} + +static dbus_bool_t +get_parameters_for_service (BusDesktopFile *desktop_file, + const char *service_name, + char **exec, + char **user, + DBusError *error) +{ + char *exec_tmp; + char *user_tmp; + + exec_tmp = NULL; + user_tmp = NULL; + + /* check the name of the service */ + if (!check_service_name (desktop_file, service_name, error)) + goto failed; + + /* get the complete path of the executable */ + if (!bus_desktop_file_get_string (desktop_file, + DBUS_SERVICE_SECTION, + DBUS_SERVICE_EXEC, + &exec_tmp, + error)) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + goto failed; + } + + /* get the user that should run this service - user is compulsary for system activation */ + if (!bus_desktop_file_get_string (desktop_file, + DBUS_SERVICE_SECTION, + DBUS_SERVICE_USER, + &user_tmp, + error)) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + goto failed; + } + + /* only assign if all the checks passed */ + *exec = exec_tmp; + *user = user_tmp; + return TRUE; + +failed: + dbus_free (exec_tmp); + dbus_free (user_tmp); + return FALSE; +} + +static dbus_bool_t +switch_user (char *user, DBusError *error) +{ +#ifndef ACTIVATION_LAUNCHER_TEST + struct passwd *pw; + + /* find user */ + pw = getpwnam (user); + if (!pw) + { + dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED, + "cannot find user '%s'\n", user); + return FALSE; + } + + /* initialize the group access list */ + if (initgroups (user, pw->pw_gid)) + { + dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED, + "could not initialize groups"); + return FALSE; + } + + /* change to the primary group for the user */ + if (setgid (pw->pw_gid)) + { + dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED, + "cannot setgid group %i", pw->pw_gid); + return FALSE; + } + + /* change to the user specified */ + if (setuid (pw->pw_uid) < 0) + { + dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED, + "cannot setuid user %i", pw->pw_uid); + return FALSE; + } +#endif + return TRUE; +} + +static dbus_bool_t +exec_for_correct_user (char *exec, char *user, DBusError *error) +{ + char **argv; + int argc; + dbus_bool_t retval; + + argc = 0; + retval = TRUE; + argv = NULL; + + if (!switch_user (user, error)) + return FALSE; + + /* convert command into arguments */ + if (!_dbus_shell_parse_argv (exec, &argc, &argv, error)) + return FALSE; + +#ifndef ACTIVATION_LAUNCHER_DO_OOM + /* replace with new binary, with no environment */ + if (execv (argv[0], argv) < 0) + { + dbus_set_error (error, DBUS_ERROR_SPAWN_EXEC_FAILED, + "Failed to exec: %s", argv[0]); + retval = FALSE; + } +#endif + + dbus_free_string_array (argv); + return retval; +} + +static dbus_bool_t +check_bus_name (const char *bus_name, + DBusError *error) +{ + DBusString str; + + _dbus_string_init_const (&str, bus_name); + if (!_dbus_validate_bus_name (&str, 0, _dbus_string_get_length (&str))) + { + dbus_set_error (error, DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND, + "bus name '%s' is not a valid bus name\n", + bus_name); + return FALSE; + } + + return TRUE; +} + +static dbus_bool_t +get_correct_parser (BusConfigParser **parser, DBusError *error) +{ + DBusString config_file; + dbus_bool_t retval; + const char *test_config_file; + + retval = FALSE; + test_config_file = NULL; + +#ifdef ACTIVATION_LAUNCHER_TEST + /* there is no _way_ we should be setuid if this define is set. + * but we should be doubly paranoid and check... */ + if (getuid() != geteuid()) + _dbus_assert_not_reached ("dbus-daemon-launch-helper-test binary is setuid!"); + + /* this is not a security hole. The environment variable is only passed in the + * dbus-daemon-lauch-helper-test NON-SETUID launcher */ + test_config_file = _dbus_getenv ("TEST_LAUNCH_HELPER_CONFIG"); + if (test_config_file == NULL) + { + dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED, + "the TEST_LAUNCH_HELPER_CONFIG env variable is not set"); + goto out; + } +#endif + + /* we _only_ use the predefined system config file */ + if (!_dbus_string_init (&config_file)) + { + BUS_SET_OOM (error); + goto out; + } +#ifndef ACTIVATION_LAUNCHER_TEST + if (!_dbus_string_append (&config_file, DBUS_SYSTEM_CONFIG_FILE)) + { + BUS_SET_OOM (error); + goto out_free_config; + } +#else + if (!_dbus_string_append (&config_file, test_config_file)) + { + BUS_SET_OOM (error); + goto out_free_config; + } +#endif + + /* where are we pointing.... */ + _dbus_verbose ("dbus-daemon-activation-helper: using config file: %s\n", + _dbus_string_get_const_data (&config_file)); + + /* get the dbus user */ + *parser = bus_config_load (&config_file, TRUE, NULL, error); + if (*parser == NULL) + { + goto out_free_config; + } + + /* woot */ + retval = TRUE; + +out_free_config: + _dbus_string_free (&config_file); +out: + return retval; +} + +static dbus_bool_t +launch_bus_name (const char *bus_name, BusConfigParser *parser, DBusError *error) +{ + BusDesktopFile *desktop_file; + char *exec, *user; + dbus_bool_t retval; + + exec = NULL; + user = NULL; + retval = FALSE; + + /* get the correct service file for the name we are trying to activate */ + desktop_file = desktop_file_for_name (parser, bus_name, error); + if (desktop_file == NULL) + return FALSE; + + /* get exec and user for service name */ + if (!get_parameters_for_service (desktop_file, bus_name, &exec, &user, error)) + goto finish; + + _dbus_verbose ("dbus-daemon-activation-helper: Name='%s'\n", bus_name); + _dbus_verbose ("dbus-daemon-activation-helper: Exec='%s'\n", exec); + _dbus_verbose ("dbus-daemon-activation-helper: User='%s'\n", user); + + /* actually execute */ + if (!exec_for_correct_user (exec, user, error)) + goto finish; + + retval = TRUE; + +finish: + dbus_free (exec); + dbus_free (user); + bus_desktop_file_free (desktop_file); + return retval; +} + +static dbus_bool_t +check_dbus_user (BusConfigParser *parser, DBusError *error) +{ + const char *dbus_user; + + dbus_user = bus_config_parser_get_user (parser); + if (dbus_user == NULL) + { + dbus_set_error (error, DBUS_ERROR_SPAWN_CONFIG_INVALID, + "could not get user from config file\n"); + return FALSE; + } + + /* check to see if permissions are correct */ + if (!check_permissions (dbus_user, error)) + return FALSE; + + return TRUE; +} + +dbus_bool_t +run_launch_helper (const char *bus_name, + DBusError *error) +{ + BusConfigParser *parser; + dbus_bool_t retval; + + parser = NULL; + retval = FALSE; + + /* clear the environment, apart from a few select settings */ + if (!clear_environment (error)) + goto error; + + /* check to see if we have a valid bus name */ + if (!check_bus_name (bus_name, error)) + goto error; + + /* get the correct parser, either the test or default parser */ + if (!get_correct_parser (&parser, error)) + goto error; + + /* check we are being invoked by the correct dbus user */ + if (!check_dbus_user (parser, error)) + goto error_free_parser; + + /* launch the bus with the service defined user */ + if (!launch_bus_name (bus_name, parser, error)) + goto error_free_parser; + + /* woohoo! */ + retval = TRUE; + +error_free_parser: + bus_config_parser_unref (parser); +error: + return retval; +} + diff --git a/bus/activation-helper.h b/bus/activation-helper.h new file mode 100644 index 00000000..361a4c6a --- /dev/null +++ b/bus/activation-helper.h @@ -0,0 +1,31 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* activation-helper.h The actual activation helper split from the main + * function for testing. + * + * Copyright (C) 2007 Red Hat, Inc. + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef BUS_ACTIVATION_HELPER_H +#define BUS_ACTIVATION_HELPER_H + +dbus_bool_t run_launch_helper (const char *bus_name, DBusError *error); + + +#endif /* BUS_ACTIVATION_HELPER_H */ diff --git a/bus/activation.c b/bus/activation.c new file mode 100644 index 00000000..2fcd85d2 --- /dev/null +++ b/bus/activation.c @@ -0,0 +1,2351 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* activation.c Activation of services + * + * Copyright (C) 2003 CodeFactory AB + * Copyright (C) 2003 Red Hat, Inc. + * Copyright (C) 2004 Imendio HB + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#include "activation.h" +#include "activation-exit-codes.h" +#include "desktop-file.h" +#include "dispatch.h" +#include "services.h" +#include "test.h" +#include "utils.h" +#include <dbus/dbus-internals.h> +#include <dbus/dbus-hash.h> +#include <dbus/dbus-list.h> +#include <dbus/dbus-shell.h> +#include <dbus/dbus-spawn.h> +#include <dbus/dbus-timeout.h> +#include <dbus/dbus-sysdeps.h> +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif + +struct BusActivation +{ + int refcount; + DBusHashTable *entries; + DBusHashTable *pending_activations; + char *server_address; + BusContext *context; + int n_pending_activations; /**< This is in fact the number of BusPendingActivationEntry, + * i.e. number of pending activation requests, not pending + * activations per se + */ + DBusHashTable *directories; + DBusHashTable *environment; +}; + +typedef struct +{ + int refcount; + char *dir_c; + DBusHashTable *entries; +} BusServiceDirectory; + +typedef struct +{ + int refcount; + char *name; + char *exec; + char *user; + unsigned long mtime; + BusServiceDirectory *s_dir; + char *filename; +} BusActivationEntry; + +typedef struct BusPendingActivationEntry BusPendingActivationEntry; + +struct BusPendingActivationEntry +{ + DBusMessage *activation_message; + DBusConnection *connection; + + dbus_bool_t auto_activation; +}; + +typedef struct +{ + int refcount; + BusActivation *activation; + char *service_name; + char *exec; + DBusList *entries; + int n_entries; + DBusBabysitter *babysitter; + DBusTimeout *timeout; + unsigned int timeout_added : 1; +} BusPendingActivation; + +#if 0 +static BusServiceDirectory * +bus_service_directory_ref (BusServiceDirectory *dir) +{ + _dbus_assert (dir->refcount); + + dir->refcount++; + + return dir; +} +#endif + +static void +bus_service_directory_unref (BusServiceDirectory *dir) +{ + if (dir == NULL) + return; + + _dbus_assert (dir->refcount > 0); + dir->refcount--; + + if (dir->refcount > 0) + return; + + if (dir->entries) + _dbus_hash_table_unref (dir->entries); + + dbus_free (dir->dir_c); + dbus_free (dir); +} + +static void +bus_pending_activation_entry_free (BusPendingActivationEntry *entry) +{ + if (entry->activation_message) + dbus_message_unref (entry->activation_message); + + if (entry->connection) + dbus_connection_unref (entry->connection); + + dbus_free (entry); +} + +static void +handle_timeout_callback (DBusTimeout *timeout, + void *data) +{ + BusPendingActivation *pending_activation = data; + + while (!dbus_timeout_handle (pending_activation->timeout)) + _dbus_wait_for_memory (); +} + +static BusPendingActivation * +bus_pending_activation_ref (BusPendingActivation *pending_activation) +{ + _dbus_assert (pending_activation->refcount > 0); + pending_activation->refcount += 1; + + return pending_activation; +} + +static void +bus_pending_activation_unref (BusPendingActivation *pending_activation) +{ + DBusList *link; + + if (pending_activation == NULL) /* hash table requires this */ + return; + + _dbus_assert (pending_activation->refcount > 0); + pending_activation->refcount -= 1; + + if (pending_activation->refcount > 0) + return; + + if (pending_activation->timeout_added) + { + _dbus_loop_remove_timeout (bus_context_get_loop (pending_activation->activation->context), + pending_activation->timeout, + handle_timeout_callback, pending_activation); + pending_activation->timeout_added = FALSE; + } + + if (pending_activation->timeout) + _dbus_timeout_unref (pending_activation->timeout); + + if (pending_activation->babysitter) + { + if (!_dbus_babysitter_set_watch_functions (pending_activation->babysitter, + NULL, NULL, NULL, + pending_activation->babysitter, + NULL)) + _dbus_assert_not_reached ("setting watch functions to NULL failed"); + + _dbus_babysitter_unref (pending_activation->babysitter); + } + + dbus_free (pending_activation->service_name); + dbus_free (pending_activation->exec); + + link = _dbus_list_get_first_link (&pending_activation->entries); + + while (link != NULL) + { + BusPendingActivationEntry *entry = link->data; + + bus_pending_activation_entry_free (entry); + + link = _dbus_list_get_next_link (&pending_activation->entries, link); + } + _dbus_list_clear (&pending_activation->entries); + + pending_activation->activation->n_pending_activations -= + pending_activation->n_entries; + + _dbus_assert (pending_activation->activation->n_pending_activations >= 0); + + dbus_free (pending_activation); +} + +static BusActivationEntry * +bus_activation_entry_ref (BusActivationEntry *entry) +{ + _dbus_assert (entry->refcount > 0); + entry->refcount++; + + return entry; +} + +static void +bus_activation_entry_unref (BusActivationEntry *entry) +{ + if (entry == NULL) /* hash table requires this */ + return; + + _dbus_assert (entry->refcount > 0); + entry->refcount--; + + if (entry->refcount > 0) + return; + + dbus_free (entry->name); + dbus_free (entry->exec); + dbus_free (entry->user); + dbus_free (entry->filename); + + dbus_free (entry); +} + +static dbus_bool_t +update_desktop_file_entry (BusActivation *activation, + BusServiceDirectory *s_dir, + DBusString *filename, + BusDesktopFile *desktop_file, + DBusError *error) +{ + char *name, *exec, *user; + BusActivationEntry *entry; + DBusStat stat_buf; + DBusString file_path; + DBusError tmp_error; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + name = NULL; + exec = NULL; + user = NULL; + entry = NULL; + + dbus_error_init (&tmp_error); + + if (!_dbus_string_init (&file_path)) + { + BUS_SET_OOM (error); + return FALSE; + } + + if (!_dbus_string_append (&file_path, s_dir->dir_c) || + !_dbus_concat_dir_and_file (&file_path, filename)) + { + BUS_SET_OOM (error); + goto failed; + } + + if (!_dbus_stat (&file_path, &stat_buf, NULL)) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "Can't stat the service file\n"); + goto failed; + } + + if (!bus_desktop_file_get_string (desktop_file, + DBUS_SERVICE_SECTION, + DBUS_SERVICE_NAME, + &name, + error)) + goto failed; + + if (!bus_desktop_file_get_string (desktop_file, + DBUS_SERVICE_SECTION, + DBUS_SERVICE_EXEC, + &exec, + error)) + goto failed; + + /* user is not _required_ unless we are using system activation */ + if (!bus_desktop_file_get_string (desktop_file, + DBUS_SERVICE_SECTION, + DBUS_SERVICE_USER, + &user, &tmp_error)) + { + _DBUS_ASSERT_ERROR_IS_SET (&tmp_error); + /* if we got OOM, then exit */ + if (dbus_error_has_name (&tmp_error, DBUS_ERROR_NO_MEMORY)) + { + dbus_move_error (&tmp_error, error); + goto failed; + } + else + { + /* if we have error because we didn't find anything then continue */ + dbus_error_free (&tmp_error); + dbus_free (user); + user = NULL; + } + } + _DBUS_ASSERT_ERROR_IS_CLEAR (&tmp_error); + + entry = _dbus_hash_table_lookup_string (s_dir->entries, + _dbus_string_get_const_data (filename)); + if (entry == NULL) /* New file */ + { + /* FIXME we need a better-defined algorithm for which service file to + * pick than "whichever one is first in the directory listing" + */ + if (_dbus_hash_table_lookup_string (activation->entries, name)) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "Service %s already exists in activation entry list\n", name); + goto failed; + } + + entry = dbus_new0 (BusActivationEntry, 1); + if (entry == NULL) + { + BUS_SET_OOM (error); + goto failed; + } + + entry->name = name; + entry->exec = exec; + entry->user = user; + entry->refcount = 1; + + entry->s_dir = s_dir; + entry->filename = _dbus_strdup (_dbus_string_get_const_data (filename)); + if (!entry->filename) + { + BUS_SET_OOM (error); + goto failed; + } + + if (!_dbus_hash_table_insert_string (activation->entries, entry->name, bus_activation_entry_ref (entry))) + { + BUS_SET_OOM (error); + goto failed; + } + + if (!_dbus_hash_table_insert_string (s_dir->entries, entry->filename, bus_activation_entry_ref (entry))) + { + /* Revert the insertion in the entries table */ + _dbus_hash_table_remove_string (activation->entries, entry->name); + BUS_SET_OOM (error); + goto failed; + } + + _dbus_verbose ("Added \"%s\" to list of services\n", entry->name); + } + else /* Just update the entry */ + { + bus_activation_entry_ref (entry); + _dbus_hash_table_remove_string (activation->entries, entry->name); + + if (_dbus_hash_table_lookup_string (activation->entries, name)) + { + _dbus_verbose ("The new service name \"%s\" of service file \"%s\" already in cache, ignoring\n", + name, _dbus_string_get_const_data (&file_path)); + goto failed; + } + + dbus_free (entry->name); + dbus_free (entry->exec); + dbus_free (entry->user); + entry->name = name; + entry->exec = exec; + entry->user = user; + if (!_dbus_hash_table_insert_string (activation->entries, + entry->name, bus_activation_entry_ref(entry))) + { + BUS_SET_OOM (error); + /* Also remove path to entries hash since we want this in sync with + * the entries hash table */ + _dbus_hash_table_remove_string (entry->s_dir->entries, + entry->filename); + bus_activation_entry_unref (entry); + return FALSE; + } + } + + entry->mtime = stat_buf.mtime; + + _dbus_string_free (&file_path); + bus_activation_entry_unref (entry); + + return TRUE; + +failed: + dbus_free (name); + dbus_free (exec); + dbus_free (user); + _dbus_string_free (&file_path); + + if (entry) + bus_activation_entry_unref (entry); + + return FALSE; +} + +static dbus_bool_t +check_service_file (BusActivation *activation, + BusActivationEntry *entry, + BusActivationEntry **updated_entry, + DBusError *error) +{ + DBusStat stat_buf; + dbus_bool_t retval; + BusActivationEntry *tmp_entry; + DBusString file_path; + DBusString filename; + + retval = TRUE; + tmp_entry = entry; + + _dbus_string_init_const (&filename, entry->filename); + + if (!_dbus_string_init (&file_path)) + { + BUS_SET_OOM (error); + return FALSE; + } + + if (!_dbus_string_append (&file_path, entry->s_dir->dir_c) || + !_dbus_concat_dir_and_file (&file_path, &filename)) + { + BUS_SET_OOM (error); + retval = FALSE; + goto out; + } + + if (!_dbus_stat (&file_path, &stat_buf, NULL)) + { + _dbus_verbose ("****** Can't stat file \"%s\", removing from cache\n", + _dbus_string_get_const_data (&file_path)); + + _dbus_hash_table_remove_string (activation->entries, entry->name); + _dbus_hash_table_remove_string (entry->s_dir->entries, entry->filename); + + tmp_entry = NULL; + retval = TRUE; + goto out; + } + else + { + if (stat_buf.mtime > entry->mtime) + { + BusDesktopFile *desktop_file; + DBusError tmp_error; + + dbus_error_init (&tmp_error); + + desktop_file = bus_desktop_file_load (&file_path, &tmp_error); + if (desktop_file == NULL) + { + _dbus_verbose ("Could not load %s: %s\n", + _dbus_string_get_const_data (&file_path), + tmp_error.message); + if (dbus_error_has_name (&tmp_error, DBUS_ERROR_NO_MEMORY)) + { + dbus_move_error (&tmp_error, error); + retval = FALSE; + goto out; + } + dbus_error_free (&tmp_error); + retval = TRUE; + goto out; + } + + /* @todo We can return OOM or a DBUS_ERROR_FAILED error + * Handle these both better + */ + if (!update_desktop_file_entry (activation, entry->s_dir, &filename, desktop_file, &tmp_error)) + { + bus_desktop_file_free (desktop_file); + if (dbus_error_has_name (&tmp_error, DBUS_ERROR_NO_MEMORY)) + { + dbus_move_error (&tmp_error, error); + retval = FALSE; + goto out; + } + dbus_error_free (&tmp_error); + retval = TRUE; + goto out; + } + + bus_desktop_file_free (desktop_file); + retval = TRUE; + } + } + +out: + _dbus_string_free (&file_path); + + if (updated_entry != NULL) + *updated_entry = tmp_entry; + return retval; +} + + +/* warning: this doesn't fully "undo" itself on failure, i.e. doesn't strip + * hash entries it already added. + */ +static dbus_bool_t +update_directory (BusActivation *activation, + BusServiceDirectory *s_dir, + DBusError *error) +{ + DBusDirIter *iter; + DBusString dir, filename; + BusDesktopFile *desktop_file; + DBusError tmp_error; + dbus_bool_t retval; + BusActivationEntry *entry; + DBusString full_path; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + iter = NULL; + desktop_file = NULL; + + _dbus_string_init_const (&dir, s_dir->dir_c); + + if (!_dbus_string_init (&filename)) + { + BUS_SET_OOM (error); + return FALSE; + } + + if (!_dbus_string_init (&full_path)) + { + BUS_SET_OOM (error); + _dbus_string_free (&filename); + return FALSE; + } + + retval = FALSE; + + /* from this point it's safe to "goto out" */ + + iter = _dbus_directory_open (&dir, error); + if (iter == NULL) + { + _dbus_verbose ("Failed to open directory %s: %s\n", + s_dir->dir_c, + error ? error->message : "unknown"); + goto out; + } + + /* Now read the files */ + dbus_error_init (&tmp_error); + while (_dbus_directory_get_next_file (iter, &filename, &tmp_error)) + { + _dbus_assert (!dbus_error_is_set (&tmp_error)); + + _dbus_string_set_length (&full_path, 0); + + if (!_dbus_string_ends_with_c_str (&filename, ".service")) + { + _dbus_verbose ("Skipping non-.service file %s\n", + _dbus_string_get_const_data (&filename)); + continue; + } + + entry = _dbus_hash_table_lookup_string (s_dir->entries, _dbus_string_get_const_data (&filename)); + if (entry) /* Already has this service file in the cache */ + { + if (!check_service_file (activation, entry, NULL, error)) + goto out; + + continue; + } + + if (!_dbus_string_append (&full_path, s_dir->dir_c) || + !_dbus_concat_dir_and_file (&full_path, &filename)) + { + BUS_SET_OOM (error); + goto out; + } + + /* New file */ + desktop_file = bus_desktop_file_load (&full_path, &tmp_error); + if (desktop_file == NULL) + { + _dbus_verbose ("Could not load %s: %s\n", + _dbus_string_get_const_data (&full_path), + tmp_error.message); + + if (dbus_error_has_name (&tmp_error, DBUS_ERROR_NO_MEMORY)) + { + dbus_move_error (&tmp_error, error); + goto out; + } + + dbus_error_free (&tmp_error); + continue; + } + + /* @todo We can return OOM or a DBUS_ERROR_FAILED error + * Handle these both better + */ + if (!update_desktop_file_entry (activation, s_dir, &filename, desktop_file, &tmp_error)) + { + bus_desktop_file_free (desktop_file); + desktop_file = NULL; + + _dbus_verbose ("Could not add %s to activation entry list: %s\n", + _dbus_string_get_const_data (&full_path), tmp_error.message); + + if (dbus_error_has_name (&tmp_error, DBUS_ERROR_NO_MEMORY)) + { + dbus_move_error (&tmp_error, error); + goto out; + } + + dbus_error_free (&tmp_error); + continue; + } + else + { + bus_desktop_file_free (desktop_file); + desktop_file = NULL; + continue; + } + } + + if (dbus_error_is_set (&tmp_error)) + { + dbus_move_error (&tmp_error, error); + goto out; + } + + retval = TRUE; + + out: + if (!retval) + _DBUS_ASSERT_ERROR_IS_SET (error); + else + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + if (iter != NULL) + _dbus_directory_close (iter); + _dbus_string_free (&filename); + _dbus_string_free (&full_path); + + return retval; +} + +static dbus_bool_t +populate_environment (BusActivation *activation) +{ + DBusString key; + DBusString value; + int i; + char **environment; + dbus_bool_t retval = FALSE; + + environment = _dbus_get_environment (); + + if (environment == NULL) + return FALSE; + + if (!_dbus_string_init (&key)) + { + dbus_free_string_array (environment); + return FALSE; + } + + if (!_dbus_string_init (&value)) + { + _dbus_string_free (&key); + dbus_free_string_array (environment); + return FALSE; + } + + for (i = 0; environment[i] != NULL; i++) + { + if (!_dbus_string_append (&key, environment[i])) + break; + + if (_dbus_string_split_on_byte (&key, '=', &value)) + { + char *hash_key, *hash_value; + + if (!_dbus_string_steal_data (&key, &hash_key)) + break; + + if (!_dbus_string_steal_data (&value, &hash_value)) + break; + + if (!_dbus_hash_table_insert_string (activation->environment, + hash_key, hash_value)) + break; + } + _dbus_string_set_length (&key, 0); + _dbus_string_set_length (&value, 0); + } + + if (environment[i] != NULL) + goto out; + + retval = TRUE; +out: + + _dbus_string_free (&key); + _dbus_string_free (&value); + dbus_free_string_array (environment); + + return retval; +} + +dbus_bool_t +bus_activation_reload (BusActivation *activation, + const DBusString *address, + DBusList **directories, + DBusError *error) +{ + DBusList *link; + char *dir; + + if (activation->server_address != NULL) + dbus_free (activation->server_address); + if (!_dbus_string_copy_data (address, &activation->server_address)) + { + BUS_SET_OOM (error); + goto failed; + } + + if (activation->entries != NULL) + _dbus_hash_table_unref (activation->entries); + activation->entries = _dbus_hash_table_new (DBUS_HASH_STRING, NULL, + (DBusFreeFunction)bus_activation_entry_unref); + if (activation->entries == NULL) + { + BUS_SET_OOM (error); + goto failed; + } + + if (activation->directories != NULL) + _dbus_hash_table_unref (activation->directories); + activation->directories = _dbus_hash_table_new (DBUS_HASH_STRING, NULL, + (DBusFreeFunction)bus_service_directory_unref); + + if (activation->directories == NULL) + { + BUS_SET_OOM (error); + goto failed; + } + + link = _dbus_list_get_first_link (directories); + while (link != NULL) + { + BusServiceDirectory *s_dir; + + dir = _dbus_strdup ((const char *) link->data); + if (!dir) + { + BUS_SET_OOM (error); + goto failed; + } + + s_dir = dbus_new0 (BusServiceDirectory, 1); + if (!s_dir) + { + dbus_free (dir); + BUS_SET_OOM (error); + goto failed; + } + + s_dir->refcount = 1; + s_dir->dir_c = dir; + + s_dir->entries = _dbus_hash_table_new (DBUS_HASH_STRING, NULL, + (DBusFreeFunction)bus_activation_entry_unref); + + if (!s_dir->entries) + { + bus_service_directory_unref (s_dir); + BUS_SET_OOM (error); + goto failed; + } + + if (!_dbus_hash_table_insert_string (activation->directories, s_dir->dir_c, s_dir)) + { + bus_service_directory_unref (s_dir); + BUS_SET_OOM (error); + goto failed; + } + + /* only fail on OOM, it is ok if we can't read the directory */ + if (!update_directory (activation, s_dir, error)) + { + if (dbus_error_has_name (error, DBUS_ERROR_NO_MEMORY)) + goto failed; + else + dbus_error_free (error); + } + + link = _dbus_list_get_next_link (directories, link); + } + + return TRUE; + failed: + return FALSE; +} + +BusActivation* +bus_activation_new (BusContext *context, + const DBusString *address, + DBusList **directories, + DBusError *error) +{ + BusActivation *activation; + DBusList *link; + char *dir; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + activation = dbus_new0 (BusActivation, 1); + if (activation == NULL) + { + BUS_SET_OOM (error); + return NULL; + } + + activation->refcount = 1; + activation->context = context; + activation->n_pending_activations = 0; + + if (!bus_activation_reload (activation, address, directories, error)) + goto failed; + + /* Initialize this hash table once, we don't want to lose pending + * activations on reload. */ + activation->pending_activations = _dbus_hash_table_new (DBUS_HASH_STRING, NULL, + (DBusFreeFunction)bus_pending_activation_unref); + + if (activation->pending_activations == NULL) + { + BUS_SET_OOM (error); + goto failed; + } + + activation->environment = _dbus_hash_table_new (DBUS_HASH_STRING, + (DBusFreeFunction) dbus_free, + (DBusFreeFunction) dbus_free); + + if (activation->environment == NULL) + { + BUS_SET_OOM (error); + goto failed; + } + + if (!populate_environment (activation)) + { + BUS_SET_OOM (error); + goto failed; + } + + return activation; + + failed: + bus_activation_unref (activation); + return NULL; +} + +BusActivation * +bus_activation_ref (BusActivation *activation) +{ + _dbus_assert (activation->refcount > 0); + + activation->refcount += 1; + + return activation; +} + +void +bus_activation_unref (BusActivation *activation) +{ + _dbus_assert (activation->refcount > 0); + + activation->refcount -= 1; + + if (activation->refcount > 0) + return; + + dbus_free (activation->server_address); + if (activation->entries) + _dbus_hash_table_unref (activation->entries); + if (activation->pending_activations) + _dbus_hash_table_unref (activation->pending_activations); + if (activation->directories) + _dbus_hash_table_unref (activation->directories); + if (activation->environment) + _dbus_hash_table_unref (activation->environment); + + dbus_free (activation); +} + +static dbus_bool_t +add_bus_environment (BusActivation *activation, + DBusError *error) +{ + const char *type; + + if (!bus_activation_set_environment_variable (activation, + "DBUS_STARTER_ADDRESS", + activation->server_address, + error)) + return FALSE; + + type = bus_context_get_type (activation->context); + if (type != NULL) + { + if (!bus_activation_set_environment_variable (activation, + "DBUS_STARTER_BUS_TYPE", type, + error)) + return FALSE; + + if (strcmp (type, "session") == 0) + { + if (!bus_activation_set_environment_variable (activation, + "DBUS_SESSION_BUS_ADDRESS", + activation->server_address, + error)) + return FALSE; + } + else if (strcmp (type, "system") == 0) + { + if (!bus_activation_set_environment_variable (activation, + "DBUS_SYSTEM_BUS_ADDRESS", + activation->server_address, + error)) + return FALSE; + } + } + + return TRUE; +} + +typedef struct +{ + BusPendingActivation *pending_activation; + DBusPreallocatedHash *hash_entry; +} RestorePendingData; + +static void +restore_pending (void *data) +{ + RestorePendingData *d = data; + + _dbus_assert (d->pending_activation != NULL); + _dbus_assert (d->hash_entry != NULL); + + _dbus_verbose ("Restoring pending activation for service %s, has timeout = %d\n", + d->pending_activation->service_name, + d->pending_activation->timeout_added); + + _dbus_hash_table_insert_string_preallocated (d->pending_activation->activation->pending_activations, + d->hash_entry, + d->pending_activation->service_name, d->pending_activation); + + bus_pending_activation_ref (d->pending_activation); + + d->hash_entry = NULL; +} + +static void +free_pending_restore_data (void *data) +{ + RestorePendingData *d = data; + + if (d->hash_entry) + _dbus_hash_table_free_preallocated_entry (d->pending_activation->activation->pending_activations, + d->hash_entry); + + bus_pending_activation_unref (d->pending_activation); + + dbus_free (d); +} + +static dbus_bool_t +add_restore_pending_to_transaction (BusTransaction *transaction, + BusPendingActivation *pending_activation) +{ + RestorePendingData *d; + + d = dbus_new (RestorePendingData, 1); + if (d == NULL) + return FALSE; + + d->pending_activation = pending_activation; + d->hash_entry = _dbus_hash_table_preallocate_entry (d->pending_activation->activation->pending_activations); + + bus_pending_activation_ref (d->pending_activation); + + if (d->hash_entry == NULL || + !bus_transaction_add_cancel_hook (transaction, restore_pending, d, + free_pending_restore_data)) + { + free_pending_restore_data (d); + return FALSE; + } + + _dbus_verbose ("Saved pending activation to be restored if the transaction fails\n"); + + return TRUE; +} + +dbus_bool_t +bus_activation_service_created (BusActivation *activation, + const char *service_name, + BusTransaction *transaction, + DBusError *error) +{ + BusPendingActivation *pending_activation; + DBusMessage *message; + DBusList *link; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + /* Check if it's a pending activation */ + pending_activation = _dbus_hash_table_lookup_string (activation->pending_activations, service_name); + + if (!pending_activation) + return TRUE; + + link = _dbus_list_get_first_link (&pending_activation->entries); + while (link != NULL) + { + BusPendingActivationEntry *entry = link->data; + DBusList *next = _dbus_list_get_next_link (&pending_activation->entries, link); + + if (dbus_connection_get_is_connected (entry->connection)) + { + /* Only send activation replies to regular activation requests. */ + if (!entry->auto_activation) + { + dbus_uint32_t result; + + message = dbus_message_new_method_return (entry->activation_message); + if (!message) + { + BUS_SET_OOM (error); + goto error; + } + + result = DBUS_START_REPLY_SUCCESS; + + if (!dbus_message_append_args (message, + DBUS_TYPE_UINT32, &result, + DBUS_TYPE_INVALID)) + { + dbus_message_unref (message); + BUS_SET_OOM (error); + goto error; + } + + if (!bus_transaction_send_from_driver (transaction, entry->connection, message)) + { + dbus_message_unref (message); + BUS_SET_OOM (error); + goto error; + } + + dbus_message_unref (message); + } + } + + link = next; + } + + return TRUE; + + error: + return FALSE; +} + +dbus_bool_t +bus_activation_send_pending_auto_activation_messages (BusActivation *activation, + BusService *service, + BusTransaction *transaction, + DBusError *error) +{ + BusPendingActivation *pending_activation; + DBusList *link; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + /* Check if it's a pending activation */ + pending_activation = _dbus_hash_table_lookup_string (activation->pending_activations, + bus_service_get_name (service)); + + if (!pending_activation) + return TRUE; + + link = _dbus_list_get_first_link (&pending_activation->entries); + while (link != NULL) + { + BusPendingActivationEntry *entry = link->data; + DBusList *next = _dbus_list_get_next_link (&pending_activation->entries, link); + + if (entry->auto_activation && dbus_connection_get_is_connected (entry->connection)) + { + DBusConnection *addressed_recipient; + + addressed_recipient = bus_service_get_primary_owners_connection (service); + + /* Resume dispatching where we left off in bus_dispatch() */ + if (!bus_dispatch_matches (transaction, + entry->connection, + addressed_recipient, + entry->activation_message, error)) + goto error; + } + + link = next; + } + + if (!add_restore_pending_to_transaction (transaction, pending_activation)) + { + _dbus_verbose ("Could not add cancel hook to transaction to revert removing pending activation\n"); + BUS_SET_OOM (error); + goto error; + } + + _dbus_hash_table_remove_string (activation->pending_activations, bus_service_get_name (service)); + + return TRUE; + + error: + return FALSE; +} + +/** + * FIXME @todo the error messages here would ideally be preallocated + * so we don't need to allocate memory to send them. + * Using the usual tactic, prealloc an OOM message, then + * if we can't alloc the real error send the OOM error instead. + */ +static dbus_bool_t +try_send_activation_failure (BusPendingActivation *pending_activation, + const DBusError *how) +{ + BusActivation *activation; + DBusList *link; + BusTransaction *transaction; + + activation = pending_activation->activation; + + transaction = bus_transaction_new (activation->context); + if (transaction == NULL) + return FALSE; + + link = _dbus_list_get_first_link (&pending_activation->entries); + while (link != NULL) + { + BusPendingActivationEntry *entry = link->data; + DBusList *next = _dbus_list_get_next_link (&pending_activation->entries, link); + + if (dbus_connection_get_is_connected (entry->connection)) + { + if (!bus_transaction_send_error_reply (transaction, + entry->connection, + how, + entry->activation_message)) + goto error; + } + + link = next; + } + + bus_transaction_execute_and_free (transaction); + + return TRUE; + + error: + if (transaction) + bus_transaction_cancel_and_free (transaction); + return FALSE; +} + +/** + * Free the pending activation and send an error message to all the + * connections that were waiting for it. + */ +static void +pending_activation_failed (BusPendingActivation *pending_activation, + const DBusError *how) +{ + /* FIXME use preallocated OOM messages instead of bus_wait_for_memory() */ + while (!try_send_activation_failure (pending_activation, how)) + _dbus_wait_for_memory (); + + /* Destroy this pending activation */ + _dbus_hash_table_remove_string (pending_activation->activation->pending_activations, + pending_activation->service_name); +} + +/** + * Depending on the exit code of the helper, set the error accordingly + */ +static void +handle_servicehelper_exit_error (int exit_code, + DBusError *error) +{ + switch (exit_code) + { + case BUS_SPAWN_EXIT_CODE_NO_MEMORY: + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, + "Launcher could not run (out of memory)"); + break; + case BUS_SPAWN_EXIT_CODE_SETUP_FAILED: + dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED, + "Failed to setup environment correctly"); + break; + case BUS_SPAWN_EXIT_CODE_NAME_INVALID: + dbus_set_error (error, DBUS_ERROR_SPAWN_SERVICE_INVALID, + "Bus name is not valid or missing"); + break; + case BUS_SPAWN_EXIT_CODE_SERVICE_NOT_FOUND: + dbus_set_error (error, DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND, + "Bus name not found in system service directory"); + break; + case BUS_SPAWN_EXIT_CODE_PERMISSIONS_INVALID: + dbus_set_error (error, DBUS_ERROR_SPAWN_PERMISSIONS_INVALID, + "The permission of the setuid helper is not correct"); + break; + case BUS_SPAWN_EXIT_CODE_FILE_INVALID: + dbus_set_error (error, DBUS_ERROR_SPAWN_PERMISSIONS_INVALID, + "The service file is incorrect or does not have all required attributes"); + break; + case BUS_SPAWN_EXIT_CODE_EXEC_FAILED: + dbus_set_error (error, DBUS_ERROR_SPAWN_EXEC_FAILED, + "Cannot launch daemon, file not found or permissions invalid"); + break; + case BUS_SPAWN_EXIT_CODE_INVALID_ARGS: + dbus_set_error (error, DBUS_ERROR_INVALID_ARGS, + "Invalid arguments to command line"); + break; + case BUS_SPAWN_EXIT_CODE_CHILD_SIGNALED: + dbus_set_error (error, DBUS_ERROR_SPAWN_CHILD_SIGNALED, + "Launched child was signaled, it probably crashed"); + break; + default: + dbus_set_error (error, DBUS_ERROR_SPAWN_CHILD_EXITED, + "Launch helper exited with unknown return code %i", exit_code); + break; + } +} + +static dbus_bool_t +babysitter_watch_callback (DBusWatch *watch, + unsigned int condition, + void *data) +{ + BusPendingActivation *pending_activation = data; + dbus_bool_t retval; + DBusBabysitter *babysitter; + dbus_bool_t uses_servicehelper; + + babysitter = pending_activation->babysitter; + + _dbus_babysitter_ref (babysitter); + + retval = dbus_watch_handle (watch, condition); + + /* There are two major cases here; are we the system bus or the session? Here this + * is distinguished by whether or not we use a setuid helper launcher. With the launch helper, + * some process exit codes are meaningful, processed by handle_servicehelper_exit_error. + * + * In both cases though, just ignore when a process exits with status 0; it's possible for + * a program to (misguidedly) "daemonize", and that appears to us as an exit. This closes a race + * condition between this code and the child process claiming the bus name. + */ + uses_servicehelper = bus_context_get_servicehelper (pending_activation->activation->context) != NULL; + + /* FIXME this is broken in the same way that + * connection watches used to be; there should be + * a separate callback for status change, instead + * of doing "if we handled a watch status might + * have changed" + * + * Fixing this lets us move dbus_watch_handle + * calls into dbus-mainloop.c + */ + if (_dbus_babysitter_get_child_exited (babysitter)) + { + DBusError error; + DBusHashIter iter; + dbus_bool_t activation_failed; + int exit_code = 0; + + dbus_error_init (&error); + + _dbus_babysitter_set_child_exit_error (babysitter, &error); + + /* Explicitly check for SPAWN_CHILD_EXITED to avoid overwriting an + * exec error */ + if (dbus_error_has_name (&error, DBUS_ERROR_SPAWN_CHILD_EXITED) + && _dbus_babysitter_get_child_exit_status (babysitter, &exit_code)) + { + activation_failed = exit_code != 0; + + dbus_error_free(&error); + + if (activation_failed) + { + if (uses_servicehelper) + handle_servicehelper_exit_error (exit_code, &error); + else + _dbus_babysitter_set_child_exit_error (babysitter, &error); + } + } + else + { + activation_failed = TRUE; + } + + if (activation_failed) + { + /* Destroy all pending activations with the same exec */ + _dbus_hash_iter_init (pending_activation->activation->pending_activations, + &iter); + while (_dbus_hash_iter_next (&iter)) + { + BusPendingActivation *p = _dbus_hash_iter_get_value (&iter); + + if (p != pending_activation && strcmp (p->exec, pending_activation->exec) == 0) + pending_activation_failed (p, &error); + } + + /* Destroys the pending activation */ + pending_activation_failed (pending_activation, &error); + + dbus_error_free (&error); + } + } + + _dbus_babysitter_unref (babysitter); + + return retval; +} + +static dbus_bool_t +add_babysitter_watch (DBusWatch *watch, + void *data) +{ + BusPendingActivation *pending_activation = data; + + return _dbus_loop_add_watch (bus_context_get_loop (pending_activation->activation->context), + watch, babysitter_watch_callback, pending_activation, + NULL); +} + +static void +remove_babysitter_watch (DBusWatch *watch, + void *data) +{ + BusPendingActivation *pending_activation = data; + + _dbus_loop_remove_watch (bus_context_get_loop (pending_activation->activation->context), + watch, babysitter_watch_callback, pending_activation); +} + +static dbus_bool_t +pending_activation_timed_out (void *data) +{ + BusPendingActivation *pending_activation = data; + DBusError error; + + /* Kill the spawned process, since it sucks + * (not sure this is what we want to do, but + * may as well try it for now) + */ + if (pending_activation->babysitter) + _dbus_babysitter_kill_child (pending_activation->babysitter); + + dbus_error_init (&error); + + dbus_set_error (&error, DBUS_ERROR_TIMED_OUT, + "Activation of %s timed out", + pending_activation->service_name); + + pending_activation_failed (pending_activation, &error); + + dbus_error_free (&error); + + return TRUE; +} + +static void +cancel_pending (void *data) +{ + BusPendingActivation *pending_activation = data; + + _dbus_verbose ("Canceling pending activation of %s\n", + pending_activation->service_name); + + if (pending_activation->babysitter) + _dbus_babysitter_kill_child (pending_activation->babysitter); + + _dbus_hash_table_remove_string (pending_activation->activation->pending_activations, + pending_activation->service_name); +} + +static void +free_pending_cancel_data (void *data) +{ + BusPendingActivation *pending_activation = data; + + bus_pending_activation_unref (pending_activation); +} + +static dbus_bool_t +add_cancel_pending_to_transaction (BusTransaction *transaction, + BusPendingActivation *pending_activation) +{ + if (!bus_transaction_add_cancel_hook (transaction, cancel_pending, + pending_activation, + free_pending_cancel_data)) + return FALSE; + + bus_pending_activation_ref (pending_activation); + + _dbus_verbose ("Saved pending activation to be canceled if the transaction fails\n"); + + return TRUE; +} + +static dbus_bool_t +update_service_cache (BusActivation *activation, DBusError *error) +{ + DBusHashIter iter; + + _dbus_hash_iter_init (activation->directories, &iter); + while (_dbus_hash_iter_next (&iter)) + { + DBusError tmp_error; + BusServiceDirectory *s_dir; + + s_dir = _dbus_hash_iter_get_value (&iter); + + dbus_error_init (&tmp_error); + if (!update_directory (activation, s_dir, &tmp_error)) + { + if (dbus_error_has_name (&tmp_error, DBUS_ERROR_NO_MEMORY)) + { + dbus_move_error (&tmp_error, error); + return FALSE; + } + + dbus_error_free (&tmp_error); + continue; + } + } + + return TRUE; +} + +static BusActivationEntry * +activation_find_entry (BusActivation *activation, + const char *service_name, + DBusError *error) +{ + BusActivationEntry *entry; + + entry = _dbus_hash_table_lookup_string (activation->entries, service_name); + if (!entry) + { + if (!update_service_cache (activation, error)) + return NULL; + + entry = _dbus_hash_table_lookup_string (activation->entries, + service_name); + } + else + { + BusActivationEntry *updated_entry; + + if (!check_service_file (activation, entry, &updated_entry, error)) + return NULL; + + entry = updated_entry; + } + + if (!entry) + { + dbus_set_error (error, DBUS_ERROR_SERVICE_UNKNOWN, + "The name %s was not provided by any .service files", + service_name); + return NULL; + } + + return entry; +} + +static char ** +bus_activation_get_environment (BusActivation *activation) +{ + char **environment; + int i, length; + DBusString entry; + DBusHashIter iter; + + length = _dbus_hash_table_get_n_entries (activation->environment); + + environment = dbus_new0 (char *, length + 1); + + if (environment == NULL) + return NULL; + + i = 0; + _dbus_hash_iter_init (activation->environment, &iter); + + if (!_dbus_string_init (&entry)) + { + dbus_free_string_array (environment); + return NULL; + } + + while (_dbus_hash_iter_next (&iter)) + { + const char *key, *value; + + key = (const char *) _dbus_hash_iter_get_string_key (&iter); + value = (const char *) _dbus_hash_iter_get_value (&iter); + + if (!_dbus_string_append_printf (&entry, "%s=%s", key, value)) + break; + + if (!_dbus_string_steal_data (&entry, environment + i)) + break; + i++; + } + + _dbus_string_free (&entry); + + if (i != length) + { + dbus_free_string_array (environment); + environment = NULL; + } + + return environment; +} + +dbus_bool_t +bus_activation_set_environment_variable (BusActivation *activation, + const char *key, + const char *value, + DBusError *error) +{ + char *hash_key; + char *hash_value; + dbus_bool_t retval; + + retval = FALSE; + hash_key = NULL; + hash_value = NULL; + hash_key = _dbus_strdup (key); + + if (hash_key == NULL) + goto out; + + hash_value = _dbus_strdup (value); + + if (hash_value == NULL) + goto out; + + if (!_dbus_hash_table_insert_string (activation->environment, + hash_key, hash_value)) + goto out; + + retval = TRUE; +out: + if (retval == FALSE) + { + dbus_free (hash_key); + dbus_free (hash_value); + BUS_SET_OOM (error); + } + + return retval; +} + +dbus_bool_t +bus_activation_activate_service (BusActivation *activation, + DBusConnection *connection, + BusTransaction *transaction, + dbus_bool_t auto_activation, + DBusMessage *activation_message, + const char *service_name, + DBusError *error) +{ + BusActivationEntry *entry; + BusPendingActivation *pending_activation; + BusPendingActivationEntry *pending_activation_entry; + DBusMessage *message; + DBusString service_str; + const char *servicehelper; + char **argv; + char **envp = NULL; + int argc; + dbus_bool_t retval; + DBusHashIter iter; + dbus_bool_t activated; + DBusString command; + + activated = TRUE; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + if (activation->n_pending_activations >= + bus_context_get_max_pending_activations (activation->context)) + { + dbus_set_error (error, DBUS_ERROR_LIMITS_EXCEEDED, + "The maximum number of pending activations has been reached, activation of %s failed", + service_name); + return FALSE; + } + + entry = activation_find_entry (activation, service_name, error); + if (!entry) + return FALSE; + + /* Bypass the registry lookup if we're auto-activating, bus_dispatch would not + * call us if the service is already active. + */ + if (!auto_activation) + { + /* Check if the service is active */ + _dbus_string_init_const (&service_str, service_name); + if (bus_registry_lookup (bus_context_get_registry (activation->context), &service_str) != NULL) + { + dbus_uint32_t result; + + _dbus_verbose ("Service \"%s\" is already active\n", service_name); + + message = dbus_message_new_method_return (activation_message); + + if (!message) + { + _dbus_verbose ("No memory to create reply to activate message\n"); + BUS_SET_OOM (error); + return FALSE; + } + + result = DBUS_START_REPLY_ALREADY_RUNNING; + + if (!dbus_message_append_args (message, + DBUS_TYPE_UINT32, &result, + DBUS_TYPE_INVALID)) + { + _dbus_verbose ("No memory to set args of reply to activate message\n"); + BUS_SET_OOM (error); + dbus_message_unref (message); + return FALSE; + } + + retval = bus_transaction_send_from_driver (transaction, connection, message); + dbus_message_unref (message); + if (!retval) + { + _dbus_verbose ("Failed to send reply\n"); + BUS_SET_OOM (error); + } + + return retval; + } + } + + pending_activation_entry = dbus_new0 (BusPendingActivationEntry, 1); + if (!pending_activation_entry) + { + _dbus_verbose ("Failed to create pending activation entry\n"); + BUS_SET_OOM (error); + return FALSE; + } + + pending_activation_entry->auto_activation = auto_activation; + + pending_activation_entry->activation_message = activation_message; + dbus_message_ref (activation_message); + pending_activation_entry->connection = connection; + dbus_connection_ref (connection); + + /* Check if the service is being activated */ + pending_activation = _dbus_hash_table_lookup_string (activation->pending_activations, service_name); + if (pending_activation) + { + if (!_dbus_list_append (&pending_activation->entries, pending_activation_entry)) + { + _dbus_verbose ("Failed to append a new entry to pending activation\n"); + + BUS_SET_OOM (error); + bus_pending_activation_entry_free (pending_activation_entry); + return FALSE; + } + + pending_activation->n_entries += 1; + pending_activation->activation->n_pending_activations += 1; + } + else + { + pending_activation = dbus_new0 (BusPendingActivation, 1); + if (!pending_activation) + { + _dbus_verbose ("Failed to create pending activation\n"); + + BUS_SET_OOM (error); + bus_pending_activation_entry_free (pending_activation_entry); + return FALSE; + } + + pending_activation->activation = activation; + pending_activation->refcount = 1; + + pending_activation->service_name = _dbus_strdup (service_name); + if (!pending_activation->service_name) + { + _dbus_verbose ("Failed to copy service name for pending activation\n"); + + BUS_SET_OOM (error); + bus_pending_activation_unref (pending_activation); + bus_pending_activation_entry_free (pending_activation_entry); + return FALSE; + } + + pending_activation->exec = _dbus_strdup (entry->exec); + if (!pending_activation->exec) + { + _dbus_verbose ("Failed to copy service exec for pending activation\n"); + BUS_SET_OOM (error); + bus_pending_activation_unref (pending_activation); + bus_pending_activation_entry_free (pending_activation_entry); + return FALSE; + } + + pending_activation->timeout = + _dbus_timeout_new (bus_context_get_activation_timeout (activation->context), + pending_activation_timed_out, + pending_activation, + NULL); + if (!pending_activation->timeout) + { + _dbus_verbose ("Failed to create timeout for pending activation\n"); + + BUS_SET_OOM (error); + bus_pending_activation_unref (pending_activation); + bus_pending_activation_entry_free (pending_activation_entry); + return FALSE; + } + + if (!_dbus_loop_add_timeout (bus_context_get_loop (activation->context), + pending_activation->timeout, + handle_timeout_callback, + pending_activation, + NULL)) + { + _dbus_verbose ("Failed to add timeout for pending activation\n"); + + BUS_SET_OOM (error); + bus_pending_activation_unref (pending_activation); + bus_pending_activation_entry_free (pending_activation_entry); + return FALSE; + } + + pending_activation->timeout_added = TRUE; + + if (!_dbus_list_append (&pending_activation->entries, pending_activation_entry)) + { + _dbus_verbose ("Failed to add entry to just-created pending activation\n"); + + BUS_SET_OOM (error); + bus_pending_activation_unref (pending_activation); + bus_pending_activation_entry_free (pending_activation_entry); + return FALSE; + } + + pending_activation->n_entries += 1; + pending_activation->activation->n_pending_activations += 1; + + activated = FALSE; + _dbus_hash_iter_init (activation->pending_activations, &iter); + while (_dbus_hash_iter_next (&iter)) + { + BusPendingActivation *p = _dbus_hash_iter_get_value (&iter); + + if (strcmp (p->exec, entry->exec) == 0) + { + activated = TRUE; + break; + } + } + + if (!_dbus_hash_table_insert_string (activation->pending_activations, + pending_activation->service_name, + pending_activation)) + { + _dbus_verbose ("Failed to put pending activation in hash table\n"); + + BUS_SET_OOM (error); + bus_pending_activation_unref (pending_activation); + return FALSE; + } + } + + if (!add_cancel_pending_to_transaction (transaction, pending_activation)) + { + _dbus_verbose ("Failed to add pending activation cancel hook to transaction\n"); + BUS_SET_OOM (error); + _dbus_hash_table_remove_string (activation->pending_activations, + pending_activation->service_name); + + return FALSE; + } + + if (activated) + return TRUE; + + /* use command as system and session different */ + if (!_dbus_string_init (&command)) + { + BUS_SET_OOM (error); + return FALSE; + } + + /* does the bus use a helper? */ + servicehelper = bus_context_get_servicehelper (activation->context); + if (servicehelper != NULL) + { + if (entry->user == NULL) + { + _dbus_string_free (&command); + dbus_set_error (error, DBUS_ERROR_SPAWN_FILE_INVALID, + "Cannot do system-bus activation with no user\n"); + return FALSE; + } + + /* join the helper path and the service name */ + if (!_dbus_string_append (&command, servicehelper)) + { + _dbus_string_free (&command); + BUS_SET_OOM (error); + return FALSE; + } + if (!_dbus_string_append (&command, " ")) + { + _dbus_string_free (&command); + BUS_SET_OOM (error); + return FALSE; + } + if (!_dbus_string_append (&command, service_name)) + { + _dbus_string_free (&command); + BUS_SET_OOM (error); + return FALSE; + } + } + else + { + /* the bus does not use a helper, so we can append arguments with the exec line */ + if (!_dbus_string_append (&command, entry->exec)) + { + _dbus_string_free (&command); + BUS_SET_OOM (error); + return FALSE; + } + } + + /* convert command into arguments */ + if (!_dbus_shell_parse_argv (_dbus_string_get_const_data (&command), &argc, &argv, error)) + { + _dbus_verbose ("Failed to parse command line: %s\n", entry->exec); + _DBUS_ASSERT_ERROR_IS_SET (error); + + _dbus_hash_table_remove_string (activation->pending_activations, + pending_activation->service_name); + + _dbus_string_free (&command); + return FALSE; + } + _dbus_string_free (&command); + + if (!add_bus_environment (activation, error)) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + dbus_free_string_array (argv); + return FALSE; + } + + envp = bus_activation_get_environment (activation); + + if (envp == NULL) + { + BUS_SET_OOM (error); + dbus_free_string_array (argv); + return FALSE; + } + + _dbus_verbose ("Spawning %s ...\n", argv[0]); + if (!_dbus_spawn_async_with_babysitter (&pending_activation->babysitter, argv, + envp, + NULL, activation, + error)) + { + _dbus_verbose ("Failed to spawn child\n"); + _DBUS_ASSERT_ERROR_IS_SET (error); + dbus_free_string_array (argv); + dbus_free_string_array (envp); + + return FALSE; + } + + dbus_free_string_array (argv); + envp = NULL; + + _dbus_assert (pending_activation->babysitter != NULL); + + if (!_dbus_babysitter_set_watch_functions (pending_activation->babysitter, + add_babysitter_watch, + remove_babysitter_watch, + NULL, + pending_activation, + NULL)) + { + BUS_SET_OOM (error); + _dbus_verbose ("Failed to set babysitter watch functions\n"); + return FALSE; + } + + return TRUE; +} + +dbus_bool_t +bus_activation_list_services (BusActivation *activation, + char ***listp, + int *array_len) +{ + int i, j, len; + char **retval; + DBusHashIter iter; + + len = _dbus_hash_table_get_n_entries (activation->entries); + retval = dbus_new (char *, len + 1); + + if (retval == NULL) + return FALSE; + + _dbus_hash_iter_init (activation->entries, &iter); + i = 0; + while (_dbus_hash_iter_next (&iter)) + { + BusActivationEntry *entry = _dbus_hash_iter_get_value (&iter); + + retval[i] = _dbus_strdup (entry->name); + if (retval[i] == NULL) + goto error; + + i++; + } + + retval[i] = NULL; + + if (array_len) + *array_len = len; + + *listp = retval; + return TRUE; + + error: + for (j = 0; j < i; j++) + dbus_free (retval[i]); + dbus_free (retval); + + return FALSE; +} + + +#ifdef DBUS_BUILD_TESTS + +#include <stdio.h> + +#define SERVICE_NAME_1 "MyService1" +#define SERVICE_NAME_2 "MyService2" +#define SERVICE_NAME_3 "MyService3" + +#define SERVICE_FILE_1 "service-1.service" +#define SERVICE_FILE_2 "service-2.service" +#define SERVICE_FILE_3 "service-3.service" + +static dbus_bool_t +test_create_service_file (DBusString *dir, + const char *filename, + const char *name, + const char *exec) +{ + DBusString file_name, full_path; + FILE *file; + dbus_bool_t ret_val; + + ret_val = TRUE; + _dbus_string_init_const (&file_name, filename); + + if (!_dbus_string_init (&full_path)) + return FALSE; + + if (!_dbus_string_append (&full_path, _dbus_string_get_const_data (dir)) || + !_dbus_concat_dir_and_file (&full_path, &file_name)) + { + ret_val = FALSE; + goto out; + } + + file = fopen (_dbus_string_get_const_data (&full_path), "w"); + if (!file) + { + ret_val = FALSE; + goto out; + } + + fprintf (file, "[D-BUS Service]\nName=%s\nExec=%s\n", name, exec); + fclose (file); + +out: + _dbus_string_free (&full_path); + return ret_val; +} + +static dbus_bool_t +test_remove_service_file (DBusString *dir, const char *filename) +{ + DBusString file_name, full_path; + dbus_bool_t ret_val; + + ret_val = TRUE; + + _dbus_string_init_const (&file_name, filename); + + if (!_dbus_string_init (&full_path)) + return FALSE; + + if (!_dbus_string_append (&full_path, _dbus_string_get_const_data (dir)) || + !_dbus_concat_dir_and_file (&full_path, &file_name)) + { + ret_val = FALSE; + goto out; + } + + if (!_dbus_delete_file (&full_path, NULL)) + { + ret_val = FALSE; + goto out; + } + +out: + _dbus_string_free (&full_path); + return ret_val; +} + +static dbus_bool_t +test_remove_directory (DBusString *dir) +{ + DBusDirIter *iter; + DBusString filename, full_path; + dbus_bool_t ret_val; + + ret_val = TRUE; + + if (!_dbus_string_init (&filename)) + return FALSE; + + if (!_dbus_string_init (&full_path)) + { + _dbus_string_free (&filename); + return FALSE; + } + + iter = _dbus_directory_open (dir, NULL); + if (iter == NULL) + { + ret_val = FALSE; + goto out; + } + + while (_dbus_directory_get_next_file (iter, &filename, NULL)) + { + if (!test_remove_service_file (dir, _dbus_string_get_const_data (&filename))) + { + ret_val = FALSE; + goto out; + } + } + _dbus_directory_close (iter); + + if (!_dbus_delete_directory (dir, NULL)) + { + ret_val = FALSE; + goto out; + } + +out: + _dbus_string_free (&filename); + _dbus_string_free (&full_path); + + return ret_val; +} + +static dbus_bool_t +init_service_reload_test (DBusString *dir) +{ + DBusStat stat_buf; + + if (!_dbus_stat (dir, &stat_buf, NULL)) + { + if (!_dbus_create_directory (dir, NULL)) + return FALSE; + } + else + { + if (!test_remove_directory (dir)) + return FALSE; + + if (!_dbus_create_directory (dir, NULL)) + return FALSE; + } + + /* Create one initial file */ + if (!test_create_service_file (dir, SERVICE_FILE_1, SERVICE_NAME_1, "exec-1")) + return FALSE; + + return TRUE; +} + +static dbus_bool_t +cleanup_service_reload_test (DBusString *dir) +{ + if (!test_remove_directory (dir)) + return FALSE; + + return TRUE; +} + +typedef struct +{ + BusActivation *activation; + const char *service_name; + dbus_bool_t expecting_find; +} CheckData; + +static dbus_bool_t +check_func (void *data) +{ + CheckData *d; + BusActivationEntry *entry; + DBusError error; + dbus_bool_t ret_val; + + ret_val = TRUE; + d = data; + + dbus_error_init (&error); + + entry = activation_find_entry (d->activation, d->service_name, &error); + if (entry == NULL) + { + if (dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY)) + { + ret_val = TRUE; + } + else + { + if (d->expecting_find) + ret_val = FALSE; + } + + dbus_error_free (&error); + } + else + { + if (!d->expecting_find) + ret_val = FALSE; + } + + return ret_val; +} + +static dbus_bool_t +do_test (const char *description, dbus_bool_t oom_test, CheckData *data) +{ + dbus_bool_t err; + + if (oom_test) + err = !_dbus_test_oom_handling (description, check_func, data); + else + err = !check_func (data); + + if (err) + _dbus_assert_not_reached ("Test failed"); + + return TRUE; +} + +static dbus_bool_t +do_service_reload_test (DBusString *dir, dbus_bool_t oom_test) +{ + BusActivation *activation; + DBusString address; + DBusList *directories; + CheckData d; + + directories = NULL; + _dbus_string_init_const (&address, ""); + + if (!_dbus_list_append (&directories, _dbus_string_get_data (dir))) + return FALSE; + + activation = bus_activation_new (NULL, &address, &directories, NULL); + if (!activation) + return FALSE; + + d.activation = activation; + + /* Check for existing service file */ + d.expecting_find = TRUE; + d.service_name = SERVICE_NAME_1; + + if (!do_test ("Existing service file", oom_test, &d)) + return FALSE; + + /* Check for non-existing service file */ + d.expecting_find = FALSE; + d.service_name = SERVICE_NAME_3; + + if (!do_test ("Nonexisting service file", oom_test, &d)) + return FALSE; + + /* Check for added service file */ + if (!test_create_service_file (dir, SERVICE_FILE_2, SERVICE_NAME_2, "exec-2")) + return FALSE; + + d.expecting_find = TRUE; + d.service_name = SERVICE_NAME_2; + + if (!do_test ("Added service file", oom_test, &d)) + return FALSE; + + /* Check for removed service file */ + if (!test_remove_service_file (dir, SERVICE_FILE_2)) + return FALSE; + + d.expecting_find = FALSE; + d.service_name = SERVICE_FILE_2; + + if (!do_test ("Removed service file", oom_test, &d)) + return FALSE; + + /* Check for updated service file */ + + _dbus_sleep_milliseconds (1000); /* Sleep a second to make sure the mtime is updated */ + + if (!test_create_service_file (dir, SERVICE_FILE_1, SERVICE_NAME_3, "exec-3")) + return FALSE; + + d.expecting_find = TRUE; + d.service_name = SERVICE_NAME_3; + + if (!do_test ("Updated service file, part 1", oom_test, &d)) + return FALSE; + + d.expecting_find = FALSE; + d.service_name = SERVICE_NAME_1; + + if (!do_test ("Updated service file, part 2", oom_test, &d)) + return FALSE; + + bus_activation_unref (activation); + _dbus_list_clear (&directories); + + return TRUE; +} + +dbus_bool_t +bus_activation_service_reload_test (const DBusString *test_data_dir) +{ + DBusString directory; + + if (!_dbus_string_init (&directory)) + return FALSE; + + if (!_dbus_string_append (&directory, _dbus_get_tmpdir())) + return FALSE; + + if (!_dbus_string_append (&directory, "/dbus-reload-test-") || + !_dbus_generate_random_ascii (&directory, 6)) + { + return FALSE; + } + + /* Do normal tests */ + if (!init_service_reload_test (&directory)) + _dbus_assert_not_reached ("could not initiate service reload test"); + + if (!do_service_reload_test (&directory, FALSE)) + ; /* Do nothing? */ + + /* Do OOM tests */ + if (!init_service_reload_test (&directory)) + _dbus_assert_not_reached ("could not initiate service reload test"); + + if (!do_service_reload_test (&directory, TRUE)) + ; /* Do nothing? */ + + /* Cleanup test directory */ + if (!cleanup_service_reload_test (&directory)) + return FALSE; + + _dbus_string_free (&directory); + + return TRUE; +} + +#endif /* DBUS_BUILD_TESTS */ diff --git a/bus/activation.h b/bus/activation.h new file mode 100644 index 00000000..03bfed28 --- /dev/null +++ b/bus/activation.h @@ -0,0 +1,68 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* activation.h Activation of services + * + * Copyright (C) 2003 CodeFactory AB + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef BUS_ACTIVATION_H +#define BUS_ACTIVATION_H + +#include <dbus/dbus.h> +#include <dbus/dbus-list.h> +#include "bus.h" + +BusActivation* bus_activation_new (BusContext *context, + const DBusString *address, + DBusList **directories, + DBusError *error); +dbus_bool_t bus_activation_reload (BusActivation *activation, + const DBusString *address, + DBusList **directories, + DBusError *error); +BusActivation* bus_activation_ref (BusActivation *activation); +void bus_activation_unref (BusActivation *activation); + +dbus_bool_t bus_activation_set_environment_variable (BusActivation *activation, + const char *key, + const char *value, + DBusError *error); +dbus_bool_t bus_activation_activate_service (BusActivation *activation, + DBusConnection *connection, + BusTransaction *transaction, + dbus_bool_t auto_activation, + DBusMessage *activation_message, + const char *service_name, + DBusError *error); +dbus_bool_t bus_activation_service_created (BusActivation *activation, + const char *service_name, + BusTransaction *transaction, + DBusError *error); +dbus_bool_t bus_activation_list_services (BusActivation *registry, + char ***listp, + int *array_len); + +dbus_bool_t bus_activation_send_pending_auto_activation_messages (BusActivation *activation, + BusService *service, + BusTransaction *transaction, + DBusError *error); + + + +#endif /* BUS_ACTIVATION_H */ diff --git a/bus/bus.c b/bus/bus.c new file mode 100644 index 00000000..fd8a8724 --- /dev/null +++ b/bus/bus.c @@ -0,0 +1,1569 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* bus.c message bus context object + * + * Copyright (C) 2003, 2004 Red Hat, Inc. + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "bus.h" +#include "activation.h" +#include "connection.h" +#include "services.h" +#include "utils.h" +#include "policy.h" +#include "config-parser.h" +#include "signals.h" +#include "selinux.h" +#include "dir-watch.h" +#include <dbus/dbus-list.h> +#include <dbus/dbus-hash.h> +#include <dbus/dbus-credentials.h> +#include <dbus/dbus-internals.h> + +struct BusContext +{ + int refcount; + DBusGUID uuid; + char *config_file; + char *type; + char *servicehelper; + char *address; + char *pidfile; + char *user; + char *log_prefix; + DBusLoop *loop; + DBusList *servers; + BusConnections *connections; + BusActivation *activation; + BusRegistry *registry; + BusPolicy *policy; + BusMatchmaker *matchmaker; + BusLimits limits; + unsigned int fork : 1; + unsigned int syslog : 1; + unsigned int keep_umask : 1; +}; + +static dbus_int32_t server_data_slot = -1; + +typedef struct +{ + BusContext *context; +} BusServerData; + +#define BUS_SERVER_DATA(server) (dbus_server_get_data ((server), server_data_slot)) + +static BusContext* +server_get_context (DBusServer *server) +{ + BusContext *context; + BusServerData *bd; + + if (!dbus_server_allocate_data_slot (&server_data_slot)) + return NULL; + + bd = BUS_SERVER_DATA (server); + if (bd == NULL) + { + dbus_server_free_data_slot (&server_data_slot); + return NULL; + } + + context = bd->context; + + dbus_server_free_data_slot (&server_data_slot); + + return context; +} + +static dbus_bool_t +server_watch_callback (DBusWatch *watch, + unsigned int condition, + void *data) +{ + /* FIXME this can be done in dbus-mainloop.c + * if the code in activation.c for the babysitter + * watch handler is fixed. + */ + + return dbus_watch_handle (watch, condition); +} + +static dbus_bool_t +add_server_watch (DBusWatch *watch, + void *data) +{ + DBusServer *server = data; + BusContext *context; + + context = server_get_context (server); + + return _dbus_loop_add_watch (context->loop, + watch, server_watch_callback, server, + NULL); +} + +static void +remove_server_watch (DBusWatch *watch, + void *data) +{ + DBusServer *server = data; + BusContext *context; + + context = server_get_context (server); + + _dbus_loop_remove_watch (context->loop, + watch, server_watch_callback, server); +} + + +static void +server_timeout_callback (DBusTimeout *timeout, + void *data) +{ + /* can return FALSE on OOM but we just let it fire again later */ + dbus_timeout_handle (timeout); +} + +static dbus_bool_t +add_server_timeout (DBusTimeout *timeout, + void *data) +{ + DBusServer *server = data; + BusContext *context; + + context = server_get_context (server); + + return _dbus_loop_add_timeout (context->loop, + timeout, server_timeout_callback, server, NULL); +} + +static void +remove_server_timeout (DBusTimeout *timeout, + void *data) +{ + DBusServer *server = data; + BusContext *context; + + context = server_get_context (server); + + _dbus_loop_remove_timeout (context->loop, + timeout, server_timeout_callback, server); +} + +static void +new_connection_callback (DBusServer *server, + DBusConnection *new_connection, + void *data) +{ + BusContext *context = data; + + if (!bus_connections_setup_connection (context->connections, new_connection)) + { + _dbus_verbose ("No memory to setup new connection\n"); + + /* if we don't do this, it will get unref'd without + * being disconnected... kind of strange really + * that we have to do this, people won't get it right + * in general. + */ + dbus_connection_close (new_connection); + } + + dbus_connection_set_max_received_size (new_connection, + context->limits.max_incoming_bytes); + + dbus_connection_set_max_message_size (new_connection, + context->limits.max_message_size); + + /* on OOM, we won't have ref'd the connection so it will die. */ +} + +static void +free_server_data (void *data) +{ + BusServerData *bd = data; + + dbus_free (bd); +} + +static dbus_bool_t +setup_server (BusContext *context, + DBusServer *server, + char **auth_mechanisms, + DBusError *error) +{ + BusServerData *bd; + + bd = dbus_new0 (BusServerData, 1); + if (bd == NULL || !dbus_server_set_data (server, + server_data_slot, + bd, free_server_data)) + { + dbus_free (bd); + BUS_SET_OOM (error); + return FALSE; + } + + bd->context = context; + + if (!dbus_server_set_auth_mechanisms (server, (const char**) auth_mechanisms)) + { + BUS_SET_OOM (error); + return FALSE; + } + + dbus_server_set_new_connection_function (server, + new_connection_callback, + context, NULL); + + if (!dbus_server_set_watch_functions (server, + add_server_watch, + remove_server_watch, + NULL, + server, + NULL)) + { + BUS_SET_OOM (error); + return FALSE; + } + + if (!dbus_server_set_timeout_functions (server, + add_server_timeout, + remove_server_timeout, + NULL, + server, NULL)) + { + BUS_SET_OOM (error); + return FALSE; + } + + return TRUE; +} + +/* This code only gets executed the first time the + * config files are parsed. It is not executed + * when config files are reloaded. + */ +static dbus_bool_t +process_config_first_time_only (BusContext *context, + BusConfigParser *parser, + DBusError *error) +{ + DBusString log_prefix; + DBusList *link; + DBusList **addresses; + const char *user, *pidfile; + char **auth_mechanisms; + DBusList **auth_mechanisms_list; + int len; + dbus_bool_t retval; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + retval = FALSE; + auth_mechanisms = NULL; + + /* Check for an existing pid file. Of course this is a race; + * we'd have to use fcntl() locks on the pid file to + * avoid that. But we want to check for the pid file + * before overwriting any existing sockets, etc. + */ + pidfile = bus_config_parser_get_pidfile (parser); + if (pidfile != NULL) + { + DBusString u; + DBusStat stbuf; + + _dbus_string_init_const (&u, pidfile); + + if (_dbus_stat (&u, &stbuf, NULL)) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "The pid file \"%s\" exists, if the message bus is not running, remove this file", + pidfile); + goto failed; + } + } + + /* keep around the pid filename so we can delete it later */ + context->pidfile = _dbus_strdup (pidfile); + + /* note that type may be NULL */ + context->type = _dbus_strdup (bus_config_parser_get_type (parser)); + if (bus_config_parser_get_type (parser) != NULL && context->type == NULL) + goto oom; + + user = bus_config_parser_get_user (parser); + if (user != NULL) + { + context->user = _dbus_strdup (user); + if (context->user == NULL) + goto oom; + } + + /* Set up the prefix for syslog messages */ + if (!_dbus_string_init (&log_prefix)) + goto oom; + if (context->type && !strcmp (context->type, "system")) + { + if (!_dbus_string_append (&log_prefix, "[system] ")) + goto oom; + } + else if (context->type && !strcmp (context->type, "session")) + { + DBusCredentials *credentials; + + credentials = _dbus_credentials_new_from_current_process (); + if (!credentials) + goto oom; + if (!_dbus_string_append (&log_prefix, "[session ")) + goto oom; + if (!_dbus_credentials_to_string_append (credentials, &log_prefix)) + goto oom; + if (!_dbus_string_append (&log_prefix, "] ")) + goto oom; + _dbus_credentials_unref (credentials); + } + if (!_dbus_string_steal_data (&log_prefix, &context->log_prefix)) + goto oom; + _dbus_string_free (&log_prefix); + + /* Build an array of auth mechanisms */ + + auth_mechanisms_list = bus_config_parser_get_mechanisms (parser); + len = _dbus_list_get_length (auth_mechanisms_list); + + if (len > 0) + { + int i; + + auth_mechanisms = dbus_new0 (char*, len + 1); + if (auth_mechanisms == NULL) + goto oom; + + i = 0; + link = _dbus_list_get_first_link (auth_mechanisms_list); + while (link != NULL) + { + auth_mechanisms[i] = _dbus_strdup (link->data); + if (auth_mechanisms[i] == NULL) + goto oom; + link = _dbus_list_get_next_link (auth_mechanisms_list, link); + } + } + else + { + auth_mechanisms = NULL; + } + + /* Listen on our addresses */ + + addresses = bus_config_parser_get_addresses (parser); + + link = _dbus_list_get_first_link (addresses); + while (link != NULL) + { + DBusServer *server; + + server = dbus_server_listen (link->data, error); + if (server == NULL) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + goto failed; + } + else if (!setup_server (context, server, auth_mechanisms, error)) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + goto failed; + } + + if (!_dbus_list_append (&context->servers, server)) + goto oom; + + link = _dbus_list_get_next_link (addresses, link); + } + + context->fork = bus_config_parser_get_fork (parser); + context->syslog = bus_config_parser_get_syslog (parser); + context->keep_umask = bus_config_parser_get_keep_umask (parser); + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + retval = TRUE; + + failed: + dbus_free_string_array (auth_mechanisms); + return retval; + + oom: + BUS_SET_OOM (error); + dbus_free_string_array (auth_mechanisms); + return FALSE; +} + +/* This code gets executed every time the config files + * are parsed: both during BusContext construction + * and on reloads. This function is slightly screwy + * since it can do a "half reload" in out-of-memory + * situations. Realistically, unlikely to ever matter. + */ +static dbus_bool_t +process_config_every_time (BusContext *context, + BusConfigParser *parser, + dbus_bool_t is_reload, + DBusError *error) +{ + DBusString full_address; + DBusList *link; + DBusList **dirs; + BusActivation *new_activation; + char *addr; + const char *servicehelper; + char *s; + + dbus_bool_t retval; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + addr = NULL; + retval = FALSE; + + if (!_dbus_string_init (&full_address)) + { + BUS_SET_OOM (error); + return FALSE; + } + + /* get our limits and timeout lengths */ + bus_config_parser_get_limits (parser, &context->limits); + + if (context->policy) + bus_policy_unref (context->policy); + context->policy = bus_config_parser_steal_policy (parser); + _dbus_assert (context->policy != NULL); + + /* We have to build the address backward, so that + * <listen> later in the config file have priority + */ + link = _dbus_list_get_last_link (&context->servers); + while (link != NULL) + { + addr = dbus_server_get_address (link->data); + if (addr == NULL) + { + BUS_SET_OOM (error); + goto failed; + } + + if (_dbus_string_get_length (&full_address) > 0) + { + if (!_dbus_string_append (&full_address, ";")) + { + BUS_SET_OOM (error); + goto failed; + } + } + + if (!_dbus_string_append (&full_address, addr)) + { + BUS_SET_OOM (error); + goto failed; + } + + dbus_free (addr); + addr = NULL; + + link = _dbus_list_get_prev_link (&context->servers, link); + } + + if (is_reload) + dbus_free (context->address); + + if (!_dbus_string_copy_data (&full_address, &context->address)) + { + BUS_SET_OOM (error); + goto failed; + } + + /* get the service directories */ + dirs = bus_config_parser_get_service_dirs (parser); + + /* and the service helper */ + servicehelper = bus_config_parser_get_servicehelper (parser); + + s = _dbus_strdup(servicehelper); + if (s == NULL && servicehelper != NULL) + { + BUS_SET_OOM (error); + goto failed; + } + else + { + dbus_free(context->servicehelper); + context->servicehelper = s; + } + + /* Create activation subsystem */ + if (context->activation) + { + if (!bus_activation_reload (context->activation, &full_address, dirs, error)) + goto failed; + } + else + { + context->activation = bus_activation_new (context, &full_address, dirs, error); + } + + if (context->activation == NULL) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + goto failed; + } + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + retval = TRUE; + + failed: + _dbus_string_free (&full_address); + + if (addr) + dbus_free (addr); + + return retval; +} + +static dbus_bool_t +list_concat_new (DBusList **a, + DBusList **b, + DBusList **result) +{ + DBusList *link; + + *result = NULL; + + link = _dbus_list_get_first_link (a); + for (link = _dbus_list_get_first_link (a); link; link = _dbus_list_get_next_link (a, link)) + { + if (!_dbus_list_append (result, link->data)) + goto oom; + } + for (link = _dbus_list_get_first_link (b); link; link = _dbus_list_get_next_link (b, link)) + { + if (!_dbus_list_append (result, link->data)) + goto oom; + } + + return TRUE; +oom: + _dbus_list_clear (result); + return FALSE; +} + +static dbus_bool_t +process_config_postinit (BusContext *context, + BusConfigParser *parser, + DBusError *error) +{ + DBusHashTable *service_context_table; + DBusList *watched_dirs = NULL; + + service_context_table = bus_config_parser_steal_service_context_table (parser); + if (!bus_registry_set_service_context_table (context->registry, + service_context_table)) + { + BUS_SET_OOM (error); + return FALSE; + } + + _dbus_hash_table_unref (service_context_table); + + /* We need to monitor both the configuration directories and directories + * containing .service files. + */ + if (!list_concat_new (bus_config_parser_get_conf_dirs (parser), + bus_config_parser_get_service_dirs (parser), + &watched_dirs)) + { + BUS_SET_OOM (error); + return FALSE; + } + + bus_set_watched_dirs (context, &watched_dirs); + + _dbus_list_clear (&watched_dirs); + + return TRUE; +} + +BusContext* +bus_context_new (const DBusString *config_file, + ForceForkSetting force_fork, + DBusPipe *print_addr_pipe, + DBusPipe *print_pid_pipe, + DBusError *error) +{ + DBusString log_prefix; + BusContext *context; + BusConfigParser *parser; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + context = NULL; + parser = NULL; + + if (!dbus_server_allocate_data_slot (&server_data_slot)) + { + BUS_SET_OOM (error); + return NULL; + } + + context = dbus_new0 (BusContext, 1); + if (context == NULL) + { + BUS_SET_OOM (error); + goto failed; + } + context->refcount = 1; + + _dbus_generate_uuid (&context->uuid); + + if (!_dbus_string_copy_data (config_file, &context->config_file)) + { + BUS_SET_OOM (error); + goto failed; + } + + context->loop = _dbus_loop_new (); + if (context->loop == NULL) + { + BUS_SET_OOM (error); + goto failed; + } + + context->registry = bus_registry_new (context); + if (context->registry == NULL) + { + BUS_SET_OOM (error); + goto failed; + } + + parser = bus_config_load (config_file, TRUE, NULL, error); + if (parser == NULL) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + goto failed; + } + + if (!process_config_first_time_only (context, parser, error)) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + goto failed; + } + if (!process_config_every_time (context, parser, FALSE, error)) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + goto failed; + } + + /* we need another ref of the server data slot for the context + * to own + */ + if (!dbus_server_allocate_data_slot (&server_data_slot)) + _dbus_assert_not_reached ("second ref of server data slot failed"); + + /* Note that we don't know whether the print_addr_pipe is + * one of the sockets we're using to listen on, or some + * other random thing. But I think the answer is "don't do + * that then" + */ + if (print_addr_pipe != NULL && _dbus_pipe_is_valid (print_addr_pipe)) + { + DBusString addr; + const char *a = bus_context_get_address (context); + int bytes; + + _dbus_assert (a != NULL); + if (!_dbus_string_init (&addr)) + { + BUS_SET_OOM (error); + goto failed; + } + + if (!_dbus_string_append (&addr, a) || + !_dbus_string_append (&addr, "\n")) + { + _dbus_string_free (&addr); + BUS_SET_OOM (error); + goto failed; + } + + bytes = _dbus_string_get_length (&addr); + if (_dbus_pipe_write (print_addr_pipe, &addr, 0, bytes, error) != bytes) + { + /* pipe write returns an error on failure but not short write */ + if (error != NULL && !dbus_error_is_set (error)) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "Printing message bus address: did not write all bytes\n"); + } + _dbus_string_free (&addr); + goto failed; + } + + if (!_dbus_pipe_is_stdout_or_stderr (print_addr_pipe)) + _dbus_pipe_close (print_addr_pipe, NULL); + + _dbus_string_free (&addr); + } + + context->connections = bus_connections_new (context); + if (context->connections == NULL) + { + BUS_SET_OOM (error); + goto failed; + } + + context->matchmaker = bus_matchmaker_new (); + if (context->matchmaker == NULL) + { + BUS_SET_OOM (error); + goto failed; + } + + /* check user before we fork */ + if (context->user != NULL) + { + if (!_dbus_verify_daemon_user (context->user)) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "Could not get UID and GID for username \"%s\"", + context->user); + goto failed; + } + } + + /* Now become a daemon if appropriate and write out pid file in any case */ + { + DBusString u; + + if (context->pidfile) + _dbus_string_init_const (&u, context->pidfile); + + if ((force_fork != FORK_NEVER && context->fork) || force_fork == FORK_ALWAYS) + { + _dbus_verbose ("Forking and becoming daemon\n"); + + if (!_dbus_become_daemon (context->pidfile ? &u : NULL, + print_pid_pipe, + error, + context->keep_umask)) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + goto failed; + } + } + else + { + _dbus_verbose ("Fork not requested\n"); + + /* Need to write PID file and to PID pipe for ourselves, + * not for the child process. This is a no-op if the pidfile + * is NULL and print_pid_pipe is NULL. + */ + if (!_dbus_write_pid_to_file_and_pipe (context->pidfile ? &u : NULL, + print_pid_pipe, + _dbus_getpid (), + error)) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + goto failed; + } + } + } + + if (print_pid_pipe && _dbus_pipe_is_valid (print_pid_pipe) && + !_dbus_pipe_is_stdout_or_stderr (print_pid_pipe)) + _dbus_pipe_close (print_pid_pipe, NULL); + + if (!bus_selinux_full_init ()) + { + bus_context_log (context, DBUS_SYSTEM_LOG_FATAL, "SELinux enabled but AVC initialization failed; check system log\n"); + } + + if (!process_config_postinit (context, parser, error)) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + goto failed; + } + + if (parser != NULL) + { + bus_config_parser_unref (parser); + parser = NULL; + } + + /* Here we change our credentials if required, + * as soon as we've set up our sockets and pidfile + */ + if (context->user != NULL) + { + if (!_dbus_change_to_daemon_user (context->user, error)) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + goto failed; + } + +#ifdef HAVE_SELINUX + /* FIXME - why not just put this in full_init() below? */ + bus_selinux_audit_init (); +#endif + } + + dbus_server_free_data_slot (&server_data_slot); + + return context; + + failed: + if (parser != NULL) + bus_config_parser_unref (parser); + if (context != NULL) + bus_context_unref (context); + + if (server_data_slot >= 0) + dbus_server_free_data_slot (&server_data_slot); + + return NULL; +} + +dbus_bool_t +bus_context_get_id (BusContext *context, + DBusString *uuid) +{ + return _dbus_uuid_encode (&context->uuid, uuid); +} + +dbus_bool_t +bus_context_reload_config (BusContext *context, + DBusError *error) +{ + BusConfigParser *parser; + DBusString config_file; + dbus_bool_t ret; + + /* Flush the user database cache */ + _dbus_flush_caches (); + + ret = FALSE; + _dbus_string_init_const (&config_file, context->config_file); + parser = bus_config_load (&config_file, TRUE, NULL, error); + if (parser == NULL) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + goto failed; + } + + if (!process_config_every_time (context, parser, TRUE, error)) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + goto failed; + } + if (!process_config_postinit (context, parser, error)) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + goto failed; + } + ret = TRUE; + + bus_context_log (context, DBUS_SYSTEM_LOG_INFO, "Reloaded configuration"); + failed: + if (!ret) + bus_context_log (context, DBUS_SYSTEM_LOG_INFO, "Unable to reload configuration: %s", error->message); + if (parser != NULL) + bus_config_parser_unref (parser); + return ret; +} + +static void +shutdown_server (BusContext *context, + DBusServer *server) +{ + if (server == NULL || + !dbus_server_get_is_connected (server)) + return; + + if (!dbus_server_set_watch_functions (server, + NULL, NULL, NULL, + context, + NULL)) + _dbus_assert_not_reached ("setting watch functions to NULL failed"); + + if (!dbus_server_set_timeout_functions (server, + NULL, NULL, NULL, + context, + NULL)) + _dbus_assert_not_reached ("setting timeout functions to NULL failed"); + + dbus_server_disconnect (server); +} + +void +bus_context_shutdown (BusContext *context) +{ + DBusList *link; + + link = _dbus_list_get_first_link (&context->servers); + while (link != NULL) + { + shutdown_server (context, link->data); + + link = _dbus_list_get_next_link (&context->servers, link); + } +} + +BusContext * +bus_context_ref (BusContext *context) +{ + _dbus_assert (context->refcount > 0); + context->refcount += 1; + + return context; +} + +void +bus_context_unref (BusContext *context) +{ + _dbus_assert (context->refcount > 0); + context->refcount -= 1; + + if (context->refcount == 0) + { + DBusList *link; + + _dbus_verbose ("Finalizing bus context %p\n", context); + + bus_context_shutdown (context); + + if (context->connections) + { + bus_connections_unref (context->connections); + context->connections = NULL; + } + + if (context->registry) + { + bus_registry_unref (context->registry); + context->registry = NULL; + } + + if (context->activation) + { + bus_activation_unref (context->activation); + context->activation = NULL; + } + + link = _dbus_list_get_first_link (&context->servers); + while (link != NULL) + { + dbus_server_unref (link->data); + + link = _dbus_list_get_next_link (&context->servers, link); + } + _dbus_list_clear (&context->servers); + + if (context->policy) + { + bus_policy_unref (context->policy); + context->policy = NULL; + } + + if (context->loop) + { + _dbus_loop_unref (context->loop); + context->loop = NULL; + } + + if (context->matchmaker) + { + bus_matchmaker_unref (context->matchmaker); + context->matchmaker = NULL; + } + + dbus_free (context->config_file); + dbus_free (context->log_prefix); + dbus_free (context->type); + dbus_free (context->address); + dbus_free (context->user); + dbus_free (context->servicehelper); + + if (context->pidfile) + { + DBusString u; + _dbus_string_init_const (&u, context->pidfile); + + /* Deliberately ignore errors here, since there's not much + * we can do about it, and we're exiting anyways. + */ + _dbus_delete_file (&u, NULL); + + dbus_free (context->pidfile); + } + dbus_free (context); + + dbus_server_free_data_slot (&server_data_slot); + } +} + +/* type may be NULL */ +const char* +bus_context_get_type (BusContext *context) +{ + return context->type; +} + +const char* +bus_context_get_address (BusContext *context) +{ + return context->address; +} + +const char* +bus_context_get_servicehelper (BusContext *context) +{ + return context->servicehelper; +} + +BusRegistry* +bus_context_get_registry (BusContext *context) +{ + return context->registry; +} + +BusConnections* +bus_context_get_connections (BusContext *context) +{ + return context->connections; +} + +BusActivation* +bus_context_get_activation (BusContext *context) +{ + return context->activation; +} + +BusMatchmaker* +bus_context_get_matchmaker (BusContext *context) +{ + return context->matchmaker; +} + +DBusLoop* +bus_context_get_loop (BusContext *context) +{ + return context->loop; +} + +dbus_bool_t +bus_context_allow_unix_user (BusContext *context, + unsigned long uid) +{ + return bus_policy_allow_unix_user (context->policy, + uid); +} + +/* For now this is never actually called because the default + * DBusConnection behavior of 'same user that owns the bus can connect' + * is all it would do. + */ +dbus_bool_t +bus_context_allow_windows_user (BusContext *context, + const char *windows_sid) +{ + return bus_policy_allow_windows_user (context->policy, + windows_sid); +} + +BusPolicy * +bus_context_get_policy (BusContext *context) +{ + return context->policy; +} + +BusClientPolicy* +bus_context_create_client_policy (BusContext *context, + DBusConnection *connection, + DBusError *error) +{ + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + return bus_policy_create_client_policy (context->policy, connection, + error); +} + +int +bus_context_get_activation_timeout (BusContext *context) +{ + + return context->limits.activation_timeout; +} + +int +bus_context_get_auth_timeout (BusContext *context) +{ + return context->limits.auth_timeout; +} + +int +bus_context_get_max_completed_connections (BusContext *context) +{ + return context->limits.max_completed_connections; +} + +int +bus_context_get_max_incomplete_connections (BusContext *context) +{ + return context->limits.max_incomplete_connections; +} + +int +bus_context_get_max_connections_per_user (BusContext *context) +{ + return context->limits.max_connections_per_user; +} + +int +bus_context_get_max_pending_activations (BusContext *context) +{ + return context->limits.max_pending_activations; +} + +int +bus_context_get_max_services_per_connection (BusContext *context) +{ + return context->limits.max_services_per_connection; +} + +int +bus_context_get_max_match_rules_per_connection (BusContext *context) +{ + return context->limits.max_match_rules_per_connection; +} + +int +bus_context_get_max_replies_per_connection (BusContext *context) +{ + return context->limits.max_replies_per_connection; +} + +int +bus_context_get_reply_timeout (BusContext *context) +{ + return context->limits.reply_timeout; +} + +void +bus_context_log (BusContext *context, DBusSystemLogSeverity severity, const char *msg, ...) _DBUS_GNUC_PRINTF (3, 4); + +void +bus_context_log (BusContext *context, DBusSystemLogSeverity severity, const char *msg, ...) +{ + va_list args; + + if (!context->syslog) + return; + + va_start (args, msg); + + if (context->log_prefix) + { + DBusString full_msg; + + if (!_dbus_string_init (&full_msg)) + goto out; + if (!_dbus_string_append (&full_msg, context->log_prefix)) + goto oom_out; + if (!_dbus_string_append_printf_valist (&full_msg, msg, args)) + goto oom_out; + + _dbus_system_log (severity, "%s", _dbus_string_get_const_data (&full_msg)); + oom_out: + _dbus_string_free (&full_msg); + } + else + _dbus_system_logv (severity, msg, args); + +out: + va_end (args); +} + +/* + * addressed_recipient is the recipient specified in the message. + * + * proposed_recipient is the recipient we're considering sending + * to right this second, and may be an eavesdropper. + * + * sender is the sender of the message. + * + * NULL for proposed_recipient or sender definitely means the bus driver. + * + * NULL for addressed_recipient may mean the bus driver, or may mean + * no destination was specified in the message (e.g. a signal). + */ +dbus_bool_t +bus_context_check_security_policy (BusContext *context, + BusTransaction *transaction, + DBusConnection *sender, + DBusConnection *addressed_recipient, + DBusConnection *proposed_recipient, + DBusMessage *message, + DBusError *error) +{ + const char *dest; + BusClientPolicy *sender_policy; + BusClientPolicy *recipient_policy; + dbus_int32_t toggles; + dbus_bool_t log; + int type; + dbus_bool_t requested_reply; + const char *sender_name; + const char *sender_loginfo; + const char *proposed_recipient_loginfo; + + type = dbus_message_get_type (message); + dest = dbus_message_get_destination (message); + + /* dispatch.c was supposed to ensure these invariants */ + _dbus_assert (dest != NULL || + type == DBUS_MESSAGE_TYPE_SIGNAL || + (sender == NULL && !bus_connection_is_active (proposed_recipient))); + _dbus_assert (type == DBUS_MESSAGE_TYPE_SIGNAL || + addressed_recipient != NULL || + strcmp (dest, DBUS_SERVICE_DBUS) == 0); + + /* Used in logging below */ + if (sender != NULL) + { + sender_name = bus_connection_get_name (sender); + sender_loginfo = bus_connection_get_loginfo (sender); + } + else + { + sender_name = NULL; + sender_loginfo = "(bus)"; + } + + if (proposed_recipient != NULL) + proposed_recipient_loginfo = bus_connection_get_loginfo (proposed_recipient); + else + proposed_recipient_loginfo = "bus"; + + switch (type) + { + case DBUS_MESSAGE_TYPE_METHOD_CALL: + case DBUS_MESSAGE_TYPE_SIGNAL: + case DBUS_MESSAGE_TYPE_METHOD_RETURN: + case DBUS_MESSAGE_TYPE_ERROR: + break; + + default: + _dbus_verbose ("security check disallowing message of unknown type %d\n", + type); + + dbus_set_error (error, DBUS_ERROR_ACCESS_DENIED, + "Message bus will not accept messages of unknown type\n"); + + return FALSE; + } + + requested_reply = FALSE; + + if (sender != NULL) + { + /* First verify the SELinux access controls. If allowed then + * go on with the standard checks. + */ + if (!bus_selinux_allows_send (sender, proposed_recipient, + dbus_message_type_to_string (dbus_message_get_type (message)), + dbus_message_get_interface (message), + dbus_message_get_member (message), + dbus_message_get_error_name (message), + dest ? dest : DBUS_SERVICE_DBUS, error)) + { + if (error != NULL && !dbus_error_is_set (error)) + { + dbus_set_error (error, DBUS_ERROR_ACCESS_DENIED, + "An SELinux policy prevents this sender " + "from sending this message to this recipient " + "(rejected message had sender \"%s\" interface \"%s\" " + "member \"%s\" error name \"%s\" destination \"%s\")", + sender_name ? sender_name : "(unset)", + dbus_message_get_interface (message) ? + dbus_message_get_interface (message) : "(unset)", + dbus_message_get_member (message) ? + dbus_message_get_member (message) : "(unset)", + dbus_message_get_error_name (message) ? + dbus_message_get_error_name (message) : "(unset)", + dest ? dest : DBUS_SERVICE_DBUS); + _dbus_verbose ("SELinux security check denying send to service\n"); + } + + return FALSE; + } + + if (bus_connection_is_active (sender)) + { + sender_policy = bus_connection_get_policy (sender); + _dbus_assert (sender_policy != NULL); + + /* Fill in requested_reply variable with TRUE if this is a + * reply and the reply was pending. + */ + if (dbus_message_get_reply_serial (message) != 0) + { + if (proposed_recipient != NULL /* not to the bus driver */ && + addressed_recipient == proposed_recipient /* not eavesdropping */) + { + DBusError error2; + + dbus_error_init (&error2); + requested_reply = bus_connections_check_reply (bus_connection_get_connections (sender), + transaction, + sender, addressed_recipient, message, + &error2); + if (dbus_error_is_set (&error2)) + { + dbus_move_error (&error2, error); + return FALSE; + } + } + } + } + else + { + /* Policy for inactive connections is that they can only send + * the hello message to the bus driver + */ + if (proposed_recipient == NULL && + dbus_message_is_method_call (message, + DBUS_INTERFACE_DBUS, + "Hello")) + { + _dbus_verbose ("security check allowing %s message\n", + "Hello"); + return TRUE; + } + else + { + _dbus_verbose ("security check disallowing non-%s message\n", + "Hello"); + + dbus_set_error (error, DBUS_ERROR_ACCESS_DENIED, + "Client tried to send a message other than %s without being registered", + "Hello"); + + return FALSE; + } + } + } + else + { + sender_policy = NULL; + + /* If the sender is the bus driver, we assume any reply was a + * requested reply as bus driver won't send bogus ones + */ + if (addressed_recipient == proposed_recipient /* not eavesdropping */ && + dbus_message_get_reply_serial (message) != 0) + requested_reply = TRUE; + } + + _dbus_assert ((sender != NULL && sender_policy != NULL) || + (sender == NULL && sender_policy == NULL)); + + if (proposed_recipient != NULL) + { + /* only the bus driver can send to an inactive recipient (as it + * owns no services, so other apps can't address it). Inactive + * recipients can receive any message. + */ + if (bus_connection_is_active (proposed_recipient)) + { + recipient_policy = bus_connection_get_policy (proposed_recipient); + _dbus_assert (recipient_policy != NULL); + } + else if (sender == NULL) + { + _dbus_verbose ("security check using NULL recipient policy for message from bus\n"); + recipient_policy = NULL; + } + else + { + _dbus_assert_not_reached ("a message was somehow sent to an inactive recipient from a source other than the message bus\n"); + recipient_policy = NULL; + } + } + else + recipient_policy = NULL; + + _dbus_assert ((proposed_recipient != NULL && recipient_policy != NULL) || + (proposed_recipient != NULL && sender == NULL && recipient_policy == NULL) || + (proposed_recipient == NULL && recipient_policy == NULL)); + + log = FALSE; + if (sender_policy && + !bus_client_policy_check_can_send (sender_policy, + context->registry, + requested_reply, + proposed_recipient, + message, &toggles, &log)) + { + const char *msg = "Rejected send message, %d matched rules; " + "type=\"%s\", sender=\"%s\" (%s) interface=\"%s\" member=\"%s\" error name=\"%s\" requested_reply=%d destination=\"%s\" (%s))"; + + dbus_set_error (error, DBUS_ERROR_ACCESS_DENIED, msg, + toggles, + dbus_message_type_to_string (dbus_message_get_type (message)), + sender_name ? sender_name : "(unset)", + sender_loginfo, + dbus_message_get_interface (message) ? + dbus_message_get_interface (message) : "(unset)", + dbus_message_get_member (message) ? + dbus_message_get_member (message) : "(unset)", + dbus_message_get_error_name (message) ? + dbus_message_get_error_name (message) : "(unset)", + requested_reply, + dest ? dest : DBUS_SERVICE_DBUS, + proposed_recipient_loginfo); + /* Needs to be duplicated to avoid calling malloc and having to handle OOM */ + if (addressed_recipient == proposed_recipient) + bus_context_log (context, DBUS_SYSTEM_LOG_SECURITY, msg, + toggles, + dbus_message_type_to_string (dbus_message_get_type (message)), + sender_name ? sender_name : "(unset)", + sender_loginfo, + dbus_message_get_interface (message) ? + dbus_message_get_interface (message) : "(unset)", + dbus_message_get_member (message) ? + dbus_message_get_member (message) : "(unset)", + dbus_message_get_error_name (message) ? + dbus_message_get_error_name (message) : "(unset)", + requested_reply, + dest ? dest : DBUS_SERVICE_DBUS, + proposed_recipient_loginfo); + _dbus_verbose ("security policy disallowing message due to sender policy\n"); + return FALSE; + } + + if (log) + bus_context_log (context, DBUS_SYSTEM_LOG_SECURITY, + "Would reject message, %d matched rules; " + "type=\"%s\", sender=\"%s\" (%s) interface=\"%s\" member=\"%s\" error name=\"%s\" requested_reply=%d destination=\"%s\" (%s))", + toggles, + dbus_message_type_to_string (dbus_message_get_type (message)), + sender_name ? sender_name : "(unset)", + sender_loginfo, + dbus_message_get_interface (message) ? + dbus_message_get_interface (message) : "(unset)", + dbus_message_get_member (message) ? + dbus_message_get_member (message) : "(unset)", + dbus_message_get_error_name (message) ? + dbus_message_get_error_name (message) : "(unset)", + requested_reply, + dest ? dest : DBUS_SERVICE_DBUS, + proposed_recipient_loginfo); + + if (recipient_policy && + !bus_client_policy_check_can_receive (recipient_policy, + context->registry, + requested_reply, + sender, + addressed_recipient, proposed_recipient, + message, &toggles)) + { + const char *msg = "Rejected receive message, %d matched rules; " + "type=\"%s\" sender=\"%s\" (%s) interface=\"%s\" member=\"%s\" error name=\"%s\" reply serial=%u requested_reply=%d destination=\"%s\" (%s))"; + + dbus_set_error (error, DBUS_ERROR_ACCESS_DENIED, msg, + toggles, + dbus_message_type_to_string (dbus_message_get_type (message)), + sender_name ? sender_name : "(unset)", + sender_loginfo, + dbus_message_get_interface (message) ? + dbus_message_get_interface (message) : "(unset)", + dbus_message_get_member (message) ? + dbus_message_get_member (message) : "(unset)", + dbus_message_get_error_name (message) ? + dbus_message_get_error_name (message) : "(unset)", + dbus_message_get_reply_serial (message), + requested_reply, + dest ? dest : DBUS_SERVICE_DBUS, + proposed_recipient_loginfo); + /* Needs to be duplicated to avoid calling malloc and having to handle OOM */ + if (addressed_recipient == proposed_recipient) + bus_context_log (context, DBUS_SYSTEM_LOG_SECURITY, msg, + toggles, + dbus_message_type_to_string (dbus_message_get_type (message)), + sender_name ? sender_name : "(unset)", + sender_loginfo, + dbus_message_get_interface (message) ? + dbus_message_get_interface (message) : "(unset)", + dbus_message_get_member (message) ? + dbus_message_get_member (message) : "(unset)", + dbus_message_get_error_name (message) ? + dbus_message_get_error_name (message) : "(unset)", + dbus_message_get_reply_serial (message), + requested_reply, + dest ? dest : DBUS_SERVICE_DBUS, + proposed_recipient_loginfo); + _dbus_verbose ("security policy disallowing message due to recipient policy\n"); + return FALSE; + } + + /* See if limits on size have been exceeded */ + if (proposed_recipient && + dbus_connection_get_outgoing_size (proposed_recipient) > + context->limits.max_outgoing_bytes) + { + dbus_set_error (error, DBUS_ERROR_LIMITS_EXCEEDED, + "The destination service \"%s\" has a full message queue", + dest ? dest : (proposed_recipient ? + bus_connection_get_name (proposed_recipient) : + DBUS_SERVICE_DBUS)); + _dbus_verbose ("security policy disallowing message due to full message queue\n"); + return FALSE; + } + + /* Record that we will allow a reply here in the future (don't + * bother if the recipient is the bus or this is an eavesdropping + * connection). Only the addressed recipient may reply. + */ + if (type == DBUS_MESSAGE_TYPE_METHOD_CALL && + sender && + addressed_recipient && + addressed_recipient == proposed_recipient && /* not eavesdropping */ + !bus_connections_expect_reply (bus_connection_get_connections (sender), + transaction, + sender, addressed_recipient, + message, error)) + { + _dbus_verbose ("Failed to record reply expectation or problem with the message expecting a reply\n"); + return FALSE; + } + + _dbus_verbose ("security policy allowing message\n"); + return TRUE; +} diff --git a/bus/bus.h b/bus/bus.h new file mode 100644 index 00000000..8a04daa1 --- /dev/null +++ b/bus/bus.h @@ -0,0 +1,122 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* bus.h message bus context object + * + * Copyright (C) 2003 Red Hat, Inc. + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef BUS_BUS_H +#define BUS_BUS_H + +#include <config.h> + +#include <dbus/dbus.h> +#include <dbus/dbus-string.h> +#include <dbus/dbus-mainloop.h> + +typedef struct BusActivation BusActivation; +typedef struct BusConnections BusConnections; +typedef struct BusContext BusContext; +typedef struct BusPolicy BusPolicy; +typedef struct BusClientPolicy BusClientPolicy; +typedef struct BusPolicyRule BusPolicyRule; +typedef struct BusRegistry BusRegistry; +typedef struct BusSELinuxID BusSELinuxID; +typedef struct BusService BusService; +typedef struct BusOwner BusOwner; +typedef struct BusTransaction BusTransaction; +typedef struct BusMatchmaker BusMatchmaker; +typedef struct BusMatchRule BusMatchRule; + +typedef struct +{ + long max_incoming_bytes; /**< How many incoming message bytes for a single connection */ + long max_outgoing_bytes; /**< How many outgoing bytes can be queued for a single connection */ + long max_message_size; /**< Max size of a single message in bytes */ + int activation_timeout; /**< How long to wait for an activation to time out */ + int auth_timeout; /**< How long to wait for an authentication to time out */ + int max_completed_connections; /**< Max number of authorized connections */ + int max_incomplete_connections; /**< Max number of incomplete connections */ + int max_connections_per_user; /**< Max number of connections auth'd as same user */ + int max_pending_activations; /**< Max number of pending activations for the entire bus */ + int max_services_per_connection; /**< Max number of owned services for a single connection */ + int max_match_rules_per_connection; /**< Max number of match rules for a single connection */ + int max_replies_per_connection; /**< Max number of replies that can be pending for each connection */ + int reply_timeout; /**< How long to wait before timing out a reply */ +} BusLimits; + +typedef enum +{ + FORK_FOLLOW_CONFIG_FILE, + FORK_ALWAYS, + FORK_NEVER +} ForceForkSetting; + +BusContext* bus_context_new (const DBusString *config_file, + ForceForkSetting force_fork, + DBusPipe *print_addr_pipe, + DBusPipe *print_pid_pipe, + DBusError *error); +dbus_bool_t bus_context_reload_config (BusContext *context, + DBusError *error); +void bus_context_shutdown (BusContext *context); +BusContext* bus_context_ref (BusContext *context); +void bus_context_unref (BusContext *context); +dbus_bool_t bus_context_get_id (BusContext *context, + DBusString *uuid); +const char* bus_context_get_type (BusContext *context); +const char* bus_context_get_address (BusContext *context); +const char* bus_context_get_servicehelper (BusContext *context); +BusRegistry* bus_context_get_registry (BusContext *context); +BusConnections* bus_context_get_connections (BusContext *context); +BusActivation* bus_context_get_activation (BusContext *context); +BusMatchmaker* bus_context_get_matchmaker (BusContext *context); +DBusLoop* bus_context_get_loop (BusContext *context); +dbus_bool_t bus_context_allow_unix_user (BusContext *context, + unsigned long uid); +dbus_bool_t bus_context_allow_windows_user (BusContext *context, + const char *windows_sid); +BusPolicy* bus_context_get_policy (BusContext *context); + +BusClientPolicy* bus_context_create_client_policy (BusContext *context, + DBusConnection *connection, + DBusError *error); +int bus_context_get_activation_timeout (BusContext *context); +int bus_context_get_auth_timeout (BusContext *context); +int bus_context_get_max_completed_connections (BusContext *context); +int bus_context_get_max_incomplete_connections (BusContext *context); +int bus_context_get_max_connections_per_user (BusContext *context); +int bus_context_get_max_pending_activations (BusContext *context); +int bus_context_get_max_services_per_connection (BusContext *context); +int bus_context_get_max_match_rules_per_connection (BusContext *context); +int bus_context_get_max_replies_per_connection (BusContext *context); +int bus_context_get_reply_timeout (BusContext *context); +void bus_context_log (BusContext *context, + DBusSystemLogSeverity severity, + const char *msg, + ...); +dbus_bool_t bus_context_check_security_policy (BusContext *context, + BusTransaction *transaction, + DBusConnection *sender, + DBusConnection *addressed_recipient, + DBusConnection *proposed_recipient, + DBusMessage *message, + DBusError *error); + +#endif /* BUS_BUS_H */ diff --git a/bus/config-loader-expat.c b/bus/config-loader-expat.c new file mode 100644 index 00000000..c0620aed --- /dev/null +++ b/bus/config-loader-expat.c @@ -0,0 +1,294 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* config-loader-expat.c expat XML loader + * + * Copyright (C) 2003 Red Hat, Inc. + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "config-parser.h" +#include <dbus/dbus-internals.h> +#include <expat.h> + +static XML_Memory_Handling_Suite memsuite = +{ + dbus_malloc, + dbus_realloc, + dbus_free +}; + +typedef struct +{ + BusConfigParser *parser; + const char *filename; + DBusString content; + DBusError *error; + dbus_bool_t failed; +} ExpatParseContext; + +static dbus_bool_t +process_content (ExpatParseContext *context) +{ + if (context->failed) + return FALSE; + + if (_dbus_string_get_length (&context->content) > 0) + { + if (!bus_config_parser_content (context->parser, + &context->content, + context->error)) + { + context->failed = TRUE; + return FALSE; + } + _dbus_string_set_length (&context->content, 0); + } + + return TRUE; +} + +static void +expat_StartElementHandler (void *userData, + const XML_Char *name, + const XML_Char **atts) +{ + ExpatParseContext *context = userData; + int i; + char **names; + char **values; + + /* Expat seems to suck and can't abort the parse if we + * throw an error. Expat 2.0 is supposed to fix this. + */ + if (context->failed) + return; + + if (!process_content (context)) + return; + + /* "atts" is key, value, key, value, NULL */ + for (i = 0; atts[i] != NULL; ++i) + ; /* nothing */ + + _dbus_assert (i % 2 == 0); + names = dbus_new0 (char *, i / 2 + 1); + values = dbus_new0 (char *, i / 2 + 1); + + if (names == NULL || values == NULL) + { + dbus_set_error (context->error, DBUS_ERROR_NO_MEMORY, NULL); + context->failed = TRUE; + dbus_free (names); + dbus_free (values); + return; + } + + i = 0; + while (atts[i] != NULL) + { + _dbus_assert (i % 2 == 0); + names [i / 2] = (char*) atts[i]; + values[i / 2] = (char*) atts[i+1]; + + i += 2; + } + + if (!bus_config_parser_start_element (context->parser, + name, + (const char **) names, + (const char **) values, + context->error)) + { + dbus_free (names); + dbus_free (values); + context->failed = TRUE; + return; + } + + dbus_free (names); + dbus_free (values); +} + +static void +expat_EndElementHandler (void *userData, + const XML_Char *name) +{ + ExpatParseContext *context = userData; + + if (!process_content (context)) + return; + + if (!bus_config_parser_end_element (context->parser, + name, + context->error)) + { + context->failed = TRUE; + return; + } +} + +/* s is not 0 terminated. */ +static void +expat_CharacterDataHandler (void *userData, + const XML_Char *s, + int len) +{ + ExpatParseContext *context = userData; + if (context->failed) + return; + + if (!_dbus_string_append_len (&context->content, + s, len)) + { + dbus_set_error (context->error, DBUS_ERROR_NO_MEMORY, NULL); + context->failed = TRUE; + return; + } +} + + +BusConfigParser* +bus_config_load (const DBusString *file, + dbus_bool_t is_toplevel, + const BusConfigParser *parent, + DBusError *error) +{ + XML_Parser expat; + const char *filename; + BusConfigParser *parser; + ExpatParseContext context; + DBusString dirname; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + parser = NULL; + expat = NULL; + context.error = error; + context.failed = FALSE; + + filename = _dbus_string_get_const_data (file); + + if (!_dbus_string_init (&context.content)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + return NULL; + } + + if (!_dbus_string_init (&dirname)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + _dbus_string_free (&context.content); + return NULL; + } + + expat = XML_ParserCreate_MM ("UTF-8", &memsuite, NULL); + if (expat == NULL) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto failed; + } + + if (!_dbus_string_get_dirname (file, &dirname)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto failed; + } + + parser = bus_config_parser_new (&dirname, is_toplevel, parent); + if (parser == NULL) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto failed; + } + context.parser = parser; + + XML_SetUserData (expat, &context); + XML_SetElementHandler (expat, + expat_StartElementHandler, + expat_EndElementHandler); + XML_SetCharacterDataHandler (expat, + expat_CharacterDataHandler); + + { + DBusString data; + const char *data_str; + + if (!_dbus_string_init (&data)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto failed; + } + + if (!_dbus_file_get_contents (&data, file, error)) + { + _dbus_string_free (&data); + goto failed; + } + + data_str = _dbus_string_get_const_data (&data); + + if (!XML_Parse (expat, data_str, _dbus_string_get_length (&data), TRUE)) + { + if (context.error != NULL && + !dbus_error_is_set (context.error)) + { + enum XML_Error e; + + e = XML_GetErrorCode (expat); + if (e == XML_ERROR_NO_MEMORY) + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + else + dbus_set_error (error, DBUS_ERROR_FAILED, + "Error in file %s, line %d, column %d: %s\n", + filename, + XML_GetCurrentLineNumber (expat), + XML_GetCurrentColumnNumber (expat), + XML_ErrorString (e)); + } + + _dbus_string_free (&data); + goto failed; + } + + _dbus_string_free (&data); + + if (context.failed) + goto failed; + } + + if (!bus_config_parser_finished (parser, error)) + goto failed; + + _dbus_string_free (&dirname); + _dbus_string_free (&context.content); + XML_ParserFree (expat); + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + return parser; + + failed: + _DBUS_ASSERT_ERROR_IS_SET (error); + + _dbus_string_free (&dirname); + _dbus_string_free (&context.content); + if (expat) + XML_ParserFree (expat); + if (parser) + bus_config_parser_unref (parser); + return NULL; +} diff --git a/bus/config-loader-libxml.c b/bus/config-loader-libxml.c new file mode 100644 index 00000000..3d82a633 --- /dev/null +++ b/bus/config-loader-libxml.c @@ -0,0 +1,323 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* config-loader-libxml.c libxml2 XML loader + * + * Copyright (C) 2003 Red Hat, Inc. + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "config-parser.h" +#include <dbus/dbus-internals.h> +#include <libxml/xmlreader.h> +#include <libxml/parser.h> +#include <libxml/globals.h> +#include <libxml/xmlmemory.h> +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#include <string.h> + +/* About the error handling: + * - setup a "structured" error handler that catches structural + * errors and some oom errors + * - assume that a libxml function returning an error code means + * out-of-memory + */ +#define _DBUS_MAYBE_SET_OOM(e) (dbus_error_is_set(e) ? (void)0 : _DBUS_SET_OOM(e)) + + +static dbus_bool_t +xml_text_start_element (BusConfigParser *parser, + xmlTextReader *reader, + DBusError *error) +{ + const char *name; + int n_attributes; + const char **attribute_names, **attribute_values; + dbus_bool_t ret; + int i, status, is_empty; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + ret = FALSE; + attribute_names = NULL; + attribute_values = NULL; + + name = xmlTextReaderConstName (reader); + n_attributes = xmlTextReaderAttributeCount (reader); + is_empty = xmlTextReaderIsEmptyElement (reader); + + if (name == NULL || n_attributes < 0 || is_empty == -1) + { + _DBUS_MAYBE_SET_OOM (error); + goto out; + } + + attribute_names = dbus_new0 (const char *, n_attributes + 1); + attribute_values = dbus_new0 (const char *, n_attributes + 1); + if (attribute_names == NULL || attribute_values == NULL) + { + _DBUS_SET_OOM (error); + goto out; + } + i = 0; + while ((status = xmlTextReaderMoveToNextAttribute (reader)) == 1) + { + _dbus_assert (i < n_attributes); + attribute_names[i] = xmlTextReaderConstName (reader); + attribute_values[i] = xmlTextReaderConstValue (reader); + if (attribute_names[i] == NULL || attribute_values[i] == NULL) + { + _DBUS_MAYBE_SET_OOM (error); + goto out; + } + i++; + } + if (status == -1) + { + _DBUS_MAYBE_SET_OOM (error); + goto out; + } + _dbus_assert (i == n_attributes); + + ret = bus_config_parser_start_element (parser, name, + attribute_names, attribute_values, + error); + if (ret && is_empty == 1) + ret = bus_config_parser_end_element (parser, name, error); + + out: + dbus_free (attribute_names); + dbus_free (attribute_values); + + return ret; +} + +static void xml_shut_up (void *ctx, const char *msg, ...) +{ + return; +} + +static void +xml_text_reader_error (void *arg, xmlErrorPtr xml_error) +{ + DBusError *error = arg; + +#if 0 + _dbus_verbose ("XML_ERROR level=%d, domain=%d, code=%d, msg=%s\n", + xml_error->level, xml_error->domain, + xml_error->code, xml_error->message); +#endif + + if (!dbus_error_is_set (error)) + { + if (xml_error->code == XML_ERR_NO_MEMORY) + _DBUS_SET_OOM (error); + else if (xml_error->level == XML_ERR_ERROR || + xml_error->level == XML_ERR_FATAL) + dbus_set_error (error, DBUS_ERROR_FAILED, + "Error loading config file: '%s'", + xml_error->message); + } +} + + +BusConfigParser* +bus_config_load (const DBusString *file, + dbus_bool_t is_toplevel, + const BusConfigParser *parent, + DBusError *error) + +{ + xmlTextReader *reader; + BusConfigParser *parser; + DBusString dirname, data; + DBusError tmp_error; + int ret; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + parser = NULL; + reader = NULL; + + if (!_dbus_string_init (&dirname)) + { + _DBUS_SET_OOM (error); + return NULL; + } + + if (!_dbus_string_init (&data)) + { + _DBUS_SET_OOM (error); + _dbus_string_free (&dirname); + return NULL; + } + + if (is_toplevel) + { + /* xmlMemSetup only fails if one of the functions is NULL */ + xmlMemSetup (dbus_free, + dbus_malloc, + dbus_realloc, + _dbus_strdup); + xmlInitParser (); + xmlSetGenericErrorFunc (NULL, xml_shut_up); + } + + if (!_dbus_string_get_dirname (file, &dirname)) + { + _DBUS_SET_OOM (error); + goto failed; + } + + parser = bus_config_parser_new (&dirname, is_toplevel, parent); + if (parser == NULL) + { + _DBUS_SET_OOM (error); + goto failed; + } + + if (!_dbus_file_get_contents (&data, file, error)) + goto failed; + + reader = xmlReaderForMemory (_dbus_string_get_const_data (&data), + _dbus_string_get_length (&data), + NULL, NULL, 0); + if (reader == NULL) + { + _DBUS_SET_OOM (error); + goto failed; + } + + xmlTextReaderSetParserProp (reader, XML_PARSER_SUBST_ENTITIES, 1); + + dbus_error_init (&tmp_error); + xmlTextReaderSetStructuredErrorHandler (reader, xml_text_reader_error, &tmp_error); + + while ((ret = xmlTextReaderRead (reader)) == 1) + { + int type; + + if (dbus_error_is_set (&tmp_error)) + goto reader_out; + + type = xmlTextReaderNodeType (reader); + if (type == -1) + { + _DBUS_MAYBE_SET_OOM (&tmp_error); + goto reader_out; + } + + switch ((xmlReaderTypes) type) { + case XML_READER_TYPE_ELEMENT: + xml_text_start_element (parser, reader, &tmp_error); + break; + + case XML_READER_TYPE_TEXT: + case XML_READER_TYPE_CDATA: + { + DBusString content; + const char *value; + value = xmlTextReaderConstValue (reader); + if (value != NULL) + { + _dbus_string_init_const (&content, value); + bus_config_parser_content (parser, &content, &tmp_error); + } + else + _DBUS_MAYBE_SET_OOM (&tmp_error); + break; + } + + case XML_READER_TYPE_DOCUMENT_TYPE: + { + const char *name; + name = xmlTextReaderConstName (reader); + if (name != NULL) + bus_config_parser_check_doctype (parser, name, &tmp_error); + else + _DBUS_MAYBE_SET_OOM (&tmp_error); + break; + } + + case XML_READER_TYPE_END_ELEMENT: + { + const char *name; + name = xmlTextReaderConstName (reader); + if (name != NULL) + bus_config_parser_end_element (parser, name, &tmp_error); + else + _DBUS_MAYBE_SET_OOM (&tmp_error); + break; + } + + case XML_READER_TYPE_DOCUMENT: + case XML_READER_TYPE_DOCUMENT_FRAGMENT: + case XML_READER_TYPE_PROCESSING_INSTRUCTION: + case XML_READER_TYPE_COMMENT: + case XML_READER_TYPE_ENTITY: + case XML_READER_TYPE_NOTATION: + case XML_READER_TYPE_WHITESPACE: + case XML_READER_TYPE_SIGNIFICANT_WHITESPACE: + case XML_READER_TYPE_END_ENTITY: + case XML_READER_TYPE_XML_DECLARATION: + /* nothing to do, just read on */ + break; + + case XML_READER_TYPE_NONE: + case XML_READER_TYPE_ATTRIBUTE: + case XML_READER_TYPE_ENTITY_REFERENCE: + _dbus_assert_not_reached ("unexpected nodes in XML"); + } + + if (dbus_error_is_set (&tmp_error)) + goto reader_out; + } + + if (ret == -1) + _DBUS_MAYBE_SET_OOM (&tmp_error); + + reader_out: + xmlFreeTextReader (reader); + reader = NULL; + if (dbus_error_is_set (&tmp_error)) + { + dbus_move_error (&tmp_error, error); + goto failed; + } + + if (!bus_config_parser_finished (parser, error)) + goto failed; + _dbus_string_free (&dirname); + _dbus_string_free (&data); + if (is_toplevel) + xmlCleanupParser(); + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + return parser; + + failed: + _DBUS_ASSERT_ERROR_IS_SET (error); + _dbus_string_free (&dirname); + _dbus_string_free (&data); + if (is_toplevel) + xmlCleanupParser(); + if (parser) + bus_config_parser_unref (parser); + _dbus_assert (reader == NULL); /* must go to reader_out first */ + return NULL; +} diff --git a/bus/config-parser-common.c b/bus/config-parser-common.c new file mode 100644 index 00000000..54a67468 --- /dev/null +++ b/bus/config-parser-common.c @@ -0,0 +1,183 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* config-parser-common.c Common defines and routines for config file parsing + * + * Copyright (C) 2007 Red Hat, Inc. + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <dbus/dbus-internals.h> +#include <string.h> + +#include "config-parser-common.h" +#include "utils.h" + +ElementType +bus_config_parser_element_name_to_type (const char *name) +{ + if (strcmp (name, "none") == 0) + { + return ELEMENT_NONE; + } + else if (strcmp (name, "busconfig") == 0) + { + return ELEMENT_BUSCONFIG; + } + else if (strcmp (name, "user") == 0) + { + return ELEMENT_USER; + } + else if (strcmp (name, "auth") == 0) + { + return ELEMENT_AUTH; + } + else if (strcmp (name, "type") == 0) + { + return ELEMENT_TYPE; + } + else if (strcmp (name, "fork") == 0) + { + return ELEMENT_FORK; + } + else if (strcmp (name, "pidfile") == 0) + { + return ELEMENT_PIDFILE; + } + else if (strcmp (name, "listen") == 0) + { + return ELEMENT_LISTEN; + } + else if (strcmp (name, "auth") == 0) + { + return ELEMENT_AUTH; + } + else if (strcmp (name, "allow") == 0) + { + return ELEMENT_ALLOW; + } + else if (strcmp (name, "deny") == 0) + { + return ELEMENT_DENY; + } + else if (strcmp (name, "servicehelper") == 0) + { + return ELEMENT_SERVICEHELPER; + } + else if (strcmp (name, "includedir") == 0) + { + return ELEMENT_INCLUDEDIR; + } + else if (strcmp (name, "standard_session_servicedirs") == 0) + { + return ELEMENT_STANDARD_SESSION_SERVICEDIRS; + } + else if (strcmp (name, "standard_system_servicedirs") == 0) + { + return ELEMENT_STANDARD_SYSTEM_SERVICEDIRS; + } + else if (strcmp (name, "servicedir") == 0) + { + return ELEMENT_SERVICEDIR; + } + else if (strcmp (name, "include") == 0) + { + return ELEMENT_INCLUDE; + } + else if (strcmp (name, "policy") == 0) + { + return ELEMENT_POLICY; + } + else if (strcmp (name, "limit") == 0) + { + return ELEMENT_LIMIT; + } + else if (strcmp (name, "selinux") == 0) + { + return ELEMENT_SELINUX; + } + else if (strcmp (name, "associate") == 0) + { + return ELEMENT_ASSOCIATE; + } + else if (strcmp (name, "syslog") == 0) + { + return ELEMENT_SYSLOG; + } + else if (strcmp (name, "keep_umask") == 0) + { + return ELEMENT_KEEP_UMASK; + } + return ELEMENT_NONE; +} + +const char* +bus_config_parser_element_type_to_name (ElementType type) +{ + switch (type) + { + case ELEMENT_NONE: + return NULL; + case ELEMENT_BUSCONFIG: + return "busconfig"; + case ELEMENT_INCLUDE: + return "include"; + case ELEMENT_USER: + return "user"; + case ELEMENT_LISTEN: + return "listen"; + case ELEMENT_AUTH: + return "auth"; + case ELEMENT_POLICY: + return "policy"; + case ELEMENT_LIMIT: + return "limit"; + case ELEMENT_ALLOW: + return "allow"; + case ELEMENT_DENY: + return "deny"; + case ELEMENT_FORK: + return "fork"; + case ELEMENT_PIDFILE: + return "pidfile"; + case ELEMENT_STANDARD_SESSION_SERVICEDIRS: + return "standard_session_servicedirs"; + case ELEMENT_STANDARD_SYSTEM_SERVICEDIRS: + return "standard_system_servicedirs"; + case ELEMENT_SERVICEDIR: + return "servicedir"; + case ELEMENT_SERVICEHELPER: + return "servicehelper"; + case ELEMENT_INCLUDEDIR: + return "includedir"; + case ELEMENT_TYPE: + return "type"; + case ELEMENT_SELINUX: + return "selinux"; + case ELEMENT_ASSOCIATE: + return "associate"; + case ELEMENT_SYSLOG: + return "syslog"; + case ELEMENT_KEEP_UMASK: + return "keep_umask"; + } + + _dbus_assert_not_reached ("bad element type"); + + return NULL; +} + diff --git a/bus/config-parser-common.h b/bus/config-parser-common.h new file mode 100644 index 00000000..8451ce08 --- /dev/null +++ b/bus/config-parser-common.h @@ -0,0 +1,59 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* config-parser-common.h Common defines and routines for config file parsing + * + * Copyright (C) 2007 Red Hat, Inc. + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef BUS_CONFIG_PARSER_COMMON_H +#define BUS_CONFIG_PARSER_COMMON_H + +#include <config.h> + +typedef enum +{ + ELEMENT_NONE, + ELEMENT_BUSCONFIG, + ELEMENT_INCLUDE, + ELEMENT_USER, + ELEMENT_LISTEN, + ELEMENT_AUTH, + ELEMENT_POLICY, + ELEMENT_LIMIT, + ELEMENT_ALLOW, + ELEMENT_DENY, + ELEMENT_FORK, + ELEMENT_PIDFILE, + ELEMENT_SERVICEDIR, + ELEMENT_SERVICEHELPER, + ELEMENT_INCLUDEDIR, + ELEMENT_TYPE, + ELEMENT_SELINUX, + ELEMENT_ASSOCIATE, + ELEMENT_STANDARD_SESSION_SERVICEDIRS, + ELEMENT_STANDARD_SYSTEM_SERVICEDIRS, + ELEMENT_SYSLOG, + ELEMENT_KEEP_UMASK +} ElementType; + +ElementType bus_config_parser_element_name_to_type (const char *element_name); +const char* bus_config_parser_element_type_to_name (ElementType type); + +#endif /* BUS_CONFIG_PARSER_COMMON_H */ + diff --git a/bus/config-parser-trivial.c b/bus/config-parser-trivial.c new file mode 100644 index 00000000..fd016a84 --- /dev/null +++ b/bus/config-parser-trivial.c @@ -0,0 +1,696 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* config-parser-trivial.c XML-library-agnostic configuration file parser + * Does not do includes or anything remotely complicated. + * + * Copyright (C) 2003, 2004, 2007 Red Hat, Inc. + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "config-parser-common.h" +#include "config-parser-trivial.h" +#include "utils.h" +#include <dbus/dbus-list.h> +#include <dbus/dbus-internals.h> +#include <string.h> + +/** + * TRIVIAL parser for bus configuration file. + */ +struct BusConfigParser +{ + ElementType type; + DBusString user; /**< User the dbus-daemon runs as */ + DBusString bus_type; /**< Message bus type */ + DBusString service_helper; /**< Location of the setuid helper */ + DBusList *service_dirs; /**< Directories to look for services in */ +}; + +static dbus_bool_t +service_dirs_find_dir (DBusList **service_dirs, + const char *dir) +{ + DBusList *link; + + _dbus_assert (dir != NULL); + + for (link = *service_dirs; link; link = _dbus_list_get_next_link(service_dirs, link)) + { + const char *link_dir; + + link_dir = (const char *)link->data; + if (strcmp (dir, link_dir) == 0) + return TRUE; + } + + return FALSE; +} + +static void +service_dirs_append_link_unique_or_free (DBusList **service_dirs, + DBusList *dir_link) +{ + if (!service_dirs_find_dir (service_dirs, dir_link->data)) + { + _dbus_list_append_link (service_dirs, dir_link); + } + else + { + dbus_free (dir_link->data); + _dbus_list_free_link (dir_link); + } +} + +BusConfigParser* +bus_config_parser_new (const DBusString *basedir, + dbus_bool_t is_toplevel, + const BusConfigParser *parent) +{ + BusConfigParser *parser; + + parser = dbus_new0 (BusConfigParser, 1); + if (parser == NULL) + goto failed; + + parser->type = ELEMENT_NONE; + + /* init the lists */ + parser->service_dirs = NULL; + + /* init the strings */ + if (!_dbus_string_init (&parser->user)) + goto failed_parser; + if (!_dbus_string_init (&parser->bus_type)) + goto failed_type; + if (!_dbus_string_init (&parser->service_helper)) + goto failed_helper; + + /* woot! */ + return parser; + +/* argh. we have do do this carefully because of OOM */ +failed_helper: + _dbus_string_free (&parser->bus_type); +failed_type: + _dbus_string_free (&parser->user); +failed_parser: + dbus_free (parser); +failed: + return NULL; +} + +void +bus_config_parser_unref (BusConfigParser *parser) +{ + _dbus_string_free (&parser->user); + _dbus_string_free (&parser->service_helper); + _dbus_string_free (&parser->bus_type); + + _dbus_list_foreach (&parser->service_dirs, + (DBusForeachFunction) dbus_free, + NULL); + + _dbus_list_clear (&parser->service_dirs); + + dbus_free (parser); +} + +dbus_bool_t +bus_config_parser_start_element (BusConfigParser *parser, + const char *element_name, + const char **attribute_names, + const char **attribute_values, + DBusError *error) +{ + /* we don't do processing of attribute names, we don't need to */ + parser->type = bus_config_parser_element_name_to_type (element_name); + + switch (parser->type) + { + case ELEMENT_SERVICEHELPER: + case ELEMENT_USER: + case ELEMENT_TYPE: + /* content about to be handled */ + break; + + case ELEMENT_STANDARD_SYSTEM_SERVICEDIRS: + { + DBusList *link; + DBusList *dirs; + dirs = NULL; + + if (!_dbus_get_standard_system_servicedirs (&dirs)) + { + BUS_SET_OOM (error); + return FALSE; + } + + while ((link = _dbus_list_pop_first_link (&dirs))) + service_dirs_append_link_unique_or_free (&parser->service_dirs, link); + break; + } + + default: + { + /* we really don't care about the others... */ + _dbus_verbose (" START We dont care about '%s' type '%i'\n", element_name, parser->type); + break; + } + } + return TRUE; +} + +dbus_bool_t +bus_config_parser_end_element (BusConfigParser *parser, + const char *element_name, + DBusError *error) +{ + /* we don't care */ + return TRUE; +} + +dbus_bool_t +bus_config_parser_content (BusConfigParser *parser, + const DBusString *content, + DBusError *error) +{ + DBusString content_sane; + dbus_bool_t retval; + + retval = FALSE; + + if (!_dbus_string_init (&content_sane)) + { + BUS_SET_OOM (error); + goto out; + } + if (!_dbus_string_copy (content, 0, &content_sane, 0)) + { + BUS_SET_OOM (error); + goto out_content; + } + + /* rip out white space */ + _dbus_string_chop_white (&content_sane); + if (_dbus_string_get_length (&content_sane) == 0) + { + /* optimise, there is no content */ + retval = TRUE; + goto out_content; + } + + switch (parser->type) + { + case ELEMENT_SERVICEDIR: + { + char *cpath; + + /* copy the sane data into a char array */ + if (!_dbus_string_copy_data(&content_sane, &cpath)) + { + BUS_SET_OOM (error); + goto out_content; + } + + /* append the dynamic char string to service dirs */ + if (!_dbus_list_append (&parser->service_dirs, cpath)) + { + dbus_free (cpath); + BUS_SET_OOM (error); + goto out_content; + } + } + break; + + case ELEMENT_SERVICEHELPER: + { + if (!_dbus_string_copy (&content_sane, 0, &parser->service_helper, 0)) + { + BUS_SET_OOM (error); + goto out_content; + } + } + break; + + case ELEMENT_USER: + { + if (!_dbus_string_copy (&content_sane, 0, &parser->user, 0)) + { + BUS_SET_OOM (error); + goto out_content; + } + } + break; + + case ELEMENT_TYPE: + { + if (!_dbus_string_copy (&content_sane, 0, &parser->bus_type, 0)) + { + BUS_SET_OOM (error); + goto out_content; + } + } + break; + default: + { + /* we don't care about the others... really */ + _dbus_verbose (" CONTENTS We dont care '%s' type '%i'\n", _dbus_string_get_const_data (&content_sane), parser->type); + break; + } + } + + /* woot! */ + retval = TRUE; + +out_content: + _dbus_string_free (&content_sane); +out: + return retval; +} + +dbus_bool_t +bus_config_parser_finished (BusConfigParser *parser, + DBusError *error) +{ + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + _dbus_verbose ("finished scanning!\n"); + return TRUE; +} + +const char* +bus_config_parser_get_user (BusConfigParser *parser) +{ + return _dbus_string_get_const_data (&parser->user); +} + +const char* +bus_config_parser_get_type (BusConfigParser *parser) +{ + return _dbus_string_get_const_data (&parser->bus_type); +} + +DBusList** +bus_config_parser_get_service_dirs (BusConfigParser *parser) +{ + return &parser->service_dirs; +} + +#ifdef DBUS_BUILD_TESTS +#include <stdio.h> +#include "test.h" + +typedef enum +{ + VALID, + INVALID, + UNKNOWN +} Validity; + +static dbus_bool_t +check_return_values (const DBusString *full_path) +{ + BusConfigParser *parser; + DBusError error; + dbus_bool_t retval; + const char *user; + const char *type; + DBusList **dirs; + + dbus_error_init (&error); + retval = FALSE; + + printf ("Testing values from: %s\n", _dbus_string_get_const_data (full_path)); + + parser = bus_config_load (full_path, TRUE, NULL, &error); + if (parser == NULL) + { + _DBUS_ASSERT_ERROR_IS_SET (&error); + if (dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY)) + _dbus_verbose ("Failed to load valid file due to OOM\n"); + goto finish; + } + _DBUS_ASSERT_ERROR_IS_CLEAR (&error); + + /* check user return value is okay */ + user = bus_config_parser_get_user (parser); + if (user == NULL) + { + _dbus_warn ("User was NULL!\n"); + goto finish; + } +#if 0 + /* the username can be configured in configure.in so this test doesn't work */ + if (strcmp (user, "dbus") != 0) + { + _dbus_warn ("User was invalid; '%s'!\n", user); + goto finish; + } + printf (" <user>dbus</user> OKAY!\n"); +#endif + + /* check type return value is okay */ + type = bus_config_parser_get_type (parser); + if (type == NULL) + { + _dbus_warn ("Type was NULL!\n"); + goto finish; + } + if (strcmp (type, "system") != 0) + { + _dbus_warn ("Type was invalid; '%s'!\n", user); + goto finish; + } + printf (" <type>system</type> OKAY!\n"); + + /* check dirs return value is okay */ + dirs = bus_config_parser_get_service_dirs (parser); + if (dirs == NULL) + { + _dbus_warn ("Service dirs are NULL!\n"); + goto finish; + } + printf (" <standard_system_service_dirs/> OKAY!\n"); + /* NOTE: We tested the specific return values in the config-parser tests */ + + /* woohoo! */ + retval = TRUE; +finish: + if (parser != NULL) + bus_config_parser_unref (parser); + dbus_error_free (&error); + return retval; +} + +static dbus_bool_t +do_load (const DBusString *full_path, + Validity validity, + dbus_bool_t oom_possible) +{ + BusConfigParser *parser; + DBusError error; + + dbus_error_init (&error); + + parser = bus_config_load (full_path, TRUE, NULL, &error); + if (parser == NULL) + { + _DBUS_ASSERT_ERROR_IS_SET (&error); + + if (oom_possible && + dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY)) + { + _dbus_verbose ("Failed to load valid file due to OOM\n"); + dbus_error_free (&error); + return TRUE; + } + else if (validity == VALID) + { + _dbus_warn ("Failed to load valid file but still had memory: %s\n", + error.message); + + dbus_error_free (&error); + return FALSE; + } + else + { + dbus_error_free (&error); + return TRUE; + } + } + else + { + _DBUS_ASSERT_ERROR_IS_CLEAR (&error); + + bus_config_parser_unref (parser); + + if (validity == INVALID) + { + _dbus_warn ("Accepted invalid file\n"); + return FALSE; + } + + return TRUE; + } +} + +typedef struct +{ + const DBusString *full_path; + Validity validity; +} LoaderOomData; + +static dbus_bool_t +check_loader_oom_func (void *data) +{ + LoaderOomData *d = data; + + return do_load (d->full_path, d->validity, TRUE); +} + +static dbus_bool_t +process_test_valid_subdir (const DBusString *test_base_dir, + const char *subdir, + Validity validity) +{ + DBusString test_directory; + DBusString filename; + DBusDirIter *dir; + dbus_bool_t retval; + DBusError error; + + retval = FALSE; + dir = NULL; + + if (!_dbus_string_init (&test_directory)) + _dbus_assert_not_reached ("didn't allocate test_directory\n"); + + _dbus_string_init_const (&filename, subdir); + + if (!_dbus_string_copy (test_base_dir, 0, + &test_directory, 0)) + _dbus_assert_not_reached ("couldn't copy test_base_dir to test_directory"); + + if (!_dbus_concat_dir_and_file (&test_directory, &filename)) + _dbus_assert_not_reached ("couldn't allocate full path"); + + _dbus_string_free (&filename); + if (!_dbus_string_init (&filename)) + _dbus_assert_not_reached ("didn't allocate filename string\n"); + + dbus_error_init (&error); + dir = _dbus_directory_open (&test_directory, &error); + if (dir == NULL) + { + _dbus_warn ("Could not open %s: %s\n", + _dbus_string_get_const_data (&test_directory), + error.message); + dbus_error_free (&error); + goto failed; + } + + if (validity == VALID) + printf ("Testing valid files:\n"); + else if (validity == INVALID) + printf ("Testing invalid files:\n"); + else + printf ("Testing unknown files:\n"); + + next: + while (_dbus_directory_get_next_file (dir, &filename, &error)) + { + DBusString full_path; + LoaderOomData d; + + if (!_dbus_string_init (&full_path)) + _dbus_assert_not_reached ("couldn't init string"); + + if (!_dbus_string_copy (&test_directory, 0, &full_path, 0)) + _dbus_assert_not_reached ("couldn't copy dir to full_path"); + + if (!_dbus_concat_dir_and_file (&full_path, &filename)) + _dbus_assert_not_reached ("couldn't concat file to dir"); + + if (!_dbus_string_ends_with_c_str (&full_path, ".conf")) + { + _dbus_verbose ("Skipping non-.conf file %s\n", + _dbus_string_get_const_data (&filename)); + _dbus_string_free (&full_path); + goto next; + } + + printf (" %s\n", _dbus_string_get_const_data (&filename)); + + _dbus_verbose (" expecting %s\n", + validity == VALID ? "valid" : + (validity == INVALID ? "invalid" : + (validity == UNKNOWN ? "unknown" : "???"))); + + d.full_path = &full_path; + d.validity = validity; + + /* FIXME hackaround for an expat problem, see + * https://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=124747 + * http://freedesktop.org/pipermail/dbus/2004-May/001153.html + */ + /* if (!_dbus_test_oom_handling ("config-loader", check_loader_oom_func, &d)) */ + if (!check_loader_oom_func (&d)) + _dbus_assert_not_reached ("test failed"); + + _dbus_string_free (&full_path); + } + + if (dbus_error_is_set (&error)) + { + _dbus_warn ("Could not get next file in %s: %s\n", + _dbus_string_get_const_data (&test_directory), + error.message); + dbus_error_free (&error); + goto failed; + } + + retval = TRUE; + + failed: + + if (dir) + _dbus_directory_close (dir); + _dbus_string_free (&test_directory); + _dbus_string_free (&filename); + + return retval; +} + +/* convenience function, do not reuse outside of TEST */ +static dbus_bool_t +make_full_path (const DBusString *test_data_dir, + const char *subdir, + const char *file, + DBusString *full_path) +{ + DBusString filename; + dbus_bool_t retval; + + retval = FALSE; + + if (!_dbus_string_init (full_path)) + { + _dbus_warn ("couldn't allocate full path"); + goto finish; + } + + if (!_dbus_string_copy (test_data_dir, 0, full_path, 0)) + { + _dbus_warn ("couldn't allocate full path"); + goto finish; + } + + _dbus_string_init_const (&filename, subdir); + if (!_dbus_concat_dir_and_file (full_path, &filename)) + { + _dbus_warn ("couldn't allocate full path"); + goto finish; + } + _dbus_string_free (&filename); + + _dbus_string_init_const (&filename, file); + if (!_dbus_concat_dir_and_file (full_path, &filename)) + { + _dbus_warn ("couldn't allocate full path"); + goto finish; + } + + /* woot! */ + retval = TRUE; + +finish: + _dbus_string_free (&filename); + return retval; +} + +static dbus_bool_t +check_file_valid (DBusString *full_path, + Validity validity) +{ + dbus_bool_t retval; + + if (validity == VALID) + printf ("Testing valid file:\n"); + else if (validity == INVALID) + printf ("Testing invalid file:\n"); + else + printf ("Testing unknown file:\n"); + + /* print the filename, just so we match the other output */ + printf (" %s\n", _dbus_string_get_const_data (full_path)); + + /* only test one file */ + retval = do_load (full_path, validity, TRUE); + + return retval; +} + +dbus_bool_t +bus_config_parser_trivial_test (const DBusString *test_data_dir) +{ + DBusString full_path; + dbus_bool_t retval; + + retval = FALSE; + + if (test_data_dir == NULL || + _dbus_string_get_length (test_data_dir) == 0) + { + printf ("No test data\n"); + return TRUE; + } + + /* We already test default_session_servicedirs and default_system_servicedirs + * in bus_config_parser_test() */ + if (!process_test_valid_subdir (test_data_dir, "valid-config-files", VALID)) + goto finish; + + /* we don't process all the invalid files, as the trivial parser can't hope + * to validate them all for all different syntaxes. We just check one broken + * file to see if junk is received */ + if (!make_full_path (test_data_dir, "invalid-config-files", "not-well-formed.conf", &full_path)) + goto finish; + if (!check_file_valid (&full_path, INVALID)) + goto finish; + _dbus_string_free (&full_path); + + /* just test if the check_file_valid works okay and we got sane values */ + if (!make_full_path (test_data_dir, "valid-config-files", "system.conf", &full_path)) + goto finish; + if (!check_file_valid (&full_path, VALID)) + goto finish; + /* check to see if we got the correct values from the parser */ + if (!check_return_values (&full_path)) + goto finish; + + /* woot! */ + retval = TRUE; + +finish: + _dbus_string_free (&full_path); + + /* we don't process equiv-config-files as we don't handle <include> */ + return retval; +} + +#endif /* DBUS_BUILD_TESTS */ + diff --git a/bus/config-parser-trivial.h b/bus/config-parser-trivial.h new file mode 100644 index 00000000..ce542bfb --- /dev/null +++ b/bus/config-parser-trivial.h @@ -0,0 +1,71 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* config-parser-trivial.h XML-library-agnostic configuration file parser + * + * Copyright (C) 2003 Red Hat, Inc. + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef BUS_CONFIG_PARSER_TRIVIAL_H +#define BUS_CONFIG_PARSER_TRIVIAL_H + +#include <config.h> + +#include <dbus/dbus.h> +#include <dbus/dbus-string.h> +#include <dbus/dbus-list.h> +#include <dbus/dbus-hash.h> + +/* Whatever XML library we're using just pushes data into this API */ + +typedef struct BusConfigParser BusConfigParser; + +BusConfigParser* bus_config_parser_new (const DBusString *basedir, + dbus_bool_t is_toplevel, + const BusConfigParser *parent); + +BusConfigParser* bus_config_parser_ref (BusConfigParser *parser); +void bus_config_parser_unref (BusConfigParser *parser); +dbus_bool_t bus_config_parser_start_element (BusConfigParser *parser, + const char *element_name, + const char **attribute_names, + const char **attribute_values, + DBusError *error); +dbus_bool_t bus_config_parser_end_element (BusConfigParser *parser, + const char *element_name, + DBusError *error); +dbus_bool_t bus_config_parser_content (BusConfigParser *parser, + const DBusString *content, + DBusError *error); +dbus_bool_t bus_config_parser_finished (BusConfigParser *parser, + DBusError *error); + +/* Functions for extracting the parse results */ +const char* bus_config_parser_get_user (BusConfigParser *parser); +const char* bus_config_parser_get_type (BusConfigParser *parser); +DBusList** bus_config_parser_get_service_dirs (BusConfigParser *parser); + +/* Loader functions (backended off one of the XML parsers). Returns a + * finished ConfigParser. + */ +BusConfigParser* bus_config_load (const DBusString *file, + dbus_bool_t is_toplevel, + const BusConfigParser *parent, + DBusError *error); + +#endif /* BUS_CONFIG_PARSER_TRIVIAL_H */ diff --git a/bus/config-parser.c b/bus/config-parser.c new file mode 100644 index 00000000..5218910b --- /dev/null +++ b/bus/config-parser.c @@ -0,0 +1,3466 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* config-parser.c XML-library-agnostic configuration file parser + * + * Copyright (C) 2003, 2004 Red Hat, Inc. + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#include "config-parser-common.h" +#include "config-parser.h" +#include "test.h" +#include "utils.h" +#include "policy.h" +#include "selinux.h" +#include <dbus/dbus-list.h> +#include <dbus/dbus-internals.h> +#include <string.h> + +typedef enum +{ + /* we ignore policies for unknown groups/users */ + POLICY_IGNORED, + + /* non-ignored */ + POLICY_DEFAULT, + POLICY_MANDATORY, + POLICY_USER, + POLICY_GROUP, + POLICY_CONSOLE +} PolicyType; + +typedef struct +{ + ElementType type; + + unsigned int had_content : 1; + + union + { + struct + { + unsigned int ignore_missing : 1; + unsigned int if_selinux_enabled : 1; + unsigned int selinux_root_relative : 1; + } include; + + struct + { + PolicyType type; + unsigned long gid_uid_or_at_console; + } policy; + + struct + { + char *name; + long value; + } limit; + + } d; + +} Element; + +/** + * Parser for bus configuration file. + */ +struct BusConfigParser +{ + int refcount; /**< Reference count */ + + DBusString basedir; /**< Directory we resolve paths relative to */ + + DBusList *stack; /**< stack of Element */ + + char *user; /**< user to run as */ + + char *servicehelper; /**< location of the setuid helper */ + + char *bus_type; /**< Message bus type */ + + DBusList *listen_on; /**< List of addresses to listen to */ + + DBusList *mechanisms; /**< Auth mechanisms */ + + DBusList *service_dirs; /**< Directories to look for session services in */ + + DBusList *conf_dirs; /**< Directories to look for policy configuration in */ + + BusPolicy *policy; /**< Security policy */ + + BusLimits limits; /**< Limits */ + + char *pidfile; /**< PID file */ + + DBusList *included_files; /**< Included files stack */ + + DBusHashTable *service_context_table; /**< Map service names to SELinux contexts */ + + unsigned int fork : 1; /**< TRUE to fork into daemon mode */ + + unsigned int syslog : 1; /**< TRUE to enable syslog */ + unsigned int keep_umask : 1; /**< TRUE to keep original umask when forking */ + + unsigned int is_toplevel : 1; /**< FALSE if we are a sub-config-file inside another one */ +}; + +static Element* +push_element (BusConfigParser *parser, + ElementType type) +{ + Element *e; + + _dbus_assert (type != ELEMENT_NONE); + + e = dbus_new0 (Element, 1); + if (e == NULL) + return NULL; + + if (!_dbus_list_append (&parser->stack, e)) + { + dbus_free (e); + return NULL; + } + + e->type = type; + + return e; +} + +static void +element_free (Element *e) +{ + if (e->type == ELEMENT_LIMIT) + dbus_free (e->d.limit.name); + + dbus_free (e); +} + +static void +pop_element (BusConfigParser *parser) +{ + Element *e; + + e = _dbus_list_pop_last (&parser->stack); + + element_free (e); +} + +static Element* +peek_element (BusConfigParser *parser) +{ + Element *e; + + e = _dbus_list_get_last (&parser->stack); + + return e; +} + +static ElementType +top_element_type (BusConfigParser *parser) +{ + Element *e; + + e = _dbus_list_get_last (&parser->stack); + + if (e) + return e->type; + else + return ELEMENT_NONE; +} + +static dbus_bool_t +merge_service_context_hash (DBusHashTable *dest, + DBusHashTable *from) +{ + DBusHashIter iter; + char *service_copy; + char *context_copy; + + service_copy = NULL; + context_copy = NULL; + + _dbus_hash_iter_init (from, &iter); + while (_dbus_hash_iter_next (&iter)) + { + const char *service = _dbus_hash_iter_get_string_key (&iter); + const char *context = _dbus_hash_iter_get_value (&iter); + + service_copy = _dbus_strdup (service); + if (service_copy == NULL) + goto fail; + context_copy = _dbus_strdup (context); + if (context_copy == NULL) + goto fail; + + if (!_dbus_hash_table_insert_string (dest, service_copy, context_copy)) + goto fail; + + service_copy = NULL; + context_copy = NULL; + } + + return TRUE; + + fail: + if (service_copy) + dbus_free (service_copy); + + if (context_copy) + dbus_free (context_copy); + + return FALSE; +} + +static dbus_bool_t +service_dirs_find_dir (DBusList **service_dirs, + const char *dir) +{ + DBusList *link; + + _dbus_assert (dir != NULL); + + for (link = *service_dirs; link; link = _dbus_list_get_next_link(service_dirs, link)) + { + const char *link_dir; + + link_dir = (const char *)link->data; + if (strcmp (dir, link_dir) == 0) + return TRUE; + } + + return FALSE; +} + +static dbus_bool_t +service_dirs_append_unique_or_free (DBusList **service_dirs, + char *dir) +{ + if (!service_dirs_find_dir (service_dirs, dir)) + return _dbus_list_append (service_dirs, dir); + + dbus_free (dir); + return TRUE; +} + +static void +service_dirs_append_link_unique_or_free (DBusList **service_dirs, + DBusList *dir_link) +{ + if (!service_dirs_find_dir (service_dirs, dir_link->data)) + { + _dbus_list_append_link (service_dirs, dir_link); + } + else + { + dbus_free (dir_link->data); + _dbus_list_free_link (dir_link); + } +} + +static dbus_bool_t +merge_included (BusConfigParser *parser, + BusConfigParser *included, + DBusError *error) +{ + DBusList *link; + + if (!bus_policy_merge (parser->policy, + included->policy)) + { + BUS_SET_OOM (error); + return FALSE; + } + + if (!merge_service_context_hash (parser->service_context_table, + included->service_context_table)) + { + BUS_SET_OOM (error); + return FALSE; + } + + if (included->user != NULL) + { + dbus_free (parser->user); + parser->user = included->user; + included->user = NULL; + } + + if (included->bus_type != NULL) + { + dbus_free (parser->bus_type); + parser->bus_type = included->bus_type; + included->bus_type = NULL; + } + + if (included->fork) + parser->fork = TRUE; + + if (included->keep_umask) + parser->keep_umask = TRUE; + + if (included->pidfile != NULL) + { + dbus_free (parser->pidfile); + parser->pidfile = included->pidfile; + included->pidfile = NULL; + } + + while ((link = _dbus_list_pop_first_link (&included->listen_on))) + _dbus_list_append_link (&parser->listen_on, link); + + while ((link = _dbus_list_pop_first_link (&included->mechanisms))) + _dbus_list_append_link (&parser->mechanisms, link); + + while ((link = _dbus_list_pop_first_link (&included->service_dirs))) + service_dirs_append_link_unique_or_free (&parser->service_dirs, link); + + while ((link = _dbus_list_pop_first_link (&included->conf_dirs))) + _dbus_list_append_link (&parser->conf_dirs, link); + + return TRUE; +} + +static dbus_bool_t +seen_include (BusConfigParser *parser, + const DBusString *file) +{ + DBusList *iter; + + iter = parser->included_files; + while (iter != NULL) + { + if (! strcmp (_dbus_string_get_const_data (file), iter->data)) + return TRUE; + + iter = _dbus_list_get_next_link (&parser->included_files, iter); + } + + return FALSE; +} + +BusConfigParser* +bus_config_parser_new (const DBusString *basedir, + dbus_bool_t is_toplevel, + const BusConfigParser *parent) +{ + BusConfigParser *parser; + + parser = dbus_new0 (BusConfigParser, 1); + if (parser == NULL) + return NULL; + + parser->is_toplevel = !!is_toplevel; + + if (!_dbus_string_init (&parser->basedir)) + { + dbus_free (parser); + return NULL; + } + + if (((parser->policy = bus_policy_new ()) == NULL) || + !_dbus_string_copy (basedir, 0, &parser->basedir, 0) || + ((parser->service_context_table = _dbus_hash_table_new (DBUS_HASH_STRING, + dbus_free, + dbus_free)) == NULL)) + { + if (parser->policy) + bus_policy_unref (parser->policy); + + _dbus_string_free (&parser->basedir); + + dbus_free (parser); + return NULL; + } + + if (parent != NULL) + { + /* Initialize the parser's limits from the parent. */ + parser->limits = parent->limits; + + /* Use the parent's list of included_files to avoid + circular inclusions. */ + parser->included_files = parent->included_files; + } + else + { + + /* Make up some numbers! woot! */ + parser->limits.max_incoming_bytes = _DBUS_ONE_MEGABYTE * 127; + parser->limits.max_outgoing_bytes = _DBUS_ONE_MEGABYTE * 127; + parser->limits.max_message_size = _DBUS_ONE_MEGABYTE * 32; + + /* Making this long means the user has to wait longer for an error + * message if something screws up, but making it too short means + * they might see a false failure. + */ + parser->limits.activation_timeout = 25000; /* 25 seconds */ + + /* Making this long risks making a DOS attack easier, but too short + * and legitimate auth will fail. If interactive auth (ask user for + * password) is allowed, then potentially it has to be quite long. + */ + parser->limits.auth_timeout = 30000; /* 30 seconds */ + + parser->limits.max_incomplete_connections = 64; + parser->limits.max_connections_per_user = 256; + + /* Note that max_completed_connections / max_connections_per_user + * is the number of users that would have to work together to + * DOS all the other users. + */ + parser->limits.max_completed_connections = 2048; + + parser->limits.max_pending_activations = 512; + parser->limits.max_services_per_connection = 512; + + /* For this one, keep in mind that it isn't only the memory used + * by the match rules, but slowdown from linearly walking a big + * list of them. A client adding more than this is almost + * certainly a bad idea for that reason, and should change to a + * smaller number of wider-net match rules - getting every last + * message to the bus is probably better than having a thousand + * match rules. + */ + parser->limits.max_match_rules_per_connection = 512; + + parser->limits.reply_timeout = -1; /* never */ + + /* this is effectively a limit on message queue size for messages + * that require a reply + */ + parser->limits.max_replies_per_connection = 1024*8; + } + + parser->refcount = 1; + + return parser; +} + +BusConfigParser * +bus_config_parser_ref (BusConfigParser *parser) +{ + _dbus_assert (parser->refcount > 0); + + parser->refcount += 1; + + return parser; +} + +void +bus_config_parser_unref (BusConfigParser *parser) +{ + _dbus_assert (parser->refcount > 0); + + parser->refcount -= 1; + + if (parser->refcount == 0) + { + while (parser->stack != NULL) + pop_element (parser); + + dbus_free (parser->user); + dbus_free (parser->servicehelper); + dbus_free (parser->bus_type); + dbus_free (parser->pidfile); + + _dbus_list_foreach (&parser->listen_on, + (DBusForeachFunction) dbus_free, + NULL); + + _dbus_list_clear (&parser->listen_on); + + _dbus_list_foreach (&parser->service_dirs, + (DBusForeachFunction) dbus_free, + NULL); + + _dbus_list_clear (&parser->service_dirs); + + _dbus_list_foreach (&parser->conf_dirs, + (DBusForeachFunction) dbus_free, + NULL); + + _dbus_list_clear (&parser->conf_dirs); + + _dbus_list_foreach (&parser->mechanisms, + (DBusForeachFunction) dbus_free, + NULL); + + _dbus_list_clear (&parser->mechanisms); + + _dbus_string_free (&parser->basedir); + + if (parser->policy) + bus_policy_unref (parser->policy); + + if (parser->service_context_table) + _dbus_hash_table_unref (parser->service_context_table); + + dbus_free (parser); + } +} + +dbus_bool_t +bus_config_parser_check_doctype (BusConfigParser *parser, + const char *doctype, + DBusError *error) +{ + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + if (strcmp (doctype, "busconfig") != 0) + { + dbus_set_error (error, + DBUS_ERROR_FAILED, + "Configuration file has the wrong document type %s", + doctype); + return FALSE; + } + else + return TRUE; +} + +typedef struct +{ + const char *name; + const char **retloc; +} LocateAttr; + +static dbus_bool_t +locate_attributes (BusConfigParser *parser, + const char *element_name, + const char **attribute_names, + const char **attribute_values, + DBusError *error, + const char *first_attribute_name, + const char **first_attribute_retloc, + ...) +{ + va_list args; + const char *name; + const char **retloc; + int n_attrs; +#define MAX_ATTRS 24 + LocateAttr attrs[MAX_ATTRS]; + dbus_bool_t retval; + int i; + + _dbus_assert (first_attribute_name != NULL); + _dbus_assert (first_attribute_retloc != NULL); + + retval = TRUE; + + n_attrs = 1; + attrs[0].name = first_attribute_name; + attrs[0].retloc = first_attribute_retloc; + *first_attribute_retloc = NULL; + + va_start (args, first_attribute_retloc); + + name = va_arg (args, const char*); + retloc = va_arg (args, const char**); + + while (name != NULL) + { + _dbus_assert (retloc != NULL); + _dbus_assert (n_attrs < MAX_ATTRS); + + attrs[n_attrs].name = name; + attrs[n_attrs].retloc = retloc; + n_attrs += 1; + *retloc = NULL; + + name = va_arg (args, const char*); + retloc = va_arg (args, const char**); + } + + va_end (args); + + i = 0; + while (attribute_names[i]) + { + int j; + dbus_bool_t found; + + found = FALSE; + j = 0; + while (j < n_attrs) + { + if (strcmp (attrs[j].name, attribute_names[i]) == 0) + { + retloc = attrs[j].retloc; + + if (*retloc != NULL) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "Attribute \"%s\" repeated twice on the same <%s> element", + attrs[j].name, element_name); + retval = FALSE; + goto out; + } + + *retloc = attribute_values[i]; + found = TRUE; + } + + ++j; + } + + if (!found) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "Attribute \"%s\" is invalid on <%s> element in this context", + attribute_names[i], element_name); + retval = FALSE; + goto out; + } + + ++i; + } + + out: + return retval; +} + +static dbus_bool_t +check_no_attributes (BusConfigParser *parser, + const char *element_name, + const char **attribute_names, + const char **attribute_values, + DBusError *error) +{ + if (attribute_names[0] != NULL) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "Attribute \"%s\" is invalid on <%s> element in this context", + attribute_names[0], element_name); + return FALSE; + } + + return TRUE; +} + +static dbus_bool_t +start_busconfig_child (BusConfigParser *parser, + const char *element_name, + const char **attribute_names, + const char **attribute_values, + DBusError *error) +{ + ElementType element_type; + + element_type = bus_config_parser_element_name_to_type (element_name); + + if (element_type == ELEMENT_USER) + { + if (!check_no_attributes (parser, "user", attribute_names, attribute_values, error)) + return FALSE; + + if (push_element (parser, ELEMENT_USER) == NULL) + { + BUS_SET_OOM (error); + return FALSE; + } + + return TRUE; + } + else if (element_type == ELEMENT_TYPE) + { + if (!check_no_attributes (parser, "type", attribute_names, attribute_values, error)) + return FALSE; + + if (push_element (parser, ELEMENT_TYPE) == NULL) + { + BUS_SET_OOM (error); + return FALSE; + } + + return TRUE; + } + else if (element_type == ELEMENT_FORK) + { + if (!check_no_attributes (parser, "fork", attribute_names, attribute_values, error)) + return FALSE; + + if (push_element (parser, ELEMENT_FORK) == NULL) + { + BUS_SET_OOM (error); + return FALSE; + } + + parser->fork = TRUE; + + return TRUE; + } + else if (element_type == ELEMENT_SYSLOG) + { + if (!check_no_attributes (parser, "syslog", attribute_names, attribute_values, error)) + return FALSE; + + if (push_element (parser, ELEMENT_SYSLOG) == NULL) + { + BUS_SET_OOM (error); + return FALSE; + } + + parser->syslog = TRUE; + + return TRUE; + } + else if (element_type == ELEMENT_KEEP_UMASK) + { + if (!check_no_attributes (parser, "keep_umask", attribute_names, attribute_values, error)) + return FALSE; + + if (push_element (parser, ELEMENT_KEEP_UMASK) == NULL) + { + BUS_SET_OOM (error); + return FALSE; + } + + parser->keep_umask = TRUE; + + return TRUE; + } + else if (element_type == ELEMENT_PIDFILE) + { + if (!check_no_attributes (parser, "pidfile", attribute_names, attribute_values, error)) + return FALSE; + + if (push_element (parser, ELEMENT_PIDFILE) == NULL) + { + BUS_SET_OOM (error); + return FALSE; + } + + return TRUE; + } + else if (element_type == ELEMENT_LISTEN) + { + if (!check_no_attributes (parser, "listen", attribute_names, attribute_values, error)) + return FALSE; + + if (push_element (parser, ELEMENT_LISTEN) == NULL) + { + BUS_SET_OOM (error); + return FALSE; + } + + return TRUE; + } + else if (element_type == ELEMENT_AUTH) + { + if (!check_no_attributes (parser, "auth", attribute_names, attribute_values, error)) + return FALSE; + + if (push_element (parser, ELEMENT_AUTH) == NULL) + { + BUS_SET_OOM (error); + return FALSE; + } + + return TRUE; + } + else if (element_type == ELEMENT_SERVICEHELPER) + { + if (!check_no_attributes (parser, "servicehelper", attribute_names, attribute_values, error)) + return FALSE; + + if (push_element (parser, ELEMENT_SERVICEHELPER) == NULL) + { + BUS_SET_OOM (error); + return FALSE; + } + + return TRUE; + } + else if (element_type == ELEMENT_INCLUDEDIR) + { + if (!check_no_attributes (parser, "includedir", attribute_names, attribute_values, error)) + return FALSE; + + if (push_element (parser, ELEMENT_INCLUDEDIR) == NULL) + { + BUS_SET_OOM (error); + return FALSE; + } + + return TRUE; + } + else if (element_type == ELEMENT_STANDARD_SESSION_SERVICEDIRS) + { + DBusList *link; + DBusList *dirs; + dirs = NULL; + + if (!check_no_attributes (parser, "standard_session_servicedirs", attribute_names, attribute_values, error)) + return FALSE; + + if (push_element (parser, ELEMENT_STANDARD_SESSION_SERVICEDIRS) == NULL) + { + BUS_SET_OOM (error); + return FALSE; + } + + if (!_dbus_get_standard_session_servicedirs (&dirs)) + { + BUS_SET_OOM (error); + return FALSE; + } + + while ((link = _dbus_list_pop_first_link (&dirs))) + service_dirs_append_link_unique_or_free (&parser->service_dirs, link); + + return TRUE; + } + else if (element_type == ELEMENT_STANDARD_SYSTEM_SERVICEDIRS) + { + DBusList *link; + DBusList *dirs; + dirs = NULL; + + if (!check_no_attributes (parser, "standard_system_servicedirs", attribute_names, attribute_values, error)) + return FALSE; + + if (push_element (parser, ELEMENT_STANDARD_SYSTEM_SERVICEDIRS) == NULL) + { + BUS_SET_OOM (error); + return FALSE; + } + + if (!_dbus_get_standard_system_servicedirs (&dirs)) + { + BUS_SET_OOM (error); + return FALSE; + } + + while ((link = _dbus_list_pop_first_link (&dirs))) + service_dirs_append_link_unique_or_free (&parser->service_dirs, link); + + return TRUE; + } + else if (element_type == ELEMENT_SERVICEDIR) + { + if (!check_no_attributes (parser, "servicedir", attribute_names, attribute_values, error)) + return FALSE; + + if (push_element (parser, ELEMENT_SERVICEDIR) == NULL) + { + BUS_SET_OOM (error); + return FALSE; + } + + return TRUE; + } + else if (element_type == ELEMENT_INCLUDE) + { + Element *e; + const char *if_selinux_enabled; + const char *ignore_missing; + const char *selinux_root_relative; + + if ((e = push_element (parser, ELEMENT_INCLUDE)) == NULL) + { + BUS_SET_OOM (error); + return FALSE; + } + + e->d.include.ignore_missing = FALSE; + e->d.include.if_selinux_enabled = FALSE; + e->d.include.selinux_root_relative = FALSE; + + if (!locate_attributes (parser, "include", + attribute_names, + attribute_values, + error, + "ignore_missing", &ignore_missing, + "if_selinux_enabled", &if_selinux_enabled, + "selinux_root_relative", &selinux_root_relative, + NULL)) + return FALSE; + + if (ignore_missing != NULL) + { + if (strcmp (ignore_missing, "yes") == 0) + e->d.include.ignore_missing = TRUE; + else if (strcmp (ignore_missing, "no") == 0) + e->d.include.ignore_missing = FALSE; + else + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "ignore_missing attribute must have value \"yes\" or \"no\""); + return FALSE; + } + } + + if (if_selinux_enabled != NULL) + { + if (strcmp (if_selinux_enabled, "yes") == 0) + e->d.include.if_selinux_enabled = TRUE; + else if (strcmp (if_selinux_enabled, "no") == 0) + e->d.include.if_selinux_enabled = FALSE; + else + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "if_selinux_enabled attribute must have value" + " \"yes\" or \"no\""); + return FALSE; + } + } + + if (selinux_root_relative != NULL) + { + if (strcmp (selinux_root_relative, "yes") == 0) + e->d.include.selinux_root_relative = TRUE; + else if (strcmp (selinux_root_relative, "no") == 0) + e->d.include.selinux_root_relative = FALSE; + else + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "selinux_root_relative attribute must have value" + " \"yes\" or \"no\""); + return FALSE; + } + } + + return TRUE; + } + else if (element_type == ELEMENT_POLICY) + { + Element *e; + const char *context; + const char *user; + const char *group; + const char *at_console; + + if ((e = push_element (parser, ELEMENT_POLICY)) == NULL) + { + BUS_SET_OOM (error); + return FALSE; + } + + e->d.policy.type = POLICY_IGNORED; + + if (!locate_attributes (parser, "policy", + attribute_names, + attribute_values, + error, + "context", &context, + "user", &user, + "group", &group, + "at_console", &at_console, + NULL)) + return FALSE; + + if (((context && user) || + (context && group) || + (context && at_console)) || + ((user && group) || + (user && at_console)) || + (group && at_console) || + !(context || user || group || at_console)) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "<policy> element must have exactly one of (context|user|group|at_console) attributes"); + return FALSE; + } + + if (context != NULL) + { + if (strcmp (context, "default") == 0) + { + e->d.policy.type = POLICY_DEFAULT; + } + else if (strcmp (context, "mandatory") == 0) + { + e->d.policy.type = POLICY_MANDATORY; + } + else + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "context attribute on <policy> must have the value \"default\" or \"mandatory\", not \"%s\"", + context); + return FALSE; + } + } + else if (user != NULL) + { + DBusString username; + _dbus_string_init_const (&username, user); + + if (_dbus_parse_unix_user_from_config (&username, + &e->d.policy.gid_uid_or_at_console)) + e->d.policy.type = POLICY_USER; + else + _dbus_warn ("Unknown username \"%s\" in message bus configuration file\n", + user); + } + else if (group != NULL) + { + DBusString group_name; + _dbus_string_init_const (&group_name, group); + + if (_dbus_parse_unix_group_from_config (&group_name, + &e->d.policy.gid_uid_or_at_console)) + e->d.policy.type = POLICY_GROUP; + else + _dbus_warn ("Unknown group \"%s\" in message bus configuration file\n", + group); + } + else if (at_console != NULL) + { + dbus_bool_t t; + t = (strcmp (at_console, "true") == 0); + if (t || strcmp (at_console, "false") == 0) + { + e->d.policy.gid_uid_or_at_console = t; + e->d.policy.type = POLICY_CONSOLE; + } + else + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "Unknown value \"%s\" for at_console in message bus configuration file", + at_console); + + return FALSE; + } + } + else + { + _dbus_assert_not_reached ("all <policy> attributes null and we didn't set error"); + } + + return TRUE; + } + else if (element_type == ELEMENT_LIMIT) + { + Element *e; + const char *name; + + if ((e = push_element (parser, ELEMENT_LIMIT)) == NULL) + { + BUS_SET_OOM (error); + return FALSE; + } + + if (!locate_attributes (parser, "limit", + attribute_names, + attribute_values, + error, + "name", &name, + NULL)) + return FALSE; + + if (name == NULL) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "<limit> element must have a \"name\" attribute"); + return FALSE; + } + + e->d.limit.name = _dbus_strdup (name); + if (e->d.limit.name == NULL) + { + BUS_SET_OOM (error); + return FALSE; + } + + return TRUE; + } + else if (element_type == ELEMENT_SELINUX) + { + if (!check_no_attributes (parser, "selinux", attribute_names, attribute_values, error)) + return FALSE; + + if (push_element (parser, ELEMENT_SELINUX) == NULL) + { + BUS_SET_OOM (error); + return FALSE; + } + + return TRUE; + } + else + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "Element <%s> not allowed inside <%s> in configuration file", + element_name, "busconfig"); + return FALSE; + } +} + +static dbus_bool_t +append_rule_from_element (BusConfigParser *parser, + const char *element_name, + const char **attribute_names, + const char **attribute_values, + dbus_bool_t allow, + DBusError *error) +{ + const char *log; + const char *send_interface; + const char *send_member; + const char *send_error; + const char *send_destination; + const char *send_path; + const char *send_type; + const char *receive_interface; + const char *receive_member; + const char *receive_error; + const char *receive_sender; + const char *receive_path; + const char *receive_type; + const char *eavesdrop; + const char *send_requested_reply; + const char *receive_requested_reply; + const char *own; + const char *user; + const char *group; + + BusPolicyRule *rule; + + if (!locate_attributes (parser, element_name, + attribute_names, + attribute_values, + error, + "send_interface", &send_interface, + "send_member", &send_member, + "send_error", &send_error, + "send_destination", &send_destination, + "send_path", &send_path, + "send_type", &send_type, + "receive_interface", &receive_interface, + "receive_member", &receive_member, + "receive_error", &receive_error, + "receive_sender", &receive_sender, + "receive_path", &receive_path, + "receive_type", &receive_type, + "eavesdrop", &eavesdrop, + "send_requested_reply", &send_requested_reply, + "receive_requested_reply", &receive_requested_reply, + "own", &own, + "user", &user, + "group", &group, + "log", &log, + NULL)) + return FALSE; + + if (!(send_interface || send_member || send_error || send_destination || + send_type || send_path || + receive_interface || receive_member || receive_error || receive_sender || + receive_type || receive_path || eavesdrop || + send_requested_reply || receive_requested_reply || + own || user || group)) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "Element <%s> must have one or more attributes", + element_name); + return FALSE; + } + + if ((send_member && (send_interface == NULL && send_path == NULL)) || + (receive_member && (receive_interface == NULL && receive_path == NULL))) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "On element <%s>, if you specify a member you must specify an interface or a path. Keep in mind that not all messages have an interface field.", + element_name); + return FALSE; + } + + /* Allowed combinations of elements are: + * + * base, must be all send or all receive: + * nothing + * interface + * interface + member + * error + * + * base send_ can combine with send_destination, send_path, send_type, send_requested_reply + * base receive_ with receive_sender, receive_path, receive_type, receive_requested_reply, eavesdrop + * + * user, group, own must occur alone + * + * Pretty sure the below stuff is broken, FIXME think about it more. + */ + + if (((send_interface && send_error) || + (send_interface && receive_interface) || + (send_interface && receive_member) || + (send_interface && receive_error) || + (send_interface && receive_sender) || + (send_interface && receive_requested_reply) || + (send_interface && own) || + (send_interface && user) || + (send_interface && group)) || + + ((send_member && send_error) || + (send_member && receive_interface) || + (send_member && receive_member) || + (send_member && receive_error) || + (send_member && receive_sender) || + (send_member && receive_requested_reply) || + (send_member && own) || + (send_member && user) || + (send_member && group)) || + + ((send_error && receive_interface) || + (send_error && receive_member) || + (send_error && receive_error) || + (send_error && receive_sender) || + (send_error && receive_requested_reply) || + (send_error && own) || + (send_error && user) || + (send_error && group)) || + + ((send_destination && receive_interface) || + (send_destination && receive_member) || + (send_destination && receive_error) || + (send_destination && receive_sender) || + (send_destination && receive_requested_reply) || + (send_destination && own) || + (send_destination && user) || + (send_destination && group)) || + + ((send_type && receive_interface) || + (send_type && receive_member) || + (send_type && receive_error) || + (send_type && receive_sender) || + (send_type && receive_requested_reply) || + (send_type && own) || + (send_type && user) || + (send_type && group)) || + + ((send_path && receive_interface) || + (send_path && receive_member) || + (send_path && receive_error) || + (send_path && receive_sender) || + (send_path && receive_requested_reply) || + (send_path && own) || + (send_path && user) || + (send_path && group)) || + + ((send_requested_reply && receive_interface) || + (send_requested_reply && receive_member) || + (send_requested_reply && receive_error) || + (send_requested_reply && receive_sender) || + (send_requested_reply && receive_requested_reply) || + (send_requested_reply && own) || + (send_requested_reply && user) || + (send_requested_reply && group)) || + + ((receive_interface && receive_error) || + (receive_interface && own) || + (receive_interface && user) || + (receive_interface && group)) || + + ((receive_member && receive_error) || + (receive_member && own) || + (receive_member && user) || + (receive_member && group)) || + + ((receive_error && own) || + (receive_error && user) || + (receive_error && group)) || + + ((eavesdrop && own) || + (eavesdrop && user) || + (eavesdrop && group)) || + + ((receive_requested_reply && own) || + (receive_requested_reply && user) || + (receive_requested_reply && group)) || + + ((own && user) || + (own && group)) || + + ((user && group))) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "Invalid combination of attributes on element <%s>", + element_name); + return FALSE; + } + + rule = NULL; + + /* In BusPolicyRule, NULL represents wildcard. + * In the config file, '*' represents it. + */ +#define IS_WILDCARD(str) ((str) && ((str)[0]) == '*' && ((str)[1]) == '\0') + + if (send_interface || send_member || send_error || send_destination || + send_path || send_type || send_requested_reply) + { + int message_type; + + if (IS_WILDCARD (send_interface)) + send_interface = NULL; + if (IS_WILDCARD (send_member)) + send_member = NULL; + if (IS_WILDCARD (send_error)) + send_error = NULL; + if (IS_WILDCARD (send_destination)) + send_destination = NULL; + if (IS_WILDCARD (send_path)) + send_path = NULL; + if (IS_WILDCARD (send_type)) + send_type = NULL; + + message_type = DBUS_MESSAGE_TYPE_INVALID; + if (send_type != NULL) + { + message_type = dbus_message_type_from_string (send_type); + if (message_type == DBUS_MESSAGE_TYPE_INVALID) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "Bad message type \"%s\"", + send_type); + return FALSE; + } + } + + if (eavesdrop && + !(strcmp (eavesdrop, "true") == 0 || + strcmp (eavesdrop, "false") == 0)) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "Bad value \"%s\" for %s attribute, must be true or false", + "eavesdrop", eavesdrop); + return FALSE; + } + + if (send_requested_reply && + !(strcmp (send_requested_reply, "true") == 0 || + strcmp (send_requested_reply, "false") == 0)) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "Bad value \"%s\" for %s attribute, must be true or false", + "send_requested_reply", send_requested_reply); + return FALSE; + } + + rule = bus_policy_rule_new (BUS_POLICY_RULE_SEND, allow); + if (rule == NULL) + goto nomem; + + if (eavesdrop) + rule->d.send.eavesdrop = (strcmp (eavesdrop, "true") == 0); + + if (log) + rule->d.send.log = (strcmp (log, "true") == 0); + + if (send_requested_reply) + rule->d.send.requested_reply = (strcmp (send_requested_reply, "true") == 0); + + rule->d.send.message_type = message_type; + rule->d.send.path = _dbus_strdup (send_path); + rule->d.send.interface = _dbus_strdup (send_interface); + rule->d.send.member = _dbus_strdup (send_member); + rule->d.send.error = _dbus_strdup (send_error); + rule->d.send.destination = _dbus_strdup (send_destination); + if (send_path && rule->d.send.path == NULL) + goto nomem; + if (send_interface && rule->d.send.interface == NULL) + goto nomem; + if (send_member && rule->d.send.member == NULL) + goto nomem; + if (send_error && rule->d.send.error == NULL) + goto nomem; + if (send_destination && rule->d.send.destination == NULL) + goto nomem; + } + else if (receive_interface || receive_member || receive_error || receive_sender || + receive_path || receive_type || eavesdrop || receive_requested_reply) + { + int message_type; + + if (IS_WILDCARD (receive_interface)) + receive_interface = NULL; + if (IS_WILDCARD (receive_member)) + receive_member = NULL; + if (IS_WILDCARD (receive_error)) + receive_error = NULL; + if (IS_WILDCARD (receive_sender)) + receive_sender = NULL; + if (IS_WILDCARD (receive_path)) + receive_path = NULL; + if (IS_WILDCARD (receive_type)) + receive_type = NULL; + + message_type = DBUS_MESSAGE_TYPE_INVALID; + if (receive_type != NULL) + { + message_type = dbus_message_type_from_string (receive_type); + if (message_type == DBUS_MESSAGE_TYPE_INVALID) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "Bad message type \"%s\"", + receive_type); + return FALSE; + } + } + + + if (eavesdrop && + !(strcmp (eavesdrop, "true") == 0 || + strcmp (eavesdrop, "false") == 0)) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "Bad value \"%s\" for %s attribute, must be true or false", + "eavesdrop", eavesdrop); + return FALSE; + } + + if (receive_requested_reply && + !(strcmp (receive_requested_reply, "true") == 0 || + strcmp (receive_requested_reply, "false") == 0)) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "Bad value \"%s\" for %s attribute, must be true or false", + "receive_requested_reply", receive_requested_reply); + return FALSE; + } + + rule = bus_policy_rule_new (BUS_POLICY_RULE_RECEIVE, allow); + if (rule == NULL) + goto nomem; + + if (eavesdrop) + rule->d.receive.eavesdrop = (strcmp (eavesdrop, "true") == 0); + + if (receive_requested_reply) + rule->d.receive.requested_reply = (strcmp (receive_requested_reply, "true") == 0); + + rule->d.receive.message_type = message_type; + rule->d.receive.path = _dbus_strdup (receive_path); + rule->d.receive.interface = _dbus_strdup (receive_interface); + rule->d.receive.member = _dbus_strdup (receive_member); + rule->d.receive.error = _dbus_strdup (receive_error); + rule->d.receive.origin = _dbus_strdup (receive_sender); + + if (receive_path && rule->d.receive.path == NULL) + goto nomem; + if (receive_interface && rule->d.receive.interface == NULL) + goto nomem; + if (receive_member && rule->d.receive.member == NULL) + goto nomem; + if (receive_error && rule->d.receive.error == NULL) + goto nomem; + if (receive_sender && rule->d.receive.origin == NULL) + goto nomem; + } + else if (own) + { + rule = bus_policy_rule_new (BUS_POLICY_RULE_OWN, allow); + if (rule == NULL) + goto nomem; + + if (IS_WILDCARD (own)) + own = NULL; + + rule->d.own.service_name = _dbus_strdup (own); + if (own && rule->d.own.service_name == NULL) + goto nomem; + } + else if (user) + { + if (IS_WILDCARD (user)) + { + rule = bus_policy_rule_new (BUS_POLICY_RULE_USER, allow); + if (rule == NULL) + goto nomem; + + rule->d.user.uid = DBUS_UID_UNSET; + } + else + { + DBusString username; + dbus_uid_t uid; + + _dbus_string_init_const (&username, user); + + if (_dbus_parse_unix_user_from_config (&username, &uid)) + { + rule = bus_policy_rule_new (BUS_POLICY_RULE_USER, allow); + if (rule == NULL) + goto nomem; + + rule->d.user.uid = uid; + } + else + { + _dbus_warn ("Unknown username \"%s\" on element <%s>\n", + user, element_name); + } + } + } + else if (group) + { + if (IS_WILDCARD (group)) + { + rule = bus_policy_rule_new (BUS_POLICY_RULE_GROUP, allow); + if (rule == NULL) + goto nomem; + + rule->d.group.gid = DBUS_GID_UNSET; + } + else + { + DBusString groupname; + dbus_gid_t gid; + + _dbus_string_init_const (&groupname, group); + + if (_dbus_parse_unix_group_from_config (&groupname, &gid)) + { + rule = bus_policy_rule_new (BUS_POLICY_RULE_GROUP, allow); + if (rule == NULL) + goto nomem; + + rule->d.group.gid = gid; + } + else + { + _dbus_warn ("Unknown group \"%s\" on element <%s>\n", + group, element_name); + } + } + } + else + _dbus_assert_not_reached ("Did not handle some combination of attributes on <allow> or <deny>"); + + if (rule != NULL) + { + Element *pe; + + pe = peek_element (parser); + _dbus_assert (pe != NULL); + _dbus_assert (pe->type == ELEMENT_POLICY); + + switch (pe->d.policy.type) + { + case POLICY_IGNORED: + /* drop the rule on the floor */ + break; + + case POLICY_DEFAULT: + if (!bus_policy_append_default_rule (parser->policy, rule)) + goto nomem; + break; + case POLICY_MANDATORY: + if (!bus_policy_append_mandatory_rule (parser->policy, rule)) + goto nomem; + break; + case POLICY_USER: + if (!BUS_POLICY_RULE_IS_PER_CLIENT (rule)) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "<%s> rule cannot be per-user because it has bus-global semantics", + element_name); + goto failed; + } + + if (!bus_policy_append_user_rule (parser->policy, pe->d.policy.gid_uid_or_at_console, + rule)) + goto nomem; + break; + case POLICY_GROUP: + if (!BUS_POLICY_RULE_IS_PER_CLIENT (rule)) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "<%s> rule cannot be per-group because it has bus-global semantics", + element_name); + goto failed; + } + + if (!bus_policy_append_group_rule (parser->policy, pe->d.policy.gid_uid_or_at_console, + rule)) + goto nomem; + break; + + + case POLICY_CONSOLE: + if (!bus_policy_append_console_rule (parser->policy, pe->d.policy.gid_uid_or_at_console, + rule)) + goto nomem; + break; + } + + bus_policy_rule_unref (rule); + rule = NULL; + } + + return TRUE; + + nomem: + BUS_SET_OOM (error); + failed: + if (rule) + bus_policy_rule_unref (rule); + return FALSE; +} + +static dbus_bool_t +start_policy_child (BusConfigParser *parser, + const char *element_name, + const char **attribute_names, + const char **attribute_values, + DBusError *error) +{ + if (strcmp (element_name, "allow") == 0) + { + if (!append_rule_from_element (parser, element_name, + attribute_names, attribute_values, + TRUE, error)) + return FALSE; + + if (push_element (parser, ELEMENT_ALLOW) == NULL) + { + BUS_SET_OOM (error); + return FALSE; + } + + return TRUE; + } + else if (strcmp (element_name, "deny") == 0) + { + if (!append_rule_from_element (parser, element_name, + attribute_names, attribute_values, + FALSE, error)) + return FALSE; + + if (push_element (parser, ELEMENT_DENY) == NULL) + { + BUS_SET_OOM (error); + return FALSE; + } + + return TRUE; + } + else + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "Element <%s> not allowed inside <%s> in configuration file", + element_name, "policy"); + return FALSE; + } +} + +static dbus_bool_t +start_selinux_child (BusConfigParser *parser, + const char *element_name, + const char **attribute_names, + const char **attribute_values, + DBusError *error) +{ + char *own_copy; + char *context_copy; + + own_copy = NULL; + context_copy = NULL; + + if (strcmp (element_name, "associate") == 0) + { + const char *own; + const char *context; + + if (!locate_attributes (parser, "associate", + attribute_names, + attribute_values, + error, + "own", &own, + "context", &context, + NULL)) + return FALSE; + + if (push_element (parser, ELEMENT_ASSOCIATE) == NULL) + { + BUS_SET_OOM (error); + return FALSE; + } + + if (own == NULL || context == NULL) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "Element <associate> must have attributes own=\"<servicename>\" and context=\"<selinux context>\""); + return FALSE; + } + + own_copy = _dbus_strdup (own); + if (own_copy == NULL) + goto oom; + context_copy = _dbus_strdup (context); + if (context_copy == NULL) + goto oom; + + if (!_dbus_hash_table_insert_string (parser->service_context_table, + own_copy, context_copy)) + goto oom; + + return TRUE; + } + else + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "Element <%s> not allowed inside <%s> in configuration file", + element_name, "selinux"); + return FALSE; + } + + oom: + if (own_copy) + dbus_free (own_copy); + + if (context_copy) + dbus_free (context_copy); + + BUS_SET_OOM (error); + return FALSE; +} + +dbus_bool_t +bus_config_parser_start_element (BusConfigParser *parser, + const char *element_name, + const char **attribute_names, + const char **attribute_values, + DBusError *error) +{ + ElementType t; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + /* printf ("START: %s\n", element_name); */ + + t = top_element_type (parser); + + if (t == ELEMENT_NONE) + { + if (strcmp (element_name, "busconfig") == 0) + { + if (!check_no_attributes (parser, "busconfig", attribute_names, attribute_values, error)) + return FALSE; + + if (push_element (parser, ELEMENT_BUSCONFIG) == NULL) + { + BUS_SET_OOM (error); + return FALSE; + } + + return TRUE; + } + else + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "Unknown element <%s> at root of configuration file", + element_name); + return FALSE; + } + } + else if (t == ELEMENT_BUSCONFIG) + { + return start_busconfig_child (parser, element_name, + attribute_names, attribute_values, + error); + } + else if (t == ELEMENT_POLICY) + { + return start_policy_child (parser, element_name, + attribute_names, attribute_values, + error); + } + else if (t == ELEMENT_SELINUX) + { + return start_selinux_child (parser, element_name, + attribute_names, attribute_values, + error); + } + else + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "Element <%s> is not allowed in this context", + element_name); + return FALSE; + } +} + +static dbus_bool_t +set_limit (BusConfigParser *parser, + const char *name, + long value, + DBusError *error) +{ + dbus_bool_t must_be_positive; + dbus_bool_t must_be_int; + + must_be_int = FALSE; + must_be_positive = FALSE; + + if (strcmp (name, "max_incoming_bytes") == 0) + { + must_be_positive = TRUE; + parser->limits.max_incoming_bytes = value; + } + else if (strcmp (name, "max_outgoing_bytes") == 0) + { + must_be_positive = TRUE; + parser->limits.max_outgoing_bytes = value; + } + else if (strcmp (name, "max_message_size") == 0) + { + must_be_positive = TRUE; + parser->limits.max_message_size = value; + } + else if (strcmp (name, "service_start_timeout") == 0) + { + must_be_positive = TRUE; + must_be_int = TRUE; + parser->limits.activation_timeout = value; + } + else if (strcmp (name, "auth_timeout") == 0) + { + must_be_positive = TRUE; + must_be_int = TRUE; + parser->limits.auth_timeout = value; + } + else if (strcmp (name, "reply_timeout") == 0) + { + must_be_positive = TRUE; + must_be_int = TRUE; + parser->limits.reply_timeout = value; + } + else if (strcmp (name, "max_completed_connections") == 0) + { + must_be_positive = TRUE; + must_be_int = TRUE; + parser->limits.max_completed_connections = value; + } + else if (strcmp (name, "max_incomplete_connections") == 0) + { + must_be_positive = TRUE; + must_be_int = TRUE; + parser->limits.max_incomplete_connections = value; + } + else if (strcmp (name, "max_connections_per_user") == 0) + { + must_be_positive = TRUE; + must_be_int = TRUE; + parser->limits.max_connections_per_user = value; + } + else if (strcmp (name, "max_pending_service_starts") == 0) + { + must_be_positive = TRUE; + must_be_int = TRUE; + parser->limits.max_pending_activations = value; + } + else if (strcmp (name, "max_names_per_connection") == 0) + { + must_be_positive = TRUE; + must_be_int = TRUE; + parser->limits.max_services_per_connection = value; + } + else if (strcmp (name, "max_match_rules_per_connection") == 0) + { + must_be_positive = TRUE; + must_be_int = TRUE; + parser->limits.max_match_rules_per_connection = value; + } + else if (strcmp (name, "max_replies_per_connection") == 0) + { + must_be_positive = TRUE; + must_be_int = TRUE; + parser->limits.max_replies_per_connection = value; + } + else + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "There is no limit called \"%s\"\n", + name); + return FALSE; + } + + if (must_be_positive && value < 0) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "<limit name=\"%s\"> must be a positive number\n", + name); + return FALSE; + } + + if (must_be_int && + (value < _DBUS_INT_MIN || value > _DBUS_INT_MAX)) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "<limit name=\"%s\"> value is too large\n", + name); + return FALSE; + } + + return TRUE; +} + +dbus_bool_t +bus_config_parser_end_element (BusConfigParser *parser, + const char *element_name, + DBusError *error) +{ + ElementType t; + const char *n; + Element *e; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + /* printf ("END: %s\n", element_name); */ + + t = top_element_type (parser); + + if (t == ELEMENT_NONE) + { + /* should probably be an assertion failure but + * being paranoid about XML parsers + */ + dbus_set_error (error, DBUS_ERROR_FAILED, + "XML parser ended element with no element on the stack"); + return FALSE; + } + + n = bus_config_parser_element_type_to_name (t); + _dbus_assert (n != NULL); + if (strcmp (n, element_name) != 0) + { + /* should probably be an assertion failure but + * being paranoid about XML parsers + */ + dbus_set_error (error, DBUS_ERROR_FAILED, + "XML element <%s> ended but topmost element on the stack was <%s>", + element_name, n); + return FALSE; + } + + e = peek_element (parser); + _dbus_assert (e != NULL); + + switch (e->type) + { + case ELEMENT_NONE: + _dbus_assert_not_reached ("element in stack has no type"); + break; + + case ELEMENT_INCLUDE: + case ELEMENT_USER: + case ELEMENT_TYPE: + case ELEMENT_LISTEN: + case ELEMENT_PIDFILE: + case ELEMENT_AUTH: + case ELEMENT_SERVICEDIR: + case ELEMENT_SERVICEHELPER: + case ELEMENT_INCLUDEDIR: + case ELEMENT_LIMIT: + if (!e->had_content) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "XML element <%s> was expected to have content inside it", + bus_config_parser_element_type_to_name (e->type)); + return FALSE; + } + + if (e->type == ELEMENT_LIMIT) + { + if (!set_limit (parser, e->d.limit.name, e->d.limit.value, + error)) + return FALSE; + } + break; + + case ELEMENT_BUSCONFIG: + case ELEMENT_POLICY: + case ELEMENT_ALLOW: + case ELEMENT_DENY: + case ELEMENT_FORK: + case ELEMENT_SYSLOG: + case ELEMENT_KEEP_UMASK: + case ELEMENT_SELINUX: + case ELEMENT_ASSOCIATE: + case ELEMENT_STANDARD_SESSION_SERVICEDIRS: + case ELEMENT_STANDARD_SYSTEM_SERVICEDIRS: + break; + } + + pop_element (parser); + + return TRUE; +} + +static dbus_bool_t +all_whitespace (const DBusString *str) +{ + int i; + + _dbus_string_skip_white (str, 0, &i); + + return i == _dbus_string_get_length (str); +} + +static dbus_bool_t +make_full_path (const DBusString *basedir, + const DBusString *filename, + DBusString *full_path) +{ + if (_dbus_path_is_absolute (filename)) + { + return _dbus_string_copy (filename, 0, full_path, 0); + } + else + { + if (!_dbus_string_copy (basedir, 0, full_path, 0)) + return FALSE; + + if (!_dbus_concat_dir_and_file (full_path, filename)) + return FALSE; + + return TRUE; + } +} + +static dbus_bool_t +include_file (BusConfigParser *parser, + const DBusString *filename, + dbus_bool_t ignore_missing, + DBusError *error) +{ + /* FIXME good test case for this would load each config file in the + * test suite both alone, and as an include, and check + * that the result is the same + */ + BusConfigParser *included; + const char *filename_str; + DBusError tmp_error; + + dbus_error_init (&tmp_error); + + filename_str = _dbus_string_get_const_data (filename); + + /* Check to make sure this file hasn't already been included. */ + if (seen_include (parser, filename)) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "Circular inclusion of file '%s'", + filename_str); + return FALSE; + } + + if (! _dbus_list_append (&parser->included_files, (void *) filename_str)) + { + BUS_SET_OOM (error); + return FALSE; + } + + /* Since parser is passed in as the parent, included + inherits parser's limits. */ + included = bus_config_load (filename, FALSE, parser, &tmp_error); + + _dbus_list_pop_last (&parser->included_files); + + if (included == NULL) + { + _DBUS_ASSERT_ERROR_IS_SET (&tmp_error); + + if (dbus_error_has_name (&tmp_error, DBUS_ERROR_FILE_NOT_FOUND) && + ignore_missing) + { + dbus_error_free (&tmp_error); + return TRUE; + } + else + { + dbus_move_error (&tmp_error, error); + return FALSE; + } + } + else + { + _DBUS_ASSERT_ERROR_IS_CLEAR (&tmp_error); + + if (!merge_included (parser, included, error)) + { + bus_config_parser_unref (included); + return FALSE; + } + + /* Copy included's limits back to parser. */ + parser->limits = included->limits; + + bus_config_parser_unref (included); + return TRUE; + } +} + +static dbus_bool_t +servicehelper_path (BusConfigParser *parser, + const DBusString *filename, + DBusError *error) +{ + const char *filename_str; + char *servicehelper; + + filename_str = _dbus_string_get_const_data (filename); + + /* copy to avoid overwriting with NULL on OOM */ + servicehelper = _dbus_strdup (filename_str); + + /* check for OOM */ + if (servicehelper == NULL) + { + BUS_SET_OOM (error); + return FALSE; + } + + /* save the latest servicehelper only if not OOM */ + dbus_free (parser->servicehelper); + parser->servicehelper = servicehelper; + + /* We don't check whether the helper exists; instead we + * would just fail to ever activate anything if it doesn't. + * This allows an admin to fix the problem if it doesn't exist. + * It also allows the parser test suite to successfully parse + * test cases without installing the helper. ;-) + */ + + return TRUE; +} + +static dbus_bool_t +include_dir (BusConfigParser *parser, + const DBusString *dirname, + DBusError *error) +{ + DBusString filename; + dbus_bool_t retval; + DBusError tmp_error; + DBusDirIter *dir; + char *s; + + if (!_dbus_string_init (&filename)) + { + BUS_SET_OOM (error); + return FALSE; + } + + retval = FALSE; + + dir = _dbus_directory_open (dirname, error); + + if (dir == NULL) + goto failed; + + dbus_error_init (&tmp_error); + while (_dbus_directory_get_next_file (dir, &filename, &tmp_error)) + { + DBusString full_path; + + if (!_dbus_string_init (&full_path)) + { + BUS_SET_OOM (error); + goto failed; + } + + if (!_dbus_string_copy (dirname, 0, &full_path, 0)) + { + BUS_SET_OOM (error); + _dbus_string_free (&full_path); + goto failed; + } + + if (!_dbus_concat_dir_and_file (&full_path, &filename)) + { + BUS_SET_OOM (error); + _dbus_string_free (&full_path); + goto failed; + } + + if (_dbus_string_ends_with_c_str (&full_path, ".conf")) + { + if (!include_file (parser, &full_path, TRUE, error)) + { + _dbus_string_free (&full_path); + goto failed; + } + } + + _dbus_string_free (&full_path); + } + + if (dbus_error_is_set (&tmp_error)) + { + dbus_move_error (&tmp_error, error); + goto failed; + } + + + if (!_dbus_string_copy_data (dirname, &s)) + { + BUS_SET_OOM (error); + goto failed; + } + + if (!_dbus_list_append (&parser->conf_dirs, s)) + { + dbus_free (s); + BUS_SET_OOM (error); + goto failed; + } + + retval = TRUE; + + failed: + _dbus_string_free (&filename); + + if (dir) + _dbus_directory_close (dir); + + return retval; +} + +dbus_bool_t +bus_config_parser_content (BusConfigParser *parser, + const DBusString *content, + DBusError *error) +{ + Element *e; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + +#if 0 + { + const char *c_str; + + _dbus_string_get_const_data (content, &c_str); + + printf ("CONTENT %d bytes: %s\n", _dbus_string_get_length (content), c_str); + } +#endif + + e = peek_element (parser); + if (e == NULL) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "Text content outside of any XML element in configuration file"); + return FALSE; + } + else if (e->had_content) + { + _dbus_assert_not_reached ("Element had multiple content blocks"); + return FALSE; + } + + switch (top_element_type (parser)) + { + case ELEMENT_NONE: + _dbus_assert_not_reached ("element at top of stack has no type"); + return FALSE; + + case ELEMENT_BUSCONFIG: + case ELEMENT_POLICY: + case ELEMENT_ALLOW: + case ELEMENT_DENY: + case ELEMENT_FORK: + case ELEMENT_SYSLOG: + case ELEMENT_KEEP_UMASK: + case ELEMENT_STANDARD_SESSION_SERVICEDIRS: + case ELEMENT_STANDARD_SYSTEM_SERVICEDIRS: + case ELEMENT_SELINUX: + case ELEMENT_ASSOCIATE: + if (all_whitespace (content)) + return TRUE; + else + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "No text content expected inside XML element %s in configuration file", + bus_config_parser_element_type_to_name (top_element_type (parser))); + return FALSE; + } + + case ELEMENT_PIDFILE: + { + char *s; + + e->had_content = TRUE; + + if (!_dbus_string_copy_data (content, &s)) + goto nomem; + + dbus_free (parser->pidfile); + parser->pidfile = s; + } + break; + + case ELEMENT_INCLUDE: + { + DBusString full_path, selinux_policy_root; + + e->had_content = TRUE; + + if (e->d.include.if_selinux_enabled + && !bus_selinux_enabled ()) + break; + + if (!_dbus_string_init (&full_path)) + goto nomem; + + if (e->d.include.selinux_root_relative) + { + if (!bus_selinux_get_policy_root ()) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "Could not determine SELinux policy root for relative inclusion"); + _dbus_string_free (&full_path); + return FALSE; + } + _dbus_string_init_const (&selinux_policy_root, + bus_selinux_get_policy_root ()); + if (!make_full_path (&selinux_policy_root, content, &full_path)) + { + _dbus_string_free (&full_path); + goto nomem; + } + } + else if (!make_full_path (&parser->basedir, content, &full_path)) + { + _dbus_string_free (&full_path); + goto nomem; + } + + if (!include_file (parser, &full_path, + e->d.include.ignore_missing, error)) + { + _dbus_string_free (&full_path); + return FALSE; + } + + _dbus_string_free (&full_path); + } + break; + + case ELEMENT_SERVICEHELPER: + { + DBusString full_path; + + e->had_content = TRUE; + + if (!_dbus_string_init (&full_path)) + goto nomem; + + if (!make_full_path (&parser->basedir, content, &full_path)) + { + _dbus_string_free (&full_path); + goto nomem; + } + + if (!servicehelper_path (parser, &full_path, error)) + { + _dbus_string_free (&full_path); + return FALSE; + } + + _dbus_string_free (&full_path); + } + break; + + case ELEMENT_INCLUDEDIR: + { + DBusString full_path; + + e->had_content = TRUE; + + if (!_dbus_string_init (&full_path)) + goto nomem; + + if (!make_full_path (&parser->basedir, content, &full_path)) + { + _dbus_string_free (&full_path); + goto nomem; + } + + if (!include_dir (parser, &full_path, error)) + { + _dbus_string_free (&full_path); + return FALSE; + } + + _dbus_string_free (&full_path); + } + break; + + case ELEMENT_USER: + { + char *s; + + e->had_content = TRUE; + + if (!_dbus_string_copy_data (content, &s)) + goto nomem; + + dbus_free (parser->user); + parser->user = s; + } + break; + + case ELEMENT_TYPE: + { + char *s; + + e->had_content = TRUE; + + if (!_dbus_string_copy_data (content, &s)) + goto nomem; + + dbus_free (parser->bus_type); + parser->bus_type = s; + } + break; + + case ELEMENT_LISTEN: + { + char *s; + + e->had_content = TRUE; + + if (!_dbus_string_copy_data (content, &s)) + goto nomem; + + if (!_dbus_list_append (&parser->listen_on, + s)) + { + dbus_free (s); + goto nomem; + } + } + break; + + case ELEMENT_AUTH: + { + char *s; + + e->had_content = TRUE; + + if (!_dbus_string_copy_data (content, &s)) + goto nomem; + + if (!_dbus_list_append (&parser->mechanisms, + s)) + { + dbus_free (s); + goto nomem; + } + } + break; + + case ELEMENT_SERVICEDIR: + { + char *s; + DBusString full_path; + + e->had_content = TRUE; + + if (!_dbus_string_init (&full_path)) + goto nomem; + + if (!make_full_path (&parser->basedir, content, &full_path)) + { + _dbus_string_free (&full_path); + goto nomem; + } + + if (!_dbus_string_copy_data (&full_path, &s)) + { + _dbus_string_free (&full_path); + goto nomem; + } + + /* _only_ extra session directories can be specified */ + if (!service_dirs_append_unique_or_free (&parser->service_dirs, s)) + { + _dbus_string_free (&full_path); + dbus_free (s); + goto nomem; + } + + _dbus_string_free (&full_path); + } + break; + + case ELEMENT_LIMIT: + { + long val; + + e->had_content = TRUE; + + val = 0; + if (!_dbus_string_parse_int (content, 0, &val, NULL)) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "<limit name=\"%s\"> element has invalid value (could not parse as integer)", + e->d.limit.name); + return FALSE; + } + + e->d.limit.value = val; + + _dbus_verbose ("Loaded value %ld for limit %s\n", + e->d.limit.value, + e->d.limit.name); + } + break; + } + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + return TRUE; + + nomem: + BUS_SET_OOM (error); + return FALSE; +} + +dbus_bool_t +bus_config_parser_finished (BusConfigParser *parser, + DBusError *error) +{ + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + if (parser->stack != NULL) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "Element <%s> was not closed in configuration file", + bus_config_parser_element_type_to_name (top_element_type (parser))); + + return FALSE; + } + + if (parser->is_toplevel && parser->listen_on == NULL) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "Configuration file needs one or more <listen> elements giving addresses"); + return FALSE; + } + + return TRUE; +} + +const char* +bus_config_parser_get_user (BusConfigParser *parser) +{ + return parser->user; +} + +const char* +bus_config_parser_get_type (BusConfigParser *parser) +{ + return parser->bus_type; +} + +DBusList** +bus_config_parser_get_addresses (BusConfigParser *parser) +{ + return &parser->listen_on; +} + +DBusList** +bus_config_parser_get_mechanisms (BusConfigParser *parser) +{ + return &parser->mechanisms; +} + +DBusList** +bus_config_parser_get_service_dirs (BusConfigParser *parser) +{ + return &parser->service_dirs; +} + +DBusList** +bus_config_parser_get_conf_dirs (BusConfigParser *parser) +{ + return &parser->conf_dirs; +} + +dbus_bool_t +bus_config_parser_get_fork (BusConfigParser *parser) +{ + return parser->fork; +} + +dbus_bool_t +bus_config_parser_get_syslog (BusConfigParser *parser) +{ + return parser->syslog; +} + +dbus_bool_t +bus_config_parser_get_keep_umask (BusConfigParser *parser) +{ + return parser->keep_umask; +} + +const char * +bus_config_parser_get_pidfile (BusConfigParser *parser) +{ + return parser->pidfile; +} + +const char * +bus_config_parser_get_servicehelper (BusConfigParser *parser) +{ + return parser->servicehelper; +} + +BusPolicy* +bus_config_parser_steal_policy (BusConfigParser *parser) +{ + BusPolicy *policy; + + _dbus_assert (parser->policy != NULL); /* can only steal the policy 1 time */ + + policy = parser->policy; + + parser->policy = NULL; + + return policy; +} + +/* Overwrite any limits that were set in the configuration file */ +void +bus_config_parser_get_limits (BusConfigParser *parser, + BusLimits *limits) +{ + *limits = parser->limits; +} + +DBusHashTable* +bus_config_parser_steal_service_context_table (BusConfigParser *parser) +{ + DBusHashTable *table; + + _dbus_assert (parser->service_context_table != NULL); /* can only steal once */ + + table = parser->service_context_table; + + parser->service_context_table = NULL; + + return table; +} + +#ifdef DBUS_BUILD_TESTS +#include <stdio.h> + +typedef enum +{ + VALID, + INVALID, + UNKNOWN +} Validity; + +static dbus_bool_t +do_load (const DBusString *full_path, + Validity validity, + dbus_bool_t oom_possible) +{ + BusConfigParser *parser; + DBusError error; + + dbus_error_init (&error); + + parser = bus_config_load (full_path, TRUE, NULL, &error); + if (parser == NULL) + { + _DBUS_ASSERT_ERROR_IS_SET (&error); + + if (oom_possible && + dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY)) + { + _dbus_verbose ("Failed to load valid file due to OOM\n"); + dbus_error_free (&error); + return TRUE; + } + else if (validity == VALID) + { + _dbus_warn ("Failed to load valid file but still had memory: %s\n", + error.message); + + dbus_error_free (&error); + return FALSE; + } + else + { + dbus_error_free (&error); + return TRUE; + } + } + else + { + _DBUS_ASSERT_ERROR_IS_CLEAR (&error); + + bus_config_parser_unref (parser); + + if (validity == INVALID) + { + _dbus_warn ("Accepted invalid file\n"); + return FALSE; + } + + return TRUE; + } +} + +typedef struct +{ + const DBusString *full_path; + Validity validity; +} LoaderOomData; + +static dbus_bool_t +check_loader_oom_func (void *data) +{ + LoaderOomData *d = data; + + return do_load (d->full_path, d->validity, TRUE); +} + +static dbus_bool_t +process_test_valid_subdir (const DBusString *test_base_dir, + const char *subdir, + Validity validity) +{ + DBusString test_directory; + DBusString filename; + DBusDirIter *dir; + dbus_bool_t retval; + DBusError error; + + retval = FALSE; + dir = NULL; + + if (!_dbus_string_init (&test_directory)) + _dbus_assert_not_reached ("didn't allocate test_directory\n"); + + _dbus_string_init_const (&filename, subdir); + + if (!_dbus_string_copy (test_base_dir, 0, + &test_directory, 0)) + _dbus_assert_not_reached ("couldn't copy test_base_dir to test_directory"); + + if (!_dbus_concat_dir_and_file (&test_directory, &filename)) + _dbus_assert_not_reached ("couldn't allocate full path"); + + _dbus_string_free (&filename); + if (!_dbus_string_init (&filename)) + _dbus_assert_not_reached ("didn't allocate filename string\n"); + + dbus_error_init (&error); + dir = _dbus_directory_open (&test_directory, &error); + if (dir == NULL) + { + _dbus_warn ("Could not open %s: %s\n", + _dbus_string_get_const_data (&test_directory), + error.message); + dbus_error_free (&error); + goto failed; + } + + if (validity == VALID) + printf ("Testing valid files:\n"); + else if (validity == INVALID) + printf ("Testing invalid files:\n"); + else + printf ("Testing unknown files:\n"); + + next: + while (_dbus_directory_get_next_file (dir, &filename, &error)) + { + DBusString full_path; + LoaderOomData d; + + if (!_dbus_string_init (&full_path)) + _dbus_assert_not_reached ("couldn't init string"); + + if (!_dbus_string_copy (&test_directory, 0, &full_path, 0)) + _dbus_assert_not_reached ("couldn't copy dir to full_path"); + + if (!_dbus_concat_dir_and_file (&full_path, &filename)) + _dbus_assert_not_reached ("couldn't concat file to dir"); + + if (!_dbus_string_ends_with_c_str (&full_path, ".conf")) + { + _dbus_verbose ("Skipping non-.conf file %s\n", + _dbus_string_get_const_data (&filename)); + _dbus_string_free (&full_path); + goto next; + } + + printf (" %s\n", _dbus_string_get_const_data (&filename)); + + _dbus_verbose (" expecting %s\n", + validity == VALID ? "valid" : + (validity == INVALID ? "invalid" : + (validity == UNKNOWN ? "unknown" : "???"))); + + d.full_path = &full_path; + d.validity = validity; + + /* FIXME hackaround for an expat problem, see + * https://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=124747 + * http://freedesktop.org/pipermail/dbus/2004-May/001153.html + */ + /* if (!_dbus_test_oom_handling ("config-loader", check_loader_oom_func, &d)) */ + if (!check_loader_oom_func (&d)) + _dbus_assert_not_reached ("test failed"); + + _dbus_string_free (&full_path); + } + + if (dbus_error_is_set (&error)) + { + _dbus_warn ("Could not get next file in %s: %s\n", + _dbus_string_get_const_data (&test_directory), + error.message); + dbus_error_free (&error); + goto failed; + } + + retval = TRUE; + + failed: + + if (dir) + _dbus_directory_close (dir); + _dbus_string_free (&test_directory); + _dbus_string_free (&filename); + + return retval; +} + +static dbus_bool_t +bools_equal (dbus_bool_t a, + dbus_bool_t b) +{ + return a ? b : !b; +} + +static dbus_bool_t +strings_equal_or_both_null (const char *a, + const char *b) +{ + if (a == NULL || b == NULL) + return a == b; + else + return !strcmp (a, b); +} + +static dbus_bool_t +elements_equal (const Element *a, + const Element *b) +{ + if (a->type != b->type) + return FALSE; + + if (!bools_equal (a->had_content, b->had_content)) + return FALSE; + + switch (a->type) + { + + case ELEMENT_INCLUDE: + if (!bools_equal (a->d.include.ignore_missing, + b->d.include.ignore_missing)) + return FALSE; + break; + + case ELEMENT_POLICY: + if (a->d.policy.type != b->d.policy.type) + return FALSE; + if (a->d.policy.gid_uid_or_at_console != b->d.policy.gid_uid_or_at_console) + return FALSE; + break; + + case ELEMENT_LIMIT: + if (strcmp (a->d.limit.name, b->d.limit.name)) + return FALSE; + if (a->d.limit.value != b->d.limit.value) + return FALSE; + break; + + default: + /* do nothing */ + break; + } + + return TRUE; + +} + +static dbus_bool_t +lists_of_elements_equal (DBusList *a, + DBusList *b) +{ + DBusList *ia; + DBusList *ib; + + ia = a; + ib = b; + + while (ia != NULL && ib != NULL) + { + if (elements_equal (ia->data, ib->data)) + return FALSE; + ia = _dbus_list_get_next_link (&a, ia); + ib = _dbus_list_get_next_link (&b, ib); + } + + return ia == NULL && ib == NULL; +} + +static dbus_bool_t +lists_of_c_strings_equal (DBusList *a, + DBusList *b) +{ + DBusList *ia; + DBusList *ib; + + ia = a; + ib = b; + + while (ia != NULL && ib != NULL) + { + if (strcmp (ia->data, ib->data)) + return FALSE; + ia = _dbus_list_get_next_link (&a, ia); + ib = _dbus_list_get_next_link (&b, ib); + } + + return ia == NULL && ib == NULL; +} + +static dbus_bool_t +limits_equal (const BusLimits *a, + const BusLimits *b) +{ + return + (a->max_incoming_bytes == b->max_incoming_bytes + || a->max_outgoing_bytes == b->max_outgoing_bytes + || a->max_message_size == b->max_message_size + || a->activation_timeout == b->activation_timeout + || a->auth_timeout == b->auth_timeout + || a->max_completed_connections == b->max_completed_connections + || a->max_incomplete_connections == b->max_incomplete_connections + || a->max_connections_per_user == b->max_connections_per_user + || a->max_pending_activations == b->max_pending_activations + || a->max_services_per_connection == b->max_services_per_connection + || a->max_match_rules_per_connection == b->max_match_rules_per_connection + || a->max_replies_per_connection == b->max_replies_per_connection + || a->reply_timeout == b->reply_timeout); +} + +static dbus_bool_t +config_parsers_equal (const BusConfigParser *a, + const BusConfigParser *b) +{ + if (!_dbus_string_equal (&a->basedir, &b->basedir)) + return FALSE; + + if (!lists_of_elements_equal (a->stack, b->stack)) + return FALSE; + + if (!strings_equal_or_both_null (a->user, b->user)) + return FALSE; + + if (!lists_of_c_strings_equal (a->listen_on, b->listen_on)) + return FALSE; + + if (!lists_of_c_strings_equal (a->mechanisms, b->mechanisms)) + return FALSE; + + if (!lists_of_c_strings_equal (a->service_dirs, b->service_dirs)) + return FALSE; + + /* FIXME: compare policy */ + + /* FIXME: compare service selinux ID table */ + + if (! limits_equal (&a->limits, &b->limits)) + return FALSE; + + if (!strings_equal_or_both_null (a->pidfile, b->pidfile)) + return FALSE; + + if (! bools_equal (a->fork, b->fork)) + return FALSE; + + if (! bools_equal (a->keep_umask, b->keep_umask)) + return FALSE; + + if (! bools_equal (a->is_toplevel, b->is_toplevel)) + return FALSE; + + return TRUE; +} + +static dbus_bool_t +all_are_equiv (const DBusString *target_directory) +{ + DBusString filename; + DBusDirIter *dir; + BusConfigParser *first_parser; + BusConfigParser *parser; + DBusError error; + dbus_bool_t equal; + dbus_bool_t retval; + + dir = NULL; + first_parser = NULL; + parser = NULL; + retval = FALSE; + + if (!_dbus_string_init (&filename)) + _dbus_assert_not_reached ("didn't allocate filename string"); + + dbus_error_init (&error); + dir = _dbus_directory_open (target_directory, &error); + if (dir == NULL) + { + _dbus_warn ("Could not open %s: %s\n", + _dbus_string_get_const_data (target_directory), + error.message); + dbus_error_free (&error); + goto finished; + } + + printf ("Comparing equivalent files:\n"); + + next: + while (_dbus_directory_get_next_file (dir, &filename, &error)) + { + DBusString full_path; + + if (!_dbus_string_init (&full_path)) + _dbus_assert_not_reached ("couldn't init string"); + + if (!_dbus_string_copy (target_directory, 0, &full_path, 0)) + _dbus_assert_not_reached ("couldn't copy dir to full_path"); + + if (!_dbus_concat_dir_and_file (&full_path, &filename)) + _dbus_assert_not_reached ("couldn't concat file to dir"); + + if (!_dbus_string_ends_with_c_str (&full_path, ".conf")) + { + _dbus_verbose ("Skipping non-.conf file %s\n", + _dbus_string_get_const_data (&filename)); + _dbus_string_free (&full_path); + goto next; + } + + printf (" %s\n", _dbus_string_get_const_data (&filename)); + + parser = bus_config_load (&full_path, TRUE, NULL, &error); + + if (parser == NULL) + { + _dbus_warn ("Could not load file %s: %s\n", + _dbus_string_get_const_data (&full_path), + error.message); + _dbus_string_free (&full_path); + dbus_error_free (&error); + goto finished; + } + else if (first_parser == NULL) + { + _dbus_string_free (&full_path); + first_parser = parser; + } + else + { + _dbus_string_free (&full_path); + equal = config_parsers_equal (first_parser, parser); + bus_config_parser_unref (parser); + if (! equal) + goto finished; + } + } + + retval = TRUE; + + finished: + _dbus_string_free (&filename); + if (first_parser) + bus_config_parser_unref (first_parser); + if (dir) + _dbus_directory_close (dir); + + return retval; + +} + +static dbus_bool_t +process_test_equiv_subdir (const DBusString *test_base_dir, + const char *subdir) +{ + DBusString test_directory; + DBusString filename; + DBusDirIter *dir; + DBusError error; + dbus_bool_t equal; + dbus_bool_t retval; + + dir = NULL; + retval = FALSE; + + if (!_dbus_string_init (&test_directory)) + _dbus_assert_not_reached ("didn't allocate test_directory"); + + _dbus_string_init_const (&filename, subdir); + + if (!_dbus_string_copy (test_base_dir, 0, + &test_directory, 0)) + _dbus_assert_not_reached ("couldn't copy test_base_dir to test_directory"); + + if (!_dbus_concat_dir_and_file (&test_directory, &filename)) + _dbus_assert_not_reached ("couldn't allocate full path"); + + _dbus_string_free (&filename); + if (!_dbus_string_init (&filename)) + _dbus_assert_not_reached ("didn't allocate filename string"); + + dbus_error_init (&error); + dir = _dbus_directory_open (&test_directory, &error); + if (dir == NULL) + { + _dbus_warn ("Could not open %s: %s\n", + _dbus_string_get_const_data (&test_directory), + error.message); + dbus_error_free (&error); + goto finished; + } + + while (_dbus_directory_get_next_file (dir, &filename, &error)) + { + DBusString full_path; + + /* Skip CVS's magic directories! */ + if (_dbus_string_equal_c_str (&filename, "CVS")) + continue; + + if (!_dbus_string_init (&full_path)) + _dbus_assert_not_reached ("couldn't init string"); + + if (!_dbus_string_copy (&test_directory, 0, &full_path, 0)) + _dbus_assert_not_reached ("couldn't copy dir to full_path"); + + if (!_dbus_concat_dir_and_file (&full_path, &filename)) + _dbus_assert_not_reached ("couldn't concat file to dir"); + + equal = all_are_equiv (&full_path); + _dbus_string_free (&full_path); + + if (!equal) + goto finished; + } + + retval = TRUE; + + finished: + _dbus_string_free (&test_directory); + _dbus_string_free (&filename); + if (dir) + _dbus_directory_close (dir); + + return retval; + +} + +static const char *test_session_service_dir_matches[] = + { +#ifdef DBUS_UNIX + "/testusr/testlocal/testshare/dbus-1/services", + "/testusr/testshare/dbus-1/services", +#endif + DBUS_DATADIR"/dbus-1/services", +#ifdef DBUS_UNIX + "/testhome/foo/.testlocal/testshare/dbus-1/services", +#endif + NULL + }; + +static dbus_bool_t +test_default_session_servicedirs (void) +{ + DBusList *dirs; + DBusList *link; + DBusString progs; + const char *common_progs; + int i; + + /* On Unix we don't actually use this variable, but it's easier to handle the + * deallocation if we always allocate it, whether needed or not */ + if (!_dbus_string_init (&progs)) + _dbus_assert_not_reached ("OOM allocating progs"); + + common_progs = _dbus_getenv ("CommonProgramFiles"); +#ifndef DBUS_UNIX + if (common_progs) + { + if (!_dbus_string_append (&progs, common_progs)) + { + _dbus_string_free (&progs); + return FALSE; + } + + if (!_dbus_string_append (&progs, "/dbus-1/services")) + { + _dbus_string_free (&progs); + return FALSE; + } + test_session_service_dir_matches[1] = _dbus_string_get_const_data(&progs); + } +#endif + dirs = NULL; + + printf ("Testing retrieving the default session service directories\n"); + if (!_dbus_get_standard_session_servicedirs (&dirs)) + _dbus_assert_not_reached ("couldn't get stardard dirs"); + + /* make sure our defaults end with share/dbus-1/service */ + while ((link = _dbus_list_pop_first_link (&dirs))) + { + DBusString path; + + printf (" default service dir: %s\n", (char *)link->data); + _dbus_string_init_const (&path, (char *)link->data); + if (!_dbus_string_ends_with_c_str (&path, "dbus-1/services")) + { + printf ("error with default session service directories\n"); + dbus_free (link->data); + _dbus_list_free_link (link); + _dbus_string_free (&progs); + return FALSE; + } + + dbus_free (link->data); + _dbus_list_free_link (link); + } + +#ifdef DBUS_UNIX + if (!_dbus_setenv ("XDG_DATA_HOME", "/testhome/foo/.testlocal/testshare")) + _dbus_assert_not_reached ("couldn't setenv XDG_DATA_HOME"); + + if (!_dbus_setenv ("XDG_DATA_DIRS", ":/testusr/testlocal/testshare: :/testusr/testshare:")) + _dbus_assert_not_reached ("couldn't setenv XDG_DATA_DIRS"); +#endif + if (!_dbus_get_standard_session_servicedirs (&dirs)) + _dbus_assert_not_reached ("couldn't get stardard dirs"); + + /* make sure we read and parse the env variable correctly */ + i = 0; + while ((link = _dbus_list_pop_first_link (&dirs))) + { + printf (" test service dir: %s\n", (char *)link->data); + if (test_session_service_dir_matches[i] == NULL) + { + printf ("more directories parsed than in match set\n"); + dbus_free (link->data); + _dbus_list_free_link (link); + _dbus_string_free (&progs); + return FALSE; + } + + if (strcmp (test_session_service_dir_matches[i], + (char *)link->data) != 0) + { + printf ("%s directory does not match %s in the match set\n", + (char *)link->data, + test_session_service_dir_matches[i]); + dbus_free (link->data); + _dbus_list_free_link (link); + _dbus_string_free (&progs); + return FALSE; + } + + ++i; + + dbus_free (link->data); + _dbus_list_free_link (link); + } + + if (test_session_service_dir_matches[i] != NULL) + { + printf ("extra data %s in the match set was not matched\n", + test_session_service_dir_matches[i]); + + _dbus_string_free (&progs); + return FALSE; + } + + _dbus_string_free (&progs); + return TRUE; +} + +static const char *test_system_service_dir_matches[] = + { +#ifdef DBUS_UNIX + "/testusr/testlocal/testshare/dbus-1/system-services", + "/testusr/testshare/dbus-1/system-services", +#endif + DBUS_DATADIR"/dbus-1/system-services", + NULL + }; + +static dbus_bool_t +test_default_system_servicedirs (void) +{ + DBusList *dirs; + DBusList *link; + DBusString progs; + const char *common_progs; + int i; + + /* On Unix we don't actually use this variable, but it's easier to handle the + * deallocation if we always allocate it, whether needed or not */ + if (!_dbus_string_init (&progs)) + _dbus_assert_not_reached ("OOM allocating progs"); + + common_progs = _dbus_getenv ("CommonProgramFiles"); +#ifndef DBUS_UNIX + if (common_progs) + { + if (!_dbus_string_append (&progs, common_progs)) + { + _dbus_string_free (&progs); + return FALSE; + } + + if (!_dbus_string_append (&progs, "/dbus-1/system-services")) + { + _dbus_string_free (&progs); + return FALSE; + } + test_system_service_dir_matches[1] = _dbus_string_get_const_data(&progs); + } +#endif + dirs = NULL; + + printf ("Testing retrieving the default system service directories\n"); + if (!_dbus_get_standard_system_servicedirs (&dirs)) + _dbus_assert_not_reached ("couldn't get stardard dirs"); + + /* make sure our defaults end with share/dbus-1/system-service */ + while ((link = _dbus_list_pop_first_link (&dirs))) + { + DBusString path; + + printf (" default service dir: %s\n", (char *)link->data); + _dbus_string_init_const (&path, (char *)link->data); + if (!_dbus_string_ends_with_c_str (&path, "dbus-1/system-services")) + { + printf ("error with default system service directories\n"); + dbus_free (link->data); + _dbus_list_free_link (link); + _dbus_string_free (&progs); + return FALSE; + } + + dbus_free (link->data); + _dbus_list_free_link (link); + } + +#ifdef DBUS_UNIX + if (!_dbus_setenv ("XDG_DATA_HOME", "/testhome/foo/.testlocal/testshare")) + _dbus_assert_not_reached ("couldn't setenv XDG_DATA_HOME"); + + if (!_dbus_setenv ("XDG_DATA_DIRS", ":/testusr/testlocal/testshare: :/testusr/testshare:")) + _dbus_assert_not_reached ("couldn't setenv XDG_DATA_DIRS"); +#endif + if (!_dbus_get_standard_system_servicedirs (&dirs)) + _dbus_assert_not_reached ("couldn't get stardard dirs"); + + /* make sure we read and parse the env variable correctly */ + i = 0; + while ((link = _dbus_list_pop_first_link (&dirs))) + { + printf (" test service dir: %s\n", (char *)link->data); + if (test_system_service_dir_matches[i] == NULL) + { + printf ("more directories parsed than in match set\n"); + dbus_free (link->data); + _dbus_list_free_link (link); + _dbus_string_free (&progs); + return FALSE; + } + + if (strcmp (test_system_service_dir_matches[i], + (char *)link->data) != 0) + { + printf ("%s directory does not match %s in the match set\n", + (char *)link->data, + test_system_service_dir_matches[i]); + dbus_free (link->data); + _dbus_list_free_link (link); + _dbus_string_free (&progs); + return FALSE; + } + + ++i; + + dbus_free (link->data); + _dbus_list_free_link (link); + } + + if (test_system_service_dir_matches[i] != NULL) + { + printf ("extra data %s in the match set was not matched\n", + test_system_service_dir_matches[i]); + + _dbus_string_free (&progs); + return FALSE; + } + + _dbus_string_free (&progs); + return TRUE; +} + +dbus_bool_t +bus_config_parser_test (const DBusString *test_data_dir) +{ + if (test_data_dir == NULL || + _dbus_string_get_length (test_data_dir) == 0) + { + printf ("No test data\n"); + return TRUE; + } + + if (!test_default_session_servicedirs()) + return FALSE; + + if (!test_default_system_servicedirs()) + return FALSE; + + if (!process_test_valid_subdir (test_data_dir, "valid-config-files", VALID)) + return FALSE; + + if (!process_test_valid_subdir (test_data_dir, "invalid-config-files", INVALID)) + return FALSE; + + if (!process_test_equiv_subdir (test_data_dir, "equiv-config-files")) + return FALSE; + + return TRUE; +} + +#endif /* DBUS_BUILD_TESTS */ + diff --git a/bus/config-parser.h b/bus/config-parser.h new file mode 100644 index 00000000..3aac1ed3 --- /dev/null +++ b/bus/config-parser.h @@ -0,0 +1,89 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* config-parser.h XML-library-agnostic configuration file parser + * + * Copyright (C) 2003 Red Hat, Inc. + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef BUS_CONFIG_PARSER_H +#define BUS_CONFIG_PARSER_H + +#include <config.h> + +#include <dbus/dbus.h> +#include <dbus/dbus-string.h> +#include <dbus/dbus-list.h> +#include <dbus/dbus-hash.h> +#include "bus.h" + +/* Whatever XML library we're using just pushes data into this API */ + +typedef struct BusConfigParser BusConfigParser; + +BusConfigParser* bus_config_parser_new (const DBusString *basedir, + dbus_bool_t is_toplevel, + const BusConfigParser *parent); + +BusConfigParser* bus_config_parser_ref (BusConfigParser *parser); +void bus_config_parser_unref (BusConfigParser *parser); +dbus_bool_t bus_config_parser_check_doctype (BusConfigParser *parser, + const char *doctype, + DBusError *error); +dbus_bool_t bus_config_parser_start_element (BusConfigParser *parser, + const char *element_name, + const char **attribute_names, + const char **attribute_values, + DBusError *error); +dbus_bool_t bus_config_parser_end_element (BusConfigParser *parser, + const char *element_name, + DBusError *error); +dbus_bool_t bus_config_parser_content (BusConfigParser *parser, + const DBusString *content, + DBusError *error); +dbus_bool_t bus_config_parser_finished (BusConfigParser *parser, + DBusError *error); + +/* Functions for extracting the parse results */ +const char* bus_config_parser_get_user (BusConfigParser *parser); +const char* bus_config_parser_get_type (BusConfigParser *parser); +DBusList** bus_config_parser_get_addresses (BusConfigParser *parser); +DBusList** bus_config_parser_get_mechanisms (BusConfigParser *parser); +dbus_bool_t bus_config_parser_get_fork (BusConfigParser *parser); +dbus_bool_t bus_config_parser_get_allow_anonymous (BusConfigParser *parser); +dbus_bool_t bus_config_parser_get_syslog (BusConfigParser *parser); +dbus_bool_t bus_config_parser_get_keep_umask (BusConfigParser *parser); +const char* bus_config_parser_get_pidfile (BusConfigParser *parser); +const char* bus_config_parser_get_servicehelper (BusConfigParser *parser); +DBusList** bus_config_parser_get_service_dirs (BusConfigParser *parser); +DBusList** bus_config_parser_get_conf_dirs (BusConfigParser *parser); +BusPolicy* bus_config_parser_steal_policy (BusConfigParser *parser); +void bus_config_parser_get_limits (BusConfigParser *parser, + BusLimits *limits); + +DBusHashTable* bus_config_parser_steal_service_context_table (BusConfigParser *parser); + +/* Loader functions (backended off one of the XML parsers). Returns a + * finished ConfigParser. + */ +BusConfigParser* bus_config_load (const DBusString *file, + dbus_bool_t is_toplevel, + const BusConfigParser *parent, + DBusError *error); + +#endif /* BUS_CONFIG_PARSER_H */ diff --git a/bus/connection.c b/bus/connection.c new file mode 100644 index 00000000..50807f1a --- /dev/null +++ b/bus/connection.c @@ -0,0 +1,2303 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* connection.c Client connections + * + * Copyright (C) 2003 Red Hat, Inc. + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#include "connection.h" +#include "dispatch.h" +#include "policy.h" +#include "services.h" +#include "utils.h" +#include "signals.h" +#include "expirelist.h" +#include "selinux.h" +#include <dbus/dbus-list.h> +#include <dbus/dbus-hash.h> +#include <dbus/dbus-timeout.h> + +/* Trim executed commands to this length; we want to keep logs readable */ +#define MAX_LOG_COMMAND_LEN 50 + +static void bus_connection_remove_transactions (DBusConnection *connection); + +typedef struct +{ + BusExpireItem expire_item; + + DBusConnection *will_get_reply; + DBusConnection *will_send_reply; + + dbus_uint32_t reply_serial; + +} BusPendingReply; + +struct BusConnections +{ + int refcount; + DBusList *completed; /**< List of all completed connections */ + int n_completed; /**< Length of completed list */ + DBusList *incomplete; /**< List of all not-yet-active connections */ + int n_incomplete; /**< Length of incomplete list */ + BusContext *context; + DBusHashTable *completed_by_user; /**< Number of completed connections for each UID */ + DBusTimeout *expire_timeout; /**< Timeout for expiring incomplete connections. */ + int stamp; /**< Incrementing number */ + BusExpireList *pending_replies; /**< List of pending replies */ +}; + +static dbus_int32_t connection_data_slot = -1; + +typedef struct +{ + BusConnections *connections; + DBusList *link_in_connection_list; + DBusConnection *connection; + DBusList *services_owned; + int n_services_owned; + DBusList *match_rules; + int n_match_rules; + char *name; + DBusList *transaction_messages; /**< Stuff we need to send as part of a transaction */ + DBusMessage *oom_message; + DBusPreallocatedSend *oom_preallocated; + BusClientPolicy *policy; + + char *cached_loginfo_string; + BusSELinuxID *selinux_id; + + long connection_tv_sec; /**< Time when we connected (seconds component) */ + long connection_tv_usec; /**< Time when we connected (microsec component) */ + int stamp; /**< connections->stamp last time we were traversed */ +} BusConnectionData; + +static dbus_bool_t bus_pending_reply_expired (BusExpireList *list, + DBusList *link, + void *data); + +static void bus_connection_drop_pending_replies (BusConnections *connections, + DBusConnection *connection); + +static dbus_bool_t expire_incomplete_timeout (void *data); + +#define BUS_CONNECTION_DATA(connection) (dbus_connection_get_data ((connection), connection_data_slot)) + +static DBusLoop* +connection_get_loop (DBusConnection *connection) +{ + BusConnectionData *d; + + d = BUS_CONNECTION_DATA (connection); + + return bus_context_get_loop (d->connections->context); +} + + +static int +get_connections_for_uid (BusConnections *connections, + dbus_uid_t uid) +{ + void *val; + int current_count; + + /* val is NULL is 0 when it isn't in the hash yet */ + + val = _dbus_hash_table_lookup_ulong (connections->completed_by_user, + uid); + + current_count = _DBUS_POINTER_TO_INT (val); + + return current_count; +} + +static dbus_bool_t +adjust_connections_for_uid (BusConnections *connections, + dbus_uid_t uid, + int adjustment) +{ + int current_count; + + current_count = get_connections_for_uid (connections, uid); + + _dbus_verbose ("Adjusting connection count for UID " DBUS_UID_FORMAT + ": was %d adjustment %d making %d\n", + uid, current_count, adjustment, current_count + adjustment); + + _dbus_assert (current_count >= 0); + + current_count += adjustment; + + _dbus_assert (current_count >= 0); + + if (current_count == 0) + { + _dbus_hash_table_remove_ulong (connections->completed_by_user, uid); + return TRUE; + } + else + { + dbus_bool_t retval; + + retval = _dbus_hash_table_insert_ulong (connections->completed_by_user, + uid, _DBUS_INT_TO_POINTER (current_count)); + + /* only positive adjustment can fail as otherwise + * a hash entry should already exist + */ + _dbus_assert (adjustment > 0 || + (adjustment <= 0 && retval)); + + return retval; + } +} + +void +bus_connection_disconnected (DBusConnection *connection) +{ + BusConnectionData *d; + BusService *service; + BusMatchmaker *matchmaker; + + d = BUS_CONNECTION_DATA (connection); + _dbus_assert (d != NULL); + + _dbus_verbose ("%s disconnected, dropping all service ownership and releasing\n", + d->name ? d->name : "(inactive)"); + + /* Delete our match rules */ + if (d->n_match_rules > 0) + { + matchmaker = bus_context_get_matchmaker (d->connections->context); + bus_matchmaker_disconnected (matchmaker, connection); + } + + /* Drop any service ownership. Unfortunately, this requires + * memory allocation and there doesn't seem to be a good way to + * handle it other than sleeping; we can't "fail" the operation of + * disconnecting a client, and preallocating a broadcast "service is + * now gone" message for every client-service pair seems kind of + * involved. + */ + while ((service = _dbus_list_get_last (&d->services_owned))) + { + BusTransaction *transaction; + DBusError error; + + retry: + + dbus_error_init (&error); + + while ((transaction = bus_transaction_new (d->connections->context)) == NULL) + _dbus_wait_for_memory (); + + if (!bus_service_remove_owner (service, connection, + transaction, &error)) + { + _DBUS_ASSERT_ERROR_IS_SET (&error); + + if (dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY)) + { + dbus_error_free (&error); + bus_transaction_cancel_and_free (transaction); + _dbus_wait_for_memory (); + goto retry; + } + else + { + _dbus_verbose ("Failed to remove service owner: %s %s\n", + error.name, error.message); + _dbus_assert_not_reached ("Removing service owner failed for non-memory-related reason"); + } + } + + bus_transaction_execute_and_free (transaction); + } + + bus_dispatch_remove_connection (connection); + + /* no more watching */ + if (!dbus_connection_set_watch_functions (connection, + NULL, NULL, NULL, + connection, + NULL)) + _dbus_assert_not_reached ("setting watch functions to NULL failed"); + + if (!dbus_connection_set_timeout_functions (connection, + NULL, NULL, NULL, + connection, + NULL)) + _dbus_assert_not_reached ("setting timeout functions to NULL failed"); + + dbus_connection_set_unix_user_function (connection, + NULL, NULL, NULL); + dbus_connection_set_windows_user_function (connection, + NULL, NULL, NULL); + + dbus_connection_set_dispatch_status_function (connection, + NULL, NULL, NULL); + + bus_connection_remove_transactions (connection); + + if (d->link_in_connection_list != NULL) + { + if (d->name != NULL) + { + unsigned long uid; + + _dbus_list_remove_link (&d->connections->completed, d->link_in_connection_list); + d->link_in_connection_list = NULL; + d->connections->n_completed -= 1; + + if (dbus_connection_get_unix_user (connection, &uid)) + { + if (!adjust_connections_for_uid (d->connections, + uid, -1)) + _dbus_assert_not_reached ("adjusting downward should never fail"); + } + } + else + { + _dbus_list_remove_link (&d->connections->incomplete, d->link_in_connection_list); + d->link_in_connection_list = NULL; + d->connections->n_incomplete -= 1; + } + + _dbus_assert (d->connections->n_incomplete >= 0); + _dbus_assert (d->connections->n_completed >= 0); + } + + bus_connection_drop_pending_replies (d->connections, connection); + + /* frees "d" as side effect */ + dbus_connection_set_data (connection, + connection_data_slot, + NULL, NULL); + + dbus_connection_unref (connection); +} + +static dbus_bool_t +connection_watch_callback (DBusWatch *watch, + unsigned int condition, + void *data) +{ + /* FIXME this can be done in dbus-mainloop.c + * if the code in activation.c for the babysitter + * watch handler is fixed. + */ + +#if 0 + _dbus_verbose ("Calling handle_watch\n"); +#endif + return dbus_watch_handle (watch, condition); +} + +static dbus_bool_t +add_connection_watch (DBusWatch *watch, + void *data) +{ + DBusConnection *connection = data; + + return _dbus_loop_add_watch (connection_get_loop (connection), + watch, connection_watch_callback, connection, + NULL); +} + +static void +remove_connection_watch (DBusWatch *watch, + void *data) +{ + DBusConnection *connection = data; + + _dbus_loop_remove_watch (connection_get_loop (connection), + watch, connection_watch_callback, connection); +} + +static void +connection_timeout_callback (DBusTimeout *timeout, + void *data) +{ + /* DBusConnection *connection = data; */ + + /* can return FALSE on OOM but we just let it fire again later */ + dbus_timeout_handle (timeout); +} + +static dbus_bool_t +add_connection_timeout (DBusTimeout *timeout, + void *data) +{ + DBusConnection *connection = data; + + return _dbus_loop_add_timeout (connection_get_loop (connection), + timeout, connection_timeout_callback, connection, NULL); +} + +static void +remove_connection_timeout (DBusTimeout *timeout, + void *data) +{ + DBusConnection *connection = data; + + _dbus_loop_remove_timeout (connection_get_loop (connection), + timeout, connection_timeout_callback, connection); +} + +static void +dispatch_status_function (DBusConnection *connection, + DBusDispatchStatus new_status, + void *data) +{ + DBusLoop *loop = data; + + if (new_status != DBUS_DISPATCH_COMPLETE) + { + while (!_dbus_loop_queue_dispatch (loop, connection)) + _dbus_wait_for_memory (); + } +} + +static dbus_bool_t +allow_unix_user_function (DBusConnection *connection, + unsigned long uid, + void *data) +{ + BusConnectionData *d; + + d = BUS_CONNECTION_DATA (connection); + + _dbus_assert (d != NULL); + + return bus_context_allow_unix_user (d->connections->context, uid); +} + +static void +free_connection_data (void *data) +{ + BusConnectionData *d = data; + + /* services_owned should be NULL since we should be disconnected */ + _dbus_assert (d->services_owned == NULL); + _dbus_assert (d->n_services_owned == 0); + /* similarly */ + _dbus_assert (d->transaction_messages == NULL); + + if (d->oom_preallocated) + dbus_connection_free_preallocated_send (d->connection, d->oom_preallocated); + + if (d->oom_message) + dbus_message_unref (d->oom_message); + + if (d->policy) + bus_client_policy_unref (d->policy); + + if (d->selinux_id) + bus_selinux_id_unref (d->selinux_id); + + dbus_free (d->cached_loginfo_string); + + dbus_free (d->name); + + dbus_free (d); +} + +static void +call_timeout_callback (DBusTimeout *timeout, + void *data) +{ + /* can return FALSE on OOM but we just let it fire again later */ + dbus_timeout_handle (timeout); +} + +BusConnections* +bus_connections_new (BusContext *context) +{ + BusConnections *connections; + + if (!dbus_connection_allocate_data_slot (&connection_data_slot)) + goto failed_0; + + connections = dbus_new0 (BusConnections, 1); + if (connections == NULL) + goto failed_1; + + connections->completed_by_user = _dbus_hash_table_new (DBUS_HASH_ULONG, + NULL, NULL); + if (connections->completed_by_user == NULL) + goto failed_2; + + connections->expire_timeout = _dbus_timeout_new (100, /* irrelevant */ + expire_incomplete_timeout, + connections, NULL); + if (connections->expire_timeout == NULL) + goto failed_3; + + _dbus_timeout_set_enabled (connections->expire_timeout, FALSE); + + connections->pending_replies = bus_expire_list_new (bus_context_get_loop (context), + bus_context_get_reply_timeout (context), + bus_pending_reply_expired, + connections); + if (connections->pending_replies == NULL) + goto failed_4; + + if (!_dbus_loop_add_timeout (bus_context_get_loop (context), + connections->expire_timeout, + call_timeout_callback, NULL, NULL)) + goto failed_5; + + connections->refcount = 1; + connections->context = context; + + return connections; + + failed_5: + bus_expire_list_free (connections->pending_replies); + failed_4: + _dbus_timeout_unref (connections->expire_timeout); + failed_3: + _dbus_hash_table_unref (connections->completed_by_user); + failed_2: + dbus_free (connections); + failed_1: + dbus_connection_free_data_slot (&connection_data_slot); + failed_0: + return NULL; +} + +BusConnections * +bus_connections_ref (BusConnections *connections) +{ + _dbus_assert (connections->refcount > 0); + connections->refcount += 1; + + return connections; +} + +void +bus_connections_unref (BusConnections *connections) +{ + _dbus_assert (connections->refcount > 0); + connections->refcount -= 1; + if (connections->refcount == 0) + { + /* drop all incomplete */ + while (connections->incomplete != NULL) + { + DBusConnection *connection; + + connection = connections->incomplete->data; + + dbus_connection_ref (connection); + dbus_connection_close (connection); + bus_connection_disconnected (connection); + dbus_connection_unref (connection); + } + + _dbus_assert (connections->n_incomplete == 0); + + /* drop all real connections */ + while (connections->completed != NULL) + { + DBusConnection *connection; + + connection = connections->completed->data; + + dbus_connection_ref (connection); + dbus_connection_close (connection); + bus_connection_disconnected (connection); + dbus_connection_unref (connection); + } + + _dbus_assert (connections->n_completed == 0); + + bus_expire_list_free (connections->pending_replies); + + _dbus_loop_remove_timeout (bus_context_get_loop (connections->context), + connections->expire_timeout, + call_timeout_callback, NULL); + + _dbus_timeout_unref (connections->expire_timeout); + + _dbus_hash_table_unref (connections->completed_by_user); + + dbus_free (connections); + + dbus_connection_free_data_slot (&connection_data_slot); + } +} + +/* Used for logging */ +static dbus_bool_t +cache_peer_loginfo_string (BusConnectionData *d, + DBusConnection *connection) +{ + DBusString loginfo_buf; + unsigned long uid; + unsigned long pid; + char *windows_sid; + dbus_bool_t prev_added; + + if (!_dbus_string_init (&loginfo_buf)) + return FALSE; + + prev_added = FALSE; + if (dbus_connection_get_unix_user (connection, &uid)) + { + if (!_dbus_string_append_printf (&loginfo_buf, "uid=%ld", uid)) + goto oom; + else + prev_added = TRUE; + } + + if (dbus_connection_get_unix_process_id (connection, &pid)) + { + if (prev_added) + { + if (!_dbus_string_append_byte (&loginfo_buf, ' ')) + goto oom; + } + if (!_dbus_string_append_printf (&loginfo_buf, "pid=%ld comm=\"", pid)) + goto oom; + /* Ignore errors here; we may not have permissions to read the + * proc file. */ + _dbus_command_for_pid (pid, &loginfo_buf, MAX_LOG_COMMAND_LEN, NULL); + if (!_dbus_string_append_byte (&loginfo_buf, '"')) + goto oom; + } + + if (dbus_connection_get_windows_user (connection, &windows_sid)) + { + if (!_dbus_string_append_printf (&loginfo_buf, "sid=\"%s\" ", windows_sid)) + goto oom; + dbus_free (windows_sid); + } + + if (!_dbus_string_steal_data (&loginfo_buf, &(d->cached_loginfo_string))) + goto oom; + + _dbus_string_free (&loginfo_buf); + + return TRUE; +oom: + _dbus_string_free (&loginfo_buf); + return FALSE; +} + +dbus_bool_t +bus_connections_setup_connection (BusConnections *connections, + DBusConnection *connection) +{ + + BusConnectionData *d; + dbus_bool_t retval; + DBusError error; + + + d = dbus_new0 (BusConnectionData, 1); + + if (d == NULL) + return FALSE; + + d->connections = connections; + d->connection = connection; + + _dbus_get_current_time (&d->connection_tv_sec, + &d->connection_tv_usec); + + _dbus_assert (connection_data_slot >= 0); + + if (!dbus_connection_set_data (connection, + connection_data_slot, + d, free_connection_data)) + { + dbus_free (d); + return FALSE; + } + + dbus_connection_set_route_peer_messages (connection, TRUE); + + retval = FALSE; + + dbus_error_init (&error); + d->selinux_id = bus_selinux_init_connection_id (connection, + &error); + if (dbus_error_is_set (&error)) + { + /* This is a bit bogus because we pretend all errors + * are OOM; this is done because we know that in bus.c + * an OOM error disconnects the connection, which is + * the same thing we want on any other error. + */ + dbus_error_free (&error); + goto out; + } + + if (!dbus_connection_set_watch_functions (connection, + add_connection_watch, + remove_connection_watch, + NULL, + connection, + NULL)) + goto out; + + if (!dbus_connection_set_timeout_functions (connection, + add_connection_timeout, + remove_connection_timeout, + NULL, + connection, NULL)) + goto out; + + /* For now we don't need to set a Windows user function because + * there are no policies in the config file controlling what + * Windows users can connect. The default 'same user that owns the + * bus can connect' behavior of DBusConnection is fine on Windows. + */ + dbus_connection_set_unix_user_function (connection, + allow_unix_user_function, + NULL, NULL); + + dbus_connection_set_dispatch_status_function (connection, + dispatch_status_function, + bus_context_get_loop (connections->context), + NULL); + + d->link_in_connection_list = _dbus_list_alloc_link (connection); + if (d->link_in_connection_list == NULL) + goto out; + + /* Setup the connection with the dispatcher */ + if (!bus_dispatch_add_connection (connection)) + goto out; + + if (dbus_connection_get_dispatch_status (connection) != DBUS_DISPATCH_COMPLETE) + { + if (!_dbus_loop_queue_dispatch (bus_context_get_loop (connections->context), connection)) + { + bus_dispatch_remove_connection (connection); + goto out; + } + } + + _dbus_list_append_link (&connections->incomplete, d->link_in_connection_list); + connections->n_incomplete += 1; + + dbus_connection_ref (connection); + + /* Note that we might disconnect ourselves here, but it only takes + * effect on return to the main loop. We call this to free up + * expired connections if possible, and to queue the timeout for our + * own expiration. + */ + bus_connections_expire_incomplete (connections); + + /* And we might also disconnect ourselves here, but again it + * only takes effect on return to main loop. + */ + if (connections->n_incomplete > + bus_context_get_max_incomplete_connections (connections->context)) + { + _dbus_verbose ("Number of incomplete connections exceeds max, dropping oldest one\n"); + + _dbus_assert (connections->incomplete != NULL); + /* Disconnect the oldest unauthenticated connection. FIXME + * would it be more secure to drop a *random* connection? This + * algorithm seems to mean that if someone can create new + * connections quickly enough, they can keep anyone else from + * completing authentication. But random may or may not really + * help with that, a more elaborate solution might be required. + */ + dbus_connection_close (connections->incomplete->data); + } + + retval = TRUE; + + out: + if (!retval) + { + if (d->selinux_id) + bus_selinux_id_unref (d->selinux_id); + d->selinux_id = NULL; + + if (!dbus_connection_set_watch_functions (connection, + NULL, NULL, NULL, + connection, + NULL)) + _dbus_assert_not_reached ("setting watch functions to NULL failed"); + + if (!dbus_connection_set_timeout_functions (connection, + NULL, NULL, NULL, + connection, + NULL)) + _dbus_assert_not_reached ("setting timeout functions to NULL failed"); + + dbus_connection_set_unix_user_function (connection, + NULL, NULL, NULL); + + dbus_connection_set_windows_user_function (connection, + NULL, NULL, NULL); + + dbus_connection_set_dispatch_status_function (connection, + NULL, NULL, NULL); + + if (d->link_in_connection_list != NULL) + { + _dbus_assert (d->link_in_connection_list->next == NULL); + _dbus_assert (d->link_in_connection_list->prev == NULL); + _dbus_list_free_link (d->link_in_connection_list); + d->link_in_connection_list = NULL; + } + + if (!dbus_connection_set_data (connection, + connection_data_slot, + NULL, NULL)) + _dbus_assert_not_reached ("failed to set connection data to null"); + + /* "d" has now been freed */ + } + + return retval; +} + +void +bus_connections_expire_incomplete (BusConnections *connections) +{ + int next_interval; + + next_interval = -1; + + if (connections->incomplete != NULL) + { + long tv_sec, tv_usec; + DBusList *link; + int auth_timeout; + + _dbus_get_current_time (&tv_sec, &tv_usec); + auth_timeout = bus_context_get_auth_timeout (connections->context); + + link = _dbus_list_get_first_link (&connections->incomplete); + while (link != NULL) + { + DBusList *next = _dbus_list_get_next_link (&connections->incomplete, link); + DBusConnection *connection; + BusConnectionData *d; + double elapsed; + + connection = link->data; + + d = BUS_CONNECTION_DATA (connection); + + _dbus_assert (d != NULL); + + elapsed = ELAPSED_MILLISECONDS_SINCE (d->connection_tv_sec, + d->connection_tv_usec, + tv_sec, tv_usec); + + if (elapsed >= (double) auth_timeout) + { + _dbus_verbose ("Timing out authentication for connection %p\n", connection); + dbus_connection_close (connection); + } + else + { + /* We can end the loop, since the connections are in oldest-first order */ + next_interval = ((double)auth_timeout) - elapsed; + _dbus_verbose ("Connection %p authentication expires in %d milliseconds\n", + connection, next_interval); + + break; + } + + link = next; + } + } + + bus_expire_timeout_set_interval (connections->expire_timeout, + next_interval); +} + +static dbus_bool_t +expire_incomplete_timeout (void *data) +{ + BusConnections *connections = data; + + _dbus_verbose ("Running %s\n", _DBUS_FUNCTION_NAME); + + /* note that this may remove the timeout */ + bus_connections_expire_incomplete (connections); + + return TRUE; +} + +dbus_bool_t +bus_connection_get_unix_groups (DBusConnection *connection, + unsigned long **groups, + int *n_groups, + DBusError *error) +{ + BusConnectionData *d; + unsigned long uid; + + d = BUS_CONNECTION_DATA (connection); + + _dbus_assert (d != NULL); + + *groups = NULL; + *n_groups = 0; + + if (dbus_connection_get_unix_user (connection, &uid)) + { + if (!_dbus_unix_groups_from_uid (uid, groups, n_groups)) + { + _dbus_verbose ("Did not get any groups for UID %lu\n", + uid); + return FALSE; + } + else + { + _dbus_verbose ("Got %d groups for UID %lu\n", + *n_groups, uid); + return TRUE; + } + } + else + return TRUE; /* successfully got 0 groups */ +} + +dbus_bool_t +bus_connection_is_in_unix_group (DBusConnection *connection, + unsigned long gid) +{ + int i; + unsigned long *group_ids; + int n_group_ids; + + if (!bus_connection_get_unix_groups (connection, &group_ids, &n_group_ids, + NULL)) + return FALSE; + + i = 0; + while (i < n_group_ids) + { + if (group_ids[i] == gid) + { + dbus_free (group_ids); + return TRUE; + } + ++i; + } + + dbus_free (group_ids); + return FALSE; +} + +const char * +bus_connection_get_loginfo (DBusConnection *connection) +{ + BusConnectionData *d; + + d = BUS_CONNECTION_DATA (connection); + + if (!bus_connection_is_active (connection)) + return "inactive"; + return d->cached_loginfo_string; +} + +BusClientPolicy* +bus_connection_get_policy (DBusConnection *connection) +{ + BusConnectionData *d; + + d = BUS_CONNECTION_DATA (connection); + + _dbus_assert (d != NULL); + _dbus_assert (d->policy != NULL); + + return d->policy; +} + +static dbus_bool_t +foreach_active (BusConnections *connections, + BusConnectionForeachFunction function, + void *data) +{ + DBusList *link; + + link = _dbus_list_get_first_link (&connections->completed); + while (link != NULL) + { + DBusConnection *connection = link->data; + DBusList *next = _dbus_list_get_next_link (&connections->completed, link); + + if (!(* function) (connection, data)) + return FALSE; + + link = next; + } + + return TRUE; +} + +static dbus_bool_t +foreach_inactive (BusConnections *connections, + BusConnectionForeachFunction function, + void *data) +{ + DBusList *link; + + link = _dbus_list_get_first_link (&connections->incomplete); + while (link != NULL) + { + DBusConnection *connection = link->data; + DBusList *next = _dbus_list_get_next_link (&connections->incomplete, link); + + if (!(* function) (connection, data)) + return FALSE; + + link = next; + } + + return TRUE; +} + +/** + * Calls function on each active connection; if the function returns + * #FALSE, stops iterating. Active connections are authenticated + * and have sent a Hello message. + * + * @param connections the connections object + * @param function the function + * @param data data to pass to it as a second arg + */ +void +bus_connections_foreach_active (BusConnections *connections, + BusConnectionForeachFunction function, + void *data) +{ + foreach_active (connections, function, data); +} + +/** + * Calls function on each connection; if the function returns + * #FALSE, stops iterating. + * + * @param connections the connections object + * @param function the function + * @param data data to pass to it as a second arg + */ +void +bus_connections_foreach (BusConnections *connections, + BusConnectionForeachFunction function, + void *data) +{ + if (!foreach_active (connections, function, data)) + return; + + foreach_inactive (connections, function, data); +} + +BusContext* +bus_connections_get_context (BusConnections *connections) +{ + return connections->context; +} + +/* + * This is used to avoid covering the same connection twice when + * traversing connections. Note that it assumes we will + * bus_connection_mark_stamp() each connection at least once per + * INT_MAX increments of the global stamp, or wraparound would break + * things. + */ +void +bus_connections_increment_stamp (BusConnections *connections) +{ + connections->stamp += 1; +} + +/* Mark connection with current stamp, return TRUE if it + * didn't already have that stamp + */ +dbus_bool_t +bus_connection_mark_stamp (DBusConnection *connection) +{ + BusConnectionData *d; + + d = BUS_CONNECTION_DATA (connection); + + _dbus_assert (d != NULL); + + if (d->stamp == d->connections->stamp) + return FALSE; + else + { + d->stamp = d->connections->stamp; + return TRUE; + } +} + +BusContext* +bus_connection_get_context (DBusConnection *connection) +{ + BusConnectionData *d; + + d = BUS_CONNECTION_DATA (connection); + + _dbus_assert (d != NULL); + + return d->connections->context; +} + +BusConnections* +bus_connection_get_connections (DBusConnection *connection) +{ + BusConnectionData *d; + + d = BUS_CONNECTION_DATA (connection); + + _dbus_assert (d != NULL); + + return d->connections; +} + +BusRegistry* +bus_connection_get_registry (DBusConnection *connection) +{ + BusConnectionData *d; + + d = BUS_CONNECTION_DATA (connection); + + _dbus_assert (d != NULL); + + return bus_context_get_registry (d->connections->context); +} + +BusActivation* +bus_connection_get_activation (DBusConnection *connection) +{ + BusConnectionData *d; + + d = BUS_CONNECTION_DATA (connection); + + _dbus_assert (d != NULL); + + return bus_context_get_activation (d->connections->context); +} + +BusMatchmaker* +bus_connection_get_matchmaker (DBusConnection *connection) +{ + BusConnectionData *d; + + d = BUS_CONNECTION_DATA (connection); + + _dbus_assert (d != NULL); + + return bus_context_get_matchmaker (d->connections->context); +} + +BusSELinuxID* +bus_connection_get_selinux_id (DBusConnection *connection) +{ + BusConnectionData *d; + + d = BUS_CONNECTION_DATA (connection); + + _dbus_assert (d != NULL); + + return d->selinux_id; +} + +/** + * Checks whether the connection is registered with the message bus. + * + * @param connection the connection + * @returns #TRUE if we're an active message bus participant + */ +dbus_bool_t +bus_connection_is_active (DBusConnection *connection) +{ + BusConnectionData *d; + + d = BUS_CONNECTION_DATA (connection); + + return d != NULL && d->name != NULL; +} + +dbus_bool_t +bus_connection_preallocate_oom_error (DBusConnection *connection) +{ + DBusMessage *message; + DBusPreallocatedSend *preallocated; + BusConnectionData *d; + + d = BUS_CONNECTION_DATA (connection); + + _dbus_assert (d != NULL); + + if (d->oom_preallocated != NULL) + return TRUE; + + preallocated = dbus_connection_preallocate_send (connection); + if (preallocated == NULL) + return FALSE; + + message = dbus_message_new (DBUS_MESSAGE_TYPE_ERROR); + + if (message == NULL) + { + dbus_connection_free_preallocated_send (connection, preallocated); + return FALSE; + } + + /* d->name may be NULL, but that is OK */ + if (!dbus_message_set_error_name (message, DBUS_ERROR_NO_MEMORY) || + !dbus_message_set_destination (message, d->name) || + !dbus_message_set_sender (message, + DBUS_SERVICE_DBUS)) + { + dbus_connection_free_preallocated_send (connection, preallocated); + dbus_message_unref (message); + return FALSE; + } + + /* set reply serial to placeholder value just so space is already allocated + * for it. + */ + if (!dbus_message_set_reply_serial (message, 14)) + { + dbus_connection_free_preallocated_send (connection, preallocated); + dbus_message_unref (message); + return FALSE; + } + + d->oom_message = message; + d->oom_preallocated = preallocated; + + return TRUE; +} + +void +bus_connection_send_oom_error (DBusConnection *connection, + DBusMessage *in_reply_to) +{ + BusConnectionData *d; + + d = BUS_CONNECTION_DATA (connection); + + _dbus_assert (d != NULL); + _dbus_assert (d->oom_message != NULL); + + /* should always succeed since we set it to a placeholder earlier */ + if (!dbus_message_set_reply_serial (d->oom_message, + dbus_message_get_serial (in_reply_to))) + _dbus_assert_not_reached ("Failed to set reply serial for preallocated oom message"); + + _dbus_assert (dbus_message_get_sender (d->oom_message) != NULL); + + dbus_connection_send_preallocated (connection, d->oom_preallocated, + d->oom_message, NULL); + + dbus_message_unref (d->oom_message); + d->oom_message = NULL; + d->oom_preallocated = NULL; +} + +void +bus_connection_add_match_rule_link (DBusConnection *connection, + DBusList *link) +{ + BusConnectionData *d; + + d = BUS_CONNECTION_DATA (connection); + _dbus_assert (d != NULL); + + _dbus_list_append_link (&d->match_rules, link); + + d->n_match_rules += 1; +} + +dbus_bool_t +bus_connection_add_match_rule (DBusConnection *connection, + BusMatchRule *rule) +{ + DBusList *link; + + link = _dbus_list_alloc_link (rule); + + if (link == NULL) + return FALSE; + + bus_connection_add_match_rule_link (connection, link); + + return TRUE; +} + +void +bus_connection_remove_match_rule (DBusConnection *connection, + BusMatchRule *rule) +{ + BusConnectionData *d; + + d = BUS_CONNECTION_DATA (connection); + _dbus_assert (d != NULL); + + _dbus_list_remove_last (&d->match_rules, rule); + + d->n_match_rules -= 1; + _dbus_assert (d->n_match_rules >= 0); +} + +int +bus_connection_get_n_match_rules (DBusConnection *connection) +{ + BusConnectionData *d; + + d = BUS_CONNECTION_DATA (connection); + _dbus_assert (d != NULL); + + return d->n_match_rules; +} + +void +bus_connection_add_owned_service_link (DBusConnection *connection, + DBusList *link) +{ + BusConnectionData *d; + + d = BUS_CONNECTION_DATA (connection); + _dbus_assert (d != NULL); + + _dbus_list_append_link (&d->services_owned, link); + + d->n_services_owned += 1; +} + +dbus_bool_t +bus_connection_add_owned_service (DBusConnection *connection, + BusService *service) +{ + DBusList *link; + + link = _dbus_list_alloc_link (service); + + if (link == NULL) + return FALSE; + + bus_connection_add_owned_service_link (connection, link); + + return TRUE; +} + +void +bus_connection_remove_owned_service (DBusConnection *connection, + BusService *service) +{ + BusConnectionData *d; + + d = BUS_CONNECTION_DATA (connection); + _dbus_assert (d != NULL); + + _dbus_list_remove_last (&d->services_owned, service); + + d->n_services_owned -= 1; + _dbus_assert (d->n_services_owned >= 0); +} + +int +bus_connection_get_n_services_owned (DBusConnection *connection) +{ + BusConnectionData *d; + + d = BUS_CONNECTION_DATA (connection); + _dbus_assert (d != NULL); + + return d->n_services_owned; +} + +dbus_bool_t +bus_connection_complete (DBusConnection *connection, + const DBusString *name, + DBusError *error) +{ + BusConnectionData *d; + unsigned long uid; + + d = BUS_CONNECTION_DATA (connection); + _dbus_assert (d != NULL); + _dbus_assert (d->name == NULL); + _dbus_assert (d->policy == NULL); + + _dbus_assert (!bus_connection_is_active (connection)); + + if (!_dbus_string_copy_data (name, &d->name)) + { + BUS_SET_OOM (error); + return FALSE; + } + + _dbus_assert (d->name != NULL); + + _dbus_verbose ("Name %s assigned to %p\n", d->name, connection); + + d->policy = bus_context_create_client_policy (d->connections->context, + connection, + error); + + /* we may have a NULL policy on OOM or error getting list of + * groups for a user. In the latter case we don't handle it so + * well currently, as it will just keep failing over and over. + */ + + if (d->policy == NULL) + { + _dbus_verbose ("Failed to create security policy for connection %p\n", + connection); + _DBUS_ASSERT_ERROR_IS_SET (error); + dbus_free (d->name); + d->name = NULL; + return FALSE; + } + + if (dbus_connection_get_unix_user (connection, &uid)) + { + if (!adjust_connections_for_uid (d->connections, + uid, 1)) + goto fail; + } + + /* Create and cache a string which holds information about the + * peer process; used for logging purposes. + */ + if (!cache_peer_loginfo_string (d, connection)) + goto fail; + + /* Now the connection is active, move it between lists */ + _dbus_list_unlink (&d->connections->incomplete, + d->link_in_connection_list); + d->connections->n_incomplete -= 1; + _dbus_list_append_link (&d->connections->completed, + d->link_in_connection_list); + d->connections->n_completed += 1; + + _dbus_assert (d->connections->n_incomplete >= 0); + _dbus_assert (d->connections->n_completed > 0); + + /* See if we can remove the timeout */ + bus_connections_expire_incomplete (d->connections); + + _dbus_assert (bus_connection_is_active (connection)); + + return TRUE; +fail: + BUS_SET_OOM (error); + dbus_free (d->name); + d->name = NULL; + if (d->policy) + bus_client_policy_unref (d->policy); + d->policy = NULL; + return FALSE; +} + +const char * +bus_connection_get_name (DBusConnection *connection) +{ + BusConnectionData *d; + + d = BUS_CONNECTION_DATA (connection); + _dbus_assert (d != NULL); + + return d->name; +} + +/** + * Check whether completing the passed-in connection would + * exceed limits, and if so set error and return #FALSE + */ +dbus_bool_t +bus_connections_check_limits (BusConnections *connections, + DBusConnection *requesting_completion, + DBusError *error) +{ + BusConnectionData *d; + unsigned long uid; + + d = BUS_CONNECTION_DATA (requesting_completion); + _dbus_assert (d != NULL); + + _dbus_assert (d->name == NULL); + + if (connections->n_completed >= + bus_context_get_max_completed_connections (connections->context)) + { + dbus_set_error (error, DBUS_ERROR_LIMITS_EXCEEDED, + "The maximum number of active connections has been reached"); + return FALSE; + } + + if (dbus_connection_get_unix_user (requesting_completion, &uid)) + { + if (get_connections_for_uid (connections, uid) >= + bus_context_get_max_connections_per_user (connections->context)) + { + dbus_set_error (error, DBUS_ERROR_LIMITS_EXCEEDED, + "The maximum number of active connections for UID %lu has been reached", + uid); + return FALSE; + } + } + + return TRUE; +} + +static void +bus_pending_reply_free (BusPendingReply *pending) +{ + _dbus_verbose ("Freeing pending reply %p, replier %p receiver %p serial %u\n", + pending, + pending->will_send_reply, + pending->will_get_reply, + pending->reply_serial); + + dbus_free (pending); +} + +static dbus_bool_t +bus_pending_reply_send_no_reply (BusConnections *connections, + BusTransaction *transaction, + BusPendingReply *pending) +{ + DBusMessage *message; + DBusMessageIter iter; + dbus_bool_t retval; + const char *errmsg; + + retval = FALSE; + + message = dbus_message_new (DBUS_MESSAGE_TYPE_ERROR); + if (message == NULL) + return FALSE; + + dbus_message_set_no_reply (message, TRUE); + + if (!dbus_message_set_reply_serial (message, + pending->reply_serial)) + goto out; + + if (!dbus_message_set_error_name (message, + DBUS_ERROR_NO_REPLY)) + goto out; + + errmsg = "Message did not receive a reply (timeout by message bus)"; + dbus_message_iter_init_append (message, &iter); + if (!dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &errmsg)) + goto out; + + if (!bus_transaction_send_from_driver (transaction, pending->will_get_reply, + message)) + goto out; + + retval = TRUE; + + out: + dbus_message_unref (message); + return retval; +} + +static dbus_bool_t +bus_pending_reply_expired (BusExpireList *list, + DBusList *link, + void *data) +{ + BusPendingReply *pending = link->data; + BusConnections *connections = data; + BusTransaction *transaction; + + /* No reply is forthcoming. So nuke it if we can. If not, + * leave it in the list to try expiring again later when we + * get more memory. + */ + + _dbus_verbose ("Expiring pending reply %p, replier %p receiver %p serial %u\n", + pending, + pending->will_send_reply, + pending->will_get_reply, + pending->reply_serial); + + transaction = bus_transaction_new (connections->context); + if (transaction == NULL) + return FALSE; + + if (!bus_pending_reply_send_no_reply (connections, + transaction, + pending)) + { + bus_transaction_cancel_and_free (transaction); + return FALSE; + } + + bus_expire_list_remove_link (connections->pending_replies, link); + + bus_pending_reply_free (pending); + bus_transaction_execute_and_free (transaction); + + return TRUE; +} + +static void +bus_connection_drop_pending_replies (BusConnections *connections, + DBusConnection *connection) +{ + /* The DBusConnection is almost 100% finalized here, so you can't + * do anything with it except check for pointer equality + */ + DBusList *link; + + _dbus_verbose ("Dropping pending replies that involve connection %p\n", + connection); + + link = bus_expire_list_get_first_link (connections->pending_replies); + while (link != NULL) + { + DBusList *next; + BusPendingReply *pending; + + next = bus_expire_list_get_next_link (connections->pending_replies, + link); + pending = link->data; + + if (pending->will_get_reply == connection) + { + /* We don't need to track this pending reply anymore */ + + _dbus_verbose ("Dropping pending reply %p, replier %p receiver %p serial %u\n", + pending, + pending->will_send_reply, + pending->will_get_reply, + pending->reply_serial); + + bus_expire_list_remove_link (connections->pending_replies, + link); + bus_pending_reply_free (pending); + } + else if (pending->will_send_reply == connection) + { + /* The reply isn't going to be sent, so set things + * up so it will be expired right away + */ + _dbus_verbose ("Will expire pending reply %p, replier %p receiver %p serial %u\n", + pending, + pending->will_send_reply, + pending->will_get_reply, + pending->reply_serial); + + pending->will_send_reply = NULL; + pending->expire_item.added_tv_sec = 0; + pending->expire_item.added_tv_usec = 0; + + bus_expire_list_recheck_immediately (connections->pending_replies); + } + + link = next; + } +} + + +typedef struct +{ + BusPendingReply *pending; + BusConnections *connections; +} CancelPendingReplyData; + +static void +cancel_pending_reply (void *data) +{ + CancelPendingReplyData *d = data; + + _dbus_verbose ("%s: d = %p\n", _DBUS_FUNCTION_NAME, d); + + if (!bus_expire_list_remove (d->connections->pending_replies, + &d->pending->expire_item)) + _dbus_assert_not_reached ("pending reply did not exist to be cancelled"); + + bus_pending_reply_free (d->pending); /* since it's been cancelled */ +} + +static void +cancel_pending_reply_data_free (void *data) +{ + CancelPendingReplyData *d = data; + + _dbus_verbose ("%s: d = %p\n", _DBUS_FUNCTION_NAME, d); + + /* d->pending should be either freed or still + * in the list of pending replies (owned by someone + * else) + */ + + dbus_free (d); +} + +/* + * Record that a reply is allowed; return TRUE on success. + */ +dbus_bool_t +bus_connections_expect_reply (BusConnections *connections, + BusTransaction *transaction, + DBusConnection *will_get_reply, + DBusConnection *will_send_reply, + DBusMessage *reply_to_this, + DBusError *error) +{ + BusPendingReply *pending; + dbus_uint32_t reply_serial; + DBusList *link; + CancelPendingReplyData *cprd; + int count; + + _dbus_assert (will_get_reply != NULL); + _dbus_assert (will_send_reply != NULL); + _dbus_assert (reply_to_this != NULL); + + if (dbus_message_get_no_reply (reply_to_this)) + return TRUE; /* we won't allow a reply, since client doesn't care for one. */ + + reply_serial = dbus_message_get_serial (reply_to_this); + + link = bus_expire_list_get_first_link (connections->pending_replies); + count = 0; + while (link != NULL) + { + pending = link->data; + + if (pending->reply_serial == reply_serial && + pending->will_get_reply == will_get_reply && + pending->will_send_reply == will_send_reply) + { + dbus_set_error (error, DBUS_ERROR_ACCESS_DENIED, + "Message has the same reply serial as a currently-outstanding existing method call"); + return FALSE; + } + + link = bus_expire_list_get_next_link (connections->pending_replies, + link); + if (pending->will_get_reply == will_get_reply) + ++count; + } + + if (count >= + bus_context_get_max_replies_per_connection (connections->context)) + { + dbus_set_error (error, DBUS_ERROR_LIMITS_EXCEEDED, + "The maximum number of pending replies per connection has been reached"); + return FALSE; + } + + pending = dbus_new0 (BusPendingReply, 1); + if (pending == NULL) + { + BUS_SET_OOM (error); + return FALSE; + } + +#ifdef DBUS_ENABLE_VERBOSE_MODE + /* so we can see a not-yet-added pending reply */ + pending->expire_item.added_tv_sec = 1; + pending->expire_item.added_tv_usec = 1; +#endif + + pending->will_get_reply = will_get_reply; + pending->will_send_reply = will_send_reply; + pending->reply_serial = reply_serial; + + cprd = dbus_new0 (CancelPendingReplyData, 1); + if (cprd == NULL) + { + BUS_SET_OOM (error); + bus_pending_reply_free (pending); + return FALSE; + } + + if (!bus_expire_list_add (connections->pending_replies, + &pending->expire_item)) + { + BUS_SET_OOM (error); + dbus_free (cprd); + bus_pending_reply_free (pending); + return FALSE; + } + + if (!bus_transaction_add_cancel_hook (transaction, + cancel_pending_reply, + cprd, + cancel_pending_reply_data_free)) + { + BUS_SET_OOM (error); + bus_expire_list_remove (connections->pending_replies, &pending->expire_item); + dbus_free (cprd); + bus_pending_reply_free (pending); + return FALSE; + } + + cprd->pending = pending; + cprd->connections = connections; + + _dbus_get_current_time (&pending->expire_item.added_tv_sec, + &pending->expire_item.added_tv_usec); + + _dbus_verbose ("Added pending reply %p, replier %p receiver %p serial %u\n", + pending, + pending->will_send_reply, + pending->will_get_reply, + pending->reply_serial); + + return TRUE; +} + +typedef struct +{ + DBusList *link; + BusConnections *connections; +} CheckPendingReplyData; + +static void +cancel_check_pending_reply (void *data) +{ + CheckPendingReplyData *d = data; + + _dbus_verbose ("%s: d = %p\n", _DBUS_FUNCTION_NAME, d); + + bus_expire_list_add_link (d->connections->pending_replies, + d->link); + d->link = NULL; +} + +static void +check_pending_reply_data_free (void *data) +{ + CheckPendingReplyData *d = data; + + _dbus_verbose ("%s: d = %p\n", _DBUS_FUNCTION_NAME, d); + + if (d->link != NULL) + { + BusPendingReply *pending = d->link->data; + + _dbus_assert (!bus_expire_list_contains_item (d->connections->pending_replies, + &pending->expire_item)); + + bus_pending_reply_free (pending); + _dbus_list_free_link (d->link); + } + + dbus_free (d); +} + +/* + * Check whether a reply is allowed, remove BusPendingReply + * if so, return TRUE if so. + */ +dbus_bool_t +bus_connections_check_reply (BusConnections *connections, + BusTransaction *transaction, + DBusConnection *sending_reply, + DBusConnection *receiving_reply, + DBusMessage *reply, + DBusError *error) +{ + CheckPendingReplyData *cprd; + DBusList *link; + dbus_uint32_t reply_serial; + + _dbus_assert (sending_reply != NULL); + _dbus_assert (receiving_reply != NULL); + + reply_serial = dbus_message_get_reply_serial (reply); + + link = bus_expire_list_get_first_link (connections->pending_replies); + while (link != NULL) + { + BusPendingReply *pending = link->data; + + if (pending->reply_serial == reply_serial && + pending->will_get_reply == receiving_reply && + pending->will_send_reply == sending_reply) + { + _dbus_verbose ("Found pending reply with serial %u\n", reply_serial); + break; + } + + link = bus_expire_list_get_next_link (connections->pending_replies, + link); + } + + if (link == NULL) + { + _dbus_verbose ("No pending reply expected\n"); + + return FALSE; + } + + cprd = dbus_new0 (CheckPendingReplyData, 1); + if (cprd == NULL) + { + BUS_SET_OOM (error); + return FALSE; + } + + if (!bus_transaction_add_cancel_hook (transaction, + cancel_check_pending_reply, + cprd, + check_pending_reply_data_free)) + { + BUS_SET_OOM (error); + dbus_free (cprd); + return FALSE; + } + + cprd->link = link; + cprd->connections = connections; + + bus_expire_list_unlink (connections->pending_replies, + link); + + _dbus_assert (!bus_expire_list_contains_item (connections->pending_replies, link->data)); + + return TRUE; +} + +/* + * Transactions + * + * Note that this is fairly fragile; in particular, don't try to use + * one transaction across any main loop iterations. + */ + +typedef struct +{ + BusTransaction *transaction; + DBusMessage *message; + DBusPreallocatedSend *preallocated; +} MessageToSend; + +typedef struct +{ + BusTransactionCancelFunction cancel_function; + DBusFreeFunction free_data_function; + void *data; +} CancelHook; + +struct BusTransaction +{ + DBusList *connections; + BusContext *context; + DBusList *cancel_hooks; +}; + +static void +message_to_send_free (DBusConnection *connection, + MessageToSend *to_send) +{ + if (to_send->message) + dbus_message_unref (to_send->message); + + if (to_send->preallocated) + dbus_connection_free_preallocated_send (connection, to_send->preallocated); + + dbus_free (to_send); +} + +static void +cancel_hook_cancel (void *element, + void *data) +{ + CancelHook *ch = element; + + _dbus_verbose ("Running transaction cancel hook\n"); + + if (ch->cancel_function) + (* ch->cancel_function) (ch->data); +} + +static void +cancel_hook_free (void *element, + void *data) +{ + CancelHook *ch = element; + + if (ch->free_data_function) + (* ch->free_data_function) (ch->data); + + dbus_free (ch); +} + +static void +free_cancel_hooks (BusTransaction *transaction) +{ + _dbus_list_foreach (&transaction->cancel_hooks, + cancel_hook_free, NULL); + + _dbus_list_clear (&transaction->cancel_hooks); +} + +BusTransaction* +bus_transaction_new (BusContext *context) +{ + BusTransaction *transaction; + + transaction = dbus_new0 (BusTransaction, 1); + if (transaction == NULL) + return NULL; + + transaction->context = context; + + return transaction; +} + +BusContext* +bus_transaction_get_context (BusTransaction *transaction) +{ + return transaction->context; +} + +BusConnections* +bus_transaction_get_connections (BusTransaction *transaction) +{ + return bus_context_get_connections (transaction->context); +} + +dbus_bool_t +bus_transaction_send_from_driver (BusTransaction *transaction, + DBusConnection *connection, + DBusMessage *message) +{ + /* We have to set the sender to the driver, and have + * to check security policy since it was not done in + * dispatch.c + */ + _dbus_verbose ("Sending %s %s %s from driver\n", + dbus_message_get_interface (message) ? + dbus_message_get_interface (message) : "(no interface)", + dbus_message_get_member (message) ? + dbus_message_get_member (message) : "(no member)", + dbus_message_get_error_name (message) ? + dbus_message_get_error_name (message) : "(no error name)"); + + if (!dbus_message_set_sender (message, DBUS_SERVICE_DBUS)) + return FALSE; + + if (bus_connection_is_active (connection)) + { + if (!dbus_message_set_destination (message, + bus_connection_get_name (connection))) + return FALSE; + } + + /* bus driver never wants a reply */ + dbus_message_set_no_reply (message, TRUE); + + /* If security policy doesn't allow the message, we silently + * eat it; the driver doesn't care about getting a reply. + */ + if (!bus_context_check_security_policy (bus_transaction_get_context (transaction), + transaction, + NULL, connection, connection, message, NULL)) + return TRUE; + + return bus_transaction_send (transaction, connection, message); +} + +dbus_bool_t +bus_transaction_send (BusTransaction *transaction, + DBusConnection *connection, + DBusMessage *message) +{ + MessageToSend *to_send; + BusConnectionData *d; + DBusList *link; + + _dbus_verbose (" trying to add %s interface=%s member=%s error=%s to transaction%s\n", + dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_ERROR ? "error" : + dbus_message_get_reply_serial (message) != 0 ? "reply" : + "message", + dbus_message_get_interface (message) ? + dbus_message_get_interface (message) : "(unset)", + dbus_message_get_member (message) ? + dbus_message_get_member (message) : "(unset)", + dbus_message_get_error_name (message) ? + dbus_message_get_error_name (message) : "(unset)", + dbus_connection_get_is_connected (connection) ? + "" : " (disconnected)"); + + _dbus_assert (dbus_message_get_sender (message) != NULL); + + if (!dbus_connection_get_is_connected (connection)) + return TRUE; /* silently ignore disconnected connections */ + + d = BUS_CONNECTION_DATA (connection); + _dbus_assert (d != NULL); + + to_send = dbus_new (MessageToSend, 1); + if (to_send == NULL) + { + return FALSE; + } + + to_send->preallocated = dbus_connection_preallocate_send (connection); + if (to_send->preallocated == NULL) + { + dbus_free (to_send); + return FALSE; + } + + dbus_message_ref (message); + to_send->message = message; + to_send->transaction = transaction; + + _dbus_verbose ("about to prepend message\n"); + + if (!_dbus_list_prepend (&d->transaction_messages, to_send)) + { + message_to_send_free (connection, to_send); + return FALSE; + } + + _dbus_verbose ("prepended message\n"); + + /* See if we already had this connection in the list + * for this transaction. If we have a pending message, + * then we should already be in transaction->connections + */ + link = _dbus_list_get_first_link (&d->transaction_messages); + _dbus_assert (link->data == to_send); + link = _dbus_list_get_next_link (&d->transaction_messages, link); + while (link != NULL) + { + MessageToSend *m = link->data; + DBusList *next = _dbus_list_get_next_link (&d->transaction_messages, link); + + if (m->transaction == transaction) + break; + + link = next; + } + + if (link == NULL) + { + if (!_dbus_list_prepend (&transaction->connections, connection)) + { + _dbus_list_remove (&d->transaction_messages, to_send); + message_to_send_free (connection, to_send); + return FALSE; + } + } + + return TRUE; +} + +static void +connection_cancel_transaction (DBusConnection *connection, + BusTransaction *transaction) +{ + DBusList *link; + BusConnectionData *d; + + d = BUS_CONNECTION_DATA (connection); + _dbus_assert (d != NULL); + + link = _dbus_list_get_first_link (&d->transaction_messages); + while (link != NULL) + { + MessageToSend *m = link->data; + DBusList *next = _dbus_list_get_next_link (&d->transaction_messages, link); + + if (m->transaction == transaction) + { + _dbus_list_remove_link (&d->transaction_messages, + link); + + message_to_send_free (connection, m); + } + + link = next; + } +} + +void +bus_transaction_cancel_and_free (BusTransaction *transaction) +{ + DBusConnection *connection; + + _dbus_verbose ("TRANSACTION: cancelled\n"); + + while ((connection = _dbus_list_pop_first (&transaction->connections))) + connection_cancel_transaction (connection, transaction); + + _dbus_assert (transaction->connections == NULL); + + _dbus_list_foreach (&transaction->cancel_hooks, + cancel_hook_cancel, NULL); + + free_cancel_hooks (transaction); + + dbus_free (transaction); +} + +static void +connection_execute_transaction (DBusConnection *connection, + BusTransaction *transaction) +{ + DBusList *link; + BusConnectionData *d; + + d = BUS_CONNECTION_DATA (connection); + _dbus_assert (d != NULL); + + /* Send the queue in order (FIFO) */ + link = _dbus_list_get_last_link (&d->transaction_messages); + while (link != NULL) + { + MessageToSend *m = link->data; + DBusList *prev = _dbus_list_get_prev_link (&d->transaction_messages, link); + + if (m->transaction == transaction) + { + _dbus_list_remove_link (&d->transaction_messages, + link); + + _dbus_assert (dbus_message_get_sender (m->message) != NULL); + + dbus_connection_send_preallocated (connection, + m->preallocated, + m->message, + NULL); + + m->preallocated = NULL; /* so we don't double-free it */ + + message_to_send_free (connection, m); + } + + link = prev; + } +} + +void +bus_transaction_execute_and_free (BusTransaction *transaction) +{ + /* For each connection in transaction->connections + * send the messages + */ + DBusConnection *connection; + + _dbus_verbose ("TRANSACTION: executing\n"); + + while ((connection = _dbus_list_pop_first (&transaction->connections))) + connection_execute_transaction (connection, transaction); + + _dbus_assert (transaction->connections == NULL); + + free_cancel_hooks (transaction); + + dbus_free (transaction); +} + +static void +bus_connection_remove_transactions (DBusConnection *connection) +{ + MessageToSend *to_send; + BusConnectionData *d; + + d = BUS_CONNECTION_DATA (connection); + _dbus_assert (d != NULL); + + while ((to_send = _dbus_list_get_first (&d->transaction_messages))) + { + /* only has an effect for the first MessageToSend listing this transaction */ + _dbus_list_remove (&to_send->transaction->connections, + connection); + + _dbus_list_remove (&d->transaction_messages, to_send); + message_to_send_free (connection, to_send); + } +} + +/** + * Converts the DBusError to a message reply + */ +dbus_bool_t +bus_transaction_send_error_reply (BusTransaction *transaction, + DBusConnection *connection, + const DBusError *error, + DBusMessage *in_reply_to) +{ + DBusMessage *reply; + + _dbus_assert (error != NULL); + _DBUS_ASSERT_ERROR_IS_SET (error); + + _dbus_verbose ("Sending error reply %s \"%s\"\n", + error->name, error->message); + + reply = dbus_message_new_error (in_reply_to, + error->name, + error->message); + if (reply == NULL) + return FALSE; + + if (!bus_transaction_send_from_driver (transaction, connection, reply)) + { + dbus_message_unref (reply); + return FALSE; + } + + dbus_message_unref (reply); + + return TRUE; +} + +dbus_bool_t +bus_transaction_add_cancel_hook (BusTransaction *transaction, + BusTransactionCancelFunction cancel_function, + void *data, + DBusFreeFunction free_data_function) +{ + CancelHook *ch; + + ch = dbus_new (CancelHook, 1); + if (ch == NULL) + return FALSE; + + _dbus_verbose (" adding cancel hook function = %p data = %p\n", + cancel_function, data); + + ch->cancel_function = cancel_function; + ch->data = data; + ch->free_data_function = free_data_function; + + /* It's important that the hooks get run in reverse order that they + * were added + */ + if (!_dbus_list_prepend (&transaction->cancel_hooks, ch)) + { + dbus_free (ch); + return FALSE; + } + + return TRUE; +} diff --git a/bus/connection.h b/bus/connection.h new file mode 100644 index 00000000..4b9a754b --- /dev/null +++ b/bus/connection.h @@ -0,0 +1,141 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* connection.h Client connections + * + * Copyright (C) 2003, 2004 Red Hat, Inc. + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef BUS_CONNECTION_H +#define BUS_CONNECTION_H + +#include <dbus/dbus.h> +#include <dbus/dbus-list.h> +#include "bus.h" + +typedef dbus_bool_t (* BusConnectionForeachFunction) (DBusConnection *connection, + void *data); + + +BusConnections* bus_connections_new (BusContext *context); +BusConnections* bus_connections_ref (BusConnections *connections); +void bus_connections_unref (BusConnections *connections); +dbus_bool_t bus_connections_setup_connection (BusConnections *connections, + DBusConnection *connection); +void bus_connections_foreach (BusConnections *connections, + BusConnectionForeachFunction function, + void *data); +void bus_connections_foreach_active (BusConnections *connections, + BusConnectionForeachFunction function, + void *data); +BusContext* bus_connections_get_context (BusConnections *connections); +void bus_connections_increment_stamp (BusConnections *connections); +BusContext* bus_connection_get_context (DBusConnection *connection); +BusConnections* bus_connection_get_connections (DBusConnection *connection); +BusRegistry* bus_connection_get_registry (DBusConnection *connection); +BusActivation* bus_connection_get_activation (DBusConnection *connection); +BusMatchmaker* bus_connection_get_matchmaker (DBusConnection *connection); +const char * bus_connection_get_loginfo (DBusConnection *connection); +BusSELinuxID* bus_connection_get_selinux_id (DBusConnection *connection); +dbus_bool_t bus_connections_check_limits (BusConnections *connections, + DBusConnection *requesting_completion, + DBusError *error); +void bus_connections_expire_incomplete (BusConnections *connections); + +dbus_bool_t bus_connections_expect_reply (BusConnections *connections, + BusTransaction *transaction, + DBusConnection *will_get_reply, + DBusConnection *will_send_reply, + DBusMessage *reply_to_this, + DBusError *error); +dbus_bool_t bus_connections_check_reply (BusConnections *connections, + BusTransaction *transaction, + DBusConnection *sending_reply, + DBusConnection *receiving_reply, + DBusMessage *reply, + DBusError *error); + +dbus_bool_t bus_connection_mark_stamp (DBusConnection *connection); + +dbus_bool_t bus_connection_is_active (DBusConnection *connection); +const char *bus_connection_get_name (DBusConnection *connection); + +dbus_bool_t bus_connection_preallocate_oom_error (DBusConnection *connection); +void bus_connection_send_oom_error (DBusConnection *connection, + DBusMessage *in_reply_to); + +/* called by signals.c */ +dbus_bool_t bus_connection_add_match_rule (DBusConnection *connection, + BusMatchRule *rule); +void bus_connection_add_match_rule_link (DBusConnection *connection, + DBusList *link); +void bus_connection_remove_match_rule (DBusConnection *connection, + BusMatchRule *rule); +int bus_connection_get_n_match_rules (DBusConnection *connection); + + +/* called by services.c */ +dbus_bool_t bus_connection_add_owned_service (DBusConnection *connection, + BusService *service); +void bus_connection_remove_owned_service (DBusConnection *connection, + BusService *service); +void bus_connection_add_owned_service_link (DBusConnection *connection, + DBusList *link); +int bus_connection_get_n_services_owned (DBusConnection *connection); + +/* called by driver.c */ +dbus_bool_t bus_connection_complete (DBusConnection *connection, + const DBusString *name, + DBusError *error); + +/* called by dispatch.c when the connection is dropped */ +void bus_connection_disconnected (DBusConnection *connection); + +dbus_bool_t bus_connection_is_in_unix_group (DBusConnection *connection, + unsigned long gid); +dbus_bool_t bus_connection_get_unix_groups (DBusConnection *connection, + unsigned long **groups, + int *n_groups, + DBusError *error); +BusClientPolicy* bus_connection_get_policy (DBusConnection *connection); + +/* transaction API so we can send or not send a block of messages as a whole */ + +typedef void (* BusTransactionCancelFunction) (void *data); + +BusTransaction* bus_transaction_new (BusContext *context); +BusContext* bus_transaction_get_context (BusTransaction *transaction); +BusConnections* bus_transaction_get_connections (BusTransaction *transaction); +dbus_bool_t bus_transaction_send (BusTransaction *transaction, + DBusConnection *connection, + DBusMessage *message); +dbus_bool_t bus_transaction_send_from_driver (BusTransaction *transaction, + DBusConnection *connection, + DBusMessage *message); +dbus_bool_t bus_transaction_send_error_reply (BusTransaction *transaction, + DBusConnection *connection, + const DBusError *error, + DBusMessage *in_reply_to); +void bus_transaction_cancel_and_free (BusTransaction *transaction); +void bus_transaction_execute_and_free (BusTransaction *transaction); +dbus_bool_t bus_transaction_add_cancel_hook (BusTransaction *transaction, + BusTransactionCancelFunction cancel_function, + void *data, + DBusFreeFunction free_data_function); + +#endif /* BUS_CONNECTION_H */ diff --git a/bus/dbus-daemon.1 b/bus/dbus-daemon.1 new file mode 100644 index 00000000..6bfca148 --- /dev/null +++ b/bus/dbus-daemon.1 @@ -0,0 +1,760 @@ +.\" +.\" dbus-daemon manual page. +.\" Copyright (C) 2003,2008 Red Hat, Inc. +.\" +.TH dbus-daemon 1 +.SH NAME +dbus-daemon \- Message bus daemon +.SH SYNOPSIS +.PP +.B dbus-daemon +dbus-daemon [\-\-version] [\-\-session] [\-\-system] [\-\-config-file=FILE] +[\-\-print-address[=DESCRIPTOR]] [\-\-print-pid[=DESCRIPTOR]] [\-\-fork] + +.SH DESCRIPTION + +\fIdbus-daemon\fP is the D-Bus message bus daemon. See +http://www.freedesktop.org/software/dbus/ for more information about +the big picture. D-Bus is first a library that provides one-to-one +communication between any two applications; \fIdbus-daemon\fP is an +application that uses this library to implement a message bus +daemon. Multiple programs connect to the message bus daemon and can +exchange messages with one another. + +.PP +There are two standard message bus instances: the systemwide message bus +(installed on many systems as the "messagebus" init service) and the +per-user-login-session message bus (started each time a user logs in). +\fIdbus-daemon\fP is used for both of these instances, but with +a different configuration file. + +.PP +The \-\-session option is equivalent to +"\-\-config-file=/src/build/dbus/etc/dbus-1/session.conf" and the \-\-system +option is equivalent to +"\-\-config-file=/src/build/dbus/etc/dbus-1/system.conf". By creating +additional configuration files and using the \-\-config-file option, +additional special-purpose message bus daemons could be created. + +.PP +The systemwide daemon is normally launched by an init script, +standardly called simply "messagebus". + +.PP +The systemwide daemon is largely used for broadcasting system events, +such as changes to the printer queue, or adding/removing devices. + +.PP +The per-session daemon is used for various interprocess communication +among desktop applications (however, it is not tied to X or the GUI +in any way). + +.PP +SIGHUP will cause the D-Bus daemon to PARTIALLY reload its +configuration file and to flush its user/group information caches. Some +configuration changes would require kicking all apps off the bus; so they will +only take effect if you restart the daemon. Policy changes should take effect +with SIGHUP. + +.SH OPTIONS +The following options are supported: +.TP +.I "--config-file=FILE" +Use the given configuration file. +.TP +.I "--fork" +Force the message bus to fork and become a daemon, even if +the configuration file does not specify that it should. +In most contexts the configuration file already gets this +right, though. +.TP +.I "--print-address[=DESCRIPTOR]" +Print the address of the message bus to standard output, or +to the given file descriptor. This is used by programs that +launch the message bus. +.TP +.I "--print-pid[=DESCRIPTOR]" +Print the process ID of the message bus to standard output, or +to the given file descriptor. This is used by programs that +launch the message bus. +.TP +.I "--session" +Use the standard configuration file for the per-login-session message +bus. +.TP +.I "--system" +Use the standard configuration file for the systemwide message bus. +.TP +.I "--version" +Print the version of the daemon. + +.SH CONFIGURATION FILE + +A message bus daemon has a configuration file that specializes it +for a particular application. For example, one configuration +file might set up the message bus to be a systemwide message bus, +while another might set it up to be a per-user-login-session bus. + +.PP +The configuration file also establishes resource limits, security +parameters, and so forth. + +.PP +The configuration file is not part of any interoperability +specification and its backward compatibility is not guaranteed; this +document is documentation, not specification. + +.PP +The standard systemwide and per-session message bus setups are +configured in the files "/src/build/dbus/etc/dbus-1/system.conf" and +"/src/build/dbus/etc/dbus-1/session.conf". These files normally +<include> a system-local.conf or session-local.conf; you can put local +overrides in those files to avoid modifying the primary configuration +files. + +.PP +The configuration file is an XML document. It must have the following +doctype declaration: +.nf + + <!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN" + "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> + +.fi + +.PP +The following elements may be present in the configuration file. + +.TP +.I "<busconfig>" + +.PP +Root element. + +.TP +.I "<type>" + +.PP +The well-known type of the message bus. Currently known values are +"system" and "session"; if other values are set, they should be +either added to the D-Bus specification, or namespaced. The last +<type> element "wins" (previous values are ignored). This element +only controls which message bus specific environment variables are +set in activated clients. Most of the policy that distinguishes a +session bus from the system bus is controlled from the other elements +in the configuration file. + +.PP +If the well-known type of the message bus is "session", then the +DBUS_STARTER_BUS_TYPE environment variable will be set to "session" +and the DBUS_SESSION_BUS_ADDRESS environment variable will be set +to the address of the session bus. Likewise, if the type of the +message bus is "system", then the DBUS_STARTER_BUS_TYPE environment +variable will be set to "system" and the DBUS_SESSION_BUS_ADDRESS +environment variable will be set to the address of the system bus +(which is normally well known anyway). + +.PP +Example: <type>session</type> + +.TP +.I "<include>" + +.PP +Include a file <include>filename.conf</include> at this point. If the +filename is relative, it is located relative to the configuration file +doing the including. + +.PP +<include> has an optional attribute "ignore_missing=(yes|no)" +which defaults to "no" if not provided. This attribute +controls whether it's a fatal error for the included file +to be absent. + +.TP +.I "<includedir>" + +.PP +Include all files in <includedir>foo.d</includedir> at this +point. Files in the directory are included in undefined order. +Only files ending in ".conf" are included. + +.PP +This is intended to allow extension of the system bus by particular +packages. For example, if CUPS wants to be able to send out +notification of printer queue changes, it could install a file to +/src/build/dbus/etc/dbus-1/system.d that allowed all apps to receive +this message and allowed the printer daemon user to send it. + +.TP +.I "<user>" + +.PP +The user account the daemon should run as, as either a username or a +UID. If the daemon cannot change to this UID on startup, it will exit. +If this element is not present, the daemon will not change or care +about its UID. + +.PP +The last <user> entry in the file "wins", the others are ignored. + +.PP +The user is changed after the bus has completed initialization. So +sockets etc. will be created before changing user, but no data will be +read from clients before changing user. This means that sockets +and PID files can be created in a location that requires root +privileges for writing. + +.TP +.I "<fork>" + +.PP +If present, the bus daemon becomes a real daemon (forks +into the background, etc.). This is generally used +rather than the \-\-fork command line option. + +.TP +.I "<keep_umask>" + +.PP +If present, the bus daemon keeps its original umask when forking. +This may be useful to avoid affecting the behavior of child processes. + +.TP +.I "<listen>" + +.PP +Add an address that the bus should listen on. The +address is in the standard D-Bus format that contains +a transport name plus possible parameters/options. + +.PP +Example: <listen>unix:path=/tmp/foo</listen> + +.PP +Example: <listen>tcp:host=localhost,port=1234</listen> + +.PP +If there are multiple <listen> elements, then the bus listens +on multiple addresses. The bus will pass its address to +started services or other interested parties with +the last address given in <listen> first. That is, +apps will try to connect to the last <listen> address first. + +.PP +tcp sockets can accept IPv4 addresses, IPv6 addresses or hostnames. +If a hostname resolves to multiple addresses, the server will bind +to all of them. The family=ipv4 or family=ipv6 options can be used +to force it to bind to a subset of addresses + +.PP +Example: <listen>tcp:host=localhost,port=0,family=ipv4</listen> + +.PP +A special case is using a port number of zero (or omitting the port), +which means to choose an available port selected by the operating +system. The port number chosen can be obtained with the +--print-address command line parameter and will be present in other +cases where the server reports its own address, such as when +DBUS_SESSION_BUS_ADDRESS is set. + +.PP +Example: <listen>tcp:host=localhost,port=0</listen> + +.PP +tcp addresses also allow a bind=hostname option, which will override +the host option specifying what address to bind to, without changing +the address reported by the bus. The bind option can also take a +special name '*' to cause the bus to listen on all local address +(INADDR_ANY). The specified host should be a valid name of the local +machine or weird stuff will happen. + +.PP +Example: <listen>tcp:host=localhost,bind=*,port=0</listen> + +.TP +.I "<auth>" + +.PP +Lists permitted authorization mechanisms. If this element doesn't +exist, then all known mechanisms are allowed. If there are multiple +<auth> elements, all the listed mechanisms are allowed. The order in +which mechanisms are listed is not meaningful. + +.PP +Example: <auth>EXTERNAL</auth> + +.PP +Example: <auth>DBUS_COOKIE_SHA1</auth> + +.TP +.I "<servicedir>" + +.PP +Adds a directory to scan for .service files. Directories are +scanned starting with the last to appear in the config file +(the first .service file found that provides a particular +service will be used). + +.PP +Service files tell the bus how to automatically start a program. +They are primarily used with the per-user-session bus, +not the systemwide bus. + +.TP +.I "<standard_session_servicedirs/>" + +.PP +<standard_session_servicedirs/> is equivalent to specifying a series +of <servicedir/> elements for each of the data directories in the "XDG +Base Directory Specification" with the subdirectory "dbus-1/services", +so for example "/usr/share/dbus-1/services" would be among the +directories searched. + +.PP +The "XDG Base Directory Specification" can be found at +http://freedesktop.org/wiki/Standards/basedir-spec if it hasn't moved, +otherwise try your favorite search engine. + +.PP +The <standard_session_servicedirs/> option is only relevant to the +per-user-session bus daemon defined in +/src/build/dbus/etc/dbus-1/session.conf. Putting it in any other +configuration file would probably be nonsense. + +.TP +.I "<standard_system_servicedirs/>" + +.PP +<standard_system_servicedirs/> specifies the standard system-wide +activation directories that should be searched for service files. +This option defaults to /src/build/dbus/share/dbus-1/system-services. + +.PP +The <standard_system_servicedirs/> option is only relevant to the +per-system bus daemon defined in +/src/build/dbus/etc/dbus-1/system.conf. Putting it in any other +configuration file would probably be nonsense. + +.TP +.I "<servicehelper/>" + +.PP +<servicehelper/> specifies the setuid helper that is used to launch +system daemons with an alternate user. Typically this should be +the dbus-daemon-launch-helper executable in located in libexec. + +.PP +The <servicehelper/> option is only relevant to the per-system bus daemon +defined in /src/build/dbus/etc/dbus-1/system.conf. Putting it in any other +configuration file would probably be nonsense. + +.TP +.I "<limit>" + +.PP +<limit> establishes a resource limit. For example: +.nf + <limit name="max_message_size">64</limit> + <limit name="max_completed_connections">512</limit> +.fi + +.PP +The name attribute is mandatory. +Available limit names are: +.nf + "max_incoming_bytes" : total size in bytes of messages + incoming from a single connection + "max_outgoing_bytes" : total size in bytes of messages + queued up for a single connection + "max_message_size" : max size of a single message in + bytes + "service_start_timeout" : milliseconds (thousandths) until + a started service has to connect + "auth_timeout" : milliseconds (thousandths) a + connection is given to + authenticate + "max_completed_connections" : max number of authenticated connections + "max_incomplete_connections" : max number of unauthenticated + connections + "max_connections_per_user" : max number of completed connections from + the same user + "max_pending_service_starts" : max number of service launches in + progress at the same time + "max_names_per_connection" : max number of names a single + connection can own + "max_match_rules_per_connection": max number of match rules for a single + connection + "max_replies_per_connection" : max number of pending method + replies per connection + (number of calls-in-progress) + "reply_timeout" : milliseconds (thousandths) + until a method call times out +.fi + +.PP +The max incoming/outgoing queue sizes allow a new message to be queued +if one byte remains below the max. So you can in fact exceed the max +by max_message_size. + +.PP +max_completed_connections divided by max_connections_per_user is the +number of users that can work together to denial-of-service all other users by using +up all connections on the systemwide bus. + +.PP +Limits are normally only of interest on the systemwide bus, not the user session +buses. + +.TP +.I "<policy>" + +.PP +The <policy> element defines a security policy to be applied to a particular +set of connections to the bus. A policy is made up of +<allow> and <deny> elements. Policies are normally used with the systemwide bus; +they are analogous to a firewall in that they allow expected traffic +and prevent unexpected traffic. + +.PP +Currently, the system bus has a default-deny policy for sending method calls +and owning bus names. Everything else, in particular reply messages, receive +checks, and signals has a default allow policy. + +.PP +In general, it is best to keep system services as small, targeted programs which +run in their own process and provide a single bus name. Then, all that is needed +is an <allow> rule for the "own" permission to let the process claim the bus +name, and a "send_destination" rule to allow traffic from some or all uids to +your service. + +.PP +The <policy> element has one of four attributes: +daemon.1.in +.nf + context="(default|mandatory)" + at_console="(true|false)" + user="username or userid" + group="group name or gid" +.fi + +.PP +Policies are applied to a connection as follows: +.nf + - all context="default" policies are applied + - all group="connection's user's group" policies are applied + in undefined order + - all user="connection's auth user" policies are applied + in undefined order + - all at_console="true" policies are applied + - all at_console="false" policies are applied + - all context="mandatory" policies are applied +.fi + +.PP +Policies applied later will override those applied earlier, +when the policies overlap. Multiple policies with the same +user/group/context are applied in the order they appear +in the config file. + +.TP +.I "<deny>" +.I "<allow>" + +.PP +A <deny> element appears below a <policy> element and prohibits some +action. The <allow> element makes an exception to previous <deny> +statements, and works just like <deny> but with the inverse meaning. + +.PP +The possible attributes of these elements are: +.nf + send_interface="interface_name" + send_member="method_or_signal_name" + send_error="error_name" + send_destination="name" + send_type="method_call" | "method_return" | "signal" | "error" + send_path="/path/name" + + receive_interface="interface_name" + receive_member="method_or_signal_name" + receive_error="error_name" + receive_sender="name" + receive_type="method_call" | "method_return" | "signal" | "error" + receive_path="/path/name" + + send_requested_reply="true" | "false" + receive_requested_reply="true" | "false" + + eavesdrop="true" | "false" + + own="name" + user="username" + group="groupname" +.fi + +.PP +Examples: +.nf + <deny send_interface="org.freedesktop.System" send_member="Reboot"/> + <deny receive_interface="org.freedesktop.System" receive_member="Reboot"/> + <deny own="org.freedesktop.System"/> + <deny send_destination="org.freedesktop.System"/> + <deny receive_sender="org.freedesktop.System"/> + <deny user="john"/> + <deny group="enemies"/> +.fi + +.PP +The <deny> element's attributes determine whether the deny "matches" a +particular action. If it matches, the action is denied (unless later +rules in the config file allow it). + +.PP +send_destination and receive_sender rules mean that messages may not be +sent to or received from the *owner* of the given name, not that +they may not be sent *to that name*. That is, if a connection +owns services A, B, C, and sending to A is denied, sending to B or C +will not work either. + +.PP +The other send_* and receive_* attributes are purely textual/by-value +matches against the given field in the message header. + +.PP +"Eavesdropping" occurs when an application receives a message that +was explicitly addressed to a name the application does not own, or +is a reply to such a message. Eavesdropping thus only applies to +messages that are addressed to services and replies to such messages +(i.e. it does not apply to signals). + +.PP +For <allow>, eavesdrop="true" indicates that the rule matches even +when eavesdropping. eavesdrop="false" is the default and means that +the rule only allows messages to go to their specified recipient. +For <deny>, eavesdrop="true" indicates that the rule matches +only when eavesdropping. eavesdrop="false" is the default for <deny> +also, but here it means that the rule applies always, even when +not eavesdropping. The eavesdrop attribute can only be combined with +send and receive rules (with send_* and receive_* attributes). + + +.PP +The [send|receive]_requested_reply attribute works similarly to the eavesdrop +attribute. It controls whether the <deny> or <allow> matches a reply +that is expected (corresponds to a previous method call message). +This attribute only makes sense for reply messages (errors and method +returns), and is ignored for other message types. + +.PP +For <allow>, [send|receive]_requested_reply="true" is the default and indicates that +only requested replies are allowed by the +rule. [send|receive]_requested_reply="false" means that the rule allows any reply +even if unexpected. + +.PP +For <deny>, [send|receive]_requested_reply="false" is the default but indicates that +the rule matches only when the reply was not +requested. [send|receive]_requested_reply="true" indicates that the rule applies +always, regardless of pending reply state. + +.PP +user and group denials mean that the given user or group may +not connect to the message bus. + +.PP +For "name", "username", "groupname", etc. +the character "*" can be substituted, meaning "any." Complex globs +like "foo.bar.*" aren't allowed for now because they'd be work to +implement and maybe encourage sloppy security anyway. + +.PP +It does not make sense to deny a user or group inside a <policy> +for a user or group; user/group denials can only be inside +context="default" or context="mandatory" policies. + +.PP +A single <deny> rule may specify combinations of attributes such as +send_destination and send_interface and send_type. In this case, the +denial applies only if both attributes match the message being denied. +e.g. <deny send_interface="foo.bar" send_destination="foo.blah"/> would +deny messages with the given interface AND the given bus name. +To get an OR effect you specify multiple <deny> rules. + +.PP +You can't include both send_ and receive_ attributes on the same +rule, since "whether the message can be sent" and "whether it can be +received" are evaluated separately. + +.PP +Be careful with send_interface/receive_interface, because the +interface field in messages is optional. In particular, do NOT +specify <deny send_interface="org.foo.Bar"/>! This will cause +no-interface messages to be blocked for all services, which is +almost certainly not what you intended. Always use rules of +the form: <deny send_interface="org.foo.Bar" send_destination="org.foo.Service"/> + +.TP +.I "<selinux>" + +.PP +The <selinux> element contains settings related to Security Enhanced Linux. +More details below. + +.TP +.I "<associate>" + +.PP +An <associate> element appears below an <selinux> element and +creates a mapping. Right now only one kind of association is possible: +.nf + <associate own="org.freedesktop.Foobar" context="foo_t"/> +.fi + +.PP +This means that if a connection asks to own the name +"org.freedesktop.Foobar" then the source context will be the context +of the connection and the target context will be "foo_t" - see the +short discussion of SELinux below. + +.PP +Note, the context here is the target context when requesting a name, +NOT the context of the connection owning the name. + +.PP +There's currently no way to set a default for owning any name, if +we add this syntax it will look like: +.nf + <associate own="*" context="foo_t"/> +.fi +If you find a reason this is useful, let the developers know. +Right now the default will be the security context of the bus itself. + +.PP +If two <associate> elements specify the same name, the element +appearing later in the configuration file will be used. + +.SH SELinux + +.PP +See http://www.nsa.gov/selinux/ for full details on SELinux. Some useful excerpts: + +.IP "" 8 +Every subject (process) and object (e.g. file, socket, IPC object, +etc) in the system is assigned a collection of security attributes, +known as a security context. A security context contains all of the +security attributes associated with a particular subject or object +that are relevant to the security policy. + +.IP "" 8 +In order to better encapsulate security contexts and to provide +greater efficiency, the policy enforcement code of SELinux typically +handles security identifiers (SIDs) rather than security contexts. A +SID is an integer that is mapped by the security server to a security +context at runtime. + +.IP "" 8 +When a security decision is required, the policy enforcement code +passes a pair of SIDs (typically the SID of a subject and the SID of +an object, but sometimes a pair of subject SIDs or a pair of object +SIDs), and an object security class to the security server. The object +security class indicates the kind of object, e.g. a process, a regular +file, a directory, a TCP socket, etc. + +.IP "" 8 +Access decisions specify whether or not a permission is granted for a +given pair of SIDs and class. Each object class has a set of +associated permissions defined to control operations on objects with +that class. + +.PP +D-Bus performs SELinux security checks in two places. + +.PP +First, any time a message is routed from one connection to another +connection, the bus daemon will check permissions with the security context of +the first connection as source, security context of the second connection +as target, object class "dbus" and requested permission "send_msg". + +.PP +If a security context is not available for a connection +(impossible when using UNIX domain sockets), then the target +context used is the context of the bus daemon itself. +There is currently no way to change this default, because we're +assuming that only UNIX domain sockets will be used to +connect to the systemwide bus. If this changes, we'll +probably add a way to set the default connection context. + +.PP +Second, any time a connection asks to own a name, +the bus daemon will check permissions with the security +context of the connection as source, the security context specified +for the name in the config file as target, object +class "dbus" and requested permission "acquire_svc". + +.PP +The security context for a bus name is specified with the +<associate> element described earlier in this document. +If a name has no security context associated in the +configuration file, the security context of the bus daemon +itself will be used. + +.SH DEBUGGING + +.PP +If you're trying to figure out where your messages are going or why +you aren't getting messages, there are several things you can try. + +.PP +Remember that the system bus is heavily locked down and if you +haven't installed a security policy file to allow your message +through, it won't work. For the session bus, this is not a concern. + +.PP +The simplest way to figure out what's happening on the bus is to run +the \fIdbus-monitor\fP program, which comes with the D-Bus +package. You can also send test messages with \fIdbus-send\fP. These +programs have their own man pages. + +.PP +If you want to know what the daemon itself is doing, you might consider +running a separate copy of the daemon to test against. This will allow you +to put the daemon under a debugger, or run it with verbose output, without +messing up your real session and system daemons. + +.PP +To run a separate test copy of the daemon, for example you might open a terminal +and type: +.nf + DBUS_VERBOSE=1 dbus-daemon --session --print-address +.fi + +.PP +The test daemon address will be printed when the daemon starts. You will need +to copy-and-paste this address and use it as the value of the +DBUS_SESSION_BUS_ADDRESS environment variable when you launch the applications +you want to test. This will cause those applications to connect to your +test bus instead of the DBUS_SESSION_BUS_ADDRESS of your real session bus. + +.PP +DBUS_VERBOSE=1 will have NO EFFECT unless your copy of D-Bus +was compiled with verbose mode enabled. This is not recommended in +production builds due to performance impact. You may need to rebuild +D-Bus if your copy was not built with debugging in mind. (DBUS_VERBOSE +also affects the D-Bus library and thus applications using D-Bus; it may +be useful to see verbose output on both the client side and from the daemon.) + +.PP +If you want to get fancy, you can create a custom bus +configuration for your test bus (see the session.conf and system.conf +files that define the two default configurations for example). This +would allow you to specify a different directory for .service files, +for example. + + +.SH AUTHOR +See http://www.freedesktop.org/software/dbus/doc/AUTHORS + +.SH BUGS +Please send bug reports to the D-Bus mailing list or bug tracker, +see http://www.freedesktop.org/software/dbus/ diff --git a/bus/dbus-daemon.1.in b/bus/dbus-daemon.1.in new file mode 100644 index 00000000..8342600e --- /dev/null +++ b/bus/dbus-daemon.1.in @@ -0,0 +1,760 @@ +.\" +.\" dbus-daemon manual page. +.\" Copyright (C) 2003,2008 Red Hat, Inc. +.\" +.TH dbus-daemon 1 +.SH NAME +dbus-daemon \- Message bus daemon +.SH SYNOPSIS +.PP +.B dbus-daemon +dbus-daemon [\-\-version] [\-\-session] [\-\-system] [\-\-config-file=FILE] +[\-\-print-address[=DESCRIPTOR]] [\-\-print-pid[=DESCRIPTOR]] [\-\-fork] + +.SH DESCRIPTION + +\fIdbus-daemon\fP is the D-Bus message bus daemon. See +http://www.freedesktop.org/software/dbus/ for more information about +the big picture. D-Bus is first a library that provides one-to-one +communication between any two applications; \fIdbus-daemon\fP is an +application that uses this library to implement a message bus +daemon. Multiple programs connect to the message bus daemon and can +exchange messages with one another. + +.PP +There are two standard message bus instances: the systemwide message bus +(installed on many systems as the "messagebus" init service) and the +per-user-login-session message bus (started each time a user logs in). +\fIdbus-daemon\fP is used for both of these instances, but with +a different configuration file. + +.PP +The \-\-session option is equivalent to +"\-\-config-file=@EXPANDED_SYSCONFDIR@/dbus-1/session.conf" and the \-\-system +option is equivalent to +"\-\-config-file=@EXPANDED_SYSCONFDIR@/dbus-1/system.conf". By creating +additional configuration files and using the \-\-config-file option, +additional special-purpose message bus daemons could be created. + +.PP +The systemwide daemon is normally launched by an init script, +standardly called simply "messagebus". + +.PP +The systemwide daemon is largely used for broadcasting system events, +such as changes to the printer queue, or adding/removing devices. + +.PP +The per-session daemon is used for various interprocess communication +among desktop applications (however, it is not tied to X or the GUI +in any way). + +.PP +SIGHUP will cause the D-Bus daemon to PARTIALLY reload its +configuration file and to flush its user/group information caches. Some +configuration changes would require kicking all apps off the bus; so they will +only take effect if you restart the daemon. Policy changes should take effect +with SIGHUP. + +.SH OPTIONS +The following options are supported: +.TP +.I "--config-file=FILE" +Use the given configuration file. +.TP +.I "--fork" +Force the message bus to fork and become a daemon, even if +the configuration file does not specify that it should. +In most contexts the configuration file already gets this +right, though. +.TP +.I "--print-address[=DESCRIPTOR]" +Print the address of the message bus to standard output, or +to the given file descriptor. This is used by programs that +launch the message bus. +.TP +.I "--print-pid[=DESCRIPTOR]" +Print the process ID of the message bus to standard output, or +to the given file descriptor. This is used by programs that +launch the message bus. +.TP +.I "--session" +Use the standard configuration file for the per-login-session message +bus. +.TP +.I "--system" +Use the standard configuration file for the systemwide message bus. +.TP +.I "--version" +Print the version of the daemon. + +.SH CONFIGURATION FILE + +A message bus daemon has a configuration file that specializes it +for a particular application. For example, one configuration +file might set up the message bus to be a systemwide message bus, +while another might set it up to be a per-user-login-session bus. + +.PP +The configuration file also establishes resource limits, security +parameters, and so forth. + +.PP +The configuration file is not part of any interoperability +specification and its backward compatibility is not guaranteed; this +document is documentation, not specification. + +.PP +The standard systemwide and per-session message bus setups are +configured in the files "@EXPANDED_SYSCONFDIR@/dbus-1/system.conf" and +"@EXPANDED_SYSCONFDIR@/dbus-1/session.conf". These files normally +<include> a system-local.conf or session-local.conf; you can put local +overrides in those files to avoid modifying the primary configuration +files. + +.PP +The configuration file is an XML document. It must have the following +doctype declaration: +.nf + + <!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN" + "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> + +.fi + +.PP +The following elements may be present in the configuration file. + +.TP +.I "<busconfig>" + +.PP +Root element. + +.TP +.I "<type>" + +.PP +The well-known type of the message bus. Currently known values are +"system" and "session"; if other values are set, they should be +either added to the D-Bus specification, or namespaced. The last +<type> element "wins" (previous values are ignored). This element +only controls which message bus specific environment variables are +set in activated clients. Most of the policy that distinguishes a +session bus from the system bus is controlled from the other elements +in the configuration file. + +.PP +If the well-known type of the message bus is "session", then the +DBUS_STARTER_BUS_TYPE environment variable will be set to "session" +and the DBUS_SESSION_BUS_ADDRESS environment variable will be set +to the address of the session bus. Likewise, if the type of the +message bus is "system", then the DBUS_STARTER_BUS_TYPE environment +variable will be set to "system" and the DBUS_SESSION_BUS_ADDRESS +environment variable will be set to the address of the system bus +(which is normally well known anyway). + +.PP +Example: <type>session</type> + +.TP +.I "<include>" + +.PP +Include a file <include>filename.conf</include> at this point. If the +filename is relative, it is located relative to the configuration file +doing the including. + +.PP +<include> has an optional attribute "ignore_missing=(yes|no)" +which defaults to "no" if not provided. This attribute +controls whether it's a fatal error for the included file +to be absent. + +.TP +.I "<includedir>" + +.PP +Include all files in <includedir>foo.d</includedir> at this +point. Files in the directory are included in undefined order. +Only files ending in ".conf" are included. + +.PP +This is intended to allow extension of the system bus by particular +packages. For example, if CUPS wants to be able to send out +notification of printer queue changes, it could install a file to +@EXPANDED_SYSCONFDIR@/dbus-1/system.d that allowed all apps to receive +this message and allowed the printer daemon user to send it. + +.TP +.I "<user>" + +.PP +The user account the daemon should run as, as either a username or a +UID. If the daemon cannot change to this UID on startup, it will exit. +If this element is not present, the daemon will not change or care +about its UID. + +.PP +The last <user> entry in the file "wins", the others are ignored. + +.PP +The user is changed after the bus has completed initialization. So +sockets etc. will be created before changing user, but no data will be +read from clients before changing user. This means that sockets +and PID files can be created in a location that requires root +privileges for writing. + +.TP +.I "<fork>" + +.PP +If present, the bus daemon becomes a real daemon (forks +into the background, etc.). This is generally used +rather than the \-\-fork command line option. + +.TP +.I "<keep_umask>" + +.PP +If present, the bus daemon keeps its original umask when forking. +This may be useful to avoid affecting the behavior of child processes. + +.TP +.I "<listen>" + +.PP +Add an address that the bus should listen on. The +address is in the standard D-Bus format that contains +a transport name plus possible parameters/options. + +.PP +Example: <listen>unix:path=/tmp/foo</listen> + +.PP +Example: <listen>tcp:host=localhost,port=1234</listen> + +.PP +If there are multiple <listen> elements, then the bus listens +on multiple addresses. The bus will pass its address to +started services or other interested parties with +the last address given in <listen> first. That is, +apps will try to connect to the last <listen> address first. + +.PP +tcp sockets can accept IPv4 addresses, IPv6 addresses or hostnames. +If a hostname resolves to multiple addresses, the server will bind +to all of them. The family=ipv4 or family=ipv6 options can be used +to force it to bind to a subset of addresses + +.PP +Example: <listen>tcp:host=localhost,port=0,family=ipv4</listen> + +.PP +A special case is using a port number of zero (or omitting the port), +which means to choose an available port selected by the operating +system. The port number chosen can be obtained with the +--print-address command line parameter and will be present in other +cases where the server reports its own address, such as when +DBUS_SESSION_BUS_ADDRESS is set. + +.PP +Example: <listen>tcp:host=localhost,port=0</listen> + +.PP +tcp addresses also allow a bind=hostname option, which will override +the host option specifying what address to bind to, without changing +the address reported by the bus. The bind option can also take a +special name '*' to cause the bus to listen on all local address +(INADDR_ANY). The specified host should be a valid name of the local +machine or weird stuff will happen. + +.PP +Example: <listen>tcp:host=localhost,bind=*,port=0</listen> + +.TP +.I "<auth>" + +.PP +Lists permitted authorization mechanisms. If this element doesn't +exist, then all known mechanisms are allowed. If there are multiple +<auth> elements, all the listed mechanisms are allowed. The order in +which mechanisms are listed is not meaningful. + +.PP +Example: <auth>EXTERNAL</auth> + +.PP +Example: <auth>DBUS_COOKIE_SHA1</auth> + +.TP +.I "<servicedir>" + +.PP +Adds a directory to scan for .service files. Directories are +scanned starting with the last to appear in the config file +(the first .service file found that provides a particular +service will be used). + +.PP +Service files tell the bus how to automatically start a program. +They are primarily used with the per-user-session bus, +not the systemwide bus. + +.TP +.I "<standard_session_servicedirs/>" + +.PP +<standard_session_servicedirs/> is equivalent to specifying a series +of <servicedir/> elements for each of the data directories in the "XDG +Base Directory Specification" with the subdirectory "dbus-1/services", +so for example "/usr/share/dbus-1/services" would be among the +directories searched. + +.PP +The "XDG Base Directory Specification" can be found at +http://freedesktop.org/wiki/Standards/basedir-spec if it hasn't moved, +otherwise try your favorite search engine. + +.PP +The <standard_session_servicedirs/> option is only relevant to the +per-user-session bus daemon defined in +@EXPANDED_SYSCONFDIR@/dbus-1/session.conf. Putting it in any other +configuration file would probably be nonsense. + +.TP +.I "<standard_system_servicedirs/>" + +.PP +<standard_system_servicedirs/> specifies the standard system-wide +activation directories that should be searched for service files. +This option defaults to @EXPANDED_DATADIR@/dbus-1/system-services. + +.PP +The <standard_system_servicedirs/> option is only relevant to the +per-system bus daemon defined in +@EXPANDED_SYSCONFDIR@/dbus-1/system.conf. Putting it in any other +configuration file would probably be nonsense. + +.TP +.I "<servicehelper/>" + +.PP +<servicehelper/> specifies the setuid helper that is used to launch +system daemons with an alternate user. Typically this should be +the dbus-daemon-launch-helper executable in located in libexec. + +.PP +The <servicehelper/> option is only relevant to the per-system bus daemon +defined in @EXPANDED_SYSCONFDIR@/dbus-1/system.conf. Putting it in any other +configuration file would probably be nonsense. + +.TP +.I "<limit>" + +.PP +<limit> establishes a resource limit. For example: +.nf + <limit name="max_message_size">64</limit> + <limit name="max_completed_connections">512</limit> +.fi + +.PP +The name attribute is mandatory. +Available limit names are: +.nf + "max_incoming_bytes" : total size in bytes of messages + incoming from a single connection + "max_outgoing_bytes" : total size in bytes of messages + queued up for a single connection + "max_message_size" : max size of a single message in + bytes + "service_start_timeout" : milliseconds (thousandths) until + a started service has to connect + "auth_timeout" : milliseconds (thousandths) a + connection is given to + authenticate + "max_completed_connections" : max number of authenticated connections + "max_incomplete_connections" : max number of unauthenticated + connections + "max_connections_per_user" : max number of completed connections from + the same user + "max_pending_service_starts" : max number of service launches in + progress at the same time + "max_names_per_connection" : max number of names a single + connection can own + "max_match_rules_per_connection": max number of match rules for a single + connection + "max_replies_per_connection" : max number of pending method + replies per connection + (number of calls-in-progress) + "reply_timeout" : milliseconds (thousandths) + until a method call times out +.fi + +.PP +The max incoming/outgoing queue sizes allow a new message to be queued +if one byte remains below the max. So you can in fact exceed the max +by max_message_size. + +.PP +max_completed_connections divided by max_connections_per_user is the +number of users that can work together to denial-of-service all other users by using +up all connections on the systemwide bus. + +.PP +Limits are normally only of interest on the systemwide bus, not the user session +buses. + +.TP +.I "<policy>" + +.PP +The <policy> element defines a security policy to be applied to a particular +set of connections to the bus. A policy is made up of +<allow> and <deny> elements. Policies are normally used with the systemwide bus; +they are analogous to a firewall in that they allow expected traffic +and prevent unexpected traffic. + +.PP +Currently, the system bus has a default-deny policy for sending method calls +and owning bus names. Everything else, in particular reply messages, receive +checks, and signals has a default allow policy. + +.PP +In general, it is best to keep system services as small, targeted programs which +run in their own process and provide a single bus name. Then, all that is needed +is an <allow> rule for the "own" permission to let the process claim the bus +name, and a "send_destination" rule to allow traffic from some or all uids to +your service. + +.PP +The <policy> element has one of four attributes: +daemon.1.in +.nf + context="(default|mandatory)" + at_console="(true|false)" + user="username or userid" + group="group name or gid" +.fi + +.PP +Policies are applied to a connection as follows: +.nf + - all context="default" policies are applied + - all group="connection's user's group" policies are applied + in undefined order + - all user="connection's auth user" policies are applied + in undefined order + - all at_console="true" policies are applied + - all at_console="false" policies are applied + - all context="mandatory" policies are applied +.fi + +.PP +Policies applied later will override those applied earlier, +when the policies overlap. Multiple policies with the same +user/group/context are applied in the order they appear +in the config file. + +.TP +.I "<deny>" +.I "<allow>" + +.PP +A <deny> element appears below a <policy> element and prohibits some +action. The <allow> element makes an exception to previous <deny> +statements, and works just like <deny> but with the inverse meaning. + +.PP +The possible attributes of these elements are: +.nf + send_interface="interface_name" + send_member="method_or_signal_name" + send_error="error_name" + send_destination="name" + send_type="method_call" | "method_return" | "signal" | "error" + send_path="/path/name" + + receive_interface="interface_name" + receive_member="method_or_signal_name" + receive_error="error_name" + receive_sender="name" + receive_type="method_call" | "method_return" | "signal" | "error" + receive_path="/path/name" + + send_requested_reply="true" | "false" + receive_requested_reply="true" | "false" + + eavesdrop="true" | "false" + + own="name" + user="username" + group="groupname" +.fi + +.PP +Examples: +.nf + <deny send_interface="org.freedesktop.System" send_member="Reboot"/> + <deny receive_interface="org.freedesktop.System" receive_member="Reboot"/> + <deny own="org.freedesktop.System"/> + <deny send_destination="org.freedesktop.System"/> + <deny receive_sender="org.freedesktop.System"/> + <deny user="john"/> + <deny group="enemies"/> +.fi + +.PP +The <deny> element's attributes determine whether the deny "matches" a +particular action. If it matches, the action is denied (unless later +rules in the config file allow it). + +.PP +send_destination and receive_sender rules mean that messages may not be +sent to or received from the *owner* of the given name, not that +they may not be sent *to that name*. That is, if a connection +owns services A, B, C, and sending to A is denied, sending to B or C +will not work either. + +.PP +The other send_* and receive_* attributes are purely textual/by-value +matches against the given field in the message header. + +.PP +"Eavesdropping" occurs when an application receives a message that +was explicitly addressed to a name the application does not own, or +is a reply to such a message. Eavesdropping thus only applies to +messages that are addressed to services and replies to such messages +(i.e. it does not apply to signals). + +.PP +For <allow>, eavesdrop="true" indicates that the rule matches even +when eavesdropping. eavesdrop="false" is the default and means that +the rule only allows messages to go to their specified recipient. +For <deny>, eavesdrop="true" indicates that the rule matches +only when eavesdropping. eavesdrop="false" is the default for <deny> +also, but here it means that the rule applies always, even when +not eavesdropping. The eavesdrop attribute can only be combined with +send and receive rules (with send_* and receive_* attributes). + + +.PP +The [send|receive]_requested_reply attribute works similarly to the eavesdrop +attribute. It controls whether the <deny> or <allow> matches a reply +that is expected (corresponds to a previous method call message). +This attribute only makes sense for reply messages (errors and method +returns), and is ignored for other message types. + +.PP +For <allow>, [send|receive]_requested_reply="true" is the default and indicates that +only requested replies are allowed by the +rule. [send|receive]_requested_reply="false" means that the rule allows any reply +even if unexpected. + +.PP +For <deny>, [send|receive]_requested_reply="false" is the default but indicates that +the rule matches only when the reply was not +requested. [send|receive]_requested_reply="true" indicates that the rule applies +always, regardless of pending reply state. + +.PP +user and group denials mean that the given user or group may +not connect to the message bus. + +.PP +For "name", "username", "groupname", etc. +the character "*" can be substituted, meaning "any." Complex globs +like "foo.bar.*" aren't allowed for now because they'd be work to +implement and maybe encourage sloppy security anyway. + +.PP +It does not make sense to deny a user or group inside a <policy> +for a user or group; user/group denials can only be inside +context="default" or context="mandatory" policies. + +.PP +A single <deny> rule may specify combinations of attributes such as +send_destination and send_interface and send_type. In this case, the +denial applies only if both attributes match the message being denied. +e.g. <deny send_interface="foo.bar" send_destination="foo.blah"/> would +deny messages with the given interface AND the given bus name. +To get an OR effect you specify multiple <deny> rules. + +.PP +You can't include both send_ and receive_ attributes on the same +rule, since "whether the message can be sent" and "whether it can be +received" are evaluated separately. + +.PP +Be careful with send_interface/receive_interface, because the +interface field in messages is optional. In particular, do NOT +specify <deny send_interface="org.foo.Bar"/>! This will cause +no-interface messages to be blocked for all services, which is +almost certainly not what you intended. Always use rules of +the form: <deny send_interface="org.foo.Bar" send_destination="org.foo.Service"/> + +.TP +.I "<selinux>" + +.PP +The <selinux> element contains settings related to Security Enhanced Linux. +More details below. + +.TP +.I "<associate>" + +.PP +An <associate> element appears below an <selinux> element and +creates a mapping. Right now only one kind of association is possible: +.nf + <associate own="org.freedesktop.Foobar" context="foo_t"/> +.fi + +.PP +This means that if a connection asks to own the name +"org.freedesktop.Foobar" then the source context will be the context +of the connection and the target context will be "foo_t" - see the +short discussion of SELinux below. + +.PP +Note, the context here is the target context when requesting a name, +NOT the context of the connection owning the name. + +.PP +There's currently no way to set a default for owning any name, if +we add this syntax it will look like: +.nf + <associate own="*" context="foo_t"/> +.fi +If you find a reason this is useful, let the developers know. +Right now the default will be the security context of the bus itself. + +.PP +If two <associate> elements specify the same name, the element +appearing later in the configuration file will be used. + +.SH SELinux + +.PP +See http://www.nsa.gov/selinux/ for full details on SELinux. Some useful excerpts: + +.IP "" 8 +Every subject (process) and object (e.g. file, socket, IPC object, +etc) in the system is assigned a collection of security attributes, +known as a security context. A security context contains all of the +security attributes associated with a particular subject or object +that are relevant to the security policy. + +.IP "" 8 +In order to better encapsulate security contexts and to provide +greater efficiency, the policy enforcement code of SELinux typically +handles security identifiers (SIDs) rather than security contexts. A +SID is an integer that is mapped by the security server to a security +context at runtime. + +.IP "" 8 +When a security decision is required, the policy enforcement code +passes a pair of SIDs (typically the SID of a subject and the SID of +an object, but sometimes a pair of subject SIDs or a pair of object +SIDs), and an object security class to the security server. The object +security class indicates the kind of object, e.g. a process, a regular +file, a directory, a TCP socket, etc. + +.IP "" 8 +Access decisions specify whether or not a permission is granted for a +given pair of SIDs and class. Each object class has a set of +associated permissions defined to control operations on objects with +that class. + +.PP +D-Bus performs SELinux security checks in two places. + +.PP +First, any time a message is routed from one connection to another +connection, the bus daemon will check permissions with the security context of +the first connection as source, security context of the second connection +as target, object class "dbus" and requested permission "send_msg". + +.PP +If a security context is not available for a connection +(impossible when using UNIX domain sockets), then the target +context used is the context of the bus daemon itself. +There is currently no way to change this default, because we're +assuming that only UNIX domain sockets will be used to +connect to the systemwide bus. If this changes, we'll +probably add a way to set the default connection context. + +.PP +Second, any time a connection asks to own a name, +the bus daemon will check permissions with the security +context of the connection as source, the security context specified +for the name in the config file as target, object +class "dbus" and requested permission "acquire_svc". + +.PP +The security context for a bus name is specified with the +<associate> element described earlier in this document. +If a name has no security context associated in the +configuration file, the security context of the bus daemon +itself will be used. + +.SH DEBUGGING + +.PP +If you're trying to figure out where your messages are going or why +you aren't getting messages, there are several things you can try. + +.PP +Remember that the system bus is heavily locked down and if you +haven't installed a security policy file to allow your message +through, it won't work. For the session bus, this is not a concern. + +.PP +The simplest way to figure out what's happening on the bus is to run +the \fIdbus-monitor\fP program, which comes with the D-Bus +package. You can also send test messages with \fIdbus-send\fP. These +programs have their own man pages. + +.PP +If you want to know what the daemon itself is doing, you might consider +running a separate copy of the daemon to test against. This will allow you +to put the daemon under a debugger, or run it with verbose output, without +messing up your real session and system daemons. + +.PP +To run a separate test copy of the daemon, for example you might open a terminal +and type: +.nf + DBUS_VERBOSE=1 dbus-daemon --session --print-address +.fi + +.PP +The test daemon address will be printed when the daemon starts. You will need +to copy-and-paste this address and use it as the value of the +DBUS_SESSION_BUS_ADDRESS environment variable when you launch the applications +you want to test. This will cause those applications to connect to your +test bus instead of the DBUS_SESSION_BUS_ADDRESS of your real session bus. + +.PP +DBUS_VERBOSE=1 will have NO EFFECT unless your copy of D-Bus +was compiled with verbose mode enabled. This is not recommended in +production builds due to performance impact. You may need to rebuild +D-Bus if your copy was not built with debugging in mind. (DBUS_VERBOSE +also affects the D-Bus library and thus applications using D-Bus; it may +be useful to see verbose output on both the client side and from the daemon.) + +.PP +If you want to get fancy, you can create a custom bus +configuration for your test bus (see the session.conf and system.conf +files that define the two default configurations for example). This +would allow you to specify a different directory for .service files, +for example. + + +.SH AUTHOR +See http://www.freedesktop.org/software/dbus/doc/AUTHORS + +.SH BUGS +Please send bug reports to the D-Bus mailing list or bug tracker, +see http://www.freedesktop.org/software/dbus/ diff --git a/bus/desktop-file.c b/bus/desktop-file.c new file mode 100644 index 00000000..754a83c3 --- /dev/null +++ b/bus/desktop-file.c @@ -0,0 +1,800 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* desktop-file.c .desktop file parser + * + * Copyright (C) 2003 CodeFactory AB + * Copyright (C) 2003 Red Hat Inc. + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#include <dbus/dbus-sysdeps.h> +#include <dbus/dbus-internals.h> +#include "desktop-file.h" +#include "utils.h" + +typedef struct +{ + char *key; + char *value; +} BusDesktopFileLine; + +typedef struct +{ + char *section_name; + + int n_lines; + BusDesktopFileLine *lines; + int n_allocated_lines; +} BusDesktopFileSection; + +struct BusDesktopFile +{ + int n_sections; + BusDesktopFileSection *sections; + int n_allocated_sections; +}; + +/** + * Parser for service files. + */ +typedef struct +{ + DBusString data; /**< The data from the file */ + + BusDesktopFile *desktop_file; /**< The resulting object */ + int current_section; /**< The current section being parsed */ + + int pos; /**< Current position */ + int len; /**< Length */ + int line_num; /**< Current line number */ + +} BusDesktopFileParser; + +#define VALID_KEY_CHAR 1 +#define VALID_LOCALE_CHAR 2 +static unsigned char valid[256] = { + 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , + 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , + 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x3 , 0x2 , 0x0 , + 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , + 0x0 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , + 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x0 , 0x0 , 0x0 , 0x0 , 0x2 , + 0x0 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , + 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , + 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , + 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , + 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , + 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , + 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , + 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , + 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , + 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , +}; + +static void report_error (BusDesktopFileParser *parser, + char *message, + const char *error_name, + DBusError *error); + +static void +parser_free (BusDesktopFileParser *parser) +{ + bus_desktop_file_free (parser->desktop_file); + + _dbus_string_free (&parser->data); +} + +static void +bus_desktop_file_line_free (BusDesktopFileLine *line) +{ + dbus_free (line->key); + dbus_free (line->value); +} + +static void +bus_desktop_file_section_free (BusDesktopFileSection *section) +{ + int i; + + for (i = 0; i < section->n_lines; i++) + bus_desktop_file_line_free (§ion->lines[i]); + + dbus_free (section->lines); + dbus_free (section->section_name); +} + +void +bus_desktop_file_free (BusDesktopFile *desktop_file) +{ + int i; + + for (i = 0; i < desktop_file->n_sections; i++) + bus_desktop_file_section_free (&desktop_file->sections[i]); + dbus_free (desktop_file->sections); + + dbus_free (desktop_file); +} + +static dbus_bool_t +grow_lines_in_section (BusDesktopFileSection *section) +{ + BusDesktopFileLine *lines; + + int new_n_lines; + + if (section->n_allocated_lines == 0) + new_n_lines = 1; + else + new_n_lines = section->n_allocated_lines*2; + + lines = dbus_realloc (section->lines, + sizeof (BusDesktopFileLine) * new_n_lines); + + if (lines == NULL) + return FALSE; + + section->lines = lines; + section->n_allocated_lines = new_n_lines; + + return TRUE; +} + +static dbus_bool_t +grow_sections (BusDesktopFile *desktop_file) +{ + int new_n_sections; + BusDesktopFileSection *sections; + + if (desktop_file->n_allocated_sections == 0) + new_n_sections = 1; + else + new_n_sections = desktop_file->n_allocated_sections*2; + + sections = dbus_realloc (desktop_file->sections, + sizeof (BusDesktopFileSection) * new_n_sections); + if (sections == NULL) + return FALSE; + + desktop_file->sections = sections; + + desktop_file->n_allocated_sections = new_n_sections; + + return TRUE; +} + +static char * +unescape_string (BusDesktopFileParser *parser, + const DBusString *str, + int pos, + int end_pos, + DBusError *error) +{ + char *retval, *q; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + /* len + 1 is enough, because unescaping never makes the + * string longer + */ + retval = dbus_malloc (end_pos - pos + 1); + if (retval == NULL) + { + BUS_SET_OOM (error); + return NULL; + } + + q = retval; + + while (pos < end_pos) + { + if (_dbus_string_get_byte (str, pos) == 0) + { + /* Found an embedded null */ + dbus_free (retval); + report_error (parser, "Text to be unescaped contains embedded nul", + BUS_DESKTOP_PARSE_ERROR_INVALID_ESCAPES, error); + return NULL; + } + + if (_dbus_string_get_byte (str, pos) == '\\') + { + pos ++; + + if (pos >= end_pos) + { + /* Escape at end of string */ + dbus_free (retval); + report_error (parser, "Text to be unescaped ended in \\", + BUS_DESKTOP_PARSE_ERROR_INVALID_ESCAPES, error); + return NULL; + } + + switch (_dbus_string_get_byte (str, pos)) + { + case 's': + *q++ = ' '; + break; + case 't': + *q++ = '\t'; + break; + case 'n': + *q++ = '\n'; + break; + case 'r': + *q++ = '\r'; + break; + case '\\': + *q++ = '\\'; + break; + default: + /* Invalid escape code */ + dbus_free (retval); + report_error (parser, "Text to be unescaped had invalid escape sequence", + BUS_DESKTOP_PARSE_ERROR_INVALID_ESCAPES, error); + return NULL; + } + pos++; + } + else + { + *q++ =_dbus_string_get_byte (str, pos); + + pos++; + } + } + + *q = 0; + + return retval; +} + +static BusDesktopFileSection* +new_section (BusDesktopFile *desktop_file, + const char *name) +{ + int n; + char *name_copy; + + if (desktop_file->n_allocated_sections == desktop_file->n_sections) + { + if (!grow_sections (desktop_file)) + return NULL; + } + + name_copy = _dbus_strdup (name); + if (name_copy == NULL) + return NULL; + + n = desktop_file->n_sections; + desktop_file->sections[n].section_name = name_copy; + + desktop_file->sections[n].n_lines = 0; + desktop_file->sections[n].lines = NULL; + desktop_file->sections[n].n_allocated_lines = 0; + + if (!grow_lines_in_section (&desktop_file->sections[n])) + { + dbus_free (desktop_file->sections[n].section_name); + desktop_file->sections[n].section_name = NULL; + return NULL; + } + + desktop_file->n_sections += 1; + + return &desktop_file->sections[n]; +} + +static BusDesktopFileSection* +open_section (BusDesktopFileParser *parser, + char *name) +{ + BusDesktopFileSection *section; + + section = new_section (parser->desktop_file, name); + if (section == NULL) + return NULL; + + parser->current_section = parser->desktop_file->n_sections - 1; + _dbus_assert (&parser->desktop_file->sections[parser->current_section] == section); + + return section; +} + +static BusDesktopFileLine * +new_line (BusDesktopFileParser *parser) +{ + BusDesktopFileSection *section; + BusDesktopFileLine *line; + + section = &parser->desktop_file->sections[parser->current_section]; + + if (section->n_allocated_lines == section->n_lines) + { + if (!grow_lines_in_section (section)) + return NULL; + } + + line = §ion->lines[section->n_lines++]; + + memset (line, 0, sizeof (BusDesktopFileLine)); + + return line; +} + +static dbus_bool_t +is_blank_line (BusDesktopFileParser *parser) +{ + int p; + char c; + + p = parser->pos; + + c = _dbus_string_get_byte (&parser->data, p); + + while (c && c != '\n') + { + if (!(c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f')) + return FALSE; + + p++; + c = _dbus_string_get_byte (&parser->data, p); + } + + return TRUE; +} + +static void +parse_comment_or_blank (BusDesktopFileParser *parser) +{ + int line_end, eol_len; + + if (!_dbus_string_find_eol (&parser->data, parser->pos, &line_end, &eol_len)) + line_end = parser->len; + + if (line_end == parser->len) + parser->pos = parser->len; + else + parser->pos = line_end + eol_len; + + parser->line_num += 1; +} + +static dbus_bool_t +is_valid_section_name (const char *name) +{ + /* 5. Group names may contain all ASCII characters except for control characters and '[' and ']'. */ + + while (*name) + { + if (!((*name >= 'A' && *name <= 'Z') || (*name >= 'a' || *name <= 'z') || + *name == '\n' || *name == '\t')) + return FALSE; + + name++; + } + + return TRUE; +} + +static dbus_bool_t +parse_section_start (BusDesktopFileParser *parser, DBusError *error) +{ + int line_end, eol_len; + char *section_name; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + if (!_dbus_string_find_eol (&parser->data, parser->pos, &line_end, &eol_len)) + line_end = parser->len; + + if (line_end - parser->pos <= 2 || + _dbus_string_get_byte (&parser->data, line_end - 1) != ']') + { + report_error (parser, "Invalid syntax for section header", BUS_DESKTOP_PARSE_ERROR_INVALID_SYNTAX, error); + parser_free (parser); + return FALSE; + } + + section_name = unescape_string (parser, + &parser->data, parser->pos + 1, line_end - 1, + error); + + if (section_name == NULL) + { + parser_free (parser); + return FALSE; + } + + if (!is_valid_section_name (section_name)) + { + report_error (parser, "Invalid characters in section name", BUS_DESKTOP_PARSE_ERROR_INVALID_CHARS, error); + parser_free (parser); + dbus_free (section_name); + return FALSE; + } + + if (open_section (parser, section_name) == NULL) + { + dbus_free (section_name); + parser_free (parser); + BUS_SET_OOM (error); + return FALSE; + } + + if (line_end == parser->len) + parser->pos = parser->len; + else + parser->pos = line_end + eol_len; + + parser->line_num += 1; + + dbus_free (section_name); + + return TRUE; +} + +static dbus_bool_t +parse_key_value (BusDesktopFileParser *parser, DBusError *error) +{ + int line_end, eol_len; + int key_start, key_end; + int value_start; + int p; + char *value, *tmp; + DBusString key; + BusDesktopFileLine *line; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + if (!_dbus_string_find_eol (&parser->data, parser->pos, &line_end, &eol_len)) + line_end = parser->len; + + p = parser->pos; + key_start = p; + while (p < line_end && + (valid[_dbus_string_get_byte (&parser->data, p)] & VALID_KEY_CHAR)) + p++; + key_end = p; + + if (key_start == key_end) + { + report_error (parser, "Empty key name", BUS_DESKTOP_PARSE_ERROR_INVALID_SYNTAX, error); + parser_free (parser); + return FALSE; + } + + /* We ignore locales for now */ + if (p < line_end && _dbus_string_get_byte (&parser->data, p) == '[') + { + if (line_end == parser->len) + parser->pos = parser->len; + else + parser->pos = line_end + eol_len; + + parser->line_num += 1; + + return TRUE; + } + + /* Skip space before '=' */ + while (p < line_end && _dbus_string_get_byte (&parser->data, p) == ' ') + p++; + + if (p < line_end && _dbus_string_get_byte (&parser->data, p) != '=') + { + report_error (parser, "Invalid characters in key name", BUS_DESKTOP_PARSE_ERROR_INVALID_CHARS, error); + parser_free (parser); + return FALSE; + } + + if (p == line_end) + { + report_error (parser, "No '=' in key/value pair", BUS_DESKTOP_PARSE_ERROR_INVALID_SYNTAX, error); + parser_free (parser); + return FALSE; + } + + /* Skip the '=' */ + p++; + + /* Skip space after '=' */ + while (p < line_end && _dbus_string_get_byte (&parser->data, p) == ' ') + p++; + + value_start = p; + + value = unescape_string (parser, &parser->data, value_start, line_end, error); + if (value == NULL) + { + parser_free (parser); + return FALSE; + } + + line = new_line (parser); + if (line == NULL) + { + dbus_free (value); + parser_free (parser); + BUS_SET_OOM (error); + return FALSE; + } + + if (!_dbus_string_init (&key)) + { + dbus_free (value); + parser_free (parser); + BUS_SET_OOM (error); + return FALSE; + } + + if (!_dbus_string_copy_len (&parser->data, key_start, key_end - key_start, + &key, 0)) + { + _dbus_string_free (&key); + dbus_free (value); + parser_free (parser); + BUS_SET_OOM (error); + return FALSE; + } + + if (!_dbus_string_steal_data (&key, &tmp)) + { + _dbus_string_free (&key); + dbus_free (value); + parser_free (parser); + BUS_SET_OOM (error); + return FALSE; + } + + _dbus_string_free (&key); + + line->key = tmp; + line->value = value; + + if (line_end == parser->len) + parser->pos = parser->len; + else + parser->pos = line_end + eol_len; + + parser->line_num += 1; + + return TRUE; +} + +static void +report_error (BusDesktopFileParser *parser, + char *message, + const char *error_name, + DBusError *error) +{ + const char *section_name = NULL; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + if (parser->current_section != -1) + section_name = parser->desktop_file->sections[parser->current_section].section_name; + + if (section_name) + dbus_set_error (error, error_name, + "Error in section %s at line %d: %s\n", section_name, parser->line_num, message); + else + dbus_set_error (error, error_name, + "Error at line %d: %s\n", parser->line_num, message); +} + +#if 0 +static void +dump_desktop_file (BusDesktopFile *file) +{ + int i; + + for (i = 0; i < file->n_sections; i++) + { + int j; + + printf ("[%s]\n", file->sections[i].section_name); + + for (j = 0; j < file->sections[i].n_lines; j++) + { + printf ("%s=%s\n", file->sections[i].lines[j].key, + file->sections[i].lines[j].value); + } + } +} +#endif + +BusDesktopFile* +bus_desktop_file_load (DBusString *filename, + DBusError *error) +{ + DBusString str; + BusDesktopFileParser parser; + DBusStat sb; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + /* Clearly there's a race here, but it's just to make it unlikely + * that we do something silly, we still handle doing it below. + */ + if (!_dbus_stat (filename, &sb, error)) + return NULL; + + if (sb.size > _DBUS_ONE_KILOBYTE * 128) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "Desktop file size (%ld bytes) is too large", (long) sb.size); + return NULL; + } + + if (!_dbus_string_init (&str)) + { + BUS_SET_OOM (error); + return NULL; + } + + if (!_dbus_file_get_contents (&str, filename, error)) + { + _dbus_string_free (&str); + return NULL; + } + + if (!_dbus_string_validate_utf8 (&str, 0, _dbus_string_get_length (&str))) + { + _dbus_string_free (&str); + dbus_set_error (error, DBUS_ERROR_FAILED, + "invalid UTF-8"); + return NULL; + } + + parser.desktop_file = dbus_new0 (BusDesktopFile, 1); + if (parser.desktop_file == NULL) + { + _dbus_string_free (&str); + BUS_SET_OOM (error); + return NULL; + } + + parser.data = str; + parser.line_num = 1; + parser.pos = 0; + parser.len = _dbus_string_get_length (&parser.data); + parser.current_section = -1; + + while (parser.pos < parser.len) + { + if (_dbus_string_get_byte (&parser.data, parser.pos) == '[') + { + if (!parse_section_start (&parser, error)) + { + return NULL; + } + } + else if (is_blank_line (&parser) || + _dbus_string_get_byte (&parser.data, parser.pos) == '#') + parse_comment_or_blank (&parser); + else + { + if (!parse_key_value (&parser, error)) + { + return NULL; + } + } + } + + _dbus_string_free (&parser.data); + + return parser.desktop_file; +} + +static BusDesktopFileSection * +lookup_section (BusDesktopFile *desktop_file, + const char *section_name) +{ + BusDesktopFileSection *section; + int i; + + if (section_name == NULL) + return NULL; + + for (i = 0; i < desktop_file->n_sections; i ++) + { + section = &desktop_file->sections[i]; + + if (strcmp (section->section_name, section_name) == 0) + return section; + } + + return NULL; +} + +static BusDesktopFileLine * +lookup_line (BusDesktopFile *desktop_file, + BusDesktopFileSection *section, + const char *keyname) +{ + BusDesktopFileLine *line; + int i; + + for (i = 0; i < section->n_lines; i++) + { + line = §ion->lines[i]; + + if (strcmp (line->key, keyname) == 0) + return line; + } + + return NULL; +} + +dbus_bool_t +bus_desktop_file_get_raw (BusDesktopFile *desktop_file, + const char *section_name, + const char *keyname, + const char **val) +{ + BusDesktopFileSection *section; + BusDesktopFileLine *line; + + *val = NULL; + + section = lookup_section (desktop_file, section_name); + + if (!section) + return FALSE; + + line = lookup_line (desktop_file, + section, + keyname); + + if (!line) + return FALSE; + + *val = line->value; + + return TRUE; +} + +dbus_bool_t +bus_desktop_file_get_string (BusDesktopFile *desktop_file, + const char *section, + const char *keyname, + char **val, + DBusError *error) +{ + const char *raw; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + *val = NULL; + + if (!bus_desktop_file_get_raw (desktop_file, section, keyname, &raw)) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "No \"%s\" key in .service file\n", keyname); + return FALSE; + } + + *val = _dbus_strdup (raw); + + if (*val == NULL) + { + BUS_SET_OOM (error); + return FALSE; + } + + return TRUE; +} diff --git a/bus/desktop-file.h b/bus/desktop-file.h new file mode 100644 index 00000000..7f43458a --- /dev/null +++ b/bus/desktop-file.h @@ -0,0 +1,56 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* desktop-file.h .desktop file parser + * + * Copyright (C) 2003 CodeFactory AB + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#ifndef BUS_DESKTOP_FILE_H +#define BUS_DESKTOP_FILE_H + +#include <dbus/dbus.h> +#include <dbus/dbus-string.h> + +#define BUS_DESKTOP_PARSE_ERROR_INVALID_SYNTAX "org.freedesktop.DBus.DesktopParseError.InvalidSyntax" +#define BUS_DESKTOP_PARSE_ERROR_INVALID_ESCAPES "org.freedesktop.DBus.DesktopParseError.InvalidEscapes" +#define BUS_DESKTOP_PARSE_ERROR_INVALID_CHARS "org.freedesktop.DBus.DesktopParseError.InvalidChars" + +#define DBUS_SERVICE_SECTION "D-BUS Service" +#define DBUS_SERVICE_NAME "Name" +#define DBUS_SERVICE_EXEC "Exec" +#define DBUS_SERVICE_USER "User" +#define DBUS_SERVICE_GROUP "Group" + +typedef struct BusDesktopFile BusDesktopFile; + +BusDesktopFile *bus_desktop_file_load (DBusString *filename, + DBusError *error); +void bus_desktop_file_free (BusDesktopFile *file); + +dbus_bool_t bus_desktop_file_get_raw (BusDesktopFile *desktop_file, + const char *section_name, + const char *keyname, + const char **val); +dbus_bool_t bus_desktop_file_get_string (BusDesktopFile *desktop_file, + const char *section, + const char *keyname, + char **val, + DBusError *error); + + +#endif /* BUS_DESKTOP_FILE_H */ diff --git a/bus/dir-watch-default.c b/bus/dir-watch-default.c new file mode 100644 index 00000000..8e457eb6 --- /dev/null +++ b/bus/dir-watch-default.c @@ -0,0 +1,40 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dir-watch-default.c OS specific directory change notification for message bus + * + * Copyright (C) 2003 Red Hat, Inc. + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> + +#include <dbus/dbus-internals.h> +#include "dir-watch.h" + + +/* NoOp */ + +void +bus_watch_directory (const char *dir, BusContext *context) +{ +} + +void +bus_set_watched_dirs (BusContext *context, DBusList **directories) +{ +} diff --git a/bus/dir-watch-dnotify.c b/bus/dir-watch-dnotify.c new file mode 100644 index 00000000..b38d7d19 --- /dev/null +++ b/bus/dir-watch-dnotify.c @@ -0,0 +1,93 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dir-watch-dnotify.c OS specific directory change notification for message bus + * + * Copyright (C) 2003 Red Hat, Inc. + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> + +#define _GNU_SOURCE +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif + +#include <dbus/dbus-internals.h> +#include "dir-watch.h" + +#define MAX_DIRS_TO_WATCH 128 + +/* use a static array to avoid handling OOM */ +static int fds[MAX_DIRS_TO_WATCH]; +static int num_fds = 0; + +void +bus_watch_directory (const char *dir, BusContext *context) +{ + int fd; + + _dbus_assert (dir != NULL); + + if (num_fds >= MAX_DIRS_TO_WATCH ) + { + _dbus_warn ("Cannot watch config directory '%s'. Already watching %d directories\n", dir, MAX_DIRS_TO_WATCH); + goto out; + } + + fd = open (dir, O_RDONLY); + if (fd < 0) + { + _dbus_warn ("Cannot open directory '%s'; error '%s'\n", dir, _dbus_strerror (errno)); + goto out; + } + + if (fcntl (fd, F_NOTIFY, DN_CREATE|DN_DELETE|DN_RENAME|DN_MODIFY) == -1) + { + _dbus_warn ("Cannot setup D_NOTIFY for '%s' error '%s'\n", dir, _dbus_strerror (errno)); + close (fd); + goto out; + } + + fds[num_fds++] = fd; + _dbus_verbose ("Added watch on config directory '%s'\n", dir); + + out: + ; +} + +void +bus_drop_all_directory_watches (void) +{ + int i; + + _dbus_verbose ("Dropping all watches on config directories\n"); + + for (i = 0; i < num_fds; i++) + { + if (close (fds[i]) != 0) + { + _dbus_verbose ("Error closing fd %d for config directory watch\n", fds[i]); + } + } + + num_fds = 0; +} diff --git a/bus/dir-watch-inotify.c b/bus/dir-watch-inotify.c new file mode 100644 index 00000000..094993bb --- /dev/null +++ b/bus/dir-watch-inotify.c @@ -0,0 +1,279 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* dir-watch-inotify.c OS specific directory change notification for message bus + * + * Copyright (C) 2003 Red Hat, Inc. + * (c) 2006 Mandriva + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> + +#define _GNU_SOURCE +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/inotify.h> +#include <sys/types.h> +#include <signal.h> +#include <errno.h> + +#include <dbus/dbus-internals.h> +#include <dbus/dbus-list.h> +#include <dbus/dbus-watch.h> +#include "dir-watch.h" + +#define MAX_DIRS_TO_WATCH 128 +#define INOTIFY_EVENT_SIZE (sizeof(struct inotify_event)) +#define INOTIFY_BUF_LEN (1024 * (INOTIFY_EVENT_SIZE + 16)) + +/* use a static array to avoid handling OOM */ +static int wds[MAX_DIRS_TO_WATCH]; +static char *dirs[MAX_DIRS_TO_WATCH]; +static int num_wds = 0; +static int inotify_fd = -1; +static DBusWatch *watch = NULL; +static DBusLoop *loop = NULL; + +static dbus_bool_t +_inotify_watch_callback (DBusWatch *watch, unsigned int condition, void *data) +{ + return dbus_watch_handle (watch, condition); +} + +static dbus_bool_t +_handle_inotify_watch (DBusWatch *passed_watch, unsigned int flags, void *data) +{ + char buffer[INOTIFY_BUF_LEN]; + ssize_t ret = 0; + int i = 0; + pid_t pid; + dbus_bool_t have_change = FALSE; + + ret = read (inotify_fd, buffer, INOTIFY_BUF_LEN); + if (ret < 0) + _dbus_verbose ("Error reading inotify event: '%s'\n", _dbus_strerror(errno)); + else if (!ret) + _dbus_verbose ("Error reading inotify event: buffer too small\n"); + + while (i < ret) + { + struct inotify_event *ev; + pid = _dbus_getpid (); + + ev = (struct inotify_event *) &buffer[i]; + i += INOTIFY_EVENT_SIZE + ev->len; +#ifdef DBUS_ENABLE_VERBOSE_MODE + if (ev->len) + _dbus_verbose ("event name: '%s'\n", ev->name); + _dbus_verbose ("inotify event: wd=%d mask=%u cookie=%u len=%u\n", ev->wd, ev->mask, ev->cookie, ev->len); +#endif + _dbus_verbose ("Sending SIGHUP signal on reception of a inotify event\n"); + have_change = TRUE; + } + if (have_change) + (void) kill (pid, SIGHUP); + + return TRUE; +} + +#include <stdio.h> + +static void +_set_watched_dirs_internal (DBusList **directories) +{ + int new_wds[MAX_DIRS_TO_WATCH]; + char *new_dirs[MAX_DIRS_TO_WATCH]; + DBusList *link; + int i, j, wd; + + for (i = 0; i < MAX_DIRS_TO_WATCH; i++) + { + new_wds[i] = -1; + new_dirs[i] = NULL; + } + + i = 0; + link = _dbus_list_get_first_link (directories); + while (link != NULL) + { + new_dirs[i++] = (char *)link->data; + link = _dbus_list_get_next_link (directories, link); + } + + /* Look for directories in both the old and new sets, if + * we find one, move its data into the new set. + */ + for (i = 0; new_dirs[i]; i++) + { + for (j = 0; j < num_wds; j++) + { + if (dirs[j] && strcmp (new_dirs[i], dirs[j]) == 0) + { + new_wds[i] = wds[j]; + new_dirs[i] = dirs[j]; + wds[j] = -1; + dirs[j] = NULL; + break; + } + } + } + + /* Any directories we find in "wds" with a nonzero fd must + * not be in the new set, so perform cleanup now. + */ + for (j = 0; j < num_wds; j++) + { + if (wds[j] != -1) + { + inotify_rm_watch (inotify_fd, wds[j]); + dbus_free (dirs[j]); + wds[j] = -1; + dirs[j] = NULL; + } + } + + for (i = 0; new_dirs[i]; i++) + { + if (new_wds[i] == -1) + { + /* FIXME - less lame error handling for failing to add a watch; we may need to sleep. */ + wd = inotify_add_watch (inotify_fd, new_dirs[i], IN_CLOSE_WRITE | IN_DELETE | IN_MOVED_TO | IN_MOVED_FROM); + if (wd < 0) + { + /* Not all service directories need to exist. */ + if (errno != ENOENT) + { + _dbus_warn ("Cannot setup inotify for '%s'; error '%s'\n", new_dirs[i], _dbus_strerror (errno)); + goto out; + } + else + { + new_wds[i] = -1; + new_dirs[i] = NULL; + continue; + } + } + new_wds[i] = wd; + new_dirs[i] = _dbus_strdup (new_dirs[i]); + if (!new_dirs[i]) + { + /* FIXME have less lame handling for OOM, we just silently fail to + * watch. (In reality though, the whole OOM handling in dbus is stupid + * but we won't go into that in this comment =) ) + */ + inotify_rm_watch (inotify_fd, wd); + new_wds[i] = -1; + } + } + } + + num_wds = i; + + for (i = 0; i < MAX_DIRS_TO_WATCH; i++) + { + wds[i] = new_wds[i]; + dirs[i] = new_dirs[i]; + } + + out:; +} + +#include <stdio.h> +static void +_shutdown_inotify (void *data) +{ + DBusList *empty = NULL; + + if (inotify_fd == -1) + return; + + _set_watched_dirs_internal (&empty); + + close (inotify_fd); + inotify_fd = -1; + if (watch != NULL) + { + _dbus_loop_remove_watch (loop, watch, _inotify_watch_callback, NULL); + _dbus_watch_unref (watch); + _dbus_loop_unref (loop); + } + watch = NULL; + loop = NULL; +} + +static int +_init_inotify (BusContext *context) +{ + int ret = 0; + + if (inotify_fd == -1) + { +#ifdef HAVE_INOTIFY_INIT1 + inotify_fd = inotify_init1 (IN_CLOEXEC); + /* This ensures we still run on older Linux kernels. + * https://bugs.freedesktop.org/show_bug.cgi?id=23957 + */ + if (inotify_fd < 0) + inotify_fd = inotify_init (); +#else + inotify_fd = inotify_init (); +#endif + if (inotify_fd <= 0) + { + _dbus_warn ("Cannot initialize inotify\n"); + goto out; + } + loop = bus_context_get_loop (context); + _dbus_loop_ref (loop); + + watch = _dbus_watch_new (inotify_fd, DBUS_WATCH_READABLE, TRUE, + _handle_inotify_watch, NULL, NULL); + + if (watch == NULL) + { + _dbus_warn ("Unable to create inotify watch\n"); + goto out; + } + + if (!_dbus_loop_add_watch (loop, watch, _inotify_watch_callback, + NULL, NULL)) + { + _dbus_warn ("Unable to add reload watch to main loop"); + _dbus_watch_unref (watch); + watch = NULL; + goto out; + } + + _dbus_register_shutdown_func (_shutdown_inotify, NULL); + } + + ret = 1; + +out: + return ret; +} + +void +bus_set_watched_dirs (BusContext *context, DBusList **directories) +{ + if (!_init_inotify (context)) + return; + + _set_watched_dirs_internal (directories); +} diff --git a/bus/dir-watch-kqueue.c b/bus/dir-watch-kqueue.c new file mode 100644 index 00000000..4a01b748 --- /dev/null +++ b/bus/dir-watch-kqueue.c @@ -0,0 +1,255 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dir-watch-kqueue.c OS specific directory change notification for message bus + * + * Copyright (C) 2003 Red Hat, Inc. + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> + +#include <sys/types.h> +#include <sys/event.h> +#include <sys/time.h> +#include <signal.h> +#include <fcntl.h> +#include <unistd.h> +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif + +#include "bus.h" +#include <dbus/dbus-watch.h> + +#include <dbus/dbus-internals.h> +#include <dbus/dbus-list.h> +#include "dir-watch.h" + +#define MAX_DIRS_TO_WATCH 128 + +static int kq = -1; +static int fds[MAX_DIRS_TO_WATCH]; +static char *dirs[MAX_DIRS_TO_WATCH]; +static int num_fds = 0; +static DBusWatch *watch = NULL; +static DBusLoop *loop = NULL; + +static dbus_bool_t +_kqueue_watch_callback (DBusWatch *watch, unsigned int condition, void *data) +{ + return dbus_watch_handle (watch, condition); +} + +static dbus_bool_t +_handle_kqueue_watch (DBusWatch *watch, unsigned int flags, void *data) +{ + struct kevent ev; + struct timespec nullts = { 0, 0 }; + int res; + pid_t pid; + + res = kevent (kq, NULL, 0, &ev, 1, &nullts); + + /* Sleep for half a second to avoid a race when files are install(1)'d + * to system.d. */ + usleep(500000); + + if (res > 0) + { + pid = getpid (); + _dbus_verbose ("Sending SIGHUP signal on reception of a kevent\n"); + (void) kill (pid, SIGHUP); + } + else if (res < 0 && errno == EBADF) + { + kq = -1; + if (watch != NULL) + { + _dbus_loop_remove_watch (loop, watch, _kqueue_watch_callback, NULL); + _dbus_watch_unref (watch); + watch = NULL; + } + pid = getpid (); + _dbus_verbose ("Sending SIGHUP signal since kqueue has been closed\n"); + (void) kill (pid, SIGHUP); + } + + return TRUE; +} + +static int +_init_kqueue (BusContext *context) +{ + int ret = 0; + + if (kq < 0) + { + + kq = kqueue (); + if (kq < 0) + { + _dbus_warn ("Cannot create kqueue; error '%s'\n", _dbus_strerror (errno)); + goto out; + } + + loop = bus_context_get_loop (context); + + watch = _dbus_watch_new (kq, DBUS_WATCH_READABLE, TRUE, + _handle_kqueue_watch, NULL, NULL); + + if (watch == NULL) + { + _dbus_warn ("Unable to create kqueue watch\n"); + close (kq); + kq = -1; + goto out; + } + + if (!_dbus_loop_add_watch (loop, watch, _kqueue_watch_callback, + NULL, NULL)) + { + _dbus_warn ("Unable to add reload watch to main loop"); + close (kq); + kq = -1; + _dbus_watch_unref (watch); + watch = NULL; + goto out; + } + } + + ret = 1; + +out: + return ret; +} + +void +bus_set_watched_dirs (BusContext *context, DBusList **directories) +{ + int new_fds[MAX_DIRS_TO_WATCH]; + char *new_dirs[MAX_DIRS_TO_WATCH]; + DBusList *link; + int i, j, f, fd; + struct kevent ev; + + if (!_init_kqueue (context)) + goto out; + + for (i = 0; i < MAX_DIRS_TO_WATCH; i++) + { + new_fds[i] = -1; + new_dirs[i] = NULL; + } + + i = 0; + link = _dbus_list_get_first_link (directories); + while (link != NULL) + { + new_dirs[i++] = (char *)link->data; + link = _dbus_list_get_next_link (directories, link); + } + + /* Look for directories in both the old and new sets, if + * we find one, move its data into the new set. + */ + for (i = 0; new_dirs[i]; i++) + { + for (j = 0; i < num_fds; j++) + { + if (dirs[j] && strcmp (new_dirs[i], dirs[j]) == 0) + { + new_fds[i] = fds[j]; + new_dirs[i] = dirs[j]; + fds[j] = -1; + dirs[j] = NULL; + break; + } + } + } + + /* Any directory we find in "fds" with a nonzero fd must + * not be in the new set, so perform cleanup now. + */ + for (j = 0; j < num_fds; j++) + { + if (fds[j] != -1) + { + close (fds[j]); + dbus_free (dirs[j]); + fds[j] = -1; + dirs[j] = NULL; + } + } + + for (i = 0; new_dirs[i]; i++) + { + if (new_fds[i] == -1) + { + /* FIXME - less lame error handling for failing to add a watch; + * we may need to sleep. + */ + fd = open (new_dirs[i], O_RDONLY); + if (fd < 0) + { + if (errno != ENOENT) + { + _dbus_warn ("Cannot open directory '%s'; error '%s'\n", new_dirs[i], _dbus_strerror (errno)); + goto out; + } + else + { + new_fds[i] = -1; + new_dirs[i] = NULL; + continue; + } + } + + EV_SET (&ev, fd, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR, + NOTE_DELETE | NOTE_EXTEND | NOTE_WRITE | NOTE_RENAME, 0, 0); + if (kevent (kq, &ev, 1, NULL, 0, NULL) == -1) + { + _dbus_warn ("Cannot setup a kevent for '%s'; error '%s'\n", new_dirs[i], _dbus_strerror (errno)); + close (fd); + goto out; + } + + new_fds[i] = fd; + new_dirs[i] = _dbus_strdup (new_dirs[i]); + if (!new_dirs[i]) + { + /* FIXME have less lame handling for OOM, we just silently fail to + * watch. (In reality though, the whole OOM handling in dbus is + * stupid but we won't go into that in this comment =) ) + */ + close (fd); + new_fds[i] = -1; + } + } + } + + num_fds = i; + + for (i = 0; i < MAX_DIRS_TO_WATCH; i++) + { + fds[i] = new_fds[i]; + dirs[i] = new_dirs[i]; + } + + out: + ; +} diff --git a/bus/dir-watch.h b/bus/dir-watch.h new file mode 100644 index 00000000..b44529e5 --- /dev/null +++ b/bus/dir-watch.h @@ -0,0 +1,40 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dir-watch.h Watch directories + * + * Copyright (C) 2005 Red Hat, Inc. + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "bus.h" + +#ifndef DIR_WATCH_H +#define DIR_WATCH_H + +/** + * Update the set of directories to monitor for changes. The + * operating-system-specific implementation of this function should + * avoid creating a window where a directory in both the + * old and new set isn't monitored. + * + * @param context The bus context + * @param dirs List of strings which are directory paths + */ +void bus_set_watched_dirs (BusContext *context, DBusList **dirs); + +#endif /* DIR_WATCH_H */ diff --git a/bus/dispatch.c b/bus/dispatch.c new file mode 100644 index 00000000..ca55177b --- /dev/null +++ b/bus/dispatch.c @@ -0,0 +1,4725 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dispatch.c Message dispatcher + * + * Copyright (C) 2003 CodeFactory AB + * Copyright (C) 2003, 2004, 2005 Red Hat, Inc. + * Copyright (C) 2004 Imendio HB + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "dispatch.h" +#include "connection.h" +#include "driver.h" +#include "services.h" +#include "activation.h" +#include "utils.h" +#include "bus.h" +#include "signals.h" +#include "test.h" +#include <dbus/dbus-internals.h> +#include <string.h> + +static dbus_bool_t +send_one_message (DBusConnection *connection, + BusContext *context, + DBusConnection *sender, + DBusConnection *addressed_recipient, + DBusMessage *message, + BusTransaction *transaction, + DBusError *error) +{ + if (!bus_context_check_security_policy (context, transaction, + sender, + addressed_recipient, + connection, + message, + NULL)) + return TRUE; /* silently don't send it */ + + if (!bus_transaction_send (transaction, + connection, + message)) + { + BUS_SET_OOM (error); + return FALSE; + } + + return TRUE; +} + +dbus_bool_t +bus_dispatch_matches (BusTransaction *transaction, + DBusConnection *sender, + DBusConnection *addressed_recipient, + DBusMessage *message, + DBusError *error) +{ + DBusError tmp_error; + BusConnections *connections; + DBusList *recipients; + BusMatchmaker *matchmaker; + DBusList *link; + BusContext *context; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + /* sender and recipient can both be NULL for the bus driver, + * or for signals with no particular recipient + */ + + _dbus_assert (sender == NULL || bus_connection_is_active (sender)); + _dbus_assert (dbus_message_get_sender (message) != NULL); + + context = bus_transaction_get_context (transaction); + + /* First, send the message to the addressed_recipient, if there is one. */ + if (addressed_recipient != NULL) + { + if (!bus_context_check_security_policy (context, transaction, + sender, addressed_recipient, + addressed_recipient, + message, error)) + return FALSE; + + /* Dispatch the message */ + if (!bus_transaction_send (transaction, addressed_recipient, message)) + { + BUS_SET_OOM (error); + return FALSE; + } + } + + /* Now dispatch to others who look interested in this message */ + connections = bus_transaction_get_connections (transaction); + dbus_error_init (&tmp_error); + matchmaker = bus_context_get_matchmaker (context); + + recipients = NULL; + if (!bus_matchmaker_get_recipients (matchmaker, connections, + sender, addressed_recipient, message, + &recipients)) + { + BUS_SET_OOM (error); + return FALSE; + } + + link = _dbus_list_get_first_link (&recipients); + while (link != NULL) + { + DBusConnection *dest; + + dest = link->data; + + if (!send_one_message (dest, context, sender, addressed_recipient, + message, transaction, &tmp_error)) + break; + + link = _dbus_list_get_next_link (&recipients, link); + } + + _dbus_list_clear (&recipients); + + if (dbus_error_is_set (&tmp_error)) + { + dbus_move_error (&tmp_error, error); + return FALSE; + } + else + return TRUE; +} + +static DBusHandlerResult +bus_dispatch (DBusConnection *connection, + DBusMessage *message) +{ + const char *sender, *service_name; + DBusError error; + BusTransaction *transaction; + BusContext *context; + DBusHandlerResult result; + DBusConnection *addressed_recipient; + + result = DBUS_HANDLER_RESULT_HANDLED; + + transaction = NULL; + addressed_recipient = NULL; + dbus_error_init (&error); + + context = bus_connection_get_context (connection); + _dbus_assert (context != NULL); + + /* If we can't even allocate an OOM error, we just go to sleep + * until we can. + */ + while (!bus_connection_preallocate_oom_error (connection)) + _dbus_wait_for_memory (); + + /* Ref connection in case we disconnect it at some point in here */ + dbus_connection_ref (connection); + + service_name = dbus_message_get_destination (message); + +#ifdef DBUS_ENABLE_VERBOSE_MODE + { + const char *interface_name, *member_name, *error_name; + + interface_name = dbus_message_get_interface (message); + member_name = dbus_message_get_member (message); + error_name = dbus_message_get_error_name (message); + + _dbus_verbose ("DISPATCH: %s %s %s to %s\n", + interface_name ? interface_name : "(no interface)", + member_name ? member_name : "(no member)", + error_name ? error_name : "(no error name)", + service_name ? service_name : "peer"); + } +#endif /* DBUS_ENABLE_VERBOSE_MODE */ + + /* If service_name is NULL, if it's a signal we send it to all + * connections with a match rule. If it's not a signal, there + * are some special cases here but mostly we just bail out. + */ + if (service_name == NULL) + { + if (dbus_message_is_signal (message, + DBUS_INTERFACE_LOCAL, + "Disconnected")) + { + bus_connection_disconnected (connection); + goto out; + } + + if (dbus_message_get_type (message) != DBUS_MESSAGE_TYPE_SIGNAL) + { + /* DBusConnection also handles some of these automatically, we leave + * it to do so. + */ + result = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + goto out; + } + } + + /* Create our transaction */ + transaction = bus_transaction_new (context); + if (transaction == NULL) + { + BUS_SET_OOM (&error); + goto out; + } + + /* Assign a sender to the message */ + if (bus_connection_is_active (connection)) + { + sender = bus_connection_get_name (connection); + _dbus_assert (sender != NULL); + + if (!dbus_message_set_sender (message, sender)) + { + BUS_SET_OOM (&error); + goto out; + } + + /* We need to refetch the service name here, because + * dbus_message_set_sender can cause the header to be + * reallocated, and thus the service_name pointer will become + * invalid. + */ + service_name = dbus_message_get_destination (message); + } + + if (service_name && + strcmp (service_name, DBUS_SERVICE_DBUS) == 0) /* to bus driver */ + { + if (!bus_context_check_security_policy (context, transaction, + connection, NULL, NULL, message, &error)) + { + _dbus_verbose ("Security policy rejected message\n"); + goto out; + } + + _dbus_verbose ("Giving message to %s\n", DBUS_SERVICE_DBUS); + if (!bus_driver_handle_message (connection, transaction, message, &error)) + goto out; + } + else if (!bus_connection_is_active (connection)) /* clients must talk to bus driver first */ + { + _dbus_verbose ("Received message from non-registered client. Disconnecting.\n"); + dbus_connection_close (connection); + goto out; + } + else if (service_name != NULL) /* route to named service */ + { + DBusString service_string; + BusService *service; + BusRegistry *registry; + + _dbus_assert (service_name != NULL); + + registry = bus_connection_get_registry (connection); + + _dbus_string_init_const (&service_string, service_name); + service = bus_registry_lookup (registry, &service_string); + + if (service == NULL && dbus_message_get_auto_start (message)) + { + BusActivation *activation; + /* We can't do the security policy check here, since the addressed + * recipient service doesn't exist yet. We do it before sending the + * message after the service has been created. + */ + activation = bus_connection_get_activation (connection); + + if (!bus_activation_activate_service (activation, connection, transaction, TRUE, + message, service_name, &error)) + { + _DBUS_ASSERT_ERROR_IS_SET (&error); + _dbus_verbose ("bus_activation_activate_service() failed: %s\n", error.name); + goto out; + } + + goto out; + } + else if (service == NULL) + { + dbus_set_error (&error, + DBUS_ERROR_NAME_HAS_NO_OWNER, + "Name \"%s\" does not exist", + service_name); + goto out; + } + else + { + addressed_recipient = bus_service_get_primary_owners_connection (service); + _dbus_assert (addressed_recipient != NULL); + } + } + + /* Now send the message to its destination (or not, if + * addressed_recipient == NULL), and match it against other connections' + * match rules. + */ + if (!bus_dispatch_matches (transaction, connection, addressed_recipient, message, &error)) + goto out; + + out: + if (dbus_error_is_set (&error)) + { + if (!dbus_connection_get_is_connected (connection)) + { + /* If we disconnected it, we won't bother to send it any error + * messages. + */ + _dbus_verbose ("Not sending error to connection we disconnected\n"); + } + else if (dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY)) + { + bus_connection_send_oom_error (connection, message); + + /* cancel transaction due to OOM */ + if (transaction != NULL) + { + bus_transaction_cancel_and_free (transaction); + transaction = NULL; + } + } + else + { + /* Try to send the real error, if no mem to do that, send + * the OOM error + */ + _dbus_assert (transaction != NULL); + if (!bus_transaction_send_error_reply (transaction, connection, + &error, message)) + { + bus_connection_send_oom_error (connection, message); + + /* cancel transaction due to OOM */ + if (transaction != NULL) + { + bus_transaction_cancel_and_free (transaction); + transaction = NULL; + } + } + } + + + dbus_error_free (&error); + } + + if (transaction != NULL) + { + bus_transaction_execute_and_free (transaction); + } + + dbus_connection_unref (connection); + + return result; +} + +static DBusHandlerResult +bus_dispatch_message_filter (DBusConnection *connection, + DBusMessage *message, + void *user_data) +{ + return bus_dispatch (connection, message); +} + +dbus_bool_t +bus_dispatch_add_connection (DBusConnection *connection) +{ + if (!dbus_connection_add_filter (connection, + bus_dispatch_message_filter, + NULL, NULL)) + return FALSE; + + return TRUE; +} + +void +bus_dispatch_remove_connection (DBusConnection *connection) +{ + /* Here we tell the bus driver that we want to get off. */ + bus_driver_remove_connection (connection); + + dbus_connection_remove_filter (connection, + bus_dispatch_message_filter, + NULL); +} + +#ifdef DBUS_BUILD_TESTS + +#include <stdio.h> + +/* This is used to know whether we need to block in order to finish + * sending a message, or whether the initial dbus_connection_send() + * already flushed the queue. + */ +#define SEND_PENDING(connection) (dbus_connection_has_messages_to_send (connection)) + +typedef dbus_bool_t (* Check1Func) (BusContext *context); +typedef dbus_bool_t (* Check2Func) (BusContext *context, + DBusConnection *connection); + +static dbus_bool_t check_no_leftovers (BusContext *context); + +static void +block_connection_until_message_from_bus (BusContext *context, + DBusConnection *connection, + const char *what_is_expected) +{ + _dbus_verbose ("expecting: %s\n", what_is_expected); + + while (dbus_connection_get_dispatch_status (connection) == + DBUS_DISPATCH_COMPLETE && + dbus_connection_get_is_connected (connection)) + { + bus_test_run_bus_loop (context, TRUE); + bus_test_run_clients_loop (FALSE); + } +} + +static void +spin_connection_until_authenticated (BusContext *context, + DBusConnection *connection) +{ + _dbus_verbose ("Spinning to auth connection %p\n", connection); + while (!dbus_connection_get_is_authenticated (connection) && + dbus_connection_get_is_connected (connection)) + { + bus_test_run_bus_loop (context, FALSE); + bus_test_run_clients_loop (FALSE); + } + _dbus_verbose (" ... done spinning to auth connection %p\n", connection); +} + +/* compensate for fact that pop_message() can return #NULL due to OOM */ +static DBusMessage* +pop_message_waiting_for_memory (DBusConnection *connection) +{ + while (dbus_connection_get_dispatch_status (connection) == + DBUS_DISPATCH_NEED_MEMORY) + _dbus_wait_for_memory (); + + return dbus_connection_pop_message (connection); +} + +static DBusMessage* +borrow_message_waiting_for_memory (DBusConnection *connection) +{ + while (dbus_connection_get_dispatch_status (connection) == + DBUS_DISPATCH_NEED_MEMORY) + _dbus_wait_for_memory (); + + return dbus_connection_borrow_message (connection); +} + +static void +warn_unexpected_real (DBusConnection *connection, + DBusMessage *message, + const char *expected, + const char *function, + int line) +{ + if (message) + _dbus_warn ("%s:%d received message interface \"%s\" member \"%s\" error name \"%s\" on %p, expecting %s\n", + function, line, + dbus_message_get_interface (message) ? + dbus_message_get_interface (message) : "(unset)", + dbus_message_get_member (message) ? + dbus_message_get_member (message) : "(unset)", + dbus_message_get_error_name (message) ? + dbus_message_get_error_name (message) : "(unset)", + connection, + expected); + else + _dbus_warn ("%s:%d received no message on %p, expecting %s\n", + function, line, connection, expected); +} + +#define warn_unexpected(connection, message, expected) \ + warn_unexpected_real (connection, message, expected, _DBUS_FUNCTION_NAME, __LINE__) + +static void +verbose_message_received (DBusConnection *connection, + DBusMessage *message) +{ + _dbus_verbose ("Received message interface \"%s\" member \"%s\" error name \"%s\" on %p\n", + dbus_message_get_interface (message) ? + dbus_message_get_interface (message) : "(unset)", + dbus_message_get_member (message) ? + dbus_message_get_member (message) : "(unset)", + dbus_message_get_error_name (message) ? + dbus_message_get_error_name (message) : "(unset)", + connection); +} + +typedef enum +{ + SERVICE_CREATED, + OWNER_CHANGED, + SERVICE_DELETED +} ServiceInfoKind; + +typedef struct +{ + ServiceInfoKind expected_kind; + const char *expected_service_name; + dbus_bool_t failed; + DBusConnection *skip_connection; +} CheckServiceOwnerChangedData; + +static dbus_bool_t +check_service_owner_changed_foreach (DBusConnection *connection, + void *data) +{ + CheckServiceOwnerChangedData *d = data; + DBusMessage *message; + DBusError error; + const char *service_name, *old_owner, *new_owner; + + if (d->expected_kind == SERVICE_CREATED + && connection == d->skip_connection) + return TRUE; + + dbus_error_init (&error); + d->failed = TRUE; + + message = pop_message_waiting_for_memory (connection); + if (message == NULL) + { + _dbus_warn ("Did not receive a message on %p, expecting %s\n", + connection, "NameOwnerChanged"); + goto out; + } + else if (!dbus_message_is_signal (message, + DBUS_INTERFACE_DBUS, + "NameOwnerChanged")) + { + warn_unexpected (connection, message, "NameOwnerChanged"); + + goto out; + } + else + { + reget_service_info_data: + service_name = NULL; + old_owner = NULL; + new_owner = NULL; + + dbus_message_get_args (message, &error, + DBUS_TYPE_STRING, &service_name, + DBUS_TYPE_STRING, &old_owner, + DBUS_TYPE_STRING, &new_owner, + DBUS_TYPE_INVALID); + + if (dbus_error_is_set (&error)) + { + if (dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY)) + { + dbus_error_free (&error); + _dbus_wait_for_memory (); + goto reget_service_info_data; + } + else + { + _dbus_warn ("Did not get the expected arguments\n"); + goto out; + } + } + + if ((d->expected_kind == SERVICE_CREATED && ( old_owner[0] || !new_owner[0])) + || (d->expected_kind == OWNER_CHANGED && (!old_owner[0] || !new_owner[0])) + || (d->expected_kind == SERVICE_DELETED && (!old_owner[0] || new_owner[0]))) + { + _dbus_warn ("inconsistent NameOwnerChanged arguments\n"); + goto out; + } + + if (strcmp (service_name, d->expected_service_name) != 0) + { + _dbus_warn ("expected info on service %s, got info on %s\n", + d->expected_service_name, + service_name); + goto out; + } + + if (*service_name == ':' && new_owner[0] + && strcmp (service_name, new_owner) != 0) + { + _dbus_warn ("inconsistent ServiceOwnedChanged message (\"%s\" [ %s -> %s ])\n", + service_name, old_owner, new_owner); + goto out; + } + } + + d->failed = FALSE; + + out: + dbus_error_free (&error); + + if (message) + dbus_message_unref (message); + + return !d->failed; +} + + +static void +kill_client_connection (BusContext *context, + DBusConnection *connection) +{ + char *base_service; + const char *s; + CheckServiceOwnerChangedData socd; + + _dbus_verbose ("killing connection %p\n", connection); + + s = dbus_bus_get_unique_name (connection); + _dbus_assert (s != NULL); + + while ((base_service = _dbus_strdup (s)) == NULL) + _dbus_wait_for_memory (); + + dbus_connection_ref (connection); + + /* kick in the disconnect handler that unrefs the connection */ + dbus_connection_close (connection); + + bus_test_run_everything (context); + + _dbus_assert (bus_test_client_listed (connection)); + + /* Run disconnect handler in test.c */ + if (bus_connection_dispatch_one_message (connection)) + _dbus_assert_not_reached ("something received on connection being killed other than the disconnect"); + + _dbus_assert (!dbus_connection_get_is_connected (connection)); + dbus_connection_unref (connection); + connection = NULL; + _dbus_assert (!bus_test_client_listed (connection)); + + socd.expected_kind = SERVICE_DELETED; + socd.expected_service_name = base_service; + socd.failed = FALSE; + socd.skip_connection = NULL; + + bus_test_clients_foreach (check_service_owner_changed_foreach, + &socd); + + dbus_free (base_service); + + if (socd.failed) + _dbus_assert_not_reached ("didn't get the expected NameOwnerChanged (deletion) messages"); + + if (!check_no_leftovers (context)) + _dbus_assert_not_reached ("stuff left in message queues after disconnecting a client"); +} + +static void +kill_client_connection_unchecked (DBusConnection *connection) +{ + /* This kills the connection without expecting it to affect + * the rest of the bus. + */ + _dbus_verbose ("Unchecked kill of connection %p\n", connection); + + dbus_connection_ref (connection); + dbus_connection_close (connection); + /* dispatching disconnect handler will unref once */ + if (bus_connection_dispatch_one_message (connection)) + _dbus_assert_not_reached ("message other than disconnect dispatched after failure to register"); + + _dbus_assert (!bus_test_client_listed (connection)); + dbus_connection_unref (connection); +} + +typedef struct +{ + dbus_bool_t failed; +} CheckNoMessagesData; + +static dbus_bool_t +check_no_messages_foreach (DBusConnection *connection, + void *data) +{ + CheckNoMessagesData *d = data; + DBusMessage *message; + + message = pop_message_waiting_for_memory (connection); + if (message != NULL) + { + warn_unexpected (connection, message, "no messages"); + + d->failed = TRUE; + } + + if (message) + dbus_message_unref (message); + return !d->failed; +} + +static dbus_bool_t +check_no_leftovers (BusContext *context) +{ + CheckNoMessagesData nmd; + + nmd.failed = FALSE; + bus_test_clients_foreach (check_no_messages_foreach, + &nmd); + + if (nmd.failed) + { + _dbus_verbose ("%s: leftover message found\n", + _DBUS_FUNCTION_NAME); + return FALSE; + } + else + return TRUE; +} + +/* returns TRUE if the correct thing happens, + * but the correct thing may include OOM errors. + */ +static dbus_bool_t +check_hello_message (BusContext *context, + DBusConnection *connection) +{ + DBusMessage *message; + DBusMessage *name_message; + dbus_uint32_t serial; + dbus_bool_t retval; + DBusError error; + const char *name; + const char *acquired; + + retval = FALSE; + dbus_error_init (&error); + name = NULL; + acquired = NULL; + message = NULL; + name_message = NULL; + + _dbus_verbose ("check_hello_message for %p\n", connection); + + message = dbus_message_new_method_call (DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "Hello"); + + if (message == NULL) + return TRUE; + + dbus_connection_ref (connection); /* because we may get disconnected */ + + if (!dbus_connection_send (connection, message, &serial)) + { + dbus_message_unref (message); + dbus_connection_unref (connection); + return TRUE; + } + + _dbus_assert (dbus_message_has_signature (message, "")); + + dbus_message_unref (message); + message = NULL; + + if (!dbus_connection_get_is_connected (connection)) + { + _dbus_verbose ("connection was disconnected (presumably auth failed)\n"); + + dbus_connection_unref (connection); + + return TRUE; + } + + /* send our message */ + bus_test_run_clients_loop (SEND_PENDING (connection)); + + if (!dbus_connection_get_is_connected (connection)) + { + _dbus_verbose ("connection was disconnected (presumably auth failed)\n"); + + dbus_connection_unref (connection); + + return TRUE; + } + + block_connection_until_message_from_bus (context, connection, "reply to Hello"); + + if (!dbus_connection_get_is_connected (connection)) + { + _dbus_verbose ("connection was disconnected (presumably auth failed)\n"); + + dbus_connection_unref (connection); + + return TRUE; + } + + dbus_connection_unref (connection); + + message = pop_message_waiting_for_memory (connection); + if (message == NULL) + { + _dbus_warn ("Did not receive a reply to %s %d on %p\n", + "Hello", serial, connection); + goto out; + } + + verbose_message_received (connection, message); + + if (!dbus_message_has_sender (message, DBUS_SERVICE_DBUS)) + { + _dbus_warn ("Message has wrong sender %s\n", + dbus_message_get_sender (message) ? + dbus_message_get_sender (message) : "(none)"); + goto out; + } + + if (dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_ERROR) + { + if (dbus_message_is_error (message, + DBUS_ERROR_NO_MEMORY)) + { + ; /* good, this is a valid response */ + } + else + { + warn_unexpected (connection, message, "not this error"); + + goto out; + } + } + else + { + CheckServiceOwnerChangedData socd; + + if (dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_METHOD_RETURN) + { + ; /* good, expected */ + } + else + { + warn_unexpected (connection, message, "method return for Hello"); + + goto out; + } + + retry_get_hello_name: + if (!dbus_message_get_args (message, &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) + { + if (dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY)) + { + _dbus_verbose ("no memory to get service name arg from hello\n"); + dbus_error_free (&error); + _dbus_wait_for_memory (); + goto retry_get_hello_name; + } + else + { + _dbus_assert (dbus_error_is_set (&error)); + _dbus_warn ("Did not get the expected single string argument to hello\n"); + goto out; + } + } + + _dbus_verbose ("Got hello name: %s\n", name); + + while (!dbus_bus_set_unique_name (connection, name)) + _dbus_wait_for_memory (); + + socd.expected_kind = SERVICE_CREATED; + socd.expected_service_name = name; + socd.failed = FALSE; + socd.skip_connection = connection; /* we haven't done AddMatch so won't get it ourselves */ + bus_test_clients_foreach (check_service_owner_changed_foreach, + &socd); + + if (socd.failed) + goto out; + + name_message = message; + /* Client should also have gotten ServiceAcquired */ + + message = pop_message_waiting_for_memory (connection); + if (message == NULL) + { + _dbus_warn ("Expecting %s, got nothing\n", + "NameAcquired"); + goto out; + } + if (! dbus_message_is_signal (message, DBUS_INTERFACE_DBUS, + "NameAcquired")) + { + _dbus_warn ("Expecting %s, got smthg else\n", + "NameAcquired"); + goto out; + } + + retry_get_acquired_name: + if (!dbus_message_get_args (message, &error, + DBUS_TYPE_STRING, &acquired, + DBUS_TYPE_INVALID)) + { + if (dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY)) + { + _dbus_verbose ("no memory to get service name arg from acquired\n"); + dbus_error_free (&error); + _dbus_wait_for_memory (); + goto retry_get_acquired_name; + } + else + { + _dbus_assert (dbus_error_is_set (&error)); + _dbus_warn ("Did not get the expected single string argument to ServiceAcquired\n"); + goto out; + } + } + + _dbus_verbose ("Got acquired name: %s\n", acquired); + + if (strcmp (acquired, name) != 0) + { + _dbus_warn ("Acquired name is %s but expected %s\n", + acquired, name); + goto out; + } + acquired = NULL; + } + + if (!check_no_leftovers (context)) + goto out; + + retval = TRUE; + + out: + _dbus_verbose ("ending %s retval = %d\n", _DBUS_FUNCTION_NAME, retval); + + dbus_error_free (&error); + + if (message) + dbus_message_unref (message); + + if (name_message) + dbus_message_unref (name_message); + + return retval; +} + +/* returns TRUE if the correct thing happens, + * but the correct thing may include OOM errors. + */ +static dbus_bool_t +check_double_hello_message (BusContext *context, + DBusConnection *connection) +{ + DBusMessage *message; + dbus_uint32_t serial; + dbus_bool_t retval; + DBusError error; + + retval = FALSE; + dbus_error_init (&error); + message = NULL; + + _dbus_verbose ("check_double_hello_message for %p\n", connection); + + message = dbus_message_new_method_call (DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "Hello"); + + if (message == NULL) + return TRUE; + + if (!dbus_connection_send (connection, message, &serial)) + { + dbus_message_unref (message); + return TRUE; + } + + dbus_message_unref (message); + message = NULL; + + /* send our message */ + bus_test_run_clients_loop (SEND_PENDING (connection)); + + dbus_connection_ref (connection); /* because we may get disconnected */ + block_connection_until_message_from_bus (context, connection, "reply to Hello"); + + if (!dbus_connection_get_is_connected (connection)) + { + _dbus_verbose ("connection was disconnected: %s %d\n", _DBUS_FUNCTION_NAME, __LINE__); + + dbus_connection_unref (connection); + + return TRUE; + } + + dbus_connection_unref (connection); + + message = pop_message_waiting_for_memory (connection); + if (message == NULL) + { + _dbus_warn ("Did not receive a reply to %s %d on %p\n", + "Hello", serial, connection); + goto out; + } + + verbose_message_received (connection, message); + + if (!dbus_message_has_sender (message, DBUS_SERVICE_DBUS)) + { + _dbus_warn ("Message has wrong sender %s\n", + dbus_message_get_sender (message) ? + dbus_message_get_sender (message) : "(none)"); + goto out; + } + + if (dbus_message_get_type (message) != DBUS_MESSAGE_TYPE_ERROR) + { + warn_unexpected (connection, message, "method return for Hello"); + goto out; + } + + if (!check_no_leftovers (context)) + goto out; + + retval = TRUE; + + out: + dbus_error_free (&error); + + if (message) + dbus_message_unref (message); + + return retval; +} + +/* returns TRUE if the correct thing happens, + * but the correct thing may include OOM errors. + */ +static dbus_bool_t +check_get_connection_unix_user (BusContext *context, + DBusConnection *connection) +{ + DBusMessage *message; + dbus_uint32_t serial; + dbus_bool_t retval; + DBusError error; + const char *base_service_name; + dbus_uint32_t uid; + + retval = FALSE; + dbus_error_init (&error); + message = NULL; + + _dbus_verbose ("check_get_connection_unix_user for %p\n", connection); + + message = dbus_message_new_method_call (DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "GetConnectionUnixUser"); + + if (message == NULL) + return TRUE; + + base_service_name = dbus_bus_get_unique_name (connection); + + if (!dbus_message_append_args (message, + DBUS_TYPE_STRING, &base_service_name, + DBUS_TYPE_INVALID)) + { + dbus_message_unref (message); + return TRUE; + } + + if (!dbus_connection_send (connection, message, &serial)) + { + dbus_message_unref (message); + return TRUE; + } + + /* send our message */ + bus_test_run_clients_loop (SEND_PENDING (connection)); + + dbus_message_unref (message); + message = NULL; + + dbus_connection_ref (connection); /* because we may get disconnected */ + block_connection_until_message_from_bus (context, connection, "reply to GetConnectionUnixUser"); + + if (!dbus_connection_get_is_connected (connection)) + { + _dbus_verbose ("connection was disconnected: %s %d\n", _DBUS_FUNCTION_NAME, __LINE__); + + dbus_connection_unref (connection); + + return TRUE; + } + + dbus_connection_unref (connection); + + message = pop_message_waiting_for_memory (connection); + if (message == NULL) + { + _dbus_warn ("Did not receive a reply to %s %d on %p\n", + "GetConnectionUnixUser", serial, connection); + goto out; + } + + verbose_message_received (connection, message); + + if (dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_ERROR) + { + if (dbus_message_is_error (message, DBUS_ERROR_NO_MEMORY)) + { + ; /* good, this is a valid response */ + } + else + { + warn_unexpected (connection, message, "not this error"); + + goto out; + } + } + else + { + if (dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_METHOD_RETURN) + { + ; /* good, expected */ + } + else + { + warn_unexpected (connection, message, + "method_return for GetConnectionUnixUser"); + + goto out; + } + + retry_get_property: + + if (!dbus_message_get_args (message, &error, + DBUS_TYPE_UINT32, &uid, + DBUS_TYPE_INVALID)) + { + if (dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY)) + { + _dbus_verbose ("no memory to get uid by GetConnectionUnixUser\n"); + dbus_error_free (&error); + _dbus_wait_for_memory (); + goto retry_get_property; + } + else + { + _dbus_assert (dbus_error_is_set (&error)); + _dbus_warn ("Did not get the expected DBUS_TYPE_UINT32 from GetConnectionUnixUser\n"); + goto out; + } + } + } + + if (!check_no_leftovers (context)) + goto out; + + retval = TRUE; + + out: + dbus_error_free (&error); + + if (message) + dbus_message_unref (message); + + return retval; +} + +/* returns TRUE if the correct thing happens, + * but the correct thing may include OOM errors. + */ +static dbus_bool_t +check_get_connection_unix_process_id (BusContext *context, + DBusConnection *connection) +{ + DBusMessage *message; + dbus_uint32_t serial; + dbus_bool_t retval; + DBusError error; + const char *base_service_name; + dbus_uint32_t pid; + + retval = FALSE; + dbus_error_init (&error); + message = NULL; + + _dbus_verbose ("check_get_connection_unix_process_id for %p\n", connection); + + message = dbus_message_new_method_call (DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "GetConnectionUnixProcessID"); + + if (message == NULL) + return TRUE; + + base_service_name = dbus_bus_get_unique_name (connection); + + if (!dbus_message_append_args (message, + DBUS_TYPE_STRING, &base_service_name, + DBUS_TYPE_INVALID)) + { + dbus_message_unref (message); + return TRUE; + } + + if (!dbus_connection_send (connection, message, &serial)) + { + dbus_message_unref (message); + return TRUE; + } + + /* send our message */ + bus_test_run_clients_loop (SEND_PENDING (connection)); + + dbus_message_unref (message); + message = NULL; + + dbus_connection_ref (connection); /* because we may get disconnected */ + block_connection_until_message_from_bus (context, connection, "reply to GetConnectionUnixProcessID"); + + if (!dbus_connection_get_is_connected (connection)) + { + _dbus_verbose ("connection was disconnected: %s %d\n", _DBUS_FUNCTION_NAME, __LINE__); + + dbus_connection_unref (connection); + + return TRUE; + } + + dbus_connection_unref (connection); + + message = pop_message_waiting_for_memory (connection); + if (message == NULL) + { + _dbus_warn ("Did not receive a reply to %s %d on %p\n", + "GetConnectionUnixProcessID", serial, connection); + goto out; + } + + verbose_message_received (connection, message); + + if (dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_ERROR) + { + if (dbus_message_is_error (message, DBUS_ERROR_NO_MEMORY)) + { + ; /* good, this is a valid response */ + } +#ifdef DBUS_WIN + else if (dbus_message_is_error (message, DBUS_ERROR_UNIX_PROCESS_ID_UNKNOWN)) + { + /* We are expecting this error, since we know in the test suite we aren't + * talking to a client running on UNIX + */ + _dbus_verbose ("Windows correctly does not support GetConnectionUnixProcessID\n"); + } +#endif + else + { + warn_unexpected (connection, message, "not this error"); + + goto out; + } + } + else + { +#ifdef DBUS_WIN + warn_unexpected (connection, message, "GetConnectionUnixProcessID to fail on Windows"); + goto out; +#else + if (dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_METHOD_RETURN) + { + ; /* good, expected */ + } + else + { + warn_unexpected (connection, message, + "method_return for GetConnectionUnixProcessID"); + + goto out; + } + + retry_get_property: + + if (!dbus_message_get_args (message, &error, + DBUS_TYPE_UINT32, &pid, + DBUS_TYPE_INVALID)) + { + if (dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY)) + { + _dbus_verbose ("no memory to get pid by GetConnectionUnixProcessID\n"); + dbus_error_free (&error); + _dbus_wait_for_memory (); + goto retry_get_property; + } + else + { + _dbus_assert (dbus_error_is_set (&error)); + _dbus_warn ("Did not get the expected DBUS_TYPE_UINT32 from GetConnectionUnixProcessID\n"); + goto out; + } + } + else + { + /* test if returned pid is the same as our own pid + * + * @todo It would probably be good to restructure the tests + * in a way so our parent is the bus that we're testing + * cause then we can test that the pid returned matches + * getppid() + */ + if (pid != (dbus_uint32_t) _dbus_getpid ()) + { + _dbus_assert (dbus_error_is_set (&error)); + _dbus_warn ("Result from GetConnectionUnixProcessID is not our own pid\n"); + goto out; + } + } +#endif /* !DBUS_WIN */ + } + + if (!check_no_leftovers (context)) + goto out; + + retval = TRUE; + + out: + dbus_error_free (&error); + + if (message) + dbus_message_unref (message); + + return retval; +} + +/* returns TRUE if the correct thing happens, + * but the correct thing may include OOM errors. + */ +static dbus_bool_t +check_add_match_all (BusContext *context, + DBusConnection *connection) +{ + DBusMessage *message; + dbus_bool_t retval; + dbus_uint32_t serial; + DBusError error; + const char *empty = ""; + + retval = FALSE; + dbus_error_init (&error); + message = NULL; + + _dbus_verbose ("check_add_match_all for %p\n", connection); + + message = dbus_message_new_method_call (DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "AddMatch"); + + if (message == NULL) + return TRUE; + + /* empty string match rule matches everything */ + if (!dbus_message_append_args (message, DBUS_TYPE_STRING, &empty, + DBUS_TYPE_INVALID)) + { + dbus_message_unref (message); + return TRUE; + } + + if (!dbus_connection_send (connection, message, &serial)) + { + dbus_message_unref (message); + return TRUE; + } + + dbus_message_unref (message); + message = NULL; + + dbus_connection_ref (connection); /* because we may get disconnected */ + + /* send our message */ + bus_test_run_clients_loop (SEND_PENDING (connection)); + + if (!dbus_connection_get_is_connected (connection)) + { + _dbus_verbose ("connection was disconnected: %s %d\n", _DBUS_FUNCTION_NAME, __LINE__); + + dbus_connection_unref (connection); + + return TRUE; + } + + block_connection_until_message_from_bus (context, connection, "reply to AddMatch"); + + if (!dbus_connection_get_is_connected (connection)) + { + _dbus_verbose ("connection was disconnected: %s %d\n", _DBUS_FUNCTION_NAME, __LINE__); + + dbus_connection_unref (connection); + + return TRUE; + } + + dbus_connection_unref (connection); + + message = pop_message_waiting_for_memory (connection); + if (message == NULL) + { + _dbus_warn ("Did not receive a reply to %s %d on %p\n", + "AddMatch", serial, connection); + goto out; + } + + verbose_message_received (connection, message); + + if (!dbus_message_has_sender (message, DBUS_SERVICE_DBUS)) + { + _dbus_warn ("Message has wrong sender %s\n", + dbus_message_get_sender (message) ? + dbus_message_get_sender (message) : "(none)"); + goto out; + } + + if (dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_ERROR) + { + if (dbus_message_is_error (message, + DBUS_ERROR_NO_MEMORY)) + { + ; /* good, this is a valid response */ + } + else + { + warn_unexpected (connection, message, "not this error"); + + goto out; + } + } + else + { + if (dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_METHOD_RETURN) + { + ; /* good, expected */ + _dbus_assert (dbus_message_get_reply_serial (message) == serial); + } + else + { + warn_unexpected (connection, message, "method return for AddMatch"); + + goto out; + } + } + + if (!check_no_leftovers (context)) + goto out; + + retval = TRUE; + + out: + dbus_error_free (&error); + + if (message) + dbus_message_unref (message); + + return retval; +} + +/* returns TRUE if the correct thing happens, + * but the correct thing may include OOM errors. + */ +static dbus_bool_t +check_hello_connection (BusContext *context) +{ + DBusConnection *connection; + DBusError error; + + dbus_error_init (&error); + + connection = dbus_connection_open_private ("debug-pipe:name=test-server", &error); + if (connection == NULL) + { + _DBUS_ASSERT_ERROR_IS_SET (&error); + dbus_error_free (&error); + return TRUE; + } + + if (!bus_setup_debug_client (connection)) + { + dbus_connection_close (connection); + dbus_connection_unref (connection); + return TRUE; + } + + spin_connection_until_authenticated (context, connection); + + if (!check_hello_message (context, connection)) + return FALSE; + + if (dbus_bus_get_unique_name (connection) == NULL) + { + /* We didn't successfully register, so we can't + * do the usual kill_client_connection() checks + */ + kill_client_connection_unchecked (connection); + } + else + { + if (!check_add_match_all (context, connection)) + return FALSE; + + kill_client_connection (context, connection); + } + + return TRUE; +} + +#define NONEXISTENT_SERVICE_NAME "test.this.service.does.not.exist.ewuoiurjdfxcvn" + +/* returns TRUE if the correct thing happens, + * but the correct thing may include OOM errors. + */ +static dbus_bool_t +check_nonexistent_service_no_auto_start (BusContext *context, + DBusConnection *connection) +{ + DBusMessage *message; + dbus_uint32_t serial; + dbus_bool_t retval; + const char *nonexistent = NONEXISTENT_SERVICE_NAME; + dbus_uint32_t flags; + + message = dbus_message_new_method_call (DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "StartServiceByName"); + + if (message == NULL) + return TRUE; + + dbus_message_set_auto_start (message, FALSE); + + flags = 0; + if (!dbus_message_append_args (message, + DBUS_TYPE_STRING, &nonexistent, + DBUS_TYPE_UINT32, &flags, + DBUS_TYPE_INVALID)) + { + dbus_message_unref (message); + return TRUE; + } + + if (!dbus_connection_send (connection, message, &serial)) + { + dbus_message_unref (message); + return TRUE; + } + + dbus_message_unref (message); + message = NULL; + + bus_test_run_everything (context); + block_connection_until_message_from_bus (context, connection, "reply to ActivateService on nonexistent"); + bus_test_run_everything (context); + + if (!dbus_connection_get_is_connected (connection)) + { + _dbus_verbose ("connection was disconnected: %s %d\n", _DBUS_FUNCTION_NAME, __LINE__); + return TRUE; + } + + retval = FALSE; + + message = pop_message_waiting_for_memory (connection); + if (message == NULL) + { + _dbus_warn ("Did not receive a reply to %s %d on %p\n", + "StartServiceByName", serial, connection); + goto out; + } + + verbose_message_received (connection, message); + + if (dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_ERROR) + { + if (!dbus_message_has_sender (message, DBUS_SERVICE_DBUS)) + { + _dbus_warn ("Message has wrong sender %s\n", + dbus_message_get_sender (message) ? + dbus_message_get_sender (message) : "(none)"); + goto out; + } + + if (dbus_message_is_error (message, + DBUS_ERROR_NO_MEMORY)) + { + ; /* good, this is a valid response */ + } + else if (dbus_message_is_error (message, + DBUS_ERROR_SERVICE_UNKNOWN)) + { + ; /* good, this is expected also */ + } + else + { + warn_unexpected (connection, message, "not this error"); + goto out; + } + } + else + { + _dbus_warn ("Did not expect to successfully activate %s\n", + NONEXISTENT_SERVICE_NAME); + goto out; + } + + retval = TRUE; + + out: + if (message) + dbus_message_unref (message); + + return retval; +} + +/* returns TRUE if the correct thing happens, + * but the correct thing may include OOM errors. + */ +static dbus_bool_t +check_nonexistent_service_auto_start (BusContext *context, + DBusConnection *connection) +{ + DBusMessage *message; + dbus_uint32_t serial; + dbus_bool_t retval; + + message = dbus_message_new_method_call (NONEXISTENT_SERVICE_NAME, + "/org/freedesktop/TestSuite", + "org.freedesktop.TestSuite", + "Echo"); + + if (message == NULL) + return TRUE; + + if (!dbus_connection_send (connection, message, &serial)) + { + dbus_message_unref (message); + return TRUE; + } + + dbus_message_unref (message); + message = NULL; + + bus_test_run_everything (context); + block_connection_until_message_from_bus (context, connection, "reply to Echo"); + bus_test_run_everything (context); + + if (!dbus_connection_get_is_connected (connection)) + { + _dbus_verbose ("connection was disconnected: %s %d\n", _DBUS_FUNCTION_NAME, __LINE__); + return TRUE; + } + + retval = FALSE; + + message = pop_message_waiting_for_memory (connection); + + if (message == NULL) + { + _dbus_warn ("Did not receive a reply to %s %d on %p\n", + "Echo message (auto activation)", serial, connection); + goto out; + } + + verbose_message_received (connection, message); + + if (dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_ERROR) + { + if (!dbus_message_has_sender (message, DBUS_SERVICE_DBUS)) + { + _dbus_warn ("Message has wrong sender %s\n", + dbus_message_get_sender (message) ? + dbus_message_get_sender (message) : "(none)"); + goto out; + } + + if (dbus_message_is_error (message, + DBUS_ERROR_NO_MEMORY)) + { + ; /* good, this is a valid response */ + } + else if (dbus_message_is_error (message, + DBUS_ERROR_SERVICE_UNKNOWN)) + { + ; /* good, this is expected also */ + } + else + { + warn_unexpected (connection, message, "not this error"); + goto out; + } + } + else + { + _dbus_warn ("Did not expect to successfully activate %s\n", + NONEXISTENT_SERVICE_NAME); + goto out; + } + + retval = TRUE; + + out: + if (message) + dbus_message_unref (message); + + return retval; +} + +static dbus_bool_t +check_base_service_activated (BusContext *context, + DBusConnection *connection, + DBusMessage *initial_message, + const char **base_service_p) +{ + DBusMessage *message; + dbus_bool_t retval; + DBusError error; + const char *base_service, *base_service_from_bus, *old_owner; + + retval = FALSE; + + dbus_error_init (&error); + base_service = NULL; + old_owner = NULL; + base_service_from_bus = NULL; + + message = initial_message; + dbus_message_ref (message); + + if (dbus_message_is_signal (message, + DBUS_INTERFACE_DBUS, + "NameOwnerChanged")) + { + CheckServiceOwnerChangedData socd; + + reget_service_name_arg: + base_service = NULL; + old_owner = NULL; + base_service_from_bus = NULL; + + if (!dbus_message_get_args (message, &error, + DBUS_TYPE_STRING, &base_service, + DBUS_TYPE_STRING, &old_owner, + DBUS_TYPE_STRING, &base_service_from_bus, + DBUS_TYPE_INVALID)) + { + if (dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY)) + { + dbus_error_free (&error); + _dbus_wait_for_memory (); + goto reget_service_name_arg; + } + else + { + _dbus_warn ("Message %s doesn't have a service name: %s\n", + "NameOwnerChanged (creation)", + error.message); + goto out; + } + } + + if (*base_service != ':') + { + _dbus_warn ("Expected base service activation, got \"%s\" instead\n", + base_service); + goto out; + } + + if (strcmp (base_service, base_service_from_bus) != 0) + { + _dbus_warn ("Expected base service activation, got \"%s\" instead with owner \"%s\"\n", + base_service, base_service_from_bus); + goto out; + } + + if (old_owner[0]) + { + _dbus_warn ("Received an old_owner argument during base service activation, \"%s\"\n", + old_owner); + goto out; + } + + socd.expected_kind = SERVICE_CREATED; + socd.expected_service_name = base_service; + socd.failed = FALSE; + socd.skip_connection = connection; + bus_test_clients_foreach (check_service_owner_changed_foreach, + &socd); + + if (socd.failed) + goto out; + } + else + { + warn_unexpected (connection, message, "NameOwnerChanged (creation) for base service"); + + goto out; + } + + if (base_service_p) + *base_service_p = base_service; + + retval = TRUE; + + out: + if (message) + dbus_message_unref (message); + dbus_error_free (&error); + + return retval; +} + +static dbus_bool_t +check_service_activated (BusContext *context, + DBusConnection *connection, + const char *activated_name, + const char *base_service_name, + DBusMessage *initial_message) +{ + DBusMessage *message; + dbus_bool_t retval; + DBusError error; + dbus_uint32_t activation_result; + + retval = FALSE; + + dbus_error_init (&error); + + message = initial_message; + dbus_message_ref (message); + + if (dbus_message_is_signal (message, + DBUS_INTERFACE_DBUS, + "NameOwnerChanged")) + { + CheckServiceOwnerChangedData socd; + const char *service_name, *base_service_from_bus, *old_owner; + + reget_service_name_arg: + service_name = NULL; + old_owner = NULL; + base_service_from_bus = NULL; + + if (!dbus_message_get_args (message, &error, + DBUS_TYPE_STRING, &service_name, + DBUS_TYPE_STRING, &old_owner, + DBUS_TYPE_STRING, &base_service_from_bus, + DBUS_TYPE_INVALID)) + { + if (dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY)) + { + dbus_error_free (&error); + _dbus_wait_for_memory (); + goto reget_service_name_arg; + } + else + { + _dbus_warn ("Message %s doesn't have a service name: %s\n", + "NameOwnerChanged (creation)", + error.message); + goto out; + } + } + + if (strcmp (service_name, activated_name) != 0) + { + _dbus_warn ("Expected to see service %s created, saw %s instead\n", + activated_name, service_name); + goto out; + } + + if (strcmp (base_service_name, base_service_from_bus) != 0) + { + _dbus_warn ("NameOwnerChanged reports wrong base service: %s owner, expected %s instead\n", + base_service_from_bus, base_service_name); + goto out; + } + + if (old_owner[0]) + { + _dbus_warn ("expected a %s, got a %s\n", + "NameOwnerChanged (creation)", + "NameOwnerChanged (change)"); + goto out; + } + + socd.expected_kind = SERVICE_CREATED; + socd.skip_connection = connection; + socd.failed = FALSE; + socd.expected_service_name = service_name; + bus_test_clients_foreach (check_service_owner_changed_foreach, + &socd); + + if (socd.failed) + goto out; + + dbus_message_unref (message); + service_name = NULL; + old_owner = NULL; + base_service_from_bus = NULL; + + message = pop_message_waiting_for_memory (connection); + if (message == NULL) + { + _dbus_warn ("Expected a reply to %s, got nothing\n", + "StartServiceByName"); + goto out; + } + } + else + { + warn_unexpected (connection, message, "NameOwnerChanged for the activated name"); + + goto out; + } + + if (dbus_message_get_type (message) != DBUS_MESSAGE_TYPE_METHOD_RETURN) + { + warn_unexpected (connection, message, "reply to StartServiceByName"); + + goto out; + } + + activation_result = 0; + if (!dbus_message_get_args (message, &error, + DBUS_TYPE_UINT32, &activation_result, + DBUS_TYPE_INVALID)) + { + if (!dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY)) + { + _dbus_warn ("Did not have activation result first argument to %s: %s\n", + "StartServiceByName", error.message); + goto out; + } + + dbus_error_free (&error); + } + else + { + if (activation_result == DBUS_START_REPLY_SUCCESS) + ; /* Good */ + else if (activation_result == DBUS_START_REPLY_ALREADY_RUNNING) + ; /* Good also */ + else + { + _dbus_warn ("Activation result was %u, no good.\n", + activation_result); + goto out; + } + } + + dbus_message_unref (message); + message = NULL; + + if (!check_no_leftovers (context)) + { + _dbus_warn ("Messages were left over after verifying existent activation results\n"); + goto out; + } + + retval = TRUE; + + out: + if (message) + dbus_message_unref (message); + dbus_error_free (&error); + + return retval; +} + +static dbus_bool_t +check_service_auto_activated (BusContext *context, + DBusConnection *connection, + const char *activated_name, + const char *base_service_name, + DBusMessage *initial_message) +{ + DBusMessage *message; + dbus_bool_t retval; + DBusError error; + + retval = FALSE; + + dbus_error_init (&error); + + message = initial_message; + dbus_message_ref (message); + + if (dbus_message_is_signal (message, + DBUS_INTERFACE_DBUS, + "NameOwnerChanged")) + { + const char *service_name; + CheckServiceOwnerChangedData socd; + + reget_service_name_arg: + if (!dbus_message_get_args (message, &error, + DBUS_TYPE_STRING, &service_name, + DBUS_TYPE_INVALID)) + { + if (dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY)) + { + dbus_error_free (&error); + _dbus_wait_for_memory (); + goto reget_service_name_arg; + } + else + { + _dbus_warn ("Message %s doesn't have a service name: %s\n", + "NameOwnerChanged", + error.message); + dbus_error_free (&error); + goto out; + } + } + + if (strcmp (service_name, activated_name) != 0) + { + _dbus_warn ("Expected to see service %s created, saw %s instead\n", + activated_name, service_name); + goto out; + } + + socd.expected_kind = SERVICE_CREATED; + socd.expected_service_name = service_name; + socd.failed = FALSE; + socd.skip_connection = connection; + bus_test_clients_foreach (check_service_owner_changed_foreach, + &socd); + + if (socd.failed) + goto out; + + /* Note that this differs from regular activation in that we don't get a + * reply to ActivateService here. + */ + + dbus_message_unref (message); + message = NULL; + service_name = NULL; + } + else + { + warn_unexpected (connection, message, "NameOwnerChanged for the activated name"); + + goto out; + } + + retval = TRUE; + + out: + if (message) + dbus_message_unref (message); + + return retval; +} + +static dbus_bool_t +check_service_deactivated (BusContext *context, + DBusConnection *connection, + const char *activated_name, + const char *base_service) +{ + dbus_bool_t retval; + CheckServiceOwnerChangedData socd; + + retval = FALSE; + + /* Now we are expecting ServiceOwnerChanged (deletion) messages for the base + * service and the activated_name. The base service + * notification is required to come last. + */ + socd.expected_kind = SERVICE_DELETED; + socd.expected_service_name = activated_name; + socd.failed = FALSE; + socd.skip_connection = NULL; + bus_test_clients_foreach (check_service_owner_changed_foreach, + &socd); + + if (socd.failed) + goto out; + + socd.expected_kind = SERVICE_DELETED; + socd.expected_service_name = base_service; + socd.failed = FALSE; + socd.skip_connection = NULL; + bus_test_clients_foreach (check_service_owner_changed_foreach, + &socd); + + if (socd.failed) + goto out; + + retval = TRUE; + + out: + return retval; +} + +static dbus_bool_t +check_send_exit_to_service (BusContext *context, + DBusConnection *connection, + const char *service_name, + const char *base_service) +{ + dbus_bool_t got_error; + DBusMessage *message; + dbus_uint32_t serial; + dbus_bool_t retval; + + _dbus_verbose ("Sending exit message to the test service\n"); + + retval = FALSE; + + /* Kill off the test service by sending it a quit message */ + message = dbus_message_new_method_call (service_name, + "/org/freedesktop/TestSuite", + "org.freedesktop.TestSuite", + "Exit"); + + if (message == NULL) + { + /* Do this again; we still need the service to exit... */ + if (!check_send_exit_to_service (context, connection, + service_name, base_service)) + goto out; + + return TRUE; + } + + if (!dbus_connection_send (connection, message, &serial)) + { + dbus_message_unref (message); + + /* Do this again; we still need the service to exit... */ + if (!check_send_exit_to_service (context, connection, + service_name, base_service)) + goto out; + + return TRUE; + } + + dbus_message_unref (message); + message = NULL; + + /* send message */ + bus_test_run_clients_loop (SEND_PENDING (connection)); + + /* read it in and write it out to test service */ + bus_test_run_bus_loop (context, FALSE); + + /* see if we got an error during message bus dispatching */ + bus_test_run_clients_loop (FALSE); + message = borrow_message_waiting_for_memory (connection); + got_error = message != NULL && dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_ERROR; + if (message) + { + dbus_connection_return_message (connection, message); + message = NULL; + } + + if (!got_error) + { + /* If no error, wait for the test service to exit */ + block_connection_until_message_from_bus (context, connection, "test service to exit"); + + bus_test_run_everything (context); + } + + if (got_error) + { + message = pop_message_waiting_for_memory (connection); + _dbus_assert (message != NULL); + + if (dbus_message_get_reply_serial (message) != serial) + { + warn_unexpected (connection, message, + "error with the correct reply serial"); + goto out; + } + + if (!dbus_message_is_error (message, + DBUS_ERROR_NO_MEMORY)) + { + warn_unexpected (connection, message, + "a no memory error from asking test service to exit"); + goto out; + } + + _dbus_verbose ("Got error %s when asking test service to exit\n", + dbus_message_get_error_name (message)); + + /* Do this again; we still need the service to exit... */ + if (!check_send_exit_to_service (context, connection, + service_name, base_service)) + goto out; + } + else + { + if (!check_service_deactivated (context, connection, + service_name, base_service)) + goto out; + + /* Should now have a NoReply error from the Exit() method + * call; it should have come after all the deactivation + * stuff. + */ + message = pop_message_waiting_for_memory (connection); + + if (message == NULL) + { + warn_unexpected (connection, NULL, + "reply to Exit() method call"); + goto out; + } + if (!dbus_message_is_error (message, + DBUS_ERROR_NO_REPLY)) + { + warn_unexpected (connection, message, + "NoReply error from Exit() method call"); + goto out; + } + + if (dbus_message_get_reply_serial (message) != serial) + { + warn_unexpected (connection, message, + "error with the correct reply serial"); + goto out; + } + + _dbus_verbose ("Got error %s after test service exited\n", + dbus_message_get_error_name (message)); + + if (!check_no_leftovers (context)) + { + _dbus_warn ("Messages were left over after %s\n", + _DBUS_FUNCTION_NAME); + goto out; + } + } + + retval = TRUE; + + out: + if (message) + dbus_message_unref (message); + + return retval; +} + +static dbus_bool_t +check_got_error (BusContext *context, + DBusConnection *connection, + const char *first_error_name, + ...) +{ + DBusMessage *message; + dbus_bool_t retval; + va_list ap; + dbus_bool_t error_found; + const char *error_name; + + retval = FALSE; + + message = pop_message_waiting_for_memory (connection); + if (message == NULL) + { + _dbus_warn ("Did not get an expected error\n"); + goto out; + } + + if (dbus_message_get_type (message) != DBUS_MESSAGE_TYPE_ERROR) + { + warn_unexpected (connection, message, "an error"); + + goto out; + } + + error_found = FALSE; + + va_start (ap, first_error_name); + error_name = first_error_name; + while (error_name != NULL) + { + if (dbus_message_is_error (message, error_name)) + { + error_found = TRUE; + break; + } + error_name = va_arg (ap, char*); + } + va_end (ap); + + if (!error_found) + { + _dbus_warn ("Expected error %s or other, got %s instead\n", + first_error_name, + dbus_message_get_error_name (message)); + goto out; + } + + retval = TRUE; + + out: + if (message) + dbus_message_unref (message); + + return retval; +} + +typedef enum +{ + GOT_SERVICE_CREATED, + GOT_SERVICE_DELETED, + GOT_ERROR, + GOT_SOMETHING_ELSE +} GotServiceInfo; + +static GotServiceInfo +check_got_service_info (DBusMessage *message) +{ + GotServiceInfo message_kind; + + if (dbus_message_is_signal (message, + DBUS_INTERFACE_DBUS, + "NameOwnerChanged")) + { + DBusError error; + const char *service_name, *old_owner, *new_owner; + dbus_error_init (&error); + + reget_service_info_data: + service_name = NULL; + old_owner = NULL; + new_owner = NULL; + + dbus_message_get_args (message, &error, + DBUS_TYPE_STRING, &service_name, + DBUS_TYPE_STRING, &old_owner, + DBUS_TYPE_STRING, &new_owner, + DBUS_TYPE_INVALID); + if (dbus_error_is_set (&error)) + { + if (dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY)) + { + dbus_error_free (&error); + goto reget_service_info_data; + } + else + { + _dbus_warn ("unexpected arguments for NameOwnerChanged message\n"); + message_kind = GOT_SOMETHING_ELSE; + } + } + else if (!old_owner[0]) + message_kind = GOT_SERVICE_CREATED; + else if (!new_owner[0]) + message_kind = GOT_SERVICE_DELETED; + else + message_kind = GOT_SOMETHING_ELSE; + + dbus_error_free (&error); + } + else if (dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_ERROR) + message_kind = GOT_ERROR; + else + message_kind = GOT_SOMETHING_ELSE; + + return message_kind; +} + +#define EXISTENT_SERVICE_NAME "org.freedesktop.DBus.TestSuiteEchoService" + +/* returns TRUE if the correct thing happens, + * but the correct thing may include OOM errors. + */ +static dbus_bool_t +check_existent_service_no_auto_start (BusContext *context, + DBusConnection *connection) +{ + DBusMessage *message; + DBusMessage *base_service_message; + const char *base_service; + dbus_uint32_t serial; + dbus_bool_t retval; + const char *existent = EXISTENT_SERVICE_NAME; + dbus_uint32_t flags; + + base_service_message = NULL; + + message = dbus_message_new_method_call (DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "StartServiceByName"); + + if (message == NULL) + return TRUE; + + dbus_message_set_auto_start (message, FALSE); + + flags = 0; + if (!dbus_message_append_args (message, + DBUS_TYPE_STRING, &existent, + DBUS_TYPE_UINT32, &flags, + DBUS_TYPE_INVALID)) + { + dbus_message_unref (message); + return TRUE; + } + + if (!dbus_connection_send (connection, message, &serial)) + { + dbus_message_unref (message); + return TRUE; + } + + dbus_message_unref (message); + message = NULL; + + bus_test_run_everything (context); + + /* now wait for the message bus to hear back from the activated + * service. + */ + block_connection_until_message_from_bus (context, connection, "activated service to connect"); + + bus_test_run_everything (context); + + if (!dbus_connection_get_is_connected (connection)) + { + _dbus_verbose ("connection was disconnected: %s %d\n", _DBUS_FUNCTION_NAME, __LINE__); + return TRUE; + } + + retval = FALSE; + + message = pop_message_waiting_for_memory (connection); + if (message == NULL) + { + _dbus_warn ("Did not receive any messages after %s %d on %p\n", + "StartServiceByName", serial, connection); + goto out; + } + + verbose_message_received (connection, message); + _dbus_verbose (" (after sending %s)\n", "StartServiceByName"); + + if (dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_ERROR) + { + if (!dbus_message_has_sender (message, DBUS_SERVICE_DBUS)) + { + _dbus_warn ("Message has wrong sender %s\n", + dbus_message_get_sender (message) ? + dbus_message_get_sender (message) : "(none)"); + goto out; + } + + if (dbus_message_is_error (message, + DBUS_ERROR_NO_MEMORY)) + { + ; /* good, this is a valid response */ + } + else if (dbus_message_is_error (message, + DBUS_ERROR_SPAWN_CHILD_EXITED) || + dbus_message_is_error (message, + DBUS_ERROR_SPAWN_CHILD_SIGNALED) || + dbus_message_is_error (message, + DBUS_ERROR_SPAWN_EXEC_FAILED)) + { + ; /* good, this is expected also */ + } + else + { + _dbus_warn ("Did not expect error %s\n", + dbus_message_get_error_name (message)); + goto out; + } + } + else + { + GotServiceInfo message_kind; + + if (!check_base_service_activated (context, connection, + message, &base_service)) + goto out; + + base_service_message = message; + message = NULL; + + /* We may need to block here for the test service to exit or finish up */ + block_connection_until_message_from_bus (context, connection, "test service to exit or finish up"); + + message = dbus_connection_borrow_message (connection); + if (message == NULL) + { + _dbus_warn ("Did not receive any messages after base service creation notification\n"); + goto out; + } + + message_kind = check_got_service_info (message); + + dbus_connection_return_message (connection, message); + message = NULL; + + switch (message_kind) + { + case GOT_SOMETHING_ELSE: + _dbus_warn ("Unexpected message after ActivateService " + "(should be an error or a service announcement"); + goto out; + + case GOT_ERROR: + if (!check_got_error (context, connection, + DBUS_ERROR_SPAWN_CHILD_EXITED, + DBUS_ERROR_NO_MEMORY, + NULL)) + goto out; + /* A service deleted should be coming along now after this error. + * We can also get the error *after* the service deleted. + */ + + /* fall through */ + + case GOT_SERVICE_DELETED: + { + /* The service started up and got a base address, but then + * failed to register under EXISTENT_SERVICE_NAME + */ + CheckServiceOwnerChangedData socd; + + socd.expected_kind = SERVICE_DELETED; + socd.expected_service_name = base_service; + socd.failed = FALSE; + socd.skip_connection = NULL; + + bus_test_clients_foreach (check_service_owner_changed_foreach, + &socd); + + if (socd.failed) + goto out; + + /* Now we should get an error about the service exiting + * if we didn't get it before. + */ + if (message_kind != GOT_ERROR) + { + block_connection_until_message_from_bus (context, connection, "error about service exiting"); + + /* and process everything again */ + bus_test_run_everything (context); + + if (!check_got_error (context, connection, + DBUS_ERROR_SPAWN_CHILD_EXITED, + DBUS_ERROR_NO_MEMORY, + NULL)) + goto out; + } + break; + } + + case GOT_SERVICE_CREATED: + message = pop_message_waiting_for_memory (connection); + if (message == NULL) + { + _dbus_warn ("Failed to pop message we just put back! " + "should have been a NameOwnerChanged (creation)\n"); + goto out; + } + + if (!check_service_activated (context, connection, EXISTENT_SERVICE_NAME, + base_service, message)) + goto out; + + dbus_message_unref (message); + message = NULL; + + if (!check_no_leftovers (context)) + { + _dbus_warn ("Messages were left over after successful activation\n"); + goto out; + } + + if (!check_send_exit_to_service (context, connection, + EXISTENT_SERVICE_NAME, base_service)) + goto out; + + break; + } + } + + retval = TRUE; + + out: + if (message) + dbus_message_unref (message); + + if (base_service_message) + dbus_message_unref (base_service_message); + + return retval; +} + +#ifndef DBUS_WIN_FIXME +/* returns TRUE if the correct thing happens, + * but the correct thing may include OOM errors. + */ +static dbus_bool_t +check_segfault_service_no_auto_start (BusContext *context, + DBusConnection *connection) +{ + DBusMessage *message; + dbus_uint32_t serial; + dbus_bool_t retval; + const char *segv_service; + dbus_uint32_t flags; + + message = dbus_message_new_method_call (DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "StartServiceByName"); + + if (message == NULL) + return TRUE; + + dbus_message_set_auto_start (message, FALSE); + + segv_service = "org.freedesktop.DBus.TestSuiteSegfaultService"; + flags = 0; + if (!dbus_message_append_args (message, + DBUS_TYPE_STRING, &segv_service, + DBUS_TYPE_UINT32, &flags, + DBUS_TYPE_INVALID)) + { + dbus_message_unref (message); + return TRUE; + } + + if (!dbus_connection_send (connection, message, &serial)) + { + dbus_message_unref (message); + return TRUE; + } + + dbus_message_unref (message); + message = NULL; + + bus_test_run_everything (context); + block_connection_until_message_from_bus (context, connection, "reply to activating segfault service"); + bus_test_run_everything (context); + + if (!dbus_connection_get_is_connected (connection)) + { + _dbus_verbose ("connection was disconnected: %s %d\n", _DBUS_FUNCTION_NAME, __LINE__); + return TRUE; + } + + retval = FALSE; + + message = pop_message_waiting_for_memory (connection); + if (message == NULL) + { + _dbus_warn ("Did not receive a reply to %s %d on %p\n", + "StartServiceByName", serial, connection); + goto out; + } + + verbose_message_received (connection, message); + + if (dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_ERROR) + { + if (!dbus_message_has_sender (message, DBUS_SERVICE_DBUS)) + { + _dbus_warn ("Message has wrong sender %s\n", + dbus_message_get_sender (message) ? + dbus_message_get_sender (message) : "(none)"); + goto out; + } + + if (dbus_message_is_error (message, + DBUS_ERROR_NO_MEMORY)) + { + ; /* good, this is a valid response */ + } + else if (dbus_message_is_error (message, + DBUS_ERROR_FAILED)) + { + const char *servicehelper; + servicehelper = bus_context_get_servicehelper (context); + /* make sure this only happens with the launch helper */ + _dbus_assert (servicehelper != NULL); + } + else if (dbus_message_is_error (message, + DBUS_ERROR_SPAWN_CHILD_SIGNALED)) + { + ; /* good, this is expected also */ + } + else + { + warn_unexpected (connection, message, "not this error"); + + goto out; + } + } + else + { + _dbus_warn ("Did not expect to successfully activate segfault service\n"); + goto out; + } + + retval = TRUE; + + out: + if (message) + dbus_message_unref (message); + + return retval; +} + + +/* returns TRUE if the correct thing happens, + * but the correct thing may include OOM errors. + */ +static dbus_bool_t +check_segfault_service_auto_start (BusContext *context, + DBusConnection *connection) +{ + DBusMessage *message; + dbus_uint32_t serial; + dbus_bool_t retval; + + message = dbus_message_new_method_call ("org.freedesktop.DBus.TestSuiteSegfaultService", + "/org/freedesktop/TestSuite", + "org.freedesktop.TestSuite", + "Echo"); + + if (message == NULL) + return TRUE; + + if (!dbus_connection_send (connection, message, &serial)) + { + dbus_message_unref (message); + return TRUE; + } + + dbus_message_unref (message); + message = NULL; + + bus_test_run_everything (context); + block_connection_until_message_from_bus (context, connection, "reply to Echo on segfault service"); + bus_test_run_everything (context); + + if (!dbus_connection_get_is_connected (connection)) + { + _dbus_verbose ("connection was disconnected: %s %d\n", _DBUS_FUNCTION_NAME, __LINE__); + return TRUE; + } + + retval = FALSE; + + message = pop_message_waiting_for_memory (connection); + if (message == NULL) + { + _dbus_warn ("Did not receive a reply to %s %d on %p\n", + "Echo message (auto activation)", serial, connection); + goto out; + } + + verbose_message_received (connection, message); + + if (dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_ERROR) + { + if (!dbus_message_has_sender (message, DBUS_SERVICE_DBUS)) + { + _dbus_warn ("Message has wrong sender %s\n", + dbus_message_get_sender (message) ? + dbus_message_get_sender (message) : "(none)"); + goto out; + } + + if (dbus_message_is_error (message, + DBUS_ERROR_NO_MEMORY)) + { + ; /* good, this is a valid response */ + } + else if (dbus_message_is_error (message, + DBUS_ERROR_SPAWN_CHILD_SIGNALED)) + { + ; /* good, this is expected also */ + } + else + { + warn_unexpected (connection, message, "not this error"); + + goto out; + } + } + else + { + _dbus_warn ("Did not expect to successfully activate segfault service\n"); + goto out; + } + + retval = TRUE; + + out: + if (message) + dbus_message_unref (message); + + return retval; +} +#endif + +#define TEST_ECHO_MESSAGE "Test echo message" +#define TEST_RUN_HELLO_FROM_SELF_MESSAGE "Test sending message to self" + +/* returns TRUE if the correct thing happens, + * but the correct thing may include OOM errors. + */ +static dbus_bool_t +check_existent_hello_from_self (BusContext *context, + DBusConnection *connection) +{ + DBusMessage *message; + dbus_uint32_t serial; + const char *text; + + message = dbus_message_new_method_call (EXISTENT_SERVICE_NAME, + "/org/freedesktop/TestSuite", + "org.freedesktop.TestSuite", + "RunHelloFromSelf"); + + if (message == NULL) + return TRUE; + + text = TEST_RUN_HELLO_FROM_SELF_MESSAGE; + if (!dbus_message_append_args (message, + DBUS_TYPE_STRING, &text, + DBUS_TYPE_INVALID)) + { + dbus_message_unref (message); + return TRUE; + } + + if (!dbus_connection_send (connection, message, &serial)) + { + dbus_message_unref (message); + return TRUE; + } + + dbus_message_unref (message); + message = NULL; + + bus_test_run_everything (context); + + /* Note: if this test is run in OOM mode, it will block when the bus + * doesn't send a reply due to OOM. + */ + block_connection_until_message_from_bus (context, connection, "reply from running hello from self"); + + message = pop_message_waiting_for_memory (connection); + if (message == NULL) + { + _dbus_warn ("Failed to pop message! Should have been reply from RunHelloFromSelf message\n"); + return FALSE; + } + + if (dbus_message_get_reply_serial (message) != serial) + { + _dbus_warn ("Wrong reply serial\n"); + dbus_message_unref (message); + return FALSE; + } + + dbus_message_unref (message); + message = NULL; + + return TRUE; +} + +/* returns TRUE if the correct thing happens, + * but the correct thing may include OOM errors. + */ +static dbus_bool_t +check_existent_ping (BusContext *context, + DBusConnection *connection) +{ + DBusMessage *message; + dbus_uint32_t serial; + message = dbus_message_new_method_call (EXISTENT_SERVICE_NAME, + "/org/freedesktop/TestSuite", + "org.freedesktop.DBus.Peer", + "Ping"); + + if (message == NULL) + return TRUE; + + if (!dbus_connection_send (connection, message, &serial)) + { + dbus_message_unref (message); + return TRUE; + } + + dbus_message_unref (message); + message = NULL; + + bus_test_run_everything (context); + + /* Note: if this test is run in OOM mode, it will block when the bus + * doesn't send a reply due to OOM. + */ + block_connection_until_message_from_bus (context, connection, "reply from running Ping"); + + message = pop_message_waiting_for_memory (connection); + if (message == NULL) + { + _dbus_warn ("Failed to pop message! Should have been reply from Ping message\n"); + return FALSE; + } + + if (dbus_message_get_reply_serial (message) != serial) + { + _dbus_warn ("Wrong reply serial\n"); + dbus_message_unref (message); + return FALSE; + } + + if (dbus_message_get_type (message) != DBUS_MESSAGE_TYPE_METHOD_RETURN) + { + _dbus_warn ("Unexpected message return during Ping\n"); + dbus_message_unref (message); + return FALSE; + } + + dbus_message_unref (message); + message = NULL; + + return TRUE; +} + +/* returns TRUE if the correct thing happens, + * but the correct thing may include OOM errors. + */ +static dbus_bool_t +check_existent_get_machine_id (BusContext *context, + DBusConnection *connection) +{ + DBusMessage *message; + dbus_uint32_t serial; + const char *machine_id; + + message = dbus_message_new_method_call (EXISTENT_SERVICE_NAME, + "/org/freedesktop/TestSuite", + "org.freedesktop.DBus.Peer", + "GetMachineId"); + + if (message == NULL) + return TRUE; + + if (!dbus_connection_send (connection, message, &serial)) + { + dbus_message_unref (message); + return TRUE; + } + + dbus_message_unref (message); + message = NULL; + + bus_test_run_everything (context); + + /* Note: if this test is run in OOM mode, it will block when the bus + * doesn't send a reply due to OOM. + */ + block_connection_until_message_from_bus (context, connection, "reply from running GetMachineId"); + + message = pop_message_waiting_for_memory (connection); + if (message == NULL) + { + _dbus_warn ("Failed to pop message! Should have been reply from GetMachineId message\n"); + return FALSE; + } + + if (dbus_message_get_reply_serial (message) != serial) + { + _dbus_warn ("Wrong reply serial\n"); + dbus_message_unref (message); + return FALSE; + } + + if (dbus_message_get_type (message) != DBUS_MESSAGE_TYPE_METHOD_RETURN) + { + _dbus_warn ("Unexpected message return during GetMachineId\n"); + dbus_message_unref (message); + return FALSE; + } + + machine_id = NULL; + if (!dbus_message_get_args (message, NULL, DBUS_TYPE_STRING, &machine_id, DBUS_TYPE_INVALID)) + { + _dbus_warn ("Did not get a machine ID in reply to GetMachineId\n"); + dbus_message_unref (message); + return FALSE; + } + + if (machine_id == NULL || strlen (machine_id) != 32) + { + _dbus_warn ("Machine id looks bogus: '%s'\n", machine_id ? machine_id : "null"); + dbus_message_unref (message); + return FALSE; + } + + /* We can't check that the machine id is correct because during make check it is + * just made up for each process separately + */ + + dbus_message_unref (message); + message = NULL; + + return TRUE; +} + +/* returns TRUE if the correct thing happens, + * but the correct thing may include OOM errors. + */ +static dbus_bool_t +check_existent_service_auto_start (BusContext *context, + DBusConnection *connection) +{ + DBusMessage *message; + DBusMessage *base_service_message; + dbus_uint32_t serial; + dbus_bool_t retval; + const char *base_service; + const char *text; + + base_service_message = NULL; + + message = dbus_message_new_method_call (EXISTENT_SERVICE_NAME, + "/org/freedesktop/TestSuite", + "org.freedesktop.TestSuite", + "Echo"); + + if (message == NULL) + return TRUE; + + text = TEST_ECHO_MESSAGE; + if (!dbus_message_append_args (message, + DBUS_TYPE_STRING, &text, + DBUS_TYPE_INVALID)) + { + dbus_message_unref (message); + return TRUE; + } + + if (!dbus_connection_send (connection, message, &serial)) + { + dbus_message_unref (message); + return TRUE; + } + + dbus_message_unref (message); + message = NULL; + + bus_test_run_everything (context); + + /* now wait for the message bus to hear back from the activated + * service. + */ + block_connection_until_message_from_bus (context, connection, "reply to Echo on existent service"); + bus_test_run_everything (context); + + if (!dbus_connection_get_is_connected (connection)) + { + _dbus_verbose ("connection was disconnected: %s %d\n", _DBUS_FUNCTION_NAME, __LINE__); + return TRUE; + } + + retval = FALSE; + + message = pop_message_waiting_for_memory (connection); + if (message == NULL) + { + _dbus_warn ("Did not receive any messages after auto start %d on %p\n", + serial, connection); + goto out; + } + + verbose_message_received (connection, message); + _dbus_verbose (" (after sending %s)\n", "auto start"); + + /* we should get zero or two ServiceOwnerChanged signals */ + if (dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_SIGNAL) + { + GotServiceInfo message_kind; + + if (!check_base_service_activated (context, connection, + message, &base_service)) + goto out; + + base_service_message = message; + message = NULL; + + /* We may need to block here for the test service to exit or finish up */ + block_connection_until_message_from_bus (context, connection, "service to exit"); + + /* Should get a service creation notification for the activated + * service name, or a service deletion on the base service name + */ + message = dbus_connection_borrow_message (connection); + if (message == NULL) + { + _dbus_warn ("No message after auto activation " + "(should be a service announcement)\n"); + dbus_connection_return_message (connection, message); + message = NULL; + goto out; + } + + message_kind = check_got_service_info (message); + + dbus_connection_return_message (connection, message); + message = NULL; + + switch (message_kind) + { + case GOT_SERVICE_CREATED: + message = pop_message_waiting_for_memory (connection); + if (message == NULL) + { + _dbus_warn ("Failed to pop message we just put back! " + "should have been a NameOwnerChanged (creation)\n"); + goto out; + } + + /* Check that ServiceOwnerChanged (creation) was correctly received */ + if (!check_service_auto_activated (context, connection, EXISTENT_SERVICE_NAME, + base_service, message)) + goto out; + + dbus_message_unref (message); + message = NULL; + + break; + + case GOT_SERVICE_DELETED: + { + /* The service started up and got a base address, but then + * failed to register under EXISTENT_SERVICE_NAME + */ + CheckServiceOwnerChangedData socd; + + socd.expected_kind = SERVICE_DELETED; + socd.expected_service_name = base_service; + socd.failed = FALSE; + socd.skip_connection = NULL; + bus_test_clients_foreach (check_service_owner_changed_foreach, + &socd); + + if (socd.failed) + goto out; + + break; + } + + case GOT_ERROR: + case GOT_SOMETHING_ELSE: + _dbus_warn ("Unexpected message after auto activation\n"); + goto out; + } + } + + /* OK, now we've dealt with ServiceOwnerChanged signals, now should + * come the method reply (or error) from the initial method call + */ + + /* Note: if this test is run in OOM mode, it will block when the bus + * doesn't send a reply due to OOM. + */ + block_connection_until_message_from_bus (context, connection, "reply from echo message after auto-activation"); + + message = pop_message_waiting_for_memory (connection); + if (message == NULL) + { + _dbus_warn ("Failed to pop message! Should have been reply from echo message\n"); + goto out; + } + + if (dbus_message_get_reply_serial (message) != serial) + { + _dbus_warn ("Wrong reply serial\n"); + goto out; + } + + dbus_message_unref (message); + message = NULL; + + if (!check_existent_ping (context, connection)) + goto out; + + if (!check_existent_get_machine_id (context, connection)) + goto out; + + if (!check_existent_hello_from_self (context, connection)) + goto out; + + if (!check_send_exit_to_service (context, connection, + EXISTENT_SERVICE_NAME, + base_service)) + goto out; + + retval = TRUE; + + out: + if (message) + dbus_message_unref (message); + + if (base_service_message) + dbus_message_unref (base_service_message); + + return retval; +} + +#define SERVICE_FILE_MISSING_NAME "org.freedesktop.DBus.TestSuiteEchoServiceDotServiceFileDoesNotExist" + +/* returns TRUE if the correct thing happens, + * but the correct thing may include OOM errors. + */ +static dbus_bool_t +check_launch_service_file_missing (BusContext *context, + DBusConnection *connection) +{ + DBusMessage *message; + dbus_uint32_t serial; + dbus_bool_t retval; + + message = dbus_message_new_method_call (SERVICE_FILE_MISSING_NAME, + "/org/freedesktop/TestSuite", + "org.freedesktop.TestSuite", + "Echo"); + + if (message == NULL) + return TRUE; + + if (!dbus_connection_send (connection, message, &serial)) + { + dbus_message_unref (message); + return TRUE; + } + + dbus_message_unref (message); + message = NULL; + + bus_test_run_everything (context); + block_connection_until_message_from_bus (context, connection, "reply to service file missing should fail to auto-start"); + bus_test_run_everything (context); + + if (!dbus_connection_get_is_connected (connection)) + { + _dbus_verbose ("connection was disconnected: %s %d\n", _DBUS_FUNCTION_NAME, __LINE__); + return TRUE; + } + + retval = FALSE; + + message = pop_message_waiting_for_memory (connection); + if (message == NULL) + { + _dbus_warn ("Did not receive a reply to %s %d on %p\n", + "Echo message (auto activation)", serial, connection); + goto out; + } + + verbose_message_received (connection, message); + + if (dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_ERROR) + { + if (!dbus_message_has_sender (message, DBUS_SERVICE_DBUS)) + { + _dbus_warn ("Message has wrong sender %s\n", + dbus_message_get_sender (message) ? + dbus_message_get_sender (message) : "(none)"); + goto out; + } + + if (dbus_message_is_error (message, + DBUS_ERROR_NO_MEMORY)) + { + ; /* good, this is a valid response */ + } + else if (dbus_message_is_error (message, + DBUS_ERROR_SERVICE_UNKNOWN)) + { + _dbus_verbose("got service unknown\n"); + ; /* good, this is expected (only valid when using launch helper) */ + } + else + { + warn_unexpected (connection, message, "not this error"); + + goto out; + } + } + else + { + _dbus_warn ("Did not expect to successfully auto-start missing service\n"); + goto out; + } + + retval = TRUE; + + out: + if (message) + dbus_message_unref (message); + + return retval; +} + +#define SERVICE_USER_MISSING_NAME "org.freedesktop.DBus.TestSuiteNoUser" + +/* returns TRUE if the correct thing happens, + * but the correct thing may include OOM errors. + */ +static dbus_bool_t +check_launch_service_user_missing (BusContext *context, + DBusConnection *connection) +{ + DBusMessage *message; + dbus_uint32_t serial; + dbus_bool_t retval; + + message = dbus_message_new_method_call (SERVICE_USER_MISSING_NAME, + "/org/freedesktop/TestSuite", + "org.freedesktop.TestSuite", + "Echo"); + + if (message == NULL) + return TRUE; + + if (!dbus_connection_send (connection, message, &serial)) + { + dbus_message_unref (message); + return TRUE; + } + + dbus_message_unref (message); + message = NULL; + + bus_test_run_everything (context); + block_connection_until_message_from_bus (context, connection, + "reply to service which should fail to auto-start (missing User)"); + bus_test_run_everything (context); + + if (!dbus_connection_get_is_connected (connection)) + { + _dbus_warn ("connection was disconnected: %s %d\n", _DBUS_FUNCTION_NAME, __LINE__); + return TRUE; + } + + retval = FALSE; + + message = pop_message_waiting_for_memory (connection); + if (message == NULL) + { + _dbus_warn ("Did not receive a reply to %s %d on %p\n", + "Echo message (auto activation)", serial, connection); + goto out; + } + + verbose_message_received (connection, message); + + if (dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_ERROR) + { + if (!dbus_message_has_sender (message, DBUS_SERVICE_DBUS)) + { + _dbus_warn ("Message has wrong sender %s\n", + dbus_message_get_sender (message) ? + dbus_message_get_sender (message) : "(none)"); + goto out; + } + + if (dbus_message_is_error (message, + DBUS_ERROR_NO_MEMORY)) + { + ; /* good, this is a valid response */ + } + else if (dbus_message_is_error (message, + DBUS_ERROR_SPAWN_FILE_INVALID)) + { + _dbus_verbose("got service file invalid\n"); + ; /* good, this is expected (only valid when using launch helper) */ + } + else + { + warn_unexpected (connection, message, "not this error"); + + goto out; + } + } + else + { + _dbus_warn ("Did not expect to successfully auto-start missing service\n"); + goto out; + } + + retval = TRUE; + + out: + if (message) + dbus_message_unref (message); + + return retval; +} + +#define SERVICE_EXEC_MISSING_NAME "org.freedesktop.DBus.TestSuiteNoExec" + +/* returns TRUE if the correct thing happens, + * but the correct thing may include OOM errors. + */ +static dbus_bool_t +check_launch_service_exec_missing (BusContext *context, + DBusConnection *connection) +{ + DBusMessage *message; + dbus_uint32_t serial; + dbus_bool_t retval; + + message = dbus_message_new_method_call (SERVICE_EXEC_MISSING_NAME, + "/org/freedesktop/TestSuite", + "org.freedesktop.TestSuite", + "Echo"); + + if (message == NULL) + return TRUE; + + if (!dbus_connection_send (connection, message, &serial)) + { + dbus_message_unref (message); + return TRUE; + } + + dbus_message_unref (message); + message = NULL; + + bus_test_run_everything (context); + block_connection_until_message_from_bus (context, connection, + "reply to service which should fail to auto-start (missing Exec)"); + bus_test_run_everything (context); + + if (!dbus_connection_get_is_connected (connection)) + { + _dbus_warn ("connection was disconnected: %s %d\n", _DBUS_FUNCTION_NAME, __LINE__); + return TRUE; + } + + retval = FALSE; + + message = pop_message_waiting_for_memory (connection); + if (message == NULL) + { + _dbus_warn ("Did not receive a reply to %s %d on %p\n", + "Echo message (auto activation)", serial, connection); + goto out; + } + + verbose_message_received (connection, message); + + if (dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_ERROR) + { + if (!dbus_message_has_sender (message, DBUS_SERVICE_DBUS)) + { + _dbus_warn ("Message has wrong sender %s\n", + dbus_message_get_sender (message) ? + dbus_message_get_sender (message) : "(none)"); + goto out; + } + + if (dbus_message_is_error (message, + DBUS_ERROR_NO_MEMORY)) + { + ; /* good, this is a valid response */ + } + else if (dbus_message_is_error (message, + DBUS_ERROR_SERVICE_UNKNOWN)) + { + _dbus_verbose("could not activate as invalid service file was not added\n"); + ; /* good, this is expected as we shouldn't have been added to + * the activation list with a missing Exec key */ + } + else if (dbus_message_is_error (message, + DBUS_ERROR_SPAWN_FILE_INVALID)) + { + _dbus_verbose("got service file invalid\n"); + ; /* good, this is allowed, and is the message passed back from the + * launch helper */ + } + else + { + warn_unexpected (connection, message, "not this error"); + + goto out; + } + } + else + { + _dbus_warn ("Did not expect to successfully auto-start missing service\n"); + goto out; + } + + retval = TRUE; + + out: + if (message) + dbus_message_unref (message); + + return retval; +} + +#define SERVICE_SERVICE_MISSING_NAME "org.freedesktop.DBus.TestSuiteNoService" + +/* returns TRUE if the correct thing happens, + * but the correct thing may include OOM errors. + */ +static dbus_bool_t +check_launch_service_service_missing (BusContext *context, + DBusConnection *connection) +{ + DBusMessage *message; + dbus_uint32_t serial; + dbus_bool_t retval; + + message = dbus_message_new_method_call (SERVICE_SERVICE_MISSING_NAME, + "/org/freedesktop/TestSuite", + "org.freedesktop.TestSuite", + "Echo"); + + if (message == NULL) + return TRUE; + + if (!dbus_connection_send (connection, message, &serial)) + { + dbus_message_unref (message); + return TRUE; + } + + dbus_message_unref (message); + message = NULL; + + bus_test_run_everything (context); + block_connection_until_message_from_bus (context, connection, + "reply to service which should fail to auto-start (missing Service)"); + bus_test_run_everything (context); + + if (!dbus_connection_get_is_connected (connection)) + { + _dbus_warn ("connection was disconnected: %s %d\n", _DBUS_FUNCTION_NAME, __LINE__); + return TRUE; + } + + retval = FALSE; + + message = pop_message_waiting_for_memory (connection); + if (message == NULL) + { + _dbus_warn ("Did not receive a reply to %s %d on %p\n", + "Echo message (auto activation)", serial, connection); + goto out; + } + + verbose_message_received (connection, message); + + if (dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_ERROR) + { + if (!dbus_message_has_sender (message, DBUS_SERVICE_DBUS)) + { + _dbus_warn ("Message has wrong sender %s\n", + dbus_message_get_sender (message) ? + dbus_message_get_sender (message) : "(none)"); + goto out; + } + + if (dbus_message_is_error (message, + DBUS_ERROR_NO_MEMORY)) + { + ; /* good, this is a valid response */ + } + else if (dbus_message_is_error (message, + DBUS_ERROR_SERVICE_UNKNOWN)) + { + _dbus_verbose("could not activate as invalid service file was not added\n"); + ; /* good, this is expected as we shouldn't have been added to + * the activation list with a missing Exec key */ + } + else if (dbus_message_is_error (message, + DBUS_ERROR_SPAWN_FILE_INVALID)) + { + _dbus_verbose("got service file invalid\n"); + ; /* good, this is allowed, and is the message passed back from the + * launch helper */ + } + else + { + warn_unexpected (connection, message, "not this error"); + + goto out; + } + } + else + { + _dbus_warn ("Did not expect to successfully auto-start missing service\n"); + goto out; + } + + retval = TRUE; + + out: + if (message) + dbus_message_unref (message); + + return retval; +} + +#define SHELL_FAIL_SERVICE_NAME "org.freedesktop.DBus.TestSuiteShellEchoServiceFail" + +/* returns TRUE if the correct thing happens, + * but the correct thing may include OOM errors. + */ +static dbus_bool_t +check_shell_fail_service_auto_start (BusContext *context, + DBusConnection *connection) +{ + DBusMessage *message; + dbus_uint32_t serial; + dbus_bool_t retval; + + message = dbus_message_new_method_call (SHELL_FAIL_SERVICE_NAME, + "/org/freedesktop/TestSuite", + "org.freedesktop.TestSuite", + "Echo"); + + if (message == NULL) + return TRUE; + + if (!dbus_connection_send (connection, message, &serial)) + { + dbus_message_unref (message); + return TRUE; + } + + dbus_message_unref (message); + message = NULL; + + bus_test_run_everything (context); + block_connection_until_message_from_bus (context, connection, "reply to shell Echo on service which should fail to auto-start"); + bus_test_run_everything (context); + + if (!dbus_connection_get_is_connected (connection)) + { + _dbus_verbose ("connection was disconnected: %s %d\n", _DBUS_FUNCTION_NAME, __LINE__); + return TRUE; + } + + retval = FALSE; + + message = pop_message_waiting_for_memory (connection); + if (message == NULL) + { + _dbus_warn ("Did not receive a reply to %s %d on %p\n", + "Echo message (auto activation)", serial, connection); + goto out; + } + + verbose_message_received (connection, message); + + if (dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_ERROR) + { + if (!dbus_message_has_sender (message, DBUS_SERVICE_DBUS)) + { + _dbus_warn ("Message has wrong sender %s\n", + dbus_message_get_sender (message) ? + dbus_message_get_sender (message) : "(none)"); + goto out; + } + + if (dbus_message_is_error (message, + DBUS_ERROR_NO_MEMORY)) + { + ; /* good, this is a valid response */ + } + else if (dbus_message_is_error (message, + DBUS_ERROR_INVALID_ARGS)) + { + _dbus_verbose("got invalid args\n"); + ; /* good, this is expected also */ + } + else + { + warn_unexpected (connection, message, "not this error"); + + goto out; + } + } + else + { + _dbus_warn ("Did not expect to successfully auto-start shell fail service\n"); + goto out; + } + + retval = TRUE; + + out: + if (message) + dbus_message_unref (message); + + return retval; +} + +#define SHELL_SUCCESS_SERVICE_NAME "org.freedesktop.DBus.TestSuiteShellEchoServiceSuccess" + +/* returns TRUE if the correct thing happens, + * but the correct thing may include OOM errors. + */ +static dbus_bool_t +check_shell_service_success_auto_start (BusContext *context, + DBusConnection *connection) +{ + DBusMessage *message; + DBusMessage *base_service_message; + dbus_uint32_t serial; + dbus_bool_t retval; + const char *base_service; + const char *argv[7] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL}; + + base_service_message = NULL; + + message = dbus_message_new_method_call (SHELL_SUCCESS_SERVICE_NAME, + "/org/freedesktop/TestSuite", + "org.freedesktop.TestSuite", + "Echo"); + + if (message == NULL) + return TRUE; + + if (!dbus_connection_send (connection, message, &serial)) + { + dbus_message_unref (message); + return TRUE; + } + + dbus_message_unref (message); + message = NULL; + + bus_test_run_everything (context); + + /* now wait for the message bus to hear back from the activated + * service. + */ + block_connection_until_message_from_bus (context, connection, "reply to Echo on shell success service"); + bus_test_run_everything (context); + + if (!dbus_connection_get_is_connected (connection)) + { + _dbus_verbose ("connection was disconnected: %s %d\n", _DBUS_FUNCTION_NAME, __LINE__); + return TRUE; + } + + retval = FALSE; + + message = pop_message_waiting_for_memory (connection); + if (message == NULL) + { + _dbus_warn ("Did not receive any messages after auto start %d on %p\n", + serial, connection); + goto out; + } + + verbose_message_received (connection, message); + _dbus_verbose (" (after sending %s)\n", "auto start"); + + /* we should get zero or two ServiceOwnerChanged signals */ + if (dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_SIGNAL) + { + GotServiceInfo message_kind; + + if (!check_base_service_activated (context, connection, + message, &base_service)) + goto out; + + base_service_message = message; + message = NULL; + + /* We may need to block here for the test service to exit or finish up */ + block_connection_until_message_from_bus (context, connection, "service to exit"); + + /* Should get a service creation notification for the activated + * service name, or a service deletion on the base service name + */ + message = dbus_connection_borrow_message (connection); + if (message == NULL) + { + _dbus_warn ("No message after auto activation " + "(should be a service announcement)\n"); + dbus_connection_return_message (connection, message); + message = NULL; + goto out; + } + + message_kind = check_got_service_info (message); + + dbus_connection_return_message (connection, message); + message = NULL; + + switch (message_kind) + { + case GOT_SERVICE_CREATED: + message = pop_message_waiting_for_memory (connection); + if (message == NULL) + { + _dbus_warn ("Failed to pop message we just put back! " + "should have been a NameOwnerChanged (creation)\n"); + goto out; + } + + /* Check that ServiceOwnerChanged (creation) was correctly received */ + if (!check_service_auto_activated (context, connection, SHELL_SUCCESS_SERVICE_NAME, + base_service, message)) + goto out; + + dbus_message_unref (message); + message = NULL; + + break; + + case GOT_SERVICE_DELETED: + { + /* The service started up and got a base address, but then + * failed to register under SHELL_SUCCESS_SERVICE_NAME + */ + CheckServiceOwnerChangedData socd; + + socd.expected_kind = SERVICE_DELETED; + socd.expected_service_name = base_service; + socd.failed = FALSE; + socd.skip_connection = NULL; + bus_test_clients_foreach (check_service_owner_changed_foreach, + &socd); + + if (socd.failed) + goto out; + + break; + } + + case GOT_ERROR: + case GOT_SOMETHING_ELSE: + _dbus_warn ("Unexpected message after auto activation\n"); + goto out; + } + } + + /* OK, now we've dealt with ServiceOwnerChanged signals, now should + * come the method reply (or error) from the initial method call + */ + + /* Note: if this test is run in OOM mode, it will block when the bus + * doesn't send a reply due to OOM. + */ + block_connection_until_message_from_bus (context, connection, "reply from echo message after auto-activation"); + + message = pop_message_waiting_for_memory (connection); + if (message == NULL) + { + _dbus_warn ("Failed to pop message! Should have been reply from echo message\n"); + goto out; + } + + if (dbus_message_get_reply_serial (message) != serial) + { + _dbus_warn ("Wrong reply serial\n"); + goto out; + } + + if (!dbus_message_get_args (message, NULL, + DBUS_TYPE_STRING, &argv[0], + DBUS_TYPE_STRING, &argv[1], + DBUS_TYPE_STRING, &argv[2], + DBUS_TYPE_STRING, &argv[3], + DBUS_TYPE_STRING, &argv[4], + DBUS_TYPE_STRING, &argv[5], + DBUS_TYPE_STRING, &argv[6], + DBUS_TYPE_INVALID)) + { + _dbus_warn ("Error getting arguments from return\n"); + goto out; + } + + /* don't worry about arg[0] as it may be different + depending on the path to the tests + */ + if (strcmp("-test", argv[1]) != 0) + { + _dbus_warn ("Unexpected argv[1] in shell success service test (expected: %s, got: %s)\n", + "-test", argv[1]); + goto out; + } + + if (strcmp("that", argv[2]) != 0) + { + _dbus_warn ("Unexpected argv[2] in shell success service test (expected: %s, got: %s)\n", + "that", argv[2]); + goto out; + } + + if (strcmp("we get", argv[3]) != 0) + { + _dbus_warn ("Unexpected argv[3] in shell success service test (expected: %s, got: %s)\n", + "we get", argv[3]); + goto out; + } + + if (strcmp("back", argv[4]) != 0) + { + _dbus_warn ("Unexpected argv[4] in shell success service test (expected: %s, got: %s)\n", + "back", argv[4]); + goto out; + } + + if (strcmp("--what", argv[5]) != 0) + { + _dbus_warn ("Unexpected argv[5] in shell success service test (expected: %s, got: %s)\n", + "--what", argv[5]); + goto out; + } + + if (strcmp("we put in", argv[6]) != 0) + { + _dbus_warn ("Unexpected argv[6] in shell success service test (expected: %s, got: %s)\n", + "we put in", argv[6]); + goto out; + } + + dbus_message_unref (message); + message = NULL; + + if (!check_send_exit_to_service (context, connection, + SHELL_SUCCESS_SERVICE_NAME, + base_service)) + goto out; + + retval = TRUE; + + out: + if (message) + dbus_message_unref (message); + + if (base_service_message) + dbus_message_unref (base_service_message); + + return retval; +} + +typedef struct +{ + Check1Func func; + BusContext *context; +} Check1Data; + +static dbus_bool_t +check_oom_check1_func (void *data) +{ + Check1Data *d = data; + + if (! (* d->func) (d->context)) + return FALSE; + + if (!check_no_leftovers (d->context)) + { + _dbus_warn ("Messages were left over, should be covered by test suite\n"); + return FALSE; + } + + return TRUE; +} + +static void +check1_try_iterations (BusContext *context, + const char *description, + Check1Func func) +{ + Check1Data d; + + d.func = func; + d.context = context; + + if (!_dbus_test_oom_handling (description, check_oom_check1_func, + &d)) + _dbus_assert_not_reached ("test failed"); +} + +static dbus_bool_t +check_get_services (BusContext *context, + DBusConnection *connection, + const char *method, + char ***services, + int *len) +{ + DBusMessage *message; + dbus_uint32_t serial; + dbus_bool_t retval; + DBusError error; + char **srvs; + int l; + + retval = FALSE; + dbus_error_init (&error); + message = NULL; + + message = dbus_message_new_method_call (DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + method); + + if (message == NULL) + return TRUE; + + if (!dbus_connection_send (connection, message, &serial)) + { + dbus_message_unref (message); + return TRUE; + } + + /* send our message */ + bus_test_run_clients_loop (SEND_PENDING (connection)); + + dbus_message_unref (message); + message = NULL; + + dbus_connection_ref (connection); /* because we may get disconnected */ + block_connection_until_message_from_bus (context, connection, "reply to ListActivatableNames/ListNames"); + + if (!dbus_connection_get_is_connected (connection)) + { + _dbus_verbose ("connection was disconnected: %s %d\n", _DBUS_FUNCTION_NAME, __LINE__); + + dbus_connection_unref (connection); + + return TRUE; + } + + dbus_connection_unref (connection); + + message = pop_message_waiting_for_memory (connection); + if (message == NULL) + { + _dbus_warn ("Did not receive a reply to %s %d on %p\n", + method, serial, connection); + goto out; + } + + verbose_message_received (connection, message); + + if (dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_ERROR) + { + if (dbus_message_is_error (message, DBUS_ERROR_NO_MEMORY)) + { + ; /* good, this is a valid response */ + } + else + { + warn_unexpected (connection, message, "not this error"); + + goto out; + } + } + else + { + if (dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_METHOD_RETURN) + { + ; /* good, expected */ + } + else + { + warn_unexpected (connection, message, + "method_return for ListActivatableNames/ListNames"); + + goto out; + } + + retry_get_property: + + if (!dbus_message_get_args (message, &error, + DBUS_TYPE_ARRAY, + DBUS_TYPE_STRING, + &srvs, &l, + DBUS_TYPE_INVALID)) + { + if (dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY)) + { + _dbus_verbose ("no memory to list services by %s\n", method); + dbus_error_free (&error); + _dbus_wait_for_memory (); + goto retry_get_property; + } + else + { + _dbus_assert (dbus_error_is_set (&error)); + _dbus_warn ("Did not get the expected DBUS_TYPE_ARRAY from %s\n", method); + goto out; + } + } else { + *services = srvs; + *len = l; + } + } + + if (!check_no_leftovers (context)) + goto out; + + retval = TRUE; + + out: + dbus_error_free (&error); + + if (message) + dbus_message_unref (message); + + return retval; +} + +/* returns TRUE if the correct thing happens, + * but the correct thing may include OOM errors. + */ +static dbus_bool_t +check_list_services (BusContext *context, + DBusConnection *connection) +{ + DBusMessage *message; + DBusMessage *base_service_message; + const char *base_service; + dbus_uint32_t serial; + dbus_bool_t retval; + const char *existent = EXISTENT_SERVICE_NAME; + dbus_uint32_t flags; + char **services; + int len; + + _dbus_verbose ("check_list_services for %p\n", connection); + + if (!check_get_services (context, connection, "ListActivatableNames", &services, &len)) + { + return TRUE; + } + + if (!_dbus_string_array_contains ((const char **)services, existent)) + { + _dbus_warn ("Did not get the expected %s from ListActivatableNames\n", existent); + return FALSE; + } + + dbus_free_string_array (services); + + base_service_message = NULL; + + message = dbus_message_new_method_call (DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "StartServiceByName"); + + if (message == NULL) + return TRUE; + + dbus_message_set_auto_start (message, FALSE); + + flags = 0; + if (!dbus_message_append_args (message, + DBUS_TYPE_STRING, &existent, + DBUS_TYPE_UINT32, &flags, + DBUS_TYPE_INVALID)) + { + dbus_message_unref (message); + return TRUE; + } + + if (!dbus_connection_send (connection, message, &serial)) + { + dbus_message_unref (message); + return TRUE; + } + + dbus_message_unref (message); + message = NULL; + + bus_test_run_everything (context); + + /* now wait for the message bus to hear back from the activated + * service. + */ + block_connection_until_message_from_bus (context, connection, "activated service to connect"); + + bus_test_run_everything (context); + + if (!dbus_connection_get_is_connected (connection)) + { + _dbus_verbose ("connection was disconnected: %s %d\n", _DBUS_FUNCTION_NAME, __LINE__); + return TRUE; + } + + retval = FALSE; + + message = pop_message_waiting_for_memory (connection); + if (message == NULL) + { + _dbus_warn ("Did not receive any messages after %s %d on %p\n", + "StartServiceByName", serial, connection); + goto out; + } + + verbose_message_received (connection, message); + _dbus_verbose (" (after sending %s)\n", "StartServiceByName"); + + if (dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_ERROR) + { + if (!dbus_message_has_sender (message, DBUS_SERVICE_DBUS)) + { + _dbus_warn ("Message has wrong sender %s\n", + dbus_message_get_sender (message) ? + dbus_message_get_sender (message) : "(none)"); + goto out; + } + + if (dbus_message_is_error (message, + DBUS_ERROR_NO_MEMORY)) + { + ; /* good, this is a valid response */ + } + else if (dbus_message_is_error (message, + DBUS_ERROR_SPAWN_CHILD_EXITED) || + dbus_message_is_error (message, + DBUS_ERROR_SPAWN_CHILD_SIGNALED) || + dbus_message_is_error (message, + DBUS_ERROR_SPAWN_EXEC_FAILED)) + { + ; /* good, this is expected also */ + } + else + { + _dbus_warn ("Did not expect error %s\n", + dbus_message_get_error_name (message)); + goto out; + } + } + else + { + GotServiceInfo message_kind; + + if (!check_base_service_activated (context, connection, + message, &base_service)) + goto out; + + base_service_message = message; + message = NULL; + + /* We may need to block here for the test service to exit or finish up */ + block_connection_until_message_from_bus (context, connection, "test service to exit or finish up"); + + message = dbus_connection_borrow_message (connection); + if (message == NULL) + { + _dbus_warn ("Did not receive any messages after base service creation notification\n"); + goto out; + } + + message_kind = check_got_service_info (message); + + dbus_connection_return_message (connection, message); + message = NULL; + + switch (message_kind) + { + case GOT_SOMETHING_ELSE: + case GOT_ERROR: + case GOT_SERVICE_DELETED: + _dbus_warn ("Unexpected message after ActivateService " + "(should be an error or a service announcement)\n"); + goto out; + + case GOT_SERVICE_CREATED: + message = pop_message_waiting_for_memory (connection); + if (message == NULL) + { + _dbus_warn ("Failed to pop message we just put back! " + "should have been a NameOwnerChanged (creation)\n"); + goto out; + } + + if (!check_service_activated (context, connection, EXISTENT_SERVICE_NAME, + base_service, message)) + goto out; + + dbus_message_unref (message); + message = NULL; + + if (!check_no_leftovers (context)) + { + _dbus_warn ("Messages were left over after successful activation\n"); + goto out; + } + + break; + } + } + + if (!check_get_services (context, connection, "ListNames", &services, &len)) + { + return TRUE; + } + + if (!_dbus_string_array_contains ((const char **)services, existent)) + { + _dbus_warn ("Did not get the expected %s from ListNames\n", existent); + goto out; + } + + dbus_free_string_array (services); + + if (!check_send_exit_to_service (context, connection, + EXISTENT_SERVICE_NAME, base_service)) + goto out; + + retval = TRUE; + + out: + if (message) + dbus_message_unref (message); + + if (base_service_message) + dbus_message_unref (base_service_message); + + return retval; +} + +typedef struct +{ + Check2Func func; + BusContext *context; + DBusConnection *connection; +} Check2Data; + +static dbus_bool_t +check_oom_check2_func (void *data) +{ + Check2Data *d = data; + + if (! (* d->func) (d->context, d->connection)) + return FALSE; + + if (!check_no_leftovers (d->context)) + { + _dbus_warn ("Messages were left over, should be covered by test suite\n"); + return FALSE; + } + + return TRUE; +} + +static void +check2_try_iterations (BusContext *context, + DBusConnection *connection, + const char *description, + Check2Func func) +{ + Check2Data d; + + d.func = func; + d.context = context; + d.connection = connection; + + if (!_dbus_test_oom_handling (description, check_oom_check2_func, + &d)) + { + _dbus_warn ("%s failed during oom\n", description); + _dbus_assert_not_reached ("test failed"); + } +} + +static dbus_bool_t +setenv_TEST_LAUNCH_HELPER_CONFIG(const DBusString *test_data_dir, + const char *filename) +{ + DBusString full; + DBusString file; + + if (!_dbus_string_init (&full)) + return FALSE; + + if (!_dbus_string_copy (test_data_dir, 0, &full, 0)) + { + _dbus_string_free (&full); + return FALSE; + } + + _dbus_string_init_const (&file, filename); + + if (!_dbus_concat_dir_and_file (&full, &file)) + { + _dbus_string_free (&full); + return FALSE; + } + + _dbus_verbose ("Setting TEST_LAUNCH_HELPER_CONFIG to '%s'\n", + _dbus_string_get_const_data (&full)); + + _dbus_setenv ("TEST_LAUNCH_HELPER_CONFIG", _dbus_string_get_const_data (&full)); + + _dbus_string_free (&full); + + return TRUE; +} + +static dbus_bool_t +bus_dispatch_test_conf (const DBusString *test_data_dir, + const char *filename, + dbus_bool_t use_launcher) +{ + BusContext *context; + DBusConnection *foo; + DBusConnection *bar; + DBusConnection *baz; + DBusError error; + + /* save the config name for the activation helper */ + if (!setenv_TEST_LAUNCH_HELPER_CONFIG (test_data_dir, filename)) + _dbus_assert_not_reached ("no memory setting TEST_LAUNCH_HELPER_CONFIG"); + + dbus_error_init (&error); + + context = bus_context_new_test (test_data_dir, filename); + if (context == NULL) + return FALSE; + + foo = dbus_connection_open_private ("debug-pipe:name=test-server", &error); + if (foo == NULL) + _dbus_assert_not_reached ("could not alloc connection"); + + if (!bus_setup_debug_client (foo)) + _dbus_assert_not_reached ("could not set up connection"); + + spin_connection_until_authenticated (context, foo); + + if (!check_hello_message (context, foo)) + _dbus_assert_not_reached ("hello message failed"); + + if (!check_double_hello_message (context, foo)) + _dbus_assert_not_reached ("double hello message failed"); + + if (!check_add_match_all (context, foo)) + _dbus_assert_not_reached ("AddMatch message failed"); + + bar = dbus_connection_open_private ("debug-pipe:name=test-server", &error); + if (bar == NULL) + _dbus_assert_not_reached ("could not alloc connection"); + + if (!bus_setup_debug_client (bar)) + _dbus_assert_not_reached ("could not set up connection"); + + spin_connection_until_authenticated (context, bar); + + if (!check_hello_message (context, bar)) + _dbus_assert_not_reached ("hello message failed"); + + if (!check_add_match_all (context, bar)) + _dbus_assert_not_reached ("AddMatch message failed"); + + baz = dbus_connection_open_private ("debug-pipe:name=test-server", &error); + if (baz == NULL) + _dbus_assert_not_reached ("could not alloc connection"); + + if (!bus_setup_debug_client (baz)) + _dbus_assert_not_reached ("could not set up connection"); + + spin_connection_until_authenticated (context, baz); + + if (!check_hello_message (context, baz)) + _dbus_assert_not_reached ("hello message failed"); + + if (!check_add_match_all (context, baz)) + _dbus_assert_not_reached ("AddMatch message failed"); + + if (!check_get_connection_unix_user (context, baz)) + _dbus_assert_not_reached ("GetConnectionUnixUser message failed"); + + if (!check_get_connection_unix_process_id (context, baz)) + _dbus_assert_not_reached ("GetConnectionUnixProcessID message failed"); + + if (!check_list_services (context, baz)) + _dbus_assert_not_reached ("ListActivatableNames message failed"); + + if (!check_no_leftovers (context)) + { + _dbus_warn ("Messages were left over after setting up initial connections\n"); + _dbus_assert_not_reached ("initial connection setup failed"); + } + + check1_try_iterations (context, "create_and_hello", + check_hello_connection); + + check2_try_iterations (context, foo, "nonexistent_service_no_auto_start", + check_nonexistent_service_no_auto_start); + +#ifdef DBUS_WIN_FIXME + _dbus_warn("TODO: dispatch.c segfault_service_no_auto_start test\n"); +#else + check2_try_iterations (context, foo, "segfault_service_no_auto_start", + check_segfault_service_no_auto_start); +#endif + + check2_try_iterations (context, foo, "existent_service_no_auto_start", + check_existent_service_no_auto_start); + + check2_try_iterations (context, foo, "nonexistent_service_auto_start", + check_nonexistent_service_auto_start); + + +#ifdef DBUS_WIN_FIXME + _dbus_warn("TODO: dispatch.c segfault_service_auto_start test\n"); +#else + /* only do the segfault test if we are not using the launcher */ + check2_try_iterations (context, foo, "segfault_service_auto_start", + check_segfault_service_auto_start); +#endif + + /* only do the shell fail test if we are not using the launcher */ + check2_try_iterations (context, foo, "shell_fail_service_auto_start", + check_shell_fail_service_auto_start); + + /* specific to launcher */ + if (use_launcher) + if (!check_launch_service_file_missing (context, foo)) + _dbus_assert_not_reached ("did not get service file not found error"); + +#if 0 + /* Note: need to resolve some issues with the testing code in order to run + * this in oom (handle that we sometimes don't get replies back from the bus + * when oom happens, without blocking the test). + */ + check2_try_iterations (context, foo, "existent_service_auto_auto_start", + check_existent_service_auto_start); +#endif + + if (!check_existent_service_auto_start (context, foo)) + _dbus_assert_not_reached ("existent service auto start failed"); + + if (!check_shell_service_success_auto_start (context, foo)) + _dbus_assert_not_reached ("shell success service auto start failed"); + + _dbus_verbose ("Disconnecting foo, bar, and baz\n"); + + kill_client_connection_unchecked (foo); + kill_client_connection_unchecked (bar); + kill_client_connection_unchecked (baz); + + bus_context_unref (context); + + return TRUE; +} + +static dbus_bool_t +bus_dispatch_test_conf_fail (const DBusString *test_data_dir, + const char *filename) +{ + BusContext *context; + DBusConnection *foo; + DBusError error; + + /* save the config name for the activation helper */ + if (!setenv_TEST_LAUNCH_HELPER_CONFIG (test_data_dir, filename)) + _dbus_assert_not_reached ("no memory setting TEST_LAUNCH_HELPER_CONFIG"); + + dbus_error_init (&error); + + context = bus_context_new_test (test_data_dir, filename); + if (context == NULL) + return FALSE; + + foo = dbus_connection_open_private ("debug-pipe:name=test-server", &error); + if (foo == NULL) + _dbus_assert_not_reached ("could not alloc connection"); + + if (!bus_setup_debug_client (foo)) + _dbus_assert_not_reached ("could not set up connection"); + + spin_connection_until_authenticated (context, foo); + + if (!check_hello_message (context, foo)) + _dbus_assert_not_reached ("hello message failed"); + + if (!check_double_hello_message (context, foo)) + _dbus_assert_not_reached ("double hello message failed"); + + if (!check_add_match_all (context, foo)) + _dbus_assert_not_reached ("AddMatch message failed"); + + /* this only tests the activation.c user check */ + if (!check_launch_service_user_missing (context, foo)) + _dbus_assert_not_reached ("user missing did not trigger error"); + + /* this only tests the desktop.c exec check */ + if (!check_launch_service_exec_missing (context, foo)) + _dbus_assert_not_reached ("exec missing did not trigger error"); + + /* this only tests the desktop.c service check */ + if (!check_launch_service_service_missing (context, foo)) + _dbus_assert_not_reached ("service missing did not trigger error"); + + _dbus_verbose ("Disconnecting foo\n"); + + kill_client_connection_unchecked (foo); + + bus_context_unref (context); + + return TRUE; +} + +dbus_bool_t +bus_dispatch_test (const DBusString *test_data_dir) +{ + /* run normal activation tests */ + _dbus_verbose ("Normal activation tests\n"); + if (!bus_dispatch_test_conf (test_data_dir, + "valid-config-files/debug-allow-all.conf", FALSE)) + return FALSE; + + /* run launch-helper activation tests */ + _dbus_verbose ("Launch helper activation tests\n"); + if (!bus_dispatch_test_conf (test_data_dir, + "valid-config-files-system/debug-allow-all-pass.conf", TRUE)) + return FALSE; + + /* run select launch-helper activation tests on broken service files */ + if (!bus_dispatch_test_conf_fail (test_data_dir, + "valid-config-files-system/debug-allow-all-fail.conf")) + return FALSE; + + return TRUE; +} + +dbus_bool_t +bus_dispatch_sha1_test (const DBusString *test_data_dir) +{ + BusContext *context; + DBusConnection *foo; + DBusError error; + + dbus_error_init (&error); + + /* Test SHA1 authentication */ + _dbus_verbose ("Testing SHA1 context\n"); + + context = bus_context_new_test (test_data_dir, + "valid-config-files/debug-allow-all-sha1.conf"); + if (context == NULL) + return FALSE; + + foo = dbus_connection_open_private ("debug-pipe:name=test-server", &error); + if (foo == NULL) + _dbus_assert_not_reached ("could not alloc connection"); + + if (!bus_setup_debug_client (foo)) + _dbus_assert_not_reached ("could not set up connection"); + + spin_connection_until_authenticated (context, foo); + + if (!check_hello_message (context, foo)) + _dbus_assert_not_reached ("hello message failed"); + + if (!check_add_match_all (context, foo)) + _dbus_assert_not_reached ("addmatch message failed"); + + if (!check_no_leftovers (context)) + { + _dbus_warn ("Messages were left over after setting up initial SHA-1 connection\n"); + _dbus_assert_not_reached ("initial connection setup failed"); + } + + check1_try_iterations (context, "create_and_hello_sha1", + check_hello_connection); + + kill_client_connection_unchecked (foo); + + bus_context_unref (context); + + return TRUE; +} + +#endif /* DBUS_BUILD_TESTS */ diff --git a/bus/dispatch.h b/bus/dispatch.h new file mode 100644 index 00000000..fb5ba7a5 --- /dev/null +++ b/bus/dispatch.h @@ -0,0 +1,38 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dispatch.h Message dispatcher + * + * Copyright (C) 2003 CodeFactory AB + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef BUS_DISPATCH_H +#define BUS_DISPATCH_H + +#include <dbus/dbus.h> +#include "connection.h" + +dbus_bool_t bus_dispatch_add_connection (DBusConnection *connection); +void bus_dispatch_remove_connection (DBusConnection *connection); +dbus_bool_t bus_dispatch_matches (BusTransaction *transaction, + DBusConnection *sender, + DBusConnection *recipient, + DBusMessage *message, + DBusError *error); + +#endif /* BUS_DISPATCH_H */ diff --git a/bus/driver.c b/bus/driver.c new file mode 100644 index 00000000..5e8a7a26 --- /dev/null +++ b/bus/driver.c @@ -0,0 +1,2022 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* driver.c Bus client (driver) + * + * Copyright (C) 2003 CodeFactory AB + * Copyright (C) 2003, 2004, 2005 Red Hat, Inc. + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "activation.h" +#include "connection.h" +#include "driver.h" +#include "dispatch.h" +#include "services.h" +#include "selinux.h" +#include "signals.h" +#include "utils.h" +#include <dbus/dbus-string.h> +#include <dbus/dbus-internals.h> +#include <dbus/dbus-message.h> +#include <dbus/dbus-marshal-recursive.h> +#include <string.h> + +static dbus_bool_t bus_driver_send_welcome_message (DBusConnection *connection, + DBusMessage *hello_message, + BusTransaction *transaction, + DBusError *error); + +dbus_bool_t +bus_driver_send_service_owner_changed (const char *service_name, + const char *old_owner, + const char *new_owner, + BusTransaction *transaction, + DBusError *error) +{ + DBusMessage *message; + dbus_bool_t retval; + const char *null_service; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + null_service = ""; + _dbus_verbose ("sending name owner changed: %s [%s -> %s]\n", + service_name, + old_owner ? old_owner : null_service, + new_owner ? new_owner : null_service); + + message = dbus_message_new_signal (DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "NameOwnerChanged"); + + if (message == NULL) + { + BUS_SET_OOM (error); + return FALSE; + } + + if (!dbus_message_set_sender (message, DBUS_SERVICE_DBUS)) + goto oom; + + if (!dbus_message_append_args (message, + DBUS_TYPE_STRING, &service_name, + DBUS_TYPE_STRING, old_owner ? &old_owner : &null_service, + DBUS_TYPE_STRING, new_owner ? &new_owner : &null_service, + DBUS_TYPE_INVALID)) + goto oom; + + _dbus_assert (dbus_message_has_signature (message, "sss")); + + retval = bus_dispatch_matches (transaction, NULL, NULL, message, error); + dbus_message_unref (message); + + return retval; + + oom: + dbus_message_unref (message); + BUS_SET_OOM (error); + return FALSE; +} + +dbus_bool_t +bus_driver_send_service_lost (DBusConnection *connection, + const char *service_name, + BusTransaction *transaction, + DBusError *error) +{ + DBusMessage *message; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + message = dbus_message_new_signal (DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "NameLost"); + + if (message == NULL) + { + BUS_SET_OOM (error); + return FALSE; + } + + if (!dbus_message_set_destination (message, bus_connection_get_name (connection)) || + !dbus_message_append_args (message, + DBUS_TYPE_STRING, &service_name, + DBUS_TYPE_INVALID)) + { + dbus_message_unref (message); + BUS_SET_OOM (error); + return FALSE; + } + + if (!bus_transaction_send_from_driver (transaction, connection, message)) + { + dbus_message_unref (message); + BUS_SET_OOM (error); + return FALSE; + } + else + { + dbus_message_unref (message); + return TRUE; + } +} + +dbus_bool_t +bus_driver_send_service_acquired (DBusConnection *connection, + const char *service_name, + BusTransaction *transaction, + DBusError *error) +{ + DBusMessage *message; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + message = dbus_message_new_signal (DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "NameAcquired"); + + if (message == NULL) + { + BUS_SET_OOM (error); + return FALSE; + } + + if (!dbus_message_set_destination (message, bus_connection_get_name (connection)) || + !dbus_message_append_args (message, + DBUS_TYPE_STRING, &service_name, + DBUS_TYPE_INVALID)) + { + dbus_message_unref (message); + BUS_SET_OOM (error); + return FALSE; + } + + if (!bus_transaction_send_from_driver (transaction, connection, message)) + { + dbus_message_unref (message); + BUS_SET_OOM (error); + return FALSE; + } + else + { + dbus_message_unref (message); + return TRUE; + } +} + +static dbus_bool_t +create_unique_client_name (BusRegistry *registry, + DBusString *str) +{ + /* We never want to use the same unique client name twice, because + * we want to guarantee that if you send a message to a given unique + * name, you always get the same application. So we use two numbers + * for INT_MAX * INT_MAX combinations, should be pretty safe against + * wraparound. + */ + /* FIXME these should be in BusRegistry rather than static vars */ + static int next_major_number = 0; + static int next_minor_number = 0; + int len; + + len = _dbus_string_get_length (str); + + while (TRUE) + { + /* start out with 1-0, go to 1-1, 1-2, 1-3, + * up to 1-MAXINT, then 2-0, 2-1, etc. + */ + if (next_minor_number <= 0) + { + next_major_number += 1; + next_minor_number = 0; + if (next_major_number <= 0) + _dbus_assert_not_reached ("INT_MAX * INT_MAX clients were added"); + } + + _dbus_assert (next_major_number > 0); + _dbus_assert (next_minor_number >= 0); + + /* appname:MAJOR-MINOR */ + + if (!_dbus_string_append (str, ":")) + return FALSE; + + if (!_dbus_string_append_int (str, next_major_number)) + return FALSE; + + if (!_dbus_string_append (str, ".")) + return FALSE; + + if (!_dbus_string_append_int (str, next_minor_number)) + return FALSE; + + next_minor_number += 1; + + /* Check if a client with the name exists */ + if (bus_registry_lookup (registry, str) == NULL) + break; + + /* drop the number again, try the next one. */ + _dbus_string_set_length (str, len); + } + + return TRUE; +} + +static dbus_bool_t +bus_driver_handle_hello (DBusConnection *connection, + BusTransaction *transaction, + DBusMessage *message, + DBusError *error) +{ + DBusString unique_name; + BusService *service; + dbus_bool_t retval; + BusRegistry *registry; + BusConnections *connections; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + if (bus_connection_is_active (connection)) + { + /* We already handled an Hello message for this connection. */ + dbus_set_error (error, DBUS_ERROR_FAILED, + "Already handled an Hello message"); + return FALSE; + } + + /* Note that when these limits are exceeded we don't disconnect the + * connection; we just sort of leave it hanging there until it times + * out or disconnects itself or is dropped due to the max number of + * incomplete connections. It's even OK if the connection wants to + * retry the hello message, we support that. + */ + connections = bus_connection_get_connections (connection); + if (!bus_connections_check_limits (connections, connection, + error)) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + return FALSE; + } + + if (!_dbus_string_init (&unique_name)) + { + BUS_SET_OOM (error); + return FALSE; + } + + retval = FALSE; + + registry = bus_connection_get_registry (connection); + + if (!create_unique_client_name (registry, &unique_name)) + { + BUS_SET_OOM (error); + goto out_0; + } + + if (!bus_connection_complete (connection, &unique_name, error)) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + goto out_0; + } + + if (!dbus_message_set_sender (message, + bus_connection_get_name (connection))) + { + BUS_SET_OOM (error); + goto out_0; + } + + if (!bus_driver_send_welcome_message (connection, message, transaction, error)) + goto out_0; + + /* Create the service */ + service = bus_registry_ensure (registry, + &unique_name, connection, 0, transaction, error); + if (service == NULL) + goto out_0; + + _dbus_assert (bus_connection_is_active (connection)); + retval = TRUE; + + out_0: + _dbus_string_free (&unique_name); + return retval; +} + +static dbus_bool_t +bus_driver_send_welcome_message (DBusConnection *connection, + DBusMessage *hello_message, + BusTransaction *transaction, + DBusError *error) +{ + DBusMessage *welcome; + const char *name; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + name = bus_connection_get_name (connection); + _dbus_assert (name != NULL); + + welcome = dbus_message_new_method_return (hello_message); + if (welcome == NULL) + { + BUS_SET_OOM (error); + return FALSE; + } + + if (!dbus_message_append_args (welcome, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) + { + dbus_message_unref (welcome); + BUS_SET_OOM (error); + return FALSE; + } + + _dbus_assert (dbus_message_has_signature (welcome, DBUS_TYPE_STRING_AS_STRING)); + + if (!bus_transaction_send_from_driver (transaction, connection, welcome)) + { + dbus_message_unref (welcome); + BUS_SET_OOM (error); + return FALSE; + } + else + { + dbus_message_unref (welcome); + return TRUE; + } +} + +static dbus_bool_t +bus_driver_handle_list_services (DBusConnection *connection, + BusTransaction *transaction, + DBusMessage *message, + DBusError *error) +{ + DBusMessage *reply; + int len; + char **services; + BusRegistry *registry; + int i; + DBusMessageIter iter; + DBusMessageIter sub; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + registry = bus_connection_get_registry (connection); + + reply = dbus_message_new_method_return (message); + if (reply == NULL) + { + BUS_SET_OOM (error); + return FALSE; + } + + if (!bus_registry_list_services (registry, &services, &len)) + { + dbus_message_unref (reply); + BUS_SET_OOM (error); + return FALSE; + } + + dbus_message_iter_init_append (reply, &iter); + + if (!dbus_message_iter_open_container (&iter, DBUS_TYPE_ARRAY, + DBUS_TYPE_STRING_AS_STRING, + &sub)) + { + dbus_free_string_array (services); + dbus_message_unref (reply); + BUS_SET_OOM (error); + return FALSE; + } + + { + /* Include the bus driver in the list */ + const char *v_STRING = DBUS_SERVICE_DBUS; + if (!dbus_message_iter_append_basic (&sub, DBUS_TYPE_STRING, + &v_STRING)) + { + dbus_free_string_array (services); + dbus_message_unref (reply); + BUS_SET_OOM (error); + return FALSE; + } + } + + i = 0; + while (i < len) + { + if (!dbus_message_iter_append_basic (&sub, DBUS_TYPE_STRING, + &services[i])) + { + dbus_free_string_array (services); + dbus_message_unref (reply); + BUS_SET_OOM (error); + return FALSE; + } + ++i; + } + + dbus_free_string_array (services); + + if (!dbus_message_iter_close_container (&iter, &sub)) + { + dbus_message_unref (reply); + BUS_SET_OOM (error); + return FALSE; + } + + if (!bus_transaction_send_from_driver (transaction, connection, reply)) + { + dbus_message_unref (reply); + BUS_SET_OOM (error); + return FALSE; + } + else + { + dbus_message_unref (reply); + return TRUE; + } +} + +static dbus_bool_t +bus_driver_handle_list_activatable_services (DBusConnection *connection, + BusTransaction *transaction, + DBusMessage *message, + DBusError *error) +{ + DBusMessage *reply; + int len; + char **services; + BusActivation *activation; + int i; + DBusMessageIter iter; + DBusMessageIter sub; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + activation = bus_connection_get_activation (connection); + + reply = dbus_message_new_method_return (message); + if (reply == NULL) + { + BUS_SET_OOM (error); + return FALSE; + } + + if (!bus_activation_list_services (activation, &services, &len)) + { + dbus_message_unref (reply); + BUS_SET_OOM (error); + return FALSE; + } + + dbus_message_iter_init_append (reply, &iter); + + if (!dbus_message_iter_open_container (&iter, DBUS_TYPE_ARRAY, + DBUS_TYPE_STRING_AS_STRING, + &sub)) + { + dbus_free_string_array (services); + dbus_message_unref (reply); + BUS_SET_OOM (error); + return FALSE; + } + + { + /* Include the bus driver in the list */ + const char *v_STRING = DBUS_SERVICE_DBUS; + if (!dbus_message_iter_append_basic (&sub, DBUS_TYPE_STRING, + &v_STRING)) + { + dbus_free_string_array (services); + dbus_message_unref (reply); + BUS_SET_OOM (error); + return FALSE; + } + } + + i = 0; + while (i < len) + { + if (!dbus_message_iter_append_basic (&sub, DBUS_TYPE_STRING, + &services[i])) + { + dbus_free_string_array (services); + dbus_message_unref (reply); + BUS_SET_OOM (error); + return FALSE; + } + ++i; + } + + dbus_free_string_array (services); + + if (!dbus_message_iter_close_container (&iter, &sub)) + { + dbus_message_unref (reply); + BUS_SET_OOM (error); + return FALSE; + } + + if (!bus_transaction_send_from_driver (transaction, connection, reply)) + { + dbus_message_unref (reply); + BUS_SET_OOM (error); + return FALSE; + } + else + { + dbus_message_unref (reply); + return TRUE; + } +} + +static dbus_bool_t +bus_driver_handle_acquire_service (DBusConnection *connection, + BusTransaction *transaction, + DBusMessage *message, + DBusError *error) +{ + DBusMessage *reply; + DBusString service_name; + const char *name; + dbus_uint32_t service_reply; + dbus_uint32_t flags; + dbus_bool_t retval; + BusRegistry *registry; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + registry = bus_connection_get_registry (connection); + + if (!dbus_message_get_args (message, error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_UINT32, &flags, + DBUS_TYPE_INVALID)) + return FALSE; + + _dbus_verbose ("Trying to own name %s with flags 0x%x\n", name, flags); + + retval = FALSE; + reply = NULL; + + _dbus_string_init_const (&service_name, name); + + if (!bus_registry_acquire_service (registry, connection, + &service_name, flags, + &service_reply, transaction, + error)) + goto out; + + reply = dbus_message_new_method_return (message); + if (reply == NULL) + { + BUS_SET_OOM (error); + goto out; + } + + if (!dbus_message_append_args (reply, DBUS_TYPE_UINT32, &service_reply, DBUS_TYPE_INVALID)) + { + BUS_SET_OOM (error); + goto out; + } + + if (!bus_transaction_send_from_driver (transaction, connection, reply)) + { + BUS_SET_OOM (error); + goto out; + } + + retval = TRUE; + + out: + if (reply) + dbus_message_unref (reply); + return retval; +} + +static dbus_bool_t +bus_driver_handle_release_service (DBusConnection *connection, + BusTransaction *transaction, + DBusMessage *message, + DBusError *error) +{ + DBusMessage *reply; + DBusString service_name; + const char *name; + dbus_uint32_t service_reply; + dbus_bool_t retval; + BusRegistry *registry; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + registry = bus_connection_get_registry (connection); + + if (!dbus_message_get_args (message, error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) + return FALSE; + + _dbus_verbose ("Trying to release name %s\n", name); + + retval = FALSE; + reply = NULL; + + _dbus_string_init_const (&service_name, name); + + if (!bus_registry_release_service (registry, connection, + &service_name, &service_reply, + transaction, error)) + goto out; + + reply = dbus_message_new_method_return (message); + if (reply == NULL) + { + BUS_SET_OOM (error); + goto out; + } + + if (!dbus_message_append_args (reply, DBUS_TYPE_UINT32, &service_reply, DBUS_TYPE_INVALID)) + { + BUS_SET_OOM (error); + goto out; + } + + if (!bus_transaction_send_from_driver (transaction, connection, reply)) + { + BUS_SET_OOM (error); + goto out; + } + + retval = TRUE; + + out: + if (reply) + dbus_message_unref (reply); + return retval; +} + +static dbus_bool_t +bus_driver_handle_service_exists (DBusConnection *connection, + BusTransaction *transaction, + DBusMessage *message, + DBusError *error) +{ + DBusMessage *reply; + DBusString service_name; + BusService *service; + dbus_bool_t service_exists; + const char *name; + dbus_bool_t retval; + BusRegistry *registry; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + registry = bus_connection_get_registry (connection); + + if (!dbus_message_get_args (message, error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) + return FALSE; + + retval = FALSE; + + if (strcmp (name, DBUS_SERVICE_DBUS) == 0) + { + service_exists = TRUE; + } + else + { + _dbus_string_init_const (&service_name, name); + service = bus_registry_lookup (registry, &service_name); + service_exists = service != NULL; + } + + reply = dbus_message_new_method_return (message); + if (reply == NULL) + { + BUS_SET_OOM (error); + goto out; + } + + if (!dbus_message_append_args (reply, + DBUS_TYPE_BOOLEAN, &service_exists, + 0)) + { + BUS_SET_OOM (error); + goto out; + } + + if (!bus_transaction_send_from_driver (transaction, connection, reply)) + { + BUS_SET_OOM (error); + goto out; + } + + retval = TRUE; + + out: + if (reply) + dbus_message_unref (reply); + + return retval; +} + +static dbus_bool_t +bus_driver_handle_activate_service (DBusConnection *connection, + BusTransaction *transaction, + DBusMessage *message, + DBusError *error) +{ + dbus_uint32_t flags; + const char *name; + dbus_bool_t retval; + BusActivation *activation; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + activation = bus_connection_get_activation (connection); + + if (!dbus_message_get_args (message, error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_UINT32, &flags, + DBUS_TYPE_INVALID)) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + _dbus_verbose ("No memory to get arguments to StartServiceByName\n"); + return FALSE; + } + + retval = FALSE; + + if (!bus_activation_activate_service (activation, connection, transaction, FALSE, + message, name, error)) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + _dbus_verbose ("bus_activation_activate_service() failed\n"); + goto out; + } + + retval = TRUE; + + out: + return retval; +} + +static dbus_bool_t +send_ack_reply (DBusConnection *connection, + BusTransaction *transaction, + DBusMessage *message, + DBusError *error) +{ + DBusMessage *reply; + + if (dbus_message_get_no_reply (message)) + return TRUE; + + reply = dbus_message_new_method_return (message); + if (reply == NULL) + { + BUS_SET_OOM (error); + return FALSE; + } + + if (!bus_transaction_send_from_driver (transaction, connection, reply)) + { + BUS_SET_OOM (error); + dbus_message_unref (reply); + return FALSE; + } + + dbus_message_unref (reply); + + return TRUE; +} + +static dbus_bool_t +bus_driver_handle_update_activation_environment (DBusConnection *connection, + BusTransaction *transaction, + DBusMessage *message, + DBusError *error) +{ + dbus_bool_t retval; + BusActivation *activation; + DBusMessageIter iter; + DBusMessageIter dict_iter; + DBusMessageIter dict_entry_iter; + int msg_type; + int array_type; + int key_type; + DBusList *keys, *key_link; + DBusList *values, *value_link; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + activation = bus_connection_get_activation (connection); + + dbus_message_iter_init (message, &iter); + + /* The message signature has already been checked for us, + * so let's just assert it's right. + */ + msg_type = dbus_message_iter_get_arg_type (&iter); + + _dbus_assert (msg_type == DBUS_TYPE_ARRAY); + + dbus_message_iter_recurse (&iter, &dict_iter); + + retval = FALSE; + + /* Then loop through the sent dictionary, add the location of + * the environment keys and values to lists. The result will + * be in reverse order, so we don't have to constantly search + * for the end of the list in a loop. + */ + keys = NULL; + values = NULL; + while ((array_type = dbus_message_iter_get_arg_type (&dict_iter)) == DBUS_TYPE_DICT_ENTRY) + { + dbus_message_iter_recurse (&dict_iter, &dict_entry_iter); + + while ((key_type = dbus_message_iter_get_arg_type (&dict_entry_iter)) == DBUS_TYPE_STRING) + { + char *key; + char *value; + int value_type; + + dbus_message_iter_get_basic (&dict_entry_iter, &key); + dbus_message_iter_next (&dict_entry_iter); + + value_type = dbus_message_iter_get_arg_type (&dict_entry_iter); + + if (value_type != DBUS_TYPE_STRING) + break; + + dbus_message_iter_get_basic (&dict_entry_iter, &value); + + if (!_dbus_list_append (&keys, key)) + { + BUS_SET_OOM (error); + break; + } + + if (!_dbus_list_append (&values, value)) + { + BUS_SET_OOM (error); + break; + } + + dbus_message_iter_next (&dict_entry_iter); + } + + if (key_type != DBUS_TYPE_INVALID) + break; + + dbus_message_iter_next (&dict_iter); + } + + if (array_type != DBUS_TYPE_INVALID) + goto out; + + _dbus_assert (_dbus_list_get_length (&keys) == _dbus_list_get_length (&values)); + + key_link = keys; + value_link = values; + while (key_link != NULL) + { + const char *key; + const char *value; + + key = key_link->data; + value = value_link->data; + + if (!bus_activation_set_environment_variable (activation, + key, value, error)) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + _dbus_verbose ("bus_activation_set_environment_variable() failed\n"); + break; + } + key_link = _dbus_list_get_next_link (&keys, key_link); + value_link = _dbus_list_get_next_link (&values, value_link); + } + + /* FIXME: We can fail early having set only some of the environment variables, + * (because of OOM failure). It's sort of hard to fix and it doesn't really + * matter, so we're punting for now. + */ + if (key_link != NULL) + goto out; + + if (!send_ack_reply (connection, transaction, + message, error)) + goto out; + + retval = TRUE; + + out: + _dbus_list_clear (&keys); + _dbus_list_clear (&values); + return retval; +} + +static dbus_bool_t +bus_driver_handle_add_match (DBusConnection *connection, + BusTransaction *transaction, + DBusMessage *message, + DBusError *error) +{ + BusMatchRule *rule; + const char *text; + DBusString str; + BusMatchmaker *matchmaker; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + text = NULL; + rule = NULL; + + if (bus_connection_get_n_match_rules (connection) >= + bus_context_get_max_match_rules_per_connection (bus_transaction_get_context (transaction))) + { + dbus_set_error (error, DBUS_ERROR_LIMITS_EXCEEDED, + "Connection \"%s\" is not allowed to add more match rules " + "(increase limits in configuration file if required)", + bus_connection_is_active (connection) ? + bus_connection_get_name (connection) : + "(inactive)"); + goto failed; + } + + if (!dbus_message_get_args (message, error, + DBUS_TYPE_STRING, &text, + DBUS_TYPE_INVALID)) + { + _dbus_verbose ("No memory to get arguments to AddMatch\n"); + goto failed; + } + + _dbus_string_init_const (&str, text); + + rule = bus_match_rule_parse (connection, &str, error); + if (rule == NULL) + goto failed; + + matchmaker = bus_connection_get_matchmaker (connection); + + if (!bus_matchmaker_add_rule (matchmaker, rule)) + { + BUS_SET_OOM (error); + goto failed; + } + + if (!send_ack_reply (connection, transaction, + message, error)) + { + bus_matchmaker_remove_rule (matchmaker, rule); + goto failed; + } + + bus_match_rule_unref (rule); + + return TRUE; + + failed: + _DBUS_ASSERT_ERROR_IS_SET (error); + if (rule) + bus_match_rule_unref (rule); + return FALSE; +} + +static dbus_bool_t +bus_driver_handle_remove_match (DBusConnection *connection, + BusTransaction *transaction, + DBusMessage *message, + DBusError *error) +{ + BusMatchRule *rule; + const char *text; + DBusString str; + BusMatchmaker *matchmaker; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + text = NULL; + rule = NULL; + + if (!dbus_message_get_args (message, error, + DBUS_TYPE_STRING, &text, + DBUS_TYPE_INVALID)) + { + _dbus_verbose ("No memory to get arguments to RemoveMatch\n"); + goto failed; + } + + _dbus_string_init_const (&str, text); + + rule = bus_match_rule_parse (connection, &str, error); + if (rule == NULL) + goto failed; + + /* Send the ack before we remove the rule, since the ack is undone + * on transaction cancel, but rule removal isn't. + */ + if (!send_ack_reply (connection, transaction, + message, error)) + goto failed; + + matchmaker = bus_connection_get_matchmaker (connection); + + if (!bus_matchmaker_remove_rule_by_value (matchmaker, rule, error)) + goto failed; + + bus_match_rule_unref (rule); + + return TRUE; + + failed: + _DBUS_ASSERT_ERROR_IS_SET (error); + if (rule) + bus_match_rule_unref (rule); + return FALSE; +} + +static dbus_bool_t +bus_driver_handle_get_service_owner (DBusConnection *connection, + BusTransaction *transaction, + DBusMessage *message, + DBusError *error) +{ + const char *text; + const char *base_name; + DBusString str; + BusRegistry *registry; + BusService *service; + DBusMessage *reply; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + registry = bus_connection_get_registry (connection); + + text = NULL; + reply = NULL; + + if (! dbus_message_get_args (message, error, + DBUS_TYPE_STRING, &text, + DBUS_TYPE_INVALID)) + goto failed; + + _dbus_string_init_const (&str, text); + service = bus_registry_lookup (registry, &str); + if (service == NULL && + _dbus_string_equal_c_str (&str, DBUS_SERVICE_DBUS)) + { + /* ORG_FREEDESKTOP_DBUS owns itself */ + base_name = DBUS_SERVICE_DBUS; + } + else if (service == NULL) + { + dbus_set_error (error, + DBUS_ERROR_NAME_HAS_NO_OWNER, + "Could not get owner of name '%s': no such name", text); + goto failed; + } + else + { + base_name = bus_connection_get_name (bus_service_get_primary_owners_connection (service)); + if (base_name == NULL) + { + /* FIXME - how is this error possible? */ + dbus_set_error (error, + DBUS_ERROR_FAILED, + "Could not determine unique name for '%s'", text); + goto failed; + } + _dbus_assert (*base_name == ':'); + } + + _dbus_assert (base_name != NULL); + + reply = dbus_message_new_method_return (message); + if (reply == NULL) + goto oom; + + if (! dbus_message_append_args (reply, + DBUS_TYPE_STRING, &base_name, + DBUS_TYPE_INVALID)) + goto oom; + + if (! bus_transaction_send_from_driver (transaction, connection, reply)) + goto oom; + + dbus_message_unref (reply); + + return TRUE; + + oom: + BUS_SET_OOM (error); + + failed: + _DBUS_ASSERT_ERROR_IS_SET (error); + if (reply) + dbus_message_unref (reply); + return FALSE; +} + +static dbus_bool_t +bus_driver_handle_list_queued_owners (DBusConnection *connection, + BusTransaction *transaction, + DBusMessage *message, + DBusError *error) +{ + const char *text; + DBusList *base_names; + DBusList *link; + DBusString str; + BusRegistry *registry; + BusService *service; + DBusMessage *reply; + DBusMessageIter iter, array_iter; + char *dbus_service_name = DBUS_SERVICE_DBUS; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + registry = bus_connection_get_registry (connection); + + base_names = NULL; + text = NULL; + reply = NULL; + + if (! dbus_message_get_args (message, error, + DBUS_TYPE_STRING, &text, + DBUS_TYPE_INVALID)) + goto failed; + + _dbus_string_init_const (&str, text); + service = bus_registry_lookup (registry, &str); + if (service == NULL && + _dbus_string_equal_c_str (&str, DBUS_SERVICE_DBUS)) + { + /* ORG_FREEDESKTOP_DBUS owns itself */ + if (! _dbus_list_append (&base_names, dbus_service_name)) + goto oom; + } + else if (service == NULL) + { + dbus_set_error (error, + DBUS_ERROR_NAME_HAS_NO_OWNER, + "Could not get owners of name '%s': no such name", text); + goto failed; + } + else + { + if (!bus_service_list_queued_owners (service, + &base_names, + error)) + goto failed; + } + + _dbus_assert (base_names != NULL); + + reply = dbus_message_new_method_return (message); + if (reply == NULL) + goto oom; + + dbus_message_iter_init_append (reply, &iter); + if (!dbus_message_iter_open_container (&iter, + DBUS_TYPE_ARRAY, + DBUS_TYPE_STRING_AS_STRING, + &array_iter)) + goto oom; + + link = _dbus_list_get_first_link (&base_names); + while (link != NULL) + { + char *uname; + + _dbus_assert (link->data != NULL); + uname = (char *)link->data; + + if (!dbus_message_iter_append_basic (&array_iter, + DBUS_TYPE_STRING, + &uname)) + goto oom; + + link = _dbus_list_get_next_link (&base_names, link); + } + + if (! dbus_message_iter_close_container (&iter, &array_iter)) + goto oom; + + + if (! bus_transaction_send_from_driver (transaction, connection, reply)) + goto oom; + + dbus_message_unref (reply); + + return TRUE; + + oom: + BUS_SET_OOM (error); + + failed: + _DBUS_ASSERT_ERROR_IS_SET (error); + if (reply) + dbus_message_unref (reply); + + if (base_names) + _dbus_list_clear (&base_names); + + return FALSE; +} + +static dbus_bool_t +bus_driver_handle_get_connection_unix_user (DBusConnection *connection, + BusTransaction *transaction, + DBusMessage *message, + DBusError *error) +{ + const char *service; + DBusString str; + BusRegistry *registry; + BusService *serv; + DBusConnection *conn; + DBusMessage *reply; + unsigned long uid; + dbus_uint32_t uid32; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + registry = bus_connection_get_registry (connection); + + service = NULL; + reply = NULL; + + if (! dbus_message_get_args (message, error, + DBUS_TYPE_STRING, &service, + DBUS_TYPE_INVALID)) + goto failed; + + _dbus_verbose ("asked for UID of connection %s\n", service); + + _dbus_string_init_const (&str, service); + serv = bus_registry_lookup (registry, &str); + if (serv == NULL) + { + dbus_set_error (error, + DBUS_ERROR_NAME_HAS_NO_OWNER, + "Could not get UID of name '%s': no such name", service); + goto failed; + } + + conn = bus_service_get_primary_owners_connection (serv); + + reply = dbus_message_new_method_return (message); + if (reply == NULL) + goto oom; + + if (!dbus_connection_get_unix_user (conn, &uid)) + { + dbus_set_error (error, + DBUS_ERROR_FAILED, + "Could not determine UID for '%s'", service); + goto failed; + } + + uid32 = uid; + if (! dbus_message_append_args (reply, + DBUS_TYPE_UINT32, &uid32, + DBUS_TYPE_INVALID)) + goto oom; + + if (! bus_transaction_send_from_driver (transaction, connection, reply)) + goto oom; + + dbus_message_unref (reply); + + return TRUE; + + oom: + BUS_SET_OOM (error); + + failed: + _DBUS_ASSERT_ERROR_IS_SET (error); + if (reply) + dbus_message_unref (reply); + return FALSE; +} + +static dbus_bool_t +bus_driver_handle_get_connection_unix_process_id (DBusConnection *connection, + BusTransaction *transaction, + DBusMessage *message, + DBusError *error) +{ + const char *service; + DBusString str; + BusRegistry *registry; + BusService *serv; + DBusConnection *conn; + DBusMessage *reply; + unsigned long pid; + dbus_uint32_t pid32; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + registry = bus_connection_get_registry (connection); + + service = NULL; + reply = NULL; + + if (! dbus_message_get_args (message, error, + DBUS_TYPE_STRING, &service, + DBUS_TYPE_INVALID)) + goto failed; + + _dbus_verbose ("asked for PID of connection %s\n", service); + + _dbus_string_init_const (&str, service); + serv = bus_registry_lookup (registry, &str); + if (serv == NULL) + { + dbus_set_error (error, + DBUS_ERROR_NAME_HAS_NO_OWNER, + "Could not get PID of name '%s': no such name", service); + goto failed; + } + + conn = bus_service_get_primary_owners_connection (serv); + + reply = dbus_message_new_method_return (message); + if (reply == NULL) + goto oom; + + if (!dbus_connection_get_unix_process_id (conn, &pid)) + { + dbus_set_error (error, + DBUS_ERROR_UNIX_PROCESS_ID_UNKNOWN, + "Could not determine PID for '%s'", service); + goto failed; + } + + pid32 = pid; + if (! dbus_message_append_args (reply, + DBUS_TYPE_UINT32, &pid32, + DBUS_TYPE_INVALID)) + goto oom; + + if (! bus_transaction_send_from_driver (transaction, connection, reply)) + goto oom; + + dbus_message_unref (reply); + + return TRUE; + + oom: + BUS_SET_OOM (error); + + failed: + _DBUS_ASSERT_ERROR_IS_SET (error); + if (reply) + dbus_message_unref (reply); + return FALSE; +} + +static dbus_bool_t +bus_driver_handle_get_adt_audit_session_data (DBusConnection *connection, + BusTransaction *transaction, + DBusMessage *message, + DBusError *error) +{ + const char *service; + DBusString str; + BusRegistry *registry; + BusService *serv; + DBusConnection *conn; + DBusMessage *reply; + void *data = NULL; + dbus_uint32_t data_size; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + registry = bus_connection_get_registry (connection); + + service = NULL; + reply = NULL; + + if (! dbus_message_get_args (message, error, + DBUS_TYPE_STRING, &service, + DBUS_TYPE_INVALID)) + goto failed; + + _dbus_verbose ("asked for audit session data for connection %s\n", service); + + _dbus_string_init_const (&str, service); + serv = bus_registry_lookup (registry, &str); + if (serv == NULL) + { + dbus_set_error (error, + DBUS_ERROR_NAME_HAS_NO_OWNER, + "Could not get audit session data for name '%s': no such name", service); + goto failed; + } + + conn = bus_service_get_primary_owners_connection (serv); + + reply = dbus_message_new_method_return (message); + if (reply == NULL) + goto oom; + + if (!dbus_connection_get_adt_audit_session_data (conn, &data, &data_size) || data == NULL) + { + dbus_set_error (error, + DBUS_ERROR_ADT_AUDIT_DATA_UNKNOWN, + "Could not determine audit session data for '%s'", service); + goto failed; + } + + if (! dbus_message_append_args (reply, + DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &data, data_size, + DBUS_TYPE_INVALID)) + goto oom; + + if (! bus_transaction_send_from_driver (transaction, connection, reply)) + goto oom; + + dbus_message_unref (reply); + + return TRUE; + + oom: + BUS_SET_OOM (error); + + failed: + _DBUS_ASSERT_ERROR_IS_SET (error); + if (reply) + dbus_message_unref (reply); + return FALSE; +} + +static dbus_bool_t +bus_driver_handle_get_connection_selinux_security_context (DBusConnection *connection, + BusTransaction *transaction, + DBusMessage *message, + DBusError *error) +{ + const char *service; + DBusString str; + BusRegistry *registry; + BusService *serv; + DBusConnection *conn; + DBusMessage *reply; + BusSELinuxID *context; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + registry = bus_connection_get_registry (connection); + + service = NULL; + reply = NULL; + + if (! dbus_message_get_args (message, error, + DBUS_TYPE_STRING, &service, + DBUS_TYPE_INVALID)) + goto failed; + + _dbus_verbose ("asked for security context of connection %s\n", service); + + _dbus_string_init_const (&str, service); + serv = bus_registry_lookup (registry, &str); + if (serv == NULL) + { + dbus_set_error (error, + DBUS_ERROR_NAME_HAS_NO_OWNER, + "Could not get security context of name '%s': no such name", service); + goto failed; + } + + conn = bus_service_get_primary_owners_connection (serv); + + reply = dbus_message_new_method_return (message); + if (reply == NULL) + goto oom; + + context = bus_connection_get_selinux_id (conn); + if (!context) + { + dbus_set_error (error, + DBUS_ERROR_SELINUX_SECURITY_CONTEXT_UNKNOWN, + "Could not determine security context for '%s'", service); + goto failed; + } + + if (! bus_selinux_append_context (reply, context, error)) + goto failed; + + if (! bus_transaction_send_from_driver (transaction, connection, reply)) + goto oom; + + dbus_message_unref (reply); + + return TRUE; + + oom: + BUS_SET_OOM (error); + + failed: + _DBUS_ASSERT_ERROR_IS_SET (error); + if (reply) + dbus_message_unref (reply); + return FALSE; +} + +static dbus_bool_t +bus_driver_handle_reload_config (DBusConnection *connection, + BusTransaction *transaction, + DBusMessage *message, + DBusError *error) +{ + BusContext *context; + DBusMessage *reply; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + reply = NULL; + + context = bus_connection_get_context (connection); + if (!bus_context_reload_config (context, error)) + goto failed; + + reply = dbus_message_new_method_return (message); + if (reply == NULL) + goto oom; + + if (! bus_transaction_send_from_driver (transaction, connection, reply)) + goto oom; + + dbus_message_unref (reply); + return TRUE; + + oom: + BUS_SET_OOM (error); + + failed: + _DBUS_ASSERT_ERROR_IS_SET (error); + if (reply) + dbus_message_unref (reply); + return FALSE; +} + +static dbus_bool_t +bus_driver_handle_get_id (DBusConnection *connection, + BusTransaction *transaction, + DBusMessage *message, + DBusError *error) +{ + BusContext *context; + DBusMessage *reply; + DBusString uuid; + const char *v_STRING; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + if (!_dbus_string_init (&uuid)) + { + BUS_SET_OOM (error); + return FALSE; + } + + reply = NULL; + + context = bus_connection_get_context (connection); + if (!bus_context_get_id (context, &uuid)) + goto oom; + + reply = dbus_message_new_method_return (message); + if (reply == NULL) + goto oom; + + v_STRING = _dbus_string_get_const_data (&uuid); + if (!dbus_message_append_args (reply, + DBUS_TYPE_STRING, &v_STRING, + DBUS_TYPE_INVALID)) + goto oom; + + _dbus_assert (dbus_message_has_signature (reply, "s")); + + if (! bus_transaction_send_from_driver (transaction, connection, reply)) + goto oom; + + _dbus_string_free (&uuid); + dbus_message_unref (reply); + return TRUE; + + oom: + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + BUS_SET_OOM (error); + + if (reply) + dbus_message_unref (reply); + _dbus_string_free (&uuid); + return FALSE; +} + +/* For speed it might be useful to sort this in order of + * frequency of use (but doesn't matter with only a few items + * anyhow) + */ +static struct +{ + const char *name; + const char *in_args; + const char *out_args; + dbus_bool_t (* handler) (DBusConnection *connection, + BusTransaction *transaction, + DBusMessage *message, + DBusError *error); +} message_handlers[] = { + { "Hello", + "", + DBUS_TYPE_STRING_AS_STRING, + bus_driver_handle_hello }, + { "RequestName", + DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_UINT32_AS_STRING, + DBUS_TYPE_UINT32_AS_STRING, + bus_driver_handle_acquire_service }, + { "ReleaseName", + DBUS_TYPE_STRING_AS_STRING, + DBUS_TYPE_UINT32_AS_STRING, + bus_driver_handle_release_service }, + { "StartServiceByName", + DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_UINT32_AS_STRING, + DBUS_TYPE_UINT32_AS_STRING, + bus_driver_handle_activate_service }, + { "UpdateActivationEnvironment", + DBUS_TYPE_ARRAY_AS_STRING DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, + "", + bus_driver_handle_update_activation_environment }, + { "NameHasOwner", + DBUS_TYPE_STRING_AS_STRING, + DBUS_TYPE_BOOLEAN_AS_STRING, + bus_driver_handle_service_exists }, + { "ListNames", + "", + DBUS_TYPE_ARRAY_AS_STRING DBUS_TYPE_STRING_AS_STRING, + bus_driver_handle_list_services }, + { "ListActivatableNames", + "", + DBUS_TYPE_ARRAY_AS_STRING DBUS_TYPE_STRING_AS_STRING, + bus_driver_handle_list_activatable_services }, + { "AddMatch", + DBUS_TYPE_STRING_AS_STRING, + "", + bus_driver_handle_add_match }, + { "RemoveMatch", + DBUS_TYPE_STRING_AS_STRING, + "", + bus_driver_handle_remove_match }, + { "GetNameOwner", + DBUS_TYPE_STRING_AS_STRING, + DBUS_TYPE_STRING_AS_STRING, + bus_driver_handle_get_service_owner }, + { "ListQueuedOwners", + DBUS_TYPE_STRING_AS_STRING, + DBUS_TYPE_ARRAY_AS_STRING DBUS_TYPE_STRING_AS_STRING, + bus_driver_handle_list_queued_owners }, + { "GetConnectionUnixUser", + DBUS_TYPE_STRING_AS_STRING, + DBUS_TYPE_UINT32_AS_STRING, + bus_driver_handle_get_connection_unix_user }, + { "GetConnectionUnixProcessID", + DBUS_TYPE_STRING_AS_STRING, + DBUS_TYPE_UINT32_AS_STRING, + bus_driver_handle_get_connection_unix_process_id }, + { "GetAdtAuditSessionData", + DBUS_TYPE_STRING_AS_STRING, + DBUS_TYPE_ARRAY_AS_STRING DBUS_TYPE_BYTE_AS_STRING, + bus_driver_handle_get_adt_audit_session_data }, + { "GetConnectionSELinuxSecurityContext", + DBUS_TYPE_STRING_AS_STRING, + DBUS_TYPE_ARRAY_AS_STRING DBUS_TYPE_BYTE_AS_STRING, + bus_driver_handle_get_connection_selinux_security_context }, + { "ReloadConfig", + "", + "", + bus_driver_handle_reload_config }, + { "GetId", + "", + DBUS_TYPE_STRING_AS_STRING, + bus_driver_handle_get_id } +}; + +static dbus_bool_t +write_args_for_direction (DBusString *xml, + const char *signature, + dbus_bool_t in) +{ + DBusTypeReader typereader; + DBusString sigstr; + int current_type; + + _dbus_string_init_const (&sigstr, signature); + _dbus_type_reader_init_types_only (&typereader, &sigstr, 0); + + while ((current_type = _dbus_type_reader_get_current_type (&typereader)) != DBUS_TYPE_INVALID) + { + const DBusString *subsig; + int start, len; + + _dbus_type_reader_get_signature (&typereader, &subsig, &start, &len); + if (!_dbus_string_append_printf (xml, " <arg direction=\"%s\" type=\"", + in ? "in" : "out")) + goto oom; + if (!_dbus_string_append_len (xml, + _dbus_string_get_const_data (subsig) + start, + len)) + goto oom; + if (!_dbus_string_append (xml, "\"/>\n")) + goto oom; + + _dbus_type_reader_next (&typereader); + } + return TRUE; + oom: + return FALSE; +} + +dbus_bool_t +bus_driver_generate_introspect_string (DBusString *xml) +{ + int i; + + if (!_dbus_string_append (xml, DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE)) + return FALSE; + if (!_dbus_string_append (xml, "<node>\n")) + return FALSE; + if (!_dbus_string_append_printf (xml, " <interface name=\"%s\">\n", DBUS_INTERFACE_INTROSPECTABLE)) + return FALSE; + if (!_dbus_string_append (xml, " <method name=\"Introspect\">\n")) + return FALSE; + if (!_dbus_string_append_printf (xml, " <arg name=\"data\" direction=\"out\" type=\"%s\"/>\n", DBUS_TYPE_STRING_AS_STRING)) + return FALSE; + if (!_dbus_string_append (xml, " </method>\n")) + return FALSE; + if (!_dbus_string_append (xml, " </interface>\n")) + return FALSE; + + if (!_dbus_string_append_printf (xml, " <interface name=\"%s\">\n", + DBUS_INTERFACE_DBUS)) + return FALSE; + + i = 0; + while (i < _DBUS_N_ELEMENTS (message_handlers)) + { + + if (!_dbus_string_append_printf (xml, " <method name=\"%s\">\n", + message_handlers[i].name)) + return FALSE; + + if (!write_args_for_direction (xml, message_handlers[i].in_args, TRUE)) + return FALSE; + + if (!write_args_for_direction (xml, message_handlers[i].out_args, FALSE)) + return FALSE; + + if (!_dbus_string_append (xml, " </method>\n")) + return FALSE; + + ++i; + } + + if (!_dbus_string_append_printf (xml, " <signal name=\"NameOwnerChanged\">\n")) + return FALSE; + + if (!_dbus_string_append_printf (xml, " <arg type=\"s\"/>\n")) + return FALSE; + + if (!_dbus_string_append_printf (xml, " <arg type=\"s\"/>\n")) + return FALSE; + + if (!_dbus_string_append_printf (xml, " <arg type=\"s\"/>\n")) + return FALSE; + + if (!_dbus_string_append_printf (xml, " </signal>\n")) + return FALSE; + + + + if (!_dbus_string_append_printf (xml, " <signal name=\"NameLost\">\n")) + return FALSE; + + if (!_dbus_string_append_printf (xml, " <arg type=\"s\"/>\n")) + return FALSE; + + if (!_dbus_string_append_printf (xml, " </signal>\n")) + return FALSE; + + + + if (!_dbus_string_append_printf (xml, " <signal name=\"NameAcquired\">\n")) + return FALSE; + + if (!_dbus_string_append_printf (xml, " <arg type=\"s\"/>\n")) + return FALSE; + + if (!_dbus_string_append_printf (xml, " </signal>\n")) + return FALSE; + + if (!_dbus_string_append (xml, " </interface>\n")) + return FALSE; + + if (!_dbus_string_append (xml, "</node>\n")) + return FALSE; + + return TRUE; +} + +static dbus_bool_t +bus_driver_handle_introspect (DBusConnection *connection, + BusTransaction *transaction, + DBusMessage *message, + DBusError *error) +{ + DBusString xml; + DBusMessage *reply; + const char *v_STRING; + + _dbus_verbose ("Introspect() on bus driver\n"); + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + reply = NULL; + + if (! dbus_message_get_args (message, error, + DBUS_TYPE_INVALID)) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + return FALSE; + } + + if (!_dbus_string_init (&xml)) + { + BUS_SET_OOM (error); + return FALSE; + } + + if (!bus_driver_generate_introspect_string (&xml)) + goto oom; + + v_STRING = _dbus_string_get_const_data (&xml); + + reply = dbus_message_new_method_return (message); + if (reply == NULL) + goto oom; + + if (! dbus_message_append_args (reply, + DBUS_TYPE_STRING, &v_STRING, + DBUS_TYPE_INVALID)) + goto oom; + + if (! bus_transaction_send_from_driver (transaction, connection, reply)) + goto oom; + + dbus_message_unref (reply); + _dbus_string_free (&xml); + + return TRUE; + + oom: + BUS_SET_OOM (error); + + if (reply) + dbus_message_unref (reply); + + _dbus_string_free (&xml); + + return FALSE; +} + +dbus_bool_t +bus_driver_handle_message (DBusConnection *connection, + BusTransaction *transaction, + DBusMessage *message, + DBusError *error) +{ + const char *name, *sender, *interface; + int i; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + if (dbus_message_get_type (message) != DBUS_MESSAGE_TYPE_METHOD_CALL) + { + _dbus_verbose ("Driver got a non-method-call message, ignoring\n"); + return TRUE; /* we just ignore this */ + } + + if (dbus_message_is_method_call (message, + DBUS_INTERFACE_INTROSPECTABLE, + "Introspect")) + return bus_driver_handle_introspect (connection, transaction, message, error); + + interface = dbus_message_get_interface (message); + if (interface == NULL) + interface = DBUS_INTERFACE_DBUS; + + _dbus_assert (dbus_message_get_member (message) != NULL); + + name = dbus_message_get_member (message); + sender = dbus_message_get_sender (message); + + if (strcmp (interface, + DBUS_INTERFACE_DBUS) != 0) + { + _dbus_verbose ("Driver got message to unknown interface \"%s\"\n", + interface); + goto unknown; + } + + _dbus_verbose ("Driver got a method call: %s\n", + dbus_message_get_member (message)); + + /* security checks should have kept this from getting here */ + _dbus_assert (sender != NULL || strcmp (name, "Hello") == 0); + + i = 0; + while (i < _DBUS_N_ELEMENTS (message_handlers)) + { + if (strcmp (message_handlers[i].name, name) == 0) + { + _dbus_verbose ("Found driver handler for %s\n", name); + + if (!dbus_message_has_signature (message, message_handlers[i].in_args)) + { + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + _dbus_verbose ("Call to %s has wrong args (%s, expected %s)\n", + name, dbus_message_get_signature (message), + message_handlers[i].in_args); + + dbus_set_error (error, DBUS_ERROR_INVALID_ARGS, + "Call to %s has wrong args (%s, expected %s)\n", + name, dbus_message_get_signature (message), + message_handlers[i].in_args); + _DBUS_ASSERT_ERROR_IS_SET (error); + return FALSE; + } + + if ((* message_handlers[i].handler) (connection, transaction, message, error)) + { + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + _dbus_verbose ("Driver handler succeeded\n"); + return TRUE; + } + else + { + _DBUS_ASSERT_ERROR_IS_SET (error); + _dbus_verbose ("Driver handler returned failure\n"); + return FALSE; + } + } + + ++i; + } + + unknown: + _dbus_verbose ("No driver handler for message \"%s\"\n", + name); + + dbus_set_error (error, DBUS_ERROR_UNKNOWN_METHOD, + "%s does not understand message %s", + DBUS_SERVICE_DBUS, name); + + return FALSE; +} + +void +bus_driver_remove_connection (DBusConnection *connection) +{ + /* FIXME 1.0 Does nothing for now, should unregister the connection + * with the bus driver. + */ +} diff --git a/bus/driver.h b/bus/driver.h new file mode 100644 index 00000000..713b2764 --- /dev/null +++ b/bus/driver.h @@ -0,0 +1,52 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* driver.h Bus client (driver) + * + * Copyright (C) 2003 CodeFactory AB + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef BUS_DRIVER_H +#define BUS_DRIVER_H + +#include <dbus/dbus.h> +#include "connection.h" + +void bus_driver_remove_connection (DBusConnection *connection); +dbus_bool_t bus_driver_handle_message (DBusConnection *connection, + BusTransaction *transaction, + DBusMessage *message, + DBusError *error); +dbus_bool_t bus_driver_send_service_lost (DBusConnection *connection, + const char *service_name, + BusTransaction *transaction, + DBusError *error); +dbus_bool_t bus_driver_send_service_acquired (DBusConnection *connection, + const char *service_name, + BusTransaction *transaction, + DBusError *error); +dbus_bool_t bus_driver_send_service_owner_changed (const char *service_name, + const char *old_owner, + const char *new_owner, + BusTransaction *transaction, + DBusError *error); +dbus_bool_t bus_driver_generate_introspect_string (DBusString *xml); + + + +#endif /* BUS_DRIVER_H */ diff --git a/bus/expirelist.c b/bus/expirelist.c new file mode 100644 index 00000000..58e1f6d1 --- /dev/null +++ b/bus/expirelist.c @@ -0,0 +1,412 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* expirelist.c List of items that expire + * + * Copyright (C) 2003 Red Hat, Inc. + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "expirelist.h" +#include "test.h" +#include <dbus/dbus-internals.h> +#include <dbus/dbus-mainloop.h> +#include <dbus/dbus-timeout.h> + +struct BusExpireList +{ + DBusList *items; /**< List of BusExpireItem */ + DBusTimeout *timeout; + DBusLoop *loop; + BusExpireFunc expire_func; + void *data; + int expire_after; /**< Expire after milliseconds (thousandths) */ +}; + +static dbus_bool_t expire_timeout_handler (void *data); + +static void +call_timeout_callback (DBusTimeout *timeout, + void *data) +{ + /* can return FALSE on OOM but we just let it fire again later */ + dbus_timeout_handle (timeout); +} + +BusExpireList* +bus_expire_list_new (DBusLoop *loop, + int expire_after, + BusExpireFunc expire_func, + void *data) +{ + BusExpireList *list; + + list = dbus_new0 (BusExpireList, 1); + if (list == NULL) + return NULL; + + list->expire_func = expire_func; + list->data = data; + list->loop = loop; + list->expire_after = expire_after; + + list->timeout = _dbus_timeout_new (100, /* irrelevant */ + expire_timeout_handler, + list, NULL); + if (list->timeout == NULL) + goto failed; + + _dbus_timeout_set_enabled (list->timeout, FALSE); + + if (!_dbus_loop_add_timeout (list->loop, + list->timeout, + call_timeout_callback, NULL, NULL)) + goto failed; + + return list; + + failed: + if (list->timeout) + _dbus_timeout_unref (list->timeout); + + dbus_free (list); + + return NULL; +} + +void +bus_expire_list_free (BusExpireList *list) +{ + _dbus_assert (list->items == NULL); + + _dbus_loop_remove_timeout (list->loop, list->timeout, + call_timeout_callback, NULL); + + _dbus_timeout_unref (list->timeout); + + dbus_free (list); +} + +void +bus_expire_timeout_set_interval (DBusTimeout *timeout, + int next_interval) +{ + if (next_interval >= 0) + { + _dbus_timeout_set_interval (timeout, + next_interval); + _dbus_timeout_set_enabled (timeout, TRUE); + + _dbus_verbose ("Enabled an expire timeout with interval %d\n", + next_interval); + } + else if (dbus_timeout_get_enabled (timeout)) + { + _dbus_timeout_set_enabled (timeout, FALSE); + + _dbus_verbose ("Disabled an expire timeout\n"); + } + else + _dbus_verbose ("No need to disable this expire timeout\n"); +} + +void +bus_expire_list_recheck_immediately (BusExpireList *list) +{ + _dbus_verbose ("setting interval on expire list to 0 for immediate recheck\n"); + + bus_expire_timeout_set_interval (list->timeout, 0); +} + +static int +do_expiration_with_current_time (BusExpireList *list, + long tv_sec, + long tv_usec) +{ + DBusList *link; + int next_interval, min_wait_time, items_to_expire; + + next_interval = -1; + min_wait_time = 3600 * 1000; /* this is reset anyway if used */ + items_to_expire = 0; + + link = _dbus_list_get_first_link (&list->items); + while (link != NULL) + { + DBusList *next = _dbus_list_get_next_link (&list->items, link); + double elapsed; + BusExpireItem *item; + + item = link->data; + + elapsed = ELAPSED_MILLISECONDS_SINCE (item->added_tv_sec, + item->added_tv_usec, + tv_sec, tv_usec); + + if (((item->added_tv_sec == 0) && (item->added_tv_usec == 0)) || + ((list->expire_after > 0) && (elapsed >= (double) list->expire_after))) + { + _dbus_verbose ("Expiring an item %p\n", item); + + /* If the expire function fails, we just end up expiring + * this item next time we walk through the list. This would + * be an indeterminate time normally, so we set up the + * next_interval to be "shortly" (just enough to avoid + * a busy loop) + */ + if (!(* list->expire_func) (list, link, list->data)) + { + next_interval = _dbus_get_oom_wait (); + break; + } + } + else if (list->expire_after > 0) + { + double to_wait; + + items_to_expire = 1; + to_wait = (double) list->expire_after - elapsed; + if (min_wait_time > to_wait) + min_wait_time = to_wait; + } + + link = next; + } + + if (next_interval < 0 && items_to_expire) + next_interval = min_wait_time; + + return next_interval; +} + +static void +bus_expirelist_expire (BusExpireList *list) +{ + int next_interval; + + next_interval = -1; + + if (list->items != NULL) + { + long tv_sec, tv_usec; + + _dbus_get_current_time (&tv_sec, &tv_usec); + + next_interval = do_expiration_with_current_time (list, tv_sec, tv_usec); + } + + bus_expire_timeout_set_interval (list->timeout, next_interval); +} + +static dbus_bool_t +expire_timeout_handler (void *data) +{ + BusExpireList *list = data; + + _dbus_verbose ("Running %s\n", _DBUS_FUNCTION_NAME); + + /* note that this may remove the timeout */ + bus_expirelist_expire (list); + + return TRUE; +} + +void +bus_expire_list_remove_link (BusExpireList *list, + DBusList *link) +{ + _dbus_list_remove_link (&list->items, link); +} + +dbus_bool_t +bus_expire_list_remove (BusExpireList *list, + BusExpireItem *item) +{ + return _dbus_list_remove (&list->items, item); +} + +void +bus_expire_list_unlink (BusExpireList *list, + DBusList *link) +{ + _dbus_list_unlink (&list->items, link); +} + +dbus_bool_t +bus_expire_list_add (BusExpireList *list, + BusExpireItem *item) +{ + dbus_bool_t ret; + + ret = _dbus_list_prepend (&list->items, item); + if (ret && !dbus_timeout_get_enabled (list->timeout)) + bus_expire_timeout_set_interval (list->timeout, 0); + + return ret; +} + +void +bus_expire_list_add_link (BusExpireList *list, + DBusList *link) +{ + _dbus_assert (link->data != NULL); + + _dbus_list_prepend_link (&list->items, link); + + if (!dbus_timeout_get_enabled (list->timeout)) + bus_expire_timeout_set_interval (list->timeout, 0); +} + +DBusList* +bus_expire_list_get_first_link (BusExpireList *list) +{ + return _dbus_list_get_first_link (&list->items); +} + +DBusList* +bus_expire_list_get_next_link (BusExpireList *list, + DBusList *link) +{ + return _dbus_list_get_next_link (&list->items, link); +} + +dbus_bool_t +bus_expire_list_contains_item (BusExpireList *list, + BusExpireItem *item) +{ + return _dbus_list_find_last (&list->items, item) != NULL; +} + +#ifdef DBUS_BUILD_TESTS + +typedef struct +{ + BusExpireItem item; + int expire_count; +} TestExpireItem; + +static dbus_bool_t +test_expire_func (BusExpireList *list, + DBusList *link, + void *data) +{ + TestExpireItem *t; + + t = (TestExpireItem*) link->data; + + t->expire_count += 1; + + return TRUE; +} + +static void +time_add_milliseconds (long *tv_sec, + long *tv_usec, + int milliseconds) +{ + *tv_sec = *tv_sec + milliseconds / 1000; + *tv_usec = *tv_usec + milliseconds * 1000; + if (*tv_usec >= 1000000) + { + *tv_usec -= 1000000; + *tv_sec += 1; + } +} + +dbus_bool_t +bus_expire_list_test (const DBusString *test_data_dir) +{ + DBusLoop *loop; + BusExpireList *list; + long tv_sec, tv_usec; + long tv_sec_not_expired, tv_usec_not_expired; + long tv_sec_expired, tv_usec_expired; + long tv_sec_past, tv_usec_past; + TestExpireItem *item; + int next_interval; + dbus_bool_t result = FALSE; + + + loop = _dbus_loop_new (); + _dbus_assert (loop != NULL); + +#define EXPIRE_AFTER 100 + + list = bus_expire_list_new (loop, EXPIRE_AFTER, + test_expire_func, NULL); + _dbus_assert (list != NULL); + + _dbus_get_current_time (&tv_sec, &tv_usec); + + tv_sec_not_expired = tv_sec; + tv_usec_not_expired = tv_usec; + time_add_milliseconds (&tv_sec_not_expired, + &tv_usec_not_expired, EXPIRE_AFTER - 1); + + tv_sec_expired = tv_sec; + tv_usec_expired = tv_usec; + time_add_milliseconds (&tv_sec_expired, + &tv_usec_expired, EXPIRE_AFTER); + + + tv_sec_past = tv_sec - 1; + tv_usec_past = tv_usec; + + item = dbus_new0 (TestExpireItem, 1); + + if (item == NULL) + goto oom; + + item->item.added_tv_sec = tv_sec; + item->item.added_tv_usec = tv_usec; + if (!bus_expire_list_add (list, &item->item)) + _dbus_assert_not_reached ("out of memory"); + + next_interval = + do_expiration_with_current_time (list, tv_sec_not_expired, + tv_usec_not_expired); + _dbus_assert (item->expire_count == 0); + _dbus_verbose ("next_interval = %d\n", next_interval); + _dbus_assert (next_interval == 1); + + next_interval = + do_expiration_with_current_time (list, tv_sec_expired, + tv_usec_expired); + _dbus_assert (item->expire_count == 1); + _dbus_verbose ("next_interval = %d\n", next_interval); + _dbus_assert (next_interval == -1); + + next_interval = + do_expiration_with_current_time (list, tv_sec_past, + tv_usec_past); + _dbus_assert (item->expire_count == 1); + _dbus_verbose ("next_interval = %d\n", next_interval); + _dbus_assert (next_interval == 1000 + EXPIRE_AFTER); + + bus_expire_list_remove (list, &item->item); + dbus_free (item); + + bus_expire_list_free (list); + _dbus_loop_unref (loop); + + result = TRUE; + + oom: + return result; +} + +#endif /* DBUS_BUILD_TESTS */ diff --git a/bus/expirelist.h b/bus/expirelist.h new file mode 100644 index 00000000..887cb97b --- /dev/null +++ b/bus/expirelist.h @@ -0,0 +1,80 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* expirelist.h List of stuff that expires + * + * Copyright (C) 2003 Red Hat, Inc. + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef BUS_EXPIRE_LIST_H +#define BUS_EXPIRE_LIST_H + +#include <dbus/dbus.h> +#include <dbus/dbus-list.h> +#include <dbus/dbus-mainloop.h> + +typedef struct BusExpireList BusExpireList; +typedef struct BusExpireItem BusExpireItem; + +typedef dbus_bool_t (* BusExpireFunc) (BusExpireList *list, + DBusList *link, + void *data); + + +/* embed this in a child expire item struct */ +struct BusExpireItem +{ + long added_tv_sec; /**< Time we were added (seconds component) */ + long added_tv_usec; /**< Time we were added (microsec component) */ +}; + +BusExpireList* bus_expire_list_new (DBusLoop *loop, + int expire_after, + BusExpireFunc expire_func, + void *data); +void bus_expire_list_free (BusExpireList *list); +void bus_expire_list_recheck_immediately (BusExpireList *list); +void bus_expire_list_remove_link (BusExpireList *list, + DBusList *link); +dbus_bool_t bus_expire_list_remove (BusExpireList *list, + BusExpireItem *item); +DBusList* bus_expire_list_get_first_link (BusExpireList *list); +DBusList* bus_expire_list_get_next_link (BusExpireList *list, + DBusList *link); +dbus_bool_t bus_expire_list_add (BusExpireList *list, + BusExpireItem *item); +void bus_expire_list_add_link (BusExpireList *list, + DBusList *link); +dbus_bool_t bus_expire_list_contains_item (BusExpireList *list, + BusExpireItem *item); +void bus_expire_list_unlink (BusExpireList *list, + DBusList *link); + +/* this macro and function are semi-related utility functions, not really part of the + * BusExpireList API + */ + +#define ELAPSED_MILLISECONDS_SINCE(orig_tv_sec, orig_tv_usec, \ + now_tv_sec, now_tv_usec) \ + (((double) (now_tv_sec) - (double) (orig_tv_sec)) * 1000.0 + \ + ((double) (now_tv_usec) - (double) (orig_tv_usec)) / 1000.0) + +void bus_expire_timeout_set_interval (DBusTimeout *timeout, + int next_interval); + +#endif /* BUS_EXPIRE_LIST_H */ diff --git a/bus/main.c b/bus/main.c new file mode 100644 index 00000000..d17486dd --- /dev/null +++ b/bus/main.c @@ -0,0 +1,479 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* main.c main() for message bus + * + * Copyright (C) 2003 Red Hat, Inc. + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#include "bus.h" +#include "driver.h" +#include <dbus/dbus-internals.h> +#include <dbus/dbus-watch.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <signal.h> +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#include "selinux.h" + +static BusContext *context; + +static int reload_pipe[2]; +#define RELOAD_READ_END 0 +#define RELOAD_WRITE_END 1 + +static void close_reload_pipe (void); + +static void +signal_handler (int sig) +{ + + switch (sig) + { +#ifdef DBUS_BUS_ENABLE_DNOTIFY_ON_LINUX + case SIGIO: + /* explicit fall-through */ +#endif /* DBUS_BUS_ENABLE_DNOTIFY_ON_LINUX */ +#ifdef SIGHUP + case SIGHUP: + { + DBusString str; + _dbus_string_init_const (&str, "foo"); + if ((reload_pipe[RELOAD_WRITE_END] > 0) && + !_dbus_write_socket (reload_pipe[RELOAD_WRITE_END], &str, 0, 1)) + { + _dbus_warn ("Unable to write to reload pipe.\n"); + close_reload_pipe (); + } + } + break; +#endif + } +} + +static void +usage (void) +{ + fprintf (stderr, DAEMON_NAME " [--version] [--session] [--system] [--config-file=FILE] [--print-address[=DESCRIPTOR]] [--print-pid[=DESCRIPTOR]] [--fork] [--nofork] [--introspect]\n"); + exit (1); +} + +static void +version (void) +{ + printf ("D-Bus Message Bus Daemon %s\n" + "Copyright (C) 2002, 2003 Red Hat, Inc., CodeFactory AB, and others\n" + "This is free software; see the source for copying conditions.\n" + "There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n", + VERSION); + exit (0); +} + +static void +introspect (void) +{ + DBusString xml; + const char *v_STRING; + + if (!_dbus_string_init (&xml)) + goto oom; + + if (!bus_driver_generate_introspect_string (&xml)) + { + _dbus_string_free (&xml); + goto oom; + } + + v_STRING = _dbus_string_get_const_data (&xml); + printf ("%s\n", v_STRING); + + exit (0); + + oom: + _dbus_warn ("Can not introspect - Out of memory\n"); + exit (1); +} +static void +check_two_config_files (const DBusString *config_file, + const char *extra_arg) +{ + if (_dbus_string_get_length (config_file) > 0) + { + fprintf (stderr, "--%s specified but configuration file %s already requested\n", + extra_arg, _dbus_string_get_const_data (config_file)); + exit (1); + } +} + +static void +check_two_addr_descriptors (const DBusString *addr_fd, + const char *extra_arg) +{ + if (_dbus_string_get_length (addr_fd) > 0) + { + fprintf (stderr, "--%s specified but printing address to %s already requested\n", + extra_arg, _dbus_string_get_const_data (addr_fd)); + exit (1); + } +} + +static void +check_two_pid_descriptors (const DBusString *pid_fd, + const char *extra_arg) +{ + if (_dbus_string_get_length (pid_fd) > 0) + { + fprintf (stderr, "--%s specified but printing pid to %s already requested\n", + extra_arg, _dbus_string_get_const_data (pid_fd)); + exit (1); + } +} + +static dbus_bool_t +handle_reload_watch (DBusWatch *watch, + unsigned int flags, + void *data) +{ + DBusError error; + DBusString str; + + while (!_dbus_string_init (&str)) + _dbus_wait_for_memory (); + + if ((reload_pipe[RELOAD_READ_END] > 0) && + _dbus_read_socket (reload_pipe[RELOAD_READ_END], &str, 1) != 1) + { + _dbus_warn ("Couldn't read from reload pipe.\n"); + close_reload_pipe (); + return TRUE; + } + _dbus_string_free (&str); + + /* this can only fail if we don't understand the config file + * or OOM. Either way we should just stick with the currently + * loaded config. + */ + dbus_error_init (&error); + if (! bus_context_reload_config (context, &error)) + { + _DBUS_ASSERT_ERROR_IS_SET (&error); + _dbus_assert (dbus_error_has_name (&error, DBUS_ERROR_FAILED) || + dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY)); + _dbus_warn ("Unable to reload configuration: %s\n", + error.message); + dbus_error_free (&error); + } + return TRUE; +} + +static dbus_bool_t +reload_watch_callback (DBusWatch *watch, + unsigned int condition, + void *data) +{ + return dbus_watch_handle (watch, condition); +} + +static void +setup_reload_pipe (DBusLoop *loop) +{ + DBusError error; + DBusWatch *watch; + + dbus_error_init (&error); + + if (!_dbus_full_duplex_pipe (&reload_pipe[0], &reload_pipe[1], + TRUE, &error)) + { + _dbus_warn ("Unable to create reload pipe: %s\n", + error.message); + dbus_error_free (&error); + exit (1); + } + + _dbus_fd_set_close_on_exec (reload_pipe[0]); + _dbus_fd_set_close_on_exec (reload_pipe[1]); + + watch = _dbus_watch_new (reload_pipe[RELOAD_READ_END], + DBUS_WATCH_READABLE, TRUE, + handle_reload_watch, NULL, NULL); + + if (watch == NULL) + { + _dbus_warn ("Unable to create reload watch: %s\n", + error.message); + dbus_error_free (&error); + exit (1); + } + + if (!_dbus_loop_add_watch (loop, watch, reload_watch_callback, + NULL, NULL)) + { + _dbus_warn ("Unable to add reload watch to main loop: %s\n", + error.message); + dbus_error_free (&error); + exit (1); + } + +} + +static void +close_reload_pipe (void) +{ + _dbus_close_socket (reload_pipe[RELOAD_READ_END], NULL); + reload_pipe[RELOAD_READ_END] = -1; + + _dbus_close_socket (reload_pipe[RELOAD_WRITE_END], NULL); + reload_pipe[RELOAD_WRITE_END] = -1; +} + +int +main (int argc, char **argv) +{ + DBusError error; + DBusString config_file; + DBusString addr_fd; + DBusString pid_fd; + const char *prev_arg; + DBusPipe print_addr_pipe; + DBusPipe print_pid_pipe; + int i; + dbus_bool_t print_address; + dbus_bool_t print_pid; + int force_fork; + + if (!_dbus_string_init (&config_file)) + return 1; + + if (!_dbus_string_init (&addr_fd)) + return 1; + + if (!_dbus_string_init (&pid_fd)) + return 1; + + print_address = FALSE; + print_pid = FALSE; + force_fork = FORK_FOLLOW_CONFIG_FILE; + + prev_arg = NULL; + i = 1; + while (i < argc) + { + const char *arg = argv[i]; + + if (strcmp (arg, "--help") == 0 || + strcmp (arg, "-h") == 0 || + strcmp (arg, "-?") == 0) + usage (); + else if (strcmp (arg, "--version") == 0) + version (); + else if (strcmp (arg, "--introspect") == 0) + introspect (); + else if (strcmp (arg, "--nofork") == 0) + force_fork = FORK_NEVER; + else if (strcmp (arg, "--fork") == 0) + force_fork = FORK_ALWAYS; + else if (strcmp (arg, "--system") == 0) + { + check_two_config_files (&config_file, "system"); + + if (!_dbus_append_system_config_file (&config_file)) + exit (1); + } + else if (strcmp (arg, "--session") == 0) + { + check_two_config_files (&config_file, "session"); + + if (!_dbus_append_session_config_file (&config_file)) + exit (1); + } + else if (strstr (arg, "--config-file=") == arg) + { + const char *file; + + check_two_config_files (&config_file, "config-file"); + + file = strchr (arg, '='); + ++file; + + if (!_dbus_string_append (&config_file, file)) + exit (1); + } + else if (prev_arg && + strcmp (prev_arg, "--config-file") == 0) + { + check_two_config_files (&config_file, "config-file"); + + if (!_dbus_string_append (&config_file, arg)) + exit (1); + } + else if (strcmp (arg, "--config-file") == 0) + ; /* wait for next arg */ + else if (strstr (arg, "--print-address=") == arg) + { + const char *desc; + + check_two_addr_descriptors (&addr_fd, "print-address"); + + desc = strchr (arg, '='); + ++desc; + + if (!_dbus_string_append (&addr_fd, desc)) + exit (1); + + print_address = TRUE; + } + else if (prev_arg && + strcmp (prev_arg, "--print-address") == 0) + { + check_two_addr_descriptors (&addr_fd, "print-address"); + + if (!_dbus_string_append (&addr_fd, arg)) + exit (1); + + print_address = TRUE; + } + else if (strcmp (arg, "--print-address") == 0) + print_address = TRUE; /* and we'll get the next arg if appropriate */ + else if (strstr (arg, "--print-pid=") == arg) + { + const char *desc; + + check_two_pid_descriptors (&pid_fd, "print-pid"); + + desc = strchr (arg, '='); + ++desc; + + if (!_dbus_string_append (&pid_fd, desc)) + exit (1); + + print_pid = TRUE; + } + else if (prev_arg && + strcmp (prev_arg, "--print-pid") == 0) + { + check_two_pid_descriptors (&pid_fd, "print-pid"); + + if (!_dbus_string_append (&pid_fd, arg)) + exit (1); + + print_pid = TRUE; + } + else if (strcmp (arg, "--print-pid") == 0) + print_pid = TRUE; /* and we'll get the next arg if appropriate */ + else + usage (); + + prev_arg = arg; + + ++i; + } + + if (_dbus_string_get_length (&config_file) == 0) + { + fprintf (stderr, "No configuration file specified.\n"); + usage (); + } + + _dbus_pipe_invalidate (&print_addr_pipe); + if (print_address) + { + _dbus_pipe_init_stdout (&print_addr_pipe); + if (_dbus_string_get_length (&addr_fd) > 0) + { + long val; + int end; + if (!_dbus_string_parse_int (&addr_fd, 0, &val, &end) || + end != _dbus_string_get_length (&addr_fd) || + val < 0 || val > _DBUS_INT_MAX) + { + fprintf (stderr, "Invalid file descriptor: \"%s\"\n", + _dbus_string_get_const_data (&addr_fd)); + exit (1); + } + + _dbus_pipe_init (&print_addr_pipe, val); + } + } + _dbus_string_free (&addr_fd); + + _dbus_pipe_invalidate (&print_pid_pipe); + if (print_pid) + { + _dbus_pipe_init_stdout (&print_pid_pipe); + if (_dbus_string_get_length (&pid_fd) > 0) + { + long val; + int end; + if (!_dbus_string_parse_int (&pid_fd, 0, &val, &end) || + end != _dbus_string_get_length (&pid_fd) || + val < 0 || val > _DBUS_INT_MAX) + { + fprintf (stderr, "Invalid file descriptor: \"%s\"\n", + _dbus_string_get_const_data (&pid_fd)); + exit (1); + } + + _dbus_pipe_init (&print_pid_pipe, val); + } + } + _dbus_string_free (&pid_fd); + + if (!bus_selinux_pre_init ()) + { + _dbus_warn ("SELinux pre-initialization failed\n"); + exit (1); + } + + dbus_error_init (&error); + context = bus_context_new (&config_file, force_fork, + &print_addr_pipe, &print_pid_pipe, + &error); + _dbus_string_free (&config_file); + if (context == NULL) + { + _dbus_warn ("Failed to start message bus: %s\n", + error.message); + dbus_error_free (&error); + exit (1); + } + + /* bus_context_new() closes the print_addr_pipe and + * print_pid_pipe + */ + + setup_reload_pipe (bus_context_get_loop (context)); + +#ifdef SIGHUP + _dbus_set_signal_handler (SIGHUP, signal_handler); +#endif +#ifdef DBUS_BUS_ENABLE_DNOTIFY_ON_LINUX + _dbus_set_signal_handler (SIGIO, signal_handler); +#endif /* DBUS_BUS_ENABLE_DNOTIFY_ON_LINUX */ + + _dbus_verbose ("We are on D-Bus...\n"); + _dbus_loop_run (bus_context_get_loop (context)); + + bus_context_shutdown (context); + bus_context_unref (context); + bus_selinux_shutdown (); + + return 0; +} diff --git a/bus/messagebus.in b/bus/messagebus.in new file mode 100755 index 00000000..1f1004b1 --- /dev/null +++ b/bus/messagebus.in @@ -0,0 +1,92 @@ +#!/bin/sh +# +# messagebus: The D-BUS systemwide message bus +# +# chkconfig: 345 22 85 +# description: This is a daemon which broadcasts notifications of system events \ +# and other messages. See http://www.freedesktop.org/software/dbus/ +# +# processname: dbus-daemon +# pidfile: @DBUS_SYSTEM_PID_FILE@ +# +### BEGIN INIT INFO +# Provides: messagebus +# Required-Start: $syslog $local_fs +# Required-Stop: $syslog $local_fs +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: The D-Bus systemwide message bus +# Description: This is a daemon which broadcasts notifications of system +# events and other messages. See http://www.freedesktop.org/software/dbus +### END INIT INFO + +# Sanity checks. +[ -x @EXPANDED_BINDIR@/dbus-daemon ] || exit 0 + +# Source function library. +. @EXPANDED_SYSCONFDIR@/rc.d/init.d/functions + +# so we can rearrange this easily +processname=dbus-daemon +servicename=messagebus + +RETVAL=0 + +start() { + echo -n $"Starting system message bus: " + if [ -x @EXPANDED_BINDIR@/dbus-uuidgen ] ; then + @EXPANDED_BINDIR@/dbus-uuidgen --ensure + fi + + daemon --check $servicename $processname --system + RETVAL=$? + echo + [ $RETVAL -eq 0 ] && touch @EXPANDED_LOCALSTATEDIR@/lock/subsys/$servicename +} + +stop() { + echo -n $"Stopping system message bus: " + + ## we don't want to kill all the per-user $processname, we want + ## to use the pid file *only*; because we use the fake nonexistent + ## program name "$servicename" that should be safe-ish + killproc $servicename -TERM + RETVAL=$? + echo + if [ $RETVAL -eq 0 ]; then + rm -f @EXPANDED_LOCALSTATEDIR@/lock/subsys/$servicename + rm -f @DBUS_SYSTEM_PID_FILE@ + fi +} + +# See how we were called. +case "$1" in + start) + start + ;; + stop) + stop + ;; + status) + status $processname + RETVAL=$? + ;; + restart) + stop + start + ;; + condrestart) + if [ -f @EXPANDED_LOCALSTATEDIR@/lock/subsys/$servicename ]; then + stop + start + fi + ;; + reload) + echo "Message bus can't reload its configuration, you have to restart it" + RETVAL=$? + ;; + *) + echo $"Usage: $0 {start|stop|status|restart|condrestart|reload}" + ;; +esac +exit $RETVAL diff --git a/bus/policy.c b/bus/policy.c new file mode 100644 index 00000000..bc1d2d9d --- /dev/null +++ b/bus/policy.c @@ -0,0 +1,1296 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* policy.c Bus security policy + * + * Copyright (C) 2003, 2004 Red Hat, Inc. + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "policy.h" +#include "services.h" +#include "test.h" +#include "utils.h" +#include <dbus/dbus-list.h> +#include <dbus/dbus-hash.h> +#include <dbus/dbus-internals.h> + +BusPolicyRule* +bus_policy_rule_new (BusPolicyRuleType type, + dbus_bool_t allow) +{ + BusPolicyRule *rule; + + rule = dbus_new0 (BusPolicyRule, 1); + if (rule == NULL) + return NULL; + + rule->type = type; + rule->refcount = 1; + rule->allow = allow; + + switch (rule->type) + { + case BUS_POLICY_RULE_USER: + rule->d.user.uid = DBUS_UID_UNSET; + break; + case BUS_POLICY_RULE_GROUP: + rule->d.group.gid = DBUS_GID_UNSET; + break; + case BUS_POLICY_RULE_SEND: + rule->d.send.message_type = DBUS_MESSAGE_TYPE_INVALID; + + /* allow rules default to TRUE (only requested replies allowed) + * deny rules default to FALSE (only unrequested replies denied) + */ + rule->d.send.requested_reply = rule->allow; + break; + case BUS_POLICY_RULE_RECEIVE: + rule->d.receive.message_type = DBUS_MESSAGE_TYPE_INVALID; + /* allow rules default to TRUE (only requested replies allowed) + * deny rules default to FALSE (only unrequested replies denied) + */ + rule->d.receive.requested_reply = rule->allow; + break; + case BUS_POLICY_RULE_OWN: + break; + } + + return rule; +} + +BusPolicyRule * +bus_policy_rule_ref (BusPolicyRule *rule) +{ + _dbus_assert (rule->refcount > 0); + + rule->refcount += 1; + + return rule; +} + +void +bus_policy_rule_unref (BusPolicyRule *rule) +{ + _dbus_assert (rule->refcount > 0); + + rule->refcount -= 1; + + if (rule->refcount == 0) + { + switch (rule->type) + { + case BUS_POLICY_RULE_SEND: + dbus_free (rule->d.send.path); + dbus_free (rule->d.send.interface); + dbus_free (rule->d.send.member); + dbus_free (rule->d.send.error); + dbus_free (rule->d.send.destination); + break; + case BUS_POLICY_RULE_RECEIVE: + dbus_free (rule->d.receive.path); + dbus_free (rule->d.receive.interface); + dbus_free (rule->d.receive.member); + dbus_free (rule->d.receive.error); + dbus_free (rule->d.receive.origin); + break; + case BUS_POLICY_RULE_OWN: + dbus_free (rule->d.own.service_name); + break; + case BUS_POLICY_RULE_USER: + break; + case BUS_POLICY_RULE_GROUP: + break; + } + + dbus_free (rule); + } +} + +struct BusPolicy +{ + int refcount; + + DBusList *default_rules; /**< Default policy rules */ + DBusList *mandatory_rules; /**< Mandatory policy rules */ + DBusHashTable *rules_by_uid; /**< per-UID policy rules */ + DBusHashTable *rules_by_gid; /**< per-GID policy rules */ + DBusList *at_console_true_rules; /**< console user policy rules where at_console="true"*/ + DBusList *at_console_false_rules; /**< console user policy rules where at_console="false"*/ +}; + +static void +free_rule_func (void *data, + void *user_data) +{ + BusPolicyRule *rule = data; + + bus_policy_rule_unref (rule); +} + +static void +free_rule_list_func (void *data) +{ + DBusList **list = data; + + if (list == NULL) /* DBusHashTable is on crack */ + return; + + _dbus_list_foreach (list, free_rule_func, NULL); + + _dbus_list_clear (list); + + dbus_free (list); +} + +BusPolicy* +bus_policy_new (void) +{ + BusPolicy *policy; + + policy = dbus_new0 (BusPolicy, 1); + if (policy == NULL) + return NULL; + + policy->refcount = 1; + + policy->rules_by_uid = _dbus_hash_table_new (DBUS_HASH_ULONG, + NULL, + free_rule_list_func); + if (policy->rules_by_uid == NULL) + goto failed; + + policy->rules_by_gid = _dbus_hash_table_new (DBUS_HASH_ULONG, + NULL, + free_rule_list_func); + if (policy->rules_by_gid == NULL) + goto failed; + + return policy; + + failed: + bus_policy_unref (policy); + return NULL; +} + +BusPolicy * +bus_policy_ref (BusPolicy *policy) +{ + _dbus_assert (policy->refcount > 0); + + policy->refcount += 1; + + return policy; +} + +void +bus_policy_unref (BusPolicy *policy) +{ + _dbus_assert (policy->refcount > 0); + + policy->refcount -= 1; + + if (policy->refcount == 0) + { + _dbus_list_foreach (&policy->default_rules, free_rule_func, NULL); + _dbus_list_clear (&policy->default_rules); + + _dbus_list_foreach (&policy->mandatory_rules, free_rule_func, NULL); + _dbus_list_clear (&policy->mandatory_rules); + + _dbus_list_foreach (&policy->at_console_true_rules, free_rule_func, NULL); + _dbus_list_clear (&policy->at_console_true_rules); + + _dbus_list_foreach (&policy->at_console_false_rules, free_rule_func, NULL); + _dbus_list_clear (&policy->at_console_false_rules); + + if (policy->rules_by_uid) + { + _dbus_hash_table_unref (policy->rules_by_uid); + policy->rules_by_uid = NULL; + } + + if (policy->rules_by_gid) + { + _dbus_hash_table_unref (policy->rules_by_gid); + policy->rules_by_gid = NULL; + } + + dbus_free (policy); + } +} + +static dbus_bool_t +add_list_to_client (DBusList **list, + BusClientPolicy *client) +{ + DBusList *link; + + link = _dbus_list_get_first_link (list); + while (link != NULL) + { + BusPolicyRule *rule = link->data; + link = _dbus_list_get_next_link (list, link); + + switch (rule->type) + { + case BUS_POLICY_RULE_USER: + case BUS_POLICY_RULE_GROUP: + /* These aren't per-connection policies */ + break; + + case BUS_POLICY_RULE_OWN: + case BUS_POLICY_RULE_SEND: + case BUS_POLICY_RULE_RECEIVE: + /* These are per-connection */ + if (!bus_client_policy_append_rule (client, rule)) + return FALSE; + break; + } + } + + return TRUE; +} + +BusClientPolicy* +bus_policy_create_client_policy (BusPolicy *policy, + DBusConnection *connection, + DBusError *error) +{ + BusClientPolicy *client; + dbus_uid_t uid; + dbus_bool_t at_console; + + _dbus_assert (dbus_connection_get_is_authenticated (connection)); + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + client = bus_client_policy_new (); + if (client == NULL) + goto nomem; + + if (!add_list_to_client (&policy->default_rules, + client)) + goto nomem; + + /* we avoid the overhead of looking up user's groups + * if we don't have any group rules anyway + */ + if (_dbus_hash_table_get_n_entries (policy->rules_by_gid) > 0) + { + unsigned long *groups; + int n_groups; + int i; + + if (!bus_connection_get_unix_groups (connection, &groups, &n_groups, error)) + goto failed; + + i = 0; + while (i < n_groups) + { + DBusList **list; + + list = _dbus_hash_table_lookup_ulong (policy->rules_by_gid, + groups[i]); + + if (list != NULL) + { + if (!add_list_to_client (list, client)) + { + dbus_free (groups); + goto nomem; + } + } + + ++i; + } + + dbus_free (groups); + } + + if (dbus_connection_get_unix_user (connection, &uid)) + { + if (_dbus_hash_table_get_n_entries (policy->rules_by_uid) > 0) + { + DBusList **list; + + list = _dbus_hash_table_lookup_ulong (policy->rules_by_uid, + uid); + + if (list != NULL) + { + if (!add_list_to_client (list, client)) + goto nomem; + } + } + + /* Add console rules */ + at_console = _dbus_unix_user_is_at_console (uid, error); + + if (at_console) + { + if (!add_list_to_client (&policy->at_console_true_rules, client)) + goto nomem; + } + else if (dbus_error_is_set (error) == TRUE) + { + goto failed; + } + else if (!add_list_to_client (&policy->at_console_false_rules, client)) + { + goto nomem; + } + } + + if (!add_list_to_client (&policy->mandatory_rules, + client)) + goto nomem; + + bus_client_policy_optimize (client); + + return client; + + nomem: + BUS_SET_OOM (error); + failed: + _DBUS_ASSERT_ERROR_IS_SET (error); + if (client) + bus_client_policy_unref (client); + return NULL; +} + +static dbus_bool_t +list_allows_user (dbus_bool_t def, + DBusList **list, + unsigned long uid, + const unsigned long *group_ids, + int n_group_ids) +{ + DBusList *link; + dbus_bool_t allowed; + + allowed = def; + + link = _dbus_list_get_first_link (list); + while (link != NULL) + { + BusPolicyRule *rule = link->data; + link = _dbus_list_get_next_link (list, link); + + if (rule->type == BUS_POLICY_RULE_USER) + { + _dbus_verbose ("List %p user rule uid="DBUS_UID_FORMAT"\n", + list, rule->d.user.uid); + + if (rule->d.user.uid == DBUS_UID_UNSET) + ; /* '*' wildcard */ + else if (rule->d.user.uid != uid) + continue; + } + else if (rule->type == BUS_POLICY_RULE_GROUP) + { + _dbus_verbose ("List %p group rule uid="DBUS_UID_FORMAT"\n", + list, rule->d.user.uid); + + if (rule->d.group.gid == DBUS_GID_UNSET) + ; /* '*' wildcard */ + else + { + int i; + + i = 0; + while (i < n_group_ids) + { + if (rule->d.group.gid == group_ids[i]) + break; + ++i; + } + + if (i == n_group_ids) + continue; + } + } + else + continue; + + allowed = rule->allow; + } + + return allowed; +} + +dbus_bool_t +bus_policy_allow_unix_user (BusPolicy *policy, + unsigned long uid) +{ + dbus_bool_t allowed; + unsigned long *group_ids; + int n_group_ids; + + /* On OOM or error we always reject the user */ + if (!_dbus_unix_groups_from_uid (uid, &group_ids, &n_group_ids)) + { + _dbus_verbose ("Did not get any groups for UID %lu\n", + uid); + return FALSE; + } + + /* Default to "user owning bus" can connect */ + allowed = _dbus_unix_user_is_process_owner (uid); + + allowed = list_allows_user (allowed, + &policy->default_rules, + uid, + group_ids, n_group_ids); + + allowed = list_allows_user (allowed, + &policy->mandatory_rules, + uid, + group_ids, n_group_ids); + + dbus_free (group_ids); + + _dbus_verbose ("UID %lu allowed = %d\n", uid, allowed); + + return allowed; +} + +/* For now this is never actually called because the default + * DBusConnection behavior of 'same user that owns the bus can + * connect' is all it would do. Set the windows user function in + * connection.c if the config file ever supports doing something + * interesting here. + */ +dbus_bool_t +bus_policy_allow_windows_user (BusPolicy *policy, + const char *windows_sid) +{ + /* Windows has no policies here since only the session bus + * is really used for now, so just checking that the + * connecting person is the same as the bus owner is fine. + */ + return _dbus_windows_user_is_process_owner (windows_sid); +} + +dbus_bool_t +bus_policy_append_default_rule (BusPolicy *policy, + BusPolicyRule *rule) +{ + if (!_dbus_list_append (&policy->default_rules, rule)) + return FALSE; + + bus_policy_rule_ref (rule); + + return TRUE; +} + +dbus_bool_t +bus_policy_append_mandatory_rule (BusPolicy *policy, + BusPolicyRule *rule) +{ + if (!_dbus_list_append (&policy->mandatory_rules, rule)) + return FALSE; + + bus_policy_rule_ref (rule); + + return TRUE; +} + + + +static DBusList** +get_list (DBusHashTable *hash, + unsigned long key) +{ + DBusList **list; + + list = _dbus_hash_table_lookup_ulong (hash, key); + + if (list == NULL) + { + list = dbus_new0 (DBusList*, 1); + if (list == NULL) + return NULL; + + if (!_dbus_hash_table_insert_ulong (hash, key, list)) + { + dbus_free (list); + return NULL; + } + } + + return list; +} + +dbus_bool_t +bus_policy_append_user_rule (BusPolicy *policy, + dbus_uid_t uid, + BusPolicyRule *rule) +{ + DBusList **list; + + list = get_list (policy->rules_by_uid, uid); + + if (list == NULL) + return FALSE; + + if (!_dbus_list_append (list, rule)) + return FALSE; + + bus_policy_rule_ref (rule); + + return TRUE; +} + +dbus_bool_t +bus_policy_append_group_rule (BusPolicy *policy, + dbus_gid_t gid, + BusPolicyRule *rule) +{ + DBusList **list; + + list = get_list (policy->rules_by_gid, gid); + + if (list == NULL) + return FALSE; + + if (!_dbus_list_append (list, rule)) + return FALSE; + + bus_policy_rule_ref (rule); + + return TRUE; +} + +dbus_bool_t +bus_policy_append_console_rule (BusPolicy *policy, + dbus_bool_t at_console, + BusPolicyRule *rule) +{ + if (at_console) + { + if (!_dbus_list_append (&policy->at_console_true_rules, rule)) + return FALSE; + } + else + { + if (!_dbus_list_append (&policy->at_console_false_rules, rule)) + return FALSE; + } + + bus_policy_rule_ref (rule); + + return TRUE; + +} + +static dbus_bool_t +append_copy_of_policy_list (DBusList **list, + DBusList **to_append) +{ + DBusList *link; + DBusList *tmp_list; + + tmp_list = NULL; + + /* Preallocate all our links */ + link = _dbus_list_get_first_link (to_append); + while (link != NULL) + { + if (!_dbus_list_append (&tmp_list, link->data)) + { + _dbus_list_clear (&tmp_list); + return FALSE; + } + + link = _dbus_list_get_next_link (to_append, link); + } + + /* Now append them */ + while ((link = _dbus_list_pop_first_link (&tmp_list))) + { + bus_policy_rule_ref (link->data); + _dbus_list_append_link (list, link); + } + + return TRUE; +} + +static dbus_bool_t +merge_id_hash (DBusHashTable *dest, + DBusHashTable *to_absorb) +{ + DBusHashIter iter; + + _dbus_hash_iter_init (to_absorb, &iter); + while (_dbus_hash_iter_next (&iter)) + { + unsigned long id = _dbus_hash_iter_get_ulong_key (&iter); + DBusList **list = _dbus_hash_iter_get_value (&iter); + DBusList **target = get_list (dest, id); + + if (target == NULL) + return FALSE; + + if (!append_copy_of_policy_list (target, list)) + return FALSE; + } + + return TRUE; +} + +dbus_bool_t +bus_policy_merge (BusPolicy *policy, + BusPolicy *to_absorb) +{ + /* FIXME Not properly atomic, but as used for configuration files we + * don't rely on it quite so much. + */ + + if (!append_copy_of_policy_list (&policy->default_rules, + &to_absorb->default_rules)) + return FALSE; + + if (!append_copy_of_policy_list (&policy->mandatory_rules, + &to_absorb->mandatory_rules)) + return FALSE; + + if (!append_copy_of_policy_list (&policy->at_console_true_rules, + &to_absorb->at_console_true_rules)) + return FALSE; + + if (!append_copy_of_policy_list (&policy->at_console_false_rules, + &to_absorb->at_console_false_rules)) + return FALSE; + + if (!merge_id_hash (policy->rules_by_uid, + to_absorb->rules_by_uid)) + return FALSE; + + if (!merge_id_hash (policy->rules_by_gid, + to_absorb->rules_by_gid)) + return FALSE; + + return TRUE; +} + +struct BusClientPolicy +{ + int refcount; + + DBusList *rules; +}; + +BusClientPolicy* +bus_client_policy_new (void) +{ + BusClientPolicy *policy; + + policy = dbus_new0 (BusClientPolicy, 1); + if (policy == NULL) + return NULL; + + policy->refcount = 1; + + return policy; +} + +BusClientPolicy * +bus_client_policy_ref (BusClientPolicy *policy) +{ + _dbus_assert (policy->refcount > 0); + + policy->refcount += 1; + + return policy; +} + +static void +rule_unref_foreach (void *data, + void *user_data) +{ + BusPolicyRule *rule = data; + + bus_policy_rule_unref (rule); +} + +void +bus_client_policy_unref (BusClientPolicy *policy) +{ + _dbus_assert (policy->refcount > 0); + + policy->refcount -= 1; + + if (policy->refcount == 0) + { + _dbus_list_foreach (&policy->rules, + rule_unref_foreach, + NULL); + + _dbus_list_clear (&policy->rules); + + dbus_free (policy); + } +} + +static void +remove_rules_by_type_up_to (BusClientPolicy *policy, + BusPolicyRuleType type, + DBusList *up_to) +{ + DBusList *link; + + link = _dbus_list_get_first_link (&policy->rules); + while (link != up_to) + { + BusPolicyRule *rule = link->data; + DBusList *next = _dbus_list_get_next_link (&policy->rules, link); + + if (rule->type == type) + { + _dbus_list_remove_link (&policy->rules, link); + bus_policy_rule_unref (rule); + } + + link = next; + } +} + +void +bus_client_policy_optimize (BusClientPolicy *policy) +{ + DBusList *link; + + /* The idea here is that if we have: + * + * <allow send_interface="foo.bar"/> + * <deny send_interface="*"/> + * + * (for example) the deny will always override the allow. So we + * delete the allow. Ditto for deny followed by allow, etc. This is + * a dumb thing to put in a config file, but the <include> feature + * of files allows for an "inheritance and override" pattern where + * it could make sense. If an included file wants to "start over" + * with a blanket deny, no point keeping the rules from the parent + * file. + */ + + _dbus_verbose ("Optimizing policy with %d rules\n", + _dbus_list_get_length (&policy->rules)); + + link = _dbus_list_get_first_link (&policy->rules); + while (link != NULL) + { + BusPolicyRule *rule; + DBusList *next; + dbus_bool_t remove_preceding; + + next = _dbus_list_get_next_link (&policy->rules, link); + rule = link->data; + + remove_preceding = FALSE; + + _dbus_assert (rule != NULL); + + switch (rule->type) + { + case BUS_POLICY_RULE_SEND: + remove_preceding = + rule->d.send.message_type == DBUS_MESSAGE_TYPE_INVALID && + rule->d.send.path == NULL && + rule->d.send.interface == NULL && + rule->d.send.member == NULL && + rule->d.send.error == NULL && + rule->d.send.destination == NULL; + break; + case BUS_POLICY_RULE_RECEIVE: + remove_preceding = + rule->d.receive.message_type == DBUS_MESSAGE_TYPE_INVALID && + rule->d.receive.path == NULL && + rule->d.receive.interface == NULL && + rule->d.receive.member == NULL && + rule->d.receive.error == NULL && + rule->d.receive.origin == NULL; + break; + case BUS_POLICY_RULE_OWN: + remove_preceding = + rule->d.own.service_name == NULL; + break; + case BUS_POLICY_RULE_USER: + case BUS_POLICY_RULE_GROUP: + _dbus_assert_not_reached ("invalid rule"); + break; + } + + if (remove_preceding) + remove_rules_by_type_up_to (policy, rule->type, + link); + + link = next; + } + + _dbus_verbose ("After optimization, policy has %d rules\n", + _dbus_list_get_length (&policy->rules)); +} + +dbus_bool_t +bus_client_policy_append_rule (BusClientPolicy *policy, + BusPolicyRule *rule) +{ + _dbus_verbose ("Appending rule %p with type %d to policy %p\n", + rule, rule->type, policy); + + if (!_dbus_list_append (&policy->rules, rule)) + return FALSE; + + bus_policy_rule_ref (rule); + + return TRUE; +} + +dbus_bool_t +bus_client_policy_check_can_send (BusClientPolicy *policy, + BusRegistry *registry, + dbus_bool_t requested_reply, + DBusConnection *receiver, + DBusMessage *message, + dbus_int32_t *toggles, + dbus_bool_t *log) +{ + DBusList *link; + dbus_bool_t allowed; + + /* policy->rules is in the order the rules appeared + * in the config file, i.e. last rule that applies wins + */ + + _dbus_verbose (" (policy) checking send rules\n"); + *toggles = 0; + + allowed = FALSE; + link = _dbus_list_get_first_link (&policy->rules); + while (link != NULL) + { + BusPolicyRule *rule = link->data; + + link = _dbus_list_get_next_link (&policy->rules, link); + + /* Rule is skipped if it specifies a different + * message name from the message, or a different + * destination from the message + */ + + if (rule->type != BUS_POLICY_RULE_SEND) + { + _dbus_verbose (" (policy) skipping non-send rule\n"); + continue; + } + + if (rule->d.send.message_type != DBUS_MESSAGE_TYPE_INVALID) + { + if (dbus_message_get_type (message) != rule->d.send.message_type) + { + _dbus_verbose (" (policy) skipping rule for different message type\n"); + continue; + } + } + + /* If it's a reply, the requested_reply flag kicks in */ + if (dbus_message_get_reply_serial (message) != 0) + { + /* for allow, requested_reply=true means the rule applies + * only when reply was requested. requested_reply=false means + * always allow. + */ + if (!requested_reply && rule->allow && rule->d.send.requested_reply && !rule->d.send.eavesdrop) + { + _dbus_verbose (" (policy) skipping allow rule since it only applies to requested replies and does not allow eavesdropping\n"); + continue; + } + + /* for deny, requested_reply=false means the rule applies only + * when the reply was not requested. requested_reply=true means the + * rule always applies. + */ + if (requested_reply && !rule->allow && !rule->d.send.requested_reply) + { + _dbus_verbose (" (policy) skipping deny rule since it only applies to unrequested replies\n"); + continue; + } + } + + if (rule->d.send.path != NULL) + { + if (dbus_message_get_path (message) != NULL && + strcmp (dbus_message_get_path (message), + rule->d.send.path) != 0) + { + _dbus_verbose (" (policy) skipping rule for different path\n"); + continue; + } + } + + if (rule->d.send.interface != NULL) + { + /* The interface is optional in messages. For allow rules, if the message + * has no interface we want to skip the rule (and thus not allow); + * for deny rules, if the message has no interface we want to use the + * rule (and thus deny). + */ + dbus_bool_t no_interface; + + no_interface = dbus_message_get_interface (message) == NULL; + + if ((no_interface && rule->allow) || + (!no_interface && + strcmp (dbus_message_get_interface (message), + rule->d.send.interface) != 0)) + { + _dbus_verbose (" (policy) skipping rule for different interface\n"); + continue; + } + } + + if (rule->d.send.member != NULL) + { + if (dbus_message_get_member (message) != NULL && + strcmp (dbus_message_get_member (message), + rule->d.send.member) != 0) + { + _dbus_verbose (" (policy) skipping rule for different member\n"); + continue; + } + } + + if (rule->d.send.error != NULL) + { + if (dbus_message_get_error_name (message) != NULL && + strcmp (dbus_message_get_error_name (message), + rule->d.send.error) != 0) + { + _dbus_verbose (" (policy) skipping rule for different error name\n"); + continue; + } + } + + if (rule->d.send.destination != NULL) + { + /* receiver can be NULL for messages that are sent to the + * message bus itself, we check the strings in that case as + * built-in services don't have a DBusConnection but messages + * to them have a destination service name. + */ + if (receiver == NULL) + { + if (!dbus_message_has_destination (message, + rule->d.send.destination)) + { + _dbus_verbose (" (policy) skipping rule because message dest is not %s\n", + rule->d.send.destination); + continue; + } + } + else + { + DBusString str; + BusService *service; + + _dbus_string_init_const (&str, rule->d.send.destination); + + service = bus_registry_lookup (registry, &str); + if (service == NULL) + { + _dbus_verbose (" (policy) skipping rule because dest %s doesn't exist\n", + rule->d.send.destination); + continue; + } + + if (!bus_service_has_owner (service, receiver)) + { + _dbus_verbose (" (policy) skipping rule because dest %s isn't owned by receiver\n", + rule->d.send.destination); + continue; + } + } + } + + /* Use this rule */ + allowed = rule->allow; + *log = rule->d.send.log; + (*toggles)++; + + _dbus_verbose (" (policy) used rule, allow now = %d\n", + allowed); + } + + return allowed; +} + +/* See docs on what the args mean on bus_context_check_security_policy() + * comment + */ +dbus_bool_t +bus_client_policy_check_can_receive (BusClientPolicy *policy, + BusRegistry *registry, + dbus_bool_t requested_reply, + DBusConnection *sender, + DBusConnection *addressed_recipient, + DBusConnection *proposed_recipient, + DBusMessage *message, + dbus_int32_t *toggles) +{ + DBusList *link; + dbus_bool_t allowed; + dbus_bool_t eavesdropping; + + eavesdropping = + addressed_recipient != proposed_recipient && + dbus_message_get_destination (message) != NULL; + + /* policy->rules is in the order the rules appeared + * in the config file, i.e. last rule that applies wins + */ + + _dbus_verbose (" (policy) checking receive rules, eavesdropping = %d\n", eavesdropping); + *toggles = 0; + + allowed = FALSE; + link = _dbus_list_get_first_link (&policy->rules); + while (link != NULL) + { + BusPolicyRule *rule = link->data; + + link = _dbus_list_get_next_link (&policy->rules, link); + + if (rule->type != BUS_POLICY_RULE_RECEIVE) + { + _dbus_verbose (" (policy) skipping non-receive rule\n"); + continue; + } + + if (rule->d.receive.message_type != DBUS_MESSAGE_TYPE_INVALID) + { + if (dbus_message_get_type (message) != rule->d.receive.message_type) + { + _dbus_verbose (" (policy) skipping rule for different message type\n"); + continue; + } + } + + /* for allow, eavesdrop=false means the rule doesn't apply when + * eavesdropping. eavesdrop=true means always allow. + */ + if (eavesdropping && rule->allow && !rule->d.receive.eavesdrop) + { + _dbus_verbose (" (policy) skipping allow rule since it doesn't apply to eavesdropping\n"); + continue; + } + + /* for deny, eavesdrop=true means the rule applies only when + * eavesdropping; eavesdrop=false means always deny. + */ + if (!eavesdropping && !rule->allow && rule->d.receive.eavesdrop) + { + _dbus_verbose (" (policy) skipping deny rule since it only applies to eavesdropping\n"); + continue; + } + + /* If it's a reply, the requested_reply flag kicks in */ + if (dbus_message_get_reply_serial (message) != 0) + { + /* for allow, requested_reply=true means the rule applies + * only when reply was requested. requested_reply=false means + * always allow. + */ + if (!requested_reply && rule->allow && rule->d.receive.requested_reply && !rule->d.receive.eavesdrop) + { + _dbus_verbose (" (policy) skipping allow rule since it only applies to requested replies and does not allow eavesdropping\n"); + continue; + } + + /* for deny, requested_reply=false means the rule applies only + * when the reply was not requested. requested_reply=true means the + * rule always applies. + */ + if (requested_reply && !rule->allow && !rule->d.receive.requested_reply) + { + _dbus_verbose (" (policy) skipping deny rule since it only applies to unrequested replies\n"); + continue; + } + } + + if (rule->d.receive.path != NULL) + { + if (dbus_message_get_path (message) != NULL && + strcmp (dbus_message_get_path (message), + rule->d.receive.path) != 0) + { + _dbus_verbose (" (policy) skipping rule for different path\n"); + continue; + } + } + + if (rule->d.receive.interface != NULL) + { + /* The interface is optional in messages. For allow rules, if the message + * has no interface we want to skip the rule (and thus not allow); + * for deny rules, if the message has no interface we want to use the + * rule (and thus deny). + */ + dbus_bool_t no_interface; + + no_interface = dbus_message_get_interface (message) == NULL; + + if ((no_interface && rule->allow) || + (!no_interface && + strcmp (dbus_message_get_interface (message), + rule->d.receive.interface) != 0)) + { + _dbus_verbose (" (policy) skipping rule for different interface\n"); + continue; + } + } + + if (rule->d.receive.member != NULL) + { + if (dbus_message_get_member (message) != NULL && + strcmp (dbus_message_get_member (message), + rule->d.receive.member) != 0) + { + _dbus_verbose (" (policy) skipping rule for different member\n"); + continue; + } + } + + if (rule->d.receive.error != NULL) + { + if (dbus_message_get_error_name (message) != NULL && + strcmp (dbus_message_get_error_name (message), + rule->d.receive.error) != 0) + { + _dbus_verbose (" (policy) skipping rule for different error name\n"); + continue; + } + } + + if (rule->d.receive.origin != NULL) + { + /* sender can be NULL for messages that originate from the + * message bus itself, we check the strings in that case as + * built-in services don't have a DBusConnection but will + * still set the sender on their messages. + */ + if (sender == NULL) + { + if (!dbus_message_has_sender (message, + rule->d.receive.origin)) + { + _dbus_verbose (" (policy) skipping rule because message sender is not %s\n", + rule->d.receive.origin); + continue; + } + } + else + { + BusService *service; + DBusString str; + + _dbus_string_init_const (&str, rule->d.receive.origin); + + service = bus_registry_lookup (registry, &str); + + if (service == NULL) + { + _dbus_verbose (" (policy) skipping rule because origin %s doesn't exist\n", + rule->d.receive.origin); + continue; + } + + if (!bus_service_has_owner (service, sender)) + { + _dbus_verbose (" (policy) skipping rule because origin %s isn't owned by sender\n", + rule->d.receive.origin); + continue; + } + } + } + + /* Use this rule */ + allowed = rule->allow; + (*toggles)++; + + _dbus_verbose (" (policy) used rule, allow now = %d\n", + allowed); + } + + return allowed; +} + +dbus_bool_t +bus_client_policy_check_can_own (BusClientPolicy *policy, + DBusConnection *connection, + const DBusString *service_name) +{ + DBusList *link; + dbus_bool_t allowed; + + /* policy->rules is in the order the rules appeared + * in the config file, i.e. last rule that applies wins + */ + + allowed = FALSE; + link = _dbus_list_get_first_link (&policy->rules); + while (link != NULL) + { + BusPolicyRule *rule = link->data; + + link = _dbus_list_get_next_link (&policy->rules, link); + + /* Rule is skipped if it specifies a different service name from + * the desired one. + */ + + if (rule->type != BUS_POLICY_RULE_OWN) + continue; + + if (rule->d.own.service_name != NULL) + { + if (!_dbus_string_equal_c_str (service_name, + rule->d.own.service_name)) + continue; + } + + /* Use this rule */ + allowed = rule->allow; + } + + return allowed; +} + +#ifdef DBUS_BUILD_TESTS + +dbus_bool_t +bus_policy_test (const DBusString *test_data_dir) +{ + /* This doesn't do anything for now because I decided to do it in + * dispatch.c instead by having some of the clients in dispatch.c + * have particular policies applied to them. + */ + + return TRUE; +} + +#endif /* DBUS_BUILD_TESTS */ diff --git a/bus/policy.h b/bus/policy.h new file mode 100644 index 00000000..1782dbf3 --- /dev/null +++ b/bus/policy.h @@ -0,0 +1,164 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* policy.h Bus security policy + * + * Copyright (C) 2003 Red Hat, Inc. + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef BUS_POLICY_H +#define BUS_POLICY_H + +#include <dbus/dbus.h> +#include <dbus/dbus-string.h> +#include <dbus/dbus-list.h> +#include <dbus/dbus-sysdeps.h> +#include "bus.h" + +typedef enum +{ + BUS_POLICY_RULE_SEND, + BUS_POLICY_RULE_RECEIVE, + BUS_POLICY_RULE_OWN, + BUS_POLICY_RULE_USER, + BUS_POLICY_RULE_GROUP +} BusPolicyRuleType; + +/** determines whether the rule affects a connection, or some global item */ +#define BUS_POLICY_RULE_IS_PER_CLIENT(rule) (!((rule)->type == BUS_POLICY_RULE_USER || \ + (rule)->type == BUS_POLICY_RULE_GROUP)) + +struct BusPolicyRule +{ + int refcount; + + BusPolicyRuleType type; + + unsigned int allow : 1; /**< #TRUE if this allows, #FALSE if it denies */ + + union + { + struct + { + /* message type can be DBUS_MESSAGE_TYPE_INVALID meaning "any" */ + int message_type; + /* any of these can be NULL meaning "any" */ + char *path; + char *interface; + char *member; + char *error; + char *destination; + unsigned int eavesdrop : 1; + unsigned int requested_reply : 1; + unsigned int log : 1; + } send; + + struct + { + /* message type can be DBUS_MESSAGE_TYPE_INVALID meaning "any" */ + int message_type; + /* any of these can be NULL meaning "any" */ + char *path; + char *interface; + char *member; + char *error; + char *origin; + unsigned int eavesdrop : 1; + unsigned int requested_reply : 1; + } receive; + + struct + { + /* can be NULL meaning "any" */ + char *service_name; + } own; + + struct + { + /* can be DBUS_UID_UNSET meaning "any" */ + dbus_uid_t uid; + } user; + + struct + { + /* can be DBUS_GID_UNSET meaning "any" */ + dbus_gid_t gid; + } group; + + } d; +}; + +BusPolicyRule* bus_policy_rule_new (BusPolicyRuleType type, + dbus_bool_t allow); +BusPolicyRule* bus_policy_rule_ref (BusPolicyRule *rule); +void bus_policy_rule_unref (BusPolicyRule *rule); + +BusPolicy* bus_policy_new (void); +BusPolicy* bus_policy_ref (BusPolicy *policy); +void bus_policy_unref (BusPolicy *policy); +BusClientPolicy* bus_policy_create_client_policy (BusPolicy *policy, + DBusConnection *connection, + DBusError *error); +dbus_bool_t bus_policy_allow_unix_user (BusPolicy *policy, + unsigned long uid); +dbus_bool_t bus_policy_allow_windows_user (BusPolicy *policy, + const char *windows_sid); +dbus_bool_t bus_policy_append_default_rule (BusPolicy *policy, + BusPolicyRule *rule); +dbus_bool_t bus_policy_append_mandatory_rule (BusPolicy *policy, + BusPolicyRule *rule); +dbus_bool_t bus_policy_append_user_rule (BusPolicy *policy, + dbus_uid_t uid, + BusPolicyRule *rule); +dbus_bool_t bus_policy_append_group_rule (BusPolicy *policy, + dbus_gid_t gid, + BusPolicyRule *rule); +dbus_bool_t bus_policy_append_console_rule (BusPolicy *policy, + dbus_bool_t at_console, + BusPolicyRule *rule); + +dbus_bool_t bus_policy_merge (BusPolicy *policy, + BusPolicy *to_absorb); + +BusClientPolicy* bus_client_policy_new (void); +BusClientPolicy* bus_client_policy_ref (BusClientPolicy *policy); +void bus_client_policy_unref (BusClientPolicy *policy); +dbus_bool_t bus_client_policy_check_can_send (BusClientPolicy *policy, + BusRegistry *registry, + dbus_bool_t requested_reply, + DBusConnection *receiver, + DBusMessage *message, + dbus_int32_t *toggles, + dbus_bool_t *log); +dbus_bool_t bus_client_policy_check_can_receive (BusClientPolicy *policy, + BusRegistry *registry, + dbus_bool_t requested_reply, + DBusConnection *sender, + DBusConnection *addressed_recipient, + DBusConnection *proposed_recipient, + DBusMessage *message, + dbus_int32_t *toggles); +dbus_bool_t bus_client_policy_check_can_own (BusClientPolicy *policy, + DBusConnection *connection, + const DBusString *service_name); +dbus_bool_t bus_client_policy_append_rule (BusClientPolicy *policy, + BusPolicyRule *rule); +void bus_client_policy_optimize (BusClientPolicy *policy); + + +#endif /* BUS_POLICY_H */ diff --git a/bus/rc.messagebus.in b/bus/rc.messagebus.in new file mode 100644 index 00000000..b147503d --- /dev/null +++ b/bus/rc.messagebus.in @@ -0,0 +1,79 @@ +#!/bin/sh +# +# messagebus: The D-BUS systemwide message bus +# +# chkconfig: 345 97 03 +# description: This is a daemon which broadcasts notifications of system events \ +# and other messages. See http://www.freedesktop.org/software/dbus/ +# +# processname: dbus-daemon +# pidfile: @DBUS_SYSTEM_PID_FILE@ +# + +# Sanity checks. +#[ -x @EXPANDED_BINDIR@/dbus-daemon ] || exit 0 + +# Source function library. +#. @EXPANDED_SYSCONFDIR@/rc.d/init.d/functions + +# so we can rearrange this easily +#processname=dbus-daemon +#servicename=messagebus + +#RETVAL=0 + +start() { + echo "Starting system message bus" + if [ -x @EXPANDED_BINDIR@/dbus-uuidgen ] ; then + @EXPANDED_BINDIR@/dbus-uuidgen --ensure + fi + + if [ -x @EXPANDED_BINDIR@/dbus-daemon ];then + @EXPANDED_BINDIR@/dbus-daemon --system + fi + #daemon --check $servicename $processname --system + #RETVAL=$? + #echo + #[ $RETVAL -eq 0 ] && touch @EXPANDED_LOCALSTATEDIR@/lock/subsys/$servicename +} + +stop() { + echo "Stopping system message bus" + + ## we don't want to kill all the per-user $processname, we want + ## to use the pid file *only*; because we use the fake nonexistent + ## program name "$servicename" that should be safe-ish + killall dbus-daemon + #RETVAL=$? + #echo + #if [ $RETVAL -eq 0 ]; then + # rm -f @EXPANDED_LOCALSTATEDIR@/lock/subsys/$servicename + # rm -f @DBUS_SYSTEM_PID_FILE@ + #fi +} + +# See how we were called. +case "$1" in + start) + start + ;; + stop) + stop + ;; + status) + status $processname + RETVAL=$? + ;; + restart) + stop + start + ;; + reload) + echo "Message bus can't reload its configuration, you have to restart it" + RETVAL=$? + ;; + *) + echo $"Usage: $0 {start|stop|status|restart|reload}" + ;; +esac +exit $RETVAL diff --git a/bus/selinux.c b/bus/selinux.c new file mode 100644 index 00000000..5a9af5ac --- /dev/null +++ b/bus/selinux.c @@ -0,0 +1,1091 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * selinux.c SELinux security checks for D-Bus + * + * Author: Matthew Rickard <mjricka@epoch.ncsc.mil> + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#include <dbus/dbus-internals.h> +#include <dbus/dbus-string.h> +#include <dbus/dbus-userdb.h> +#include "selinux.h" +#include "services.h" +#include "policy.h" +#include "utils.h" +#include "config-parser.h" + +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#ifdef HAVE_SELINUX +#include <sys/types.h> +#include <unistd.h> +#include <limits.h> +#include <pthread.h> +#include <syslog.h> +#include <selinux/selinux.h> +#include <selinux/avc.h> +#include <selinux/av_permissions.h> +#include <selinux/flask.h> +#include <signal.h> +#include <stdarg.h> +#include <stdio.h> +#include <grp.h> +#endif /* HAVE_SELINUX */ +#ifdef HAVE_LIBAUDIT +#include <cap-ng.h> +#include <libaudit.h> +#endif /* HAVE_LIBAUDIT */ + +#define BUS_SID_FROM_SELINUX(sid) ((BusSELinuxID*) (sid)) +#define SELINUX_SID_FROM_BUS(sid) ((security_id_t) (sid)) + +#ifdef HAVE_SELINUX +/* Store the value telling us if SELinux is enabled in the kernel. */ +static dbus_bool_t selinux_enabled = FALSE; + +/* Store an avc_entry_ref to speed AVC decisions. */ +static struct avc_entry_ref aeref; + +/* Store the SID of the bus itself to use as the default. */ +static security_id_t bus_sid = SECSID_WILD; + +/* Thread to listen for SELinux status changes via netlink. */ +static pthread_t avc_notify_thread; + +/* Prototypes for AVC callback functions. */ +static void log_callback (const char *fmt, ...); +static void log_audit_callback (void *data, security_class_t class, char *buf, size_t bufleft); +static void *avc_create_thread (void (*run) (void)); +static void avc_stop_thread (void *thread); +static void *avc_alloc_lock (void); +static void avc_get_lock (void *lock); +static void avc_release_lock (void *lock); +static void avc_free_lock (void *lock); + +/* AVC callback structures for use in avc_init. */ +static const struct avc_memory_callback mem_cb = +{ + .func_malloc = dbus_malloc, + .func_free = dbus_free +}; +static const struct avc_log_callback log_cb = +{ + .func_log = log_callback, + .func_audit = log_audit_callback +}; +static const struct avc_thread_callback thread_cb = +{ + .func_create_thread = avc_create_thread, + .func_stop_thread = avc_stop_thread +}; +static const struct avc_lock_callback lock_cb = +{ + .func_alloc_lock = avc_alloc_lock, + .func_get_lock = avc_get_lock, + .func_release_lock = avc_release_lock, + .func_free_lock = avc_free_lock +}; +#endif /* HAVE_SELINUX */ + +/** + * Log callback to log denial messages from the AVC. + * This is used in avc_init. Logs to both standard + * error and syslogd. + * + * @param fmt the format string + * @param variable argument list + */ +#ifdef HAVE_SELINUX + +#ifdef HAVE_LIBAUDIT +static int audit_fd = -1; +#endif + +void +bus_selinux_audit_init(void) +{ +#ifdef HAVE_LIBAUDIT + audit_fd = audit_open (); + + if (audit_fd < 0) + { + /* If kernel doesn't support audit, bail out */ + if (errno == EINVAL || errno == EPROTONOSUPPORT || errno == EAFNOSUPPORT) + return; + /* If user bus, bail out */ + if (errno == EPERM && getuid() != 0) + return; + _dbus_warn ("Failed opening connection to the audit subsystem"); + } +#endif /* HAVE_LIBAUDIT */ +} + +static void +log_callback (const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + +#ifdef HAVE_LIBAUDIT + if (audit_fd >= 0) + { + capng_get_caps_process(); + if (capng_have_capability(CAPNG_EFFECTIVE, CAP_AUDIT_WRITE)) + { + char buf[PATH_MAX*2]; + + /* FIXME: need to change this to show real user */ + vsnprintf(buf, sizeof(buf), fmt, ap); + audit_log_user_avc_message(audit_fd, AUDIT_USER_AVC, buf, NULL, NULL, + NULL, getuid()); + return; + } + } +#endif /* HAVE_LIBAUDIT */ + + vsyslog (LOG_INFO, fmt, ap); + va_end(ap); +} + +/** + * On a policy reload we need to reparse the SELinux configuration file, since + * this could have changed. Send a SIGHUP to reload all configs. + */ +static int +policy_reload_callback (u_int32_t event, security_id_t ssid, + security_id_t tsid, security_class_t tclass, + access_vector_t perms, access_vector_t *out_retained) +{ + if (event == AVC_CALLBACK_RESET) + return raise (SIGHUP); + + return 0; +} + +/** + * Log any auxiliary data + */ +static void +log_audit_callback (void *data, security_class_t class, char *buf, size_t bufleft) +{ + DBusString *audmsg = data; + + if (bufleft > (size_t) _dbus_string_get_length(audmsg)) + { + _dbus_string_copy_to_buffer_with_nul (audmsg, buf, bufleft); + } + else + { + DBusString s; + + _dbus_string_init_const(&s, "Buffer too small for audit message"); + + if (bufleft > (size_t) _dbus_string_get_length(&s)) + _dbus_string_copy_to_buffer_with_nul (&s, buf, bufleft); + } +} + +/** + * Create thread to notify the AVC of enforcing and policy reload + * changes via netlink. + * + * @param run the thread run function + * @return pointer to the thread + */ +static void * +avc_create_thread (void (*run) (void)) +{ + int rc; + + rc = pthread_create (&avc_notify_thread, NULL, (void *(*) (void *)) run, NULL); + if (rc != 0) + { + _dbus_warn ("Failed to start AVC thread: %s\n", _dbus_strerror (rc)); + exit (1); + } + return &avc_notify_thread; +} + +/* Stop AVC netlink thread. */ +static void +avc_stop_thread (void *thread) +{ + pthread_cancel (*(pthread_t *) thread); +} + +/* Allocate a new AVC lock. */ +static void * +avc_alloc_lock (void) +{ + pthread_mutex_t *avc_mutex; + + avc_mutex = dbus_new (pthread_mutex_t, 1); + if (avc_mutex == NULL) + { + _dbus_warn ("Could not create mutex: %s\n", _dbus_strerror (errno)); + exit (1); + } + pthread_mutex_init (avc_mutex, NULL); + + return avc_mutex; +} + +/* Acquire an AVC lock. */ +static void +avc_get_lock (void *lock) +{ + pthread_mutex_lock (lock); +} + +/* Release an AVC lock. */ +static void +avc_release_lock (void *lock) +{ + pthread_mutex_unlock (lock); +} + +/* Free an AVC lock. */ +static void +avc_free_lock (void *lock) +{ + pthread_mutex_destroy (lock); + dbus_free (lock); +} +#endif /* HAVE_SELINUX */ + +/** + * Return whether or not SELinux is enabled; must be + * called after bus_selinux_init. + */ +dbus_bool_t +bus_selinux_enabled (void) +{ +#ifdef HAVE_SELINUX + return selinux_enabled; +#else + return FALSE; +#endif /* HAVE_SELINUX */ +} + +/** + * Do early initialization; determine whether SELinux is enabled. + */ +dbus_bool_t +bus_selinux_pre_init (void) +{ +#ifdef HAVE_SELINUX + int r; + _dbus_assert (bus_sid == SECSID_WILD); + + /* Determine if we are running an SELinux kernel. */ + r = is_selinux_enabled (); + if (r < 0) + { + _dbus_warn ("Could not tell if SELinux is enabled: %s\n", + _dbus_strerror (errno)); + return FALSE; + } + + selinux_enabled = r != 0; + return TRUE; +#else + return TRUE; +#endif +} + +/** + * Initialize the user space access vector cache (AVC) for D-Bus and set up + * logging callbacks. + */ +dbus_bool_t +bus_selinux_full_init (void) +{ +#ifdef HAVE_SELINUX + char *bus_context; + + _dbus_assert (bus_sid == SECSID_WILD); + + if (!selinux_enabled) + { + _dbus_verbose ("SELinux not enabled in this kernel.\n"); + return TRUE; + } + + _dbus_verbose ("SELinux is enabled in this kernel.\n"); + + avc_entry_ref_init (&aeref); + if (avc_init ("avc", &mem_cb, &log_cb, &thread_cb, &lock_cb) < 0) + { + _dbus_warn ("Failed to start Access Vector Cache (AVC).\n"); + return FALSE; + } + else + { + openlog ("dbus", LOG_PERROR, LOG_USER); + _dbus_verbose ("Access Vector Cache (AVC) started.\n"); + } + + if (avc_add_callback (policy_reload_callback, AVC_CALLBACK_RESET, + NULL, NULL, 0, 0) < 0) + { + _dbus_warn ("Failed to add policy reload callback: %s\n", + _dbus_strerror (errno)); + avc_destroy (); + return FALSE; + } + + bus_context = NULL; + bus_sid = SECSID_WILD; + + if (getcon (&bus_context) < 0) + { + _dbus_verbose ("Error getting context of bus: %s\n", + _dbus_strerror (errno)); + return FALSE; + } + + if (avc_context_to_sid (bus_context, &bus_sid) < 0) + { + _dbus_verbose ("Error getting SID from bus context: %s\n", + _dbus_strerror (errno)); + freecon (bus_context); + return FALSE; + } + + freecon (bus_context); + +#endif /* HAVE_SELINUX */ + return TRUE; +} + +/** + * Decrement SID reference count. + * + * @param sid the SID to decrement + */ +void +bus_selinux_id_unref (BusSELinuxID *sid) +{ +#ifdef HAVE_SELINUX + if (!selinux_enabled) + return; + + _dbus_assert (sid != NULL); + + sidput (SELINUX_SID_FROM_BUS (sid)); +#endif /* HAVE_SELINUX */ +} + +void +bus_selinux_id_ref (BusSELinuxID *sid) +{ +#ifdef HAVE_SELINUX + if (!selinux_enabled) + return; + + _dbus_assert (sid != NULL); + + sidget (SELINUX_SID_FROM_BUS (sid)); +#endif /* HAVE_SELINUX */ +} + +/** + * Determine if the SELinux security policy allows the given sender + * security context to go to the given recipient security context. + * This function determines if the requested permissions are to be + * granted from the connection to the message bus or to another + * optionally supplied security identifier (e.g. for a service + * context). Currently these permissions are either send_msg or + * acquire_svc in the dbus class. + * + * @param sender_sid source security context + * @param override_sid is the target security context. If SECSID_WILD this will + * use the context of the bus itself (e.g. the default). + * @param target_class is the target security class. + * @param requested is the requested permissions. + * @returns #TRUE if security policy allows the send. + */ +#ifdef HAVE_SELINUX +static dbus_bool_t +bus_selinux_check (BusSELinuxID *sender_sid, + BusSELinuxID *override_sid, + security_class_t target_class, + access_vector_t requested, + DBusString *auxdata) +{ + if (!selinux_enabled) + return TRUE; + + /* Make the security check. AVC checks enforcing mode here as well. */ + if (avc_has_perm (SELINUX_SID_FROM_BUS (sender_sid), + override_sid ? + SELINUX_SID_FROM_BUS (override_sid) : + SELINUX_SID_FROM_BUS (bus_sid), + target_class, requested, &aeref, auxdata) < 0) + { + switch (errno) + { + case EACCES: + _dbus_verbose ("SELinux denying due to security policy.\n"); + return FALSE; + case EINVAL: + _dbus_verbose ("SELinux denying due to invalid security context.\n"); + return FALSE; + default: + _dbus_verbose ("SELinux denying due to: %s\n", _dbus_strerror (errno)); + return FALSE; + } + } + else + return TRUE; +} +#endif /* HAVE_SELINUX */ + +/** + * Returns true if the given connection can acquire a service, + * assuming the given security ID is needed for that service. + * + * @param connection connection that wants to own the service + * @param service_sid the SID of the service from the table + * @returns #TRUE if acquire is permitted. + */ +dbus_bool_t +bus_selinux_allows_acquire_service (DBusConnection *connection, + BusSELinuxID *service_sid, + const char *service_name, + DBusError *error) +{ +#ifdef HAVE_SELINUX + BusSELinuxID *connection_sid; + unsigned long spid; + DBusString auxdata; + dbus_bool_t ret; + + if (!selinux_enabled) + return TRUE; + + connection_sid = bus_connection_get_selinux_id (connection); + if (!dbus_connection_get_unix_process_id (connection, &spid)) + spid = 0; + + if (!_dbus_string_init (&auxdata)) + goto oom; + + if (!_dbus_string_append (&auxdata, "service=")) + goto oom; + + if (!_dbus_string_append (&auxdata, service_name)) + goto oom; + + if (spid) + { + if (!_dbus_string_append (&auxdata, " spid=")) + goto oom; + + if (!_dbus_string_append_uint (&auxdata, spid)) + goto oom; + } + + ret = bus_selinux_check (connection_sid, + service_sid, + SECCLASS_DBUS, + DBUS__ACQUIRE_SVC, + &auxdata); + + _dbus_string_free (&auxdata); + return ret; + + oom: + _dbus_string_free (&auxdata); + BUS_SET_OOM (error); + return FALSE; + +#else + return TRUE; +#endif /* HAVE_SELINUX */ +} + +/** + * Check if SELinux security controls allow the message to be sent to a + * particular connection based on the security context of the sender and + * that of the receiver. The destination connection need not be the + * addressed recipient, it could be an "eavesdropper" + * + * @param sender the sender of the message. + * @param proposed_recipient the connection the message is to be sent to. + * @returns whether to allow the send + */ +dbus_bool_t +bus_selinux_allows_send (DBusConnection *sender, + DBusConnection *proposed_recipient, + const char *msgtype, + const char *interface, + const char *member, + const char *error_name, + const char *destination, + DBusError *error) +{ +#ifdef HAVE_SELINUX + BusSELinuxID *recipient_sid; + BusSELinuxID *sender_sid; + unsigned long spid, tpid; + DBusString auxdata; + dbus_bool_t ret; + dbus_bool_t string_alloced; + + if (!selinux_enabled) + return TRUE; + + if (!sender || !dbus_connection_get_unix_process_id (sender, &spid)) + spid = 0; + if (!proposed_recipient || !dbus_connection_get_unix_process_id (proposed_recipient, &tpid)) + tpid = 0; + + string_alloced = FALSE; + if (!_dbus_string_init (&auxdata)) + goto oom; + string_alloced = TRUE; + + if (!_dbus_string_append (&auxdata, "msgtype=")) + goto oom; + + if (!_dbus_string_append (&auxdata, msgtype)) + goto oom; + + if (interface) + { + if (!_dbus_string_append (&auxdata, " interface=")) + goto oom; + if (!_dbus_string_append (&auxdata, interface)) + goto oom; + } + + if (member) + { + if (!_dbus_string_append (&auxdata, " member=")) + goto oom; + if (!_dbus_string_append (&auxdata, member)) + goto oom; + } + + if (error_name) + { + if (!_dbus_string_append (&auxdata, " error_name=")) + goto oom; + if (!_dbus_string_append (&auxdata, error_name)) + goto oom; + } + + if (destination) + { + if (!_dbus_string_append (&auxdata, " dest=")) + goto oom; + if (!_dbus_string_append (&auxdata, destination)) + goto oom; + } + + if (spid) + { + if (!_dbus_string_append (&auxdata, " spid=")) + goto oom; + + if (!_dbus_string_append_uint (&auxdata, spid)) + goto oom; + } + + if (tpid) + { + if (!_dbus_string_append (&auxdata, " tpid=")) + goto oom; + + if (!_dbus_string_append_uint (&auxdata, tpid)) + goto oom; + } + + sender_sid = bus_connection_get_selinux_id (sender); + /* A NULL proposed_recipient means the bus itself. */ + if (proposed_recipient) + recipient_sid = bus_connection_get_selinux_id (proposed_recipient); + else + recipient_sid = BUS_SID_FROM_SELINUX (bus_sid); + + ret = bus_selinux_check (sender_sid, + recipient_sid, + SECCLASS_DBUS, + DBUS__SEND_MSG, + &auxdata); + + _dbus_string_free (&auxdata); + + return ret; + + oom: + if (string_alloced) + _dbus_string_free (&auxdata); + BUS_SET_OOM (error); + return FALSE; + +#else + return TRUE; +#endif /* HAVE_SELINUX */ +} + +dbus_bool_t +bus_selinux_append_context (DBusMessage *message, + BusSELinuxID *sid, + DBusError *error) +{ +#ifdef HAVE_SELINUX + char *context; + + if (avc_sid_to_context (SELINUX_SID_FROM_BUS (sid), &context) < 0) + { + if (errno == ENOMEM) + BUS_SET_OOM (error); + else + dbus_set_error (error, DBUS_ERROR_FAILED, + "Error getting context from SID: %s\n", + _dbus_strerror (errno)); + return FALSE; + } + if (!dbus_message_append_args (message, + DBUS_TYPE_ARRAY, + DBUS_TYPE_BYTE, + &context, + strlen (context), + DBUS_TYPE_INVALID)) + { + _DBUS_SET_OOM (error); + return FALSE; + } + freecon (context); + return TRUE; +#else + return TRUE; +#endif +} + +/** + * Gets the security context of a connection to the bus. It is up to + * the caller to freecon() when they are done. + * + * @param connection the connection to get the context of. + * @param con the location to store the security context. + * @returns #TRUE if context is successfully obtained. + */ +#ifdef HAVE_SELINUX +static dbus_bool_t +bus_connection_read_selinux_context (DBusConnection *connection, + char **con) +{ + int fd; + + if (!selinux_enabled) + return FALSE; + + _dbus_assert (connection != NULL); + + if (!dbus_connection_get_unix_fd (connection, &fd)) + { + _dbus_verbose ("Failed to get file descriptor of socket.\n"); + return FALSE; + } + + if (getpeercon (fd, con) < 0) + { + _dbus_verbose ("Error getting context of socket peer: %s\n", + _dbus_strerror (errno)); + return FALSE; + } + + _dbus_verbose ("Successfully read connection context.\n"); + return TRUE; +} +#endif /* HAVE_SELINUX */ + +/** + * Read the SELinux ID from the connection. + * + * @param connection the connection to read from + * @returns the SID if successfully determined, #NULL otherwise. + */ +BusSELinuxID* +bus_selinux_init_connection_id (DBusConnection *connection, + DBusError *error) +{ +#ifdef HAVE_SELINUX + char *con; + security_id_t sid; + + if (!selinux_enabled) + return NULL; + + if (!bus_connection_read_selinux_context (connection, &con)) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "Failed to read an SELinux context from connection"); + _dbus_verbose ("Error getting peer context.\n"); + return NULL; + } + + _dbus_verbose ("Converting context to SID to store on connection\n"); + + if (avc_context_to_sid (con, &sid) < 0) + { + if (errno == ENOMEM) + BUS_SET_OOM (error); + else + dbus_set_error (error, DBUS_ERROR_FAILED, + "Error getting SID from context \"%s\": %s\n", + con, _dbus_strerror (errno)); + + _dbus_warn ("Error getting SID from context \"%s\": %s\n", + con, _dbus_strerror (errno)); + + freecon (con); + return NULL; + } + + freecon (con); + return BUS_SID_FROM_SELINUX (sid); +#else + return NULL; +#endif /* HAVE_SELINUX */ +} + + +/** + * Function for freeing hash table data. These SIDs + * should no longer be referenced. + */ +static void +bus_selinux_id_table_free_value (BusSELinuxID *sid) +{ +#ifdef HAVE_SELINUX + /* NULL sometimes due to how DBusHashTable works */ + if (sid) + bus_selinux_id_unref (sid); +#endif /* HAVE_SELINUX */ +} + +/** + * Creates a new table mapping service names to security ID. + * A security ID is a "compiled" security context, a security + * context is just a string. + * + * @returns the new table or #NULL if no memory + */ +DBusHashTable* +bus_selinux_id_table_new (void) +{ + return _dbus_hash_table_new (DBUS_HASH_STRING, + (DBusFreeFunction) dbus_free, + (DBusFreeFunction) bus_selinux_id_table_free_value); +} + +/** + * Hashes a service name and service context into the service SID + * table as a string and a SID. + * + * @param service_name is the name of the service. + * @param service_context is the context of the service. + * @param service_table is the table to hash them into. + * @return #FALSE if not enough memory + */ +dbus_bool_t +bus_selinux_id_table_insert (DBusHashTable *service_table, + const char *service_name, + const char *service_context) +{ +#ifdef HAVE_SELINUX + dbus_bool_t retval; + security_id_t sid; + char *key; + + if (!selinux_enabled) + return TRUE; + + sid = SECSID_WILD; + retval = FALSE; + + key = _dbus_strdup (service_name); + if (key == NULL) + return retval; + + if (avc_context_to_sid ((char *) service_context, &sid) < 0) + { + if (errno == ENOMEM) + { + dbus_free (key); + return FALSE; + } + + _dbus_warn ("Error getting SID from context \"%s\": %s\n", + (char *) service_context, + _dbus_strerror (errno)); + goto out; + } + + if (!_dbus_hash_table_insert_string (service_table, + key, + BUS_SID_FROM_SELINUX (sid))) + goto out; + + _dbus_verbose ("Parsed \tservice: %s \n\t\tcontext: %s\n", + key, + sid->ctx); + + /* These are owned by the hash, so clear them to avoid unref */ + key = NULL; + sid = SECSID_WILD; + + retval = TRUE; + + out: + if (sid != SECSID_WILD) + sidput (sid); + + if (key) + dbus_free (key); + + return retval; +#else + return TRUE; +#endif /* HAVE_SELINUX */ +} + + +/** + * Find the security identifier associated with a particular service + * name. Return a pointer to this SID, or #NULL/SECSID_WILD if the + * service is not found in the hash table. This should be nearly a + * constant time operation. If SELinux support is not available, + * always return NULL. + * + * @param service_table the hash table to check for service name. + * @param service_name the name of the service to look for. + * @returns the SELinux ID associated with the service + */ +BusSELinuxID* +bus_selinux_id_table_lookup (DBusHashTable *service_table, + const DBusString *service_name) +{ +#ifdef HAVE_SELINUX + security_id_t sid; + + sid = SECSID_WILD; /* default context */ + + if (!selinux_enabled) + return NULL; + + _dbus_verbose ("Looking up service SID for %s\n", + _dbus_string_get_const_data (service_name)); + + sid = _dbus_hash_table_lookup_string (service_table, + _dbus_string_get_const_data (service_name)); + + if (sid == SECSID_WILD) + _dbus_verbose ("Service %s not found\n", + _dbus_string_get_const_data (service_name)); + else + _dbus_verbose ("Service %s found\n", + _dbus_string_get_const_data (service_name)); + + return BUS_SID_FROM_SELINUX (sid); +#endif /* HAVE_SELINUX */ + return NULL; +} + +/** + * Get the SELinux policy root. This is used to find the D-Bus + * specific config file within the policy. + */ +const char * +bus_selinux_get_policy_root (void) +{ +#ifdef HAVE_SELINUX + return selinux_policy_root (); +#else + return NULL; +#endif /* HAVE_SELINUX */ +} + +/** + * For debugging: Print out the current hash table of service SIDs. + */ +void +bus_selinux_id_table_print (DBusHashTable *service_table) +{ +#ifdef DBUS_ENABLE_VERBOSE_MODE +#ifdef HAVE_SELINUX + DBusHashIter iter; + + if (!selinux_enabled) + return; + + _dbus_verbose ("Service SID Table:\n"); + _dbus_hash_iter_init (service_table, &iter); + while (_dbus_hash_iter_next (&iter)) + { + const char *key = _dbus_hash_iter_get_string_key (&iter); + security_id_t sid = _dbus_hash_iter_get_value (&iter); + _dbus_verbose ("The key is %s\n", key); + _dbus_verbose ("The context is %s\n", sid->ctx); + _dbus_verbose ("The refcount is %d\n", sid->refcnt); + } +#endif /* HAVE_SELINUX */ +#endif /* DBUS_ENABLE_VERBOSE_MODE */ +} + + +#ifdef DBUS_ENABLE_VERBOSE_MODE +#ifdef HAVE_SELINUX +/** + * Print out some AVC statistics. + */ +static void +bus_avc_print_stats (void) +{ + struct avc_cache_stats cstats; + + if (!selinux_enabled) + return; + + _dbus_verbose ("AVC Statistics:\n"); + avc_cache_stats (&cstats); + avc_av_stats (); + _dbus_verbose ("AVC Cache Statistics:\n"); + _dbus_verbose ("Entry lookups: %d\n", cstats.entry_lookups); + _dbus_verbose ("Entry hits: %d\n", cstats.entry_hits); + _dbus_verbose ("Entry misses %d\n", cstats.entry_misses); + _dbus_verbose ("Entry discards: %d\n", cstats.entry_discards); + _dbus_verbose ("CAV lookups: %d\n", cstats.cav_lookups); + _dbus_verbose ("CAV hits: %d\n", cstats.cav_hits); + _dbus_verbose ("CAV probes: %d\n", cstats.cav_probes); + _dbus_verbose ("CAV misses: %d\n", cstats.cav_misses); +} +#endif /* HAVE_SELINUX */ +#endif /* DBUS_ENABLE_VERBOSE_MODE */ + + +/** + * Destroy the AVC before we terminate. + */ +void +bus_selinux_shutdown (void) +{ +#ifdef HAVE_SELINUX + if (!selinux_enabled) + return; + + _dbus_verbose ("AVC shutdown\n"); + + if (bus_sid != SECSID_WILD) + { + sidput (bus_sid); + bus_sid = SECSID_WILD; + +#ifdef DBUS_ENABLE_VERBOSE_MODE + + if (_dbus_is_verbose()) + bus_avc_print_stats (); + +#endif /* DBUS_ENABLE_VERBOSE_MODE */ + + avc_destroy (); +#ifdef HAVE_LIBAUDIT + audit_close (audit_fd); +#endif /* HAVE_LIBAUDIT */ + } +#endif /* HAVE_SELINUX */ +} + +/* The !HAVE_LIBAUDIT case lives in dbus-sysdeps-util-unix.c */ +#ifdef HAVE_LIBAUDIT +/** + * Changes the user and group the bus is running as. + * + * @param user the user to become + * @param error return location for errors + * @returns #FALSE on failure + */ +dbus_bool_t +_dbus_change_to_daemon_user (const char *user, + DBusError *error) +{ + dbus_uid_t uid; + dbus_gid_t gid; + DBusString u; + + _dbus_string_init_const (&u, user); + + if (!_dbus_get_user_id_and_primary_group (&u, &uid, &gid)) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "User '%s' does not appear to exist?", + user); + return FALSE; + } + + /* If we were root */ + if (_dbus_geteuid () == 0) + { + int rc; + + capng_clear (CAPNG_SELECT_BOTH); + capng_update (CAPNG_ADD, CAPNG_EFFECTIVE | CAPNG_PERMITTED, + CAP_AUDIT_WRITE); + rc = capng_change_id (uid, gid, 0); + if (rc) + { + switch (rc) { + default: + dbus_set_error (error, DBUS_ERROR_FAILED, + "Failed to drop capabilities: %s\n", + _dbus_strerror (errno)); + break; + case -4: + dbus_set_error (error, _dbus_error_from_errno (errno), + "Failed to set GID to %lu: %s", gid, + _dbus_strerror (errno)); + break; + case -5: + _dbus_warn ("Failed to drop supplementary groups: %s\n", + _dbus_strerror (errno)); + break; + case -6: + dbus_set_error (error, _dbus_error_from_errno (errno), + "Failed to set UID to %lu: %s", uid, + _dbus_strerror (errno)); + break; + case -7: + dbus_set_error (error, _dbus_error_from_errno (errno), + "Failed to unset keep-capabilities: %s\n", + _dbus_strerror (errno)); + break; + } + return FALSE; + } + } + + return TRUE; +} +#endif + diff --git a/bus/selinux.h b/bus/selinux.h new file mode 100644 index 00000000..3bab36de --- /dev/null +++ b/bus/selinux.h @@ -0,0 +1,72 @@ +/* selinux.h SELinux security check headers for D-BUS + * + * Author: Matthew Rickard <mjricka@epoch.ncsc.mil> + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef BUS_SELINUX_H +#define BUS_SELINUX_H + +#include <dbus/dbus-hash.h> +#include <dbus/dbus-connection.h> +#include "services.h" + +dbus_bool_t bus_selinux_pre_init (void); +dbus_bool_t bus_selinux_full_init(void); +void bus_selinux_shutdown (void); + +dbus_bool_t bus_selinux_enabled (void); + +void bus_selinux_id_ref (BusSELinuxID *sid); +void bus_selinux_id_unref (BusSELinuxID *sid); + +DBusHashTable* bus_selinux_id_table_new (void); +BusSELinuxID* bus_selinux_id_table_lookup (DBusHashTable *service_table, + const DBusString *service_name); +dbus_bool_t bus_selinux_id_table_insert (DBusHashTable *service_table, + const char *service_name, + const char *service_context); +void bus_selinux_id_table_print (DBusHashTable *service_table); +const char* bus_selinux_get_policy_root (void); + +dbus_bool_t bus_selinux_append_context (DBusMessage *message, + BusSELinuxID *context, + DBusError *error); + +dbus_bool_t bus_selinux_allows_acquire_service (DBusConnection *connection, + BusSELinuxID *service_sid, + const char *service_name, + DBusError *error); + +dbus_bool_t bus_selinux_allows_send (DBusConnection *sender, + DBusConnection *proposed_recipient, + const char *msgtype, /* Supplementary audit data */ + const char *interface, + const char *member, + const char *error_name, + const char *destination, + DBusError *error); + +BusSELinuxID* bus_selinux_init_connection_id (DBusConnection *connection, + DBusError *error); + + +void bus_selinux_audit_init(void); + +#endif /* BUS_SELINUX_H */ diff --git a/bus/services.c b/bus/services.c new file mode 100644 index 00000000..b260c633 --- /dev/null +++ b/bus/services.c @@ -0,0 +1,1306 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* services.c Service management + * + * Copyright (C) 2003 Red Hat, Inc. + * Copyright (C) 2003 CodeFactory AB + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#include <dbus/dbus-hash.h> +#include <dbus/dbus-list.h> +#include <dbus/dbus-mempool.h> +#include <dbus/dbus-marshal-validate.h> + +#include "driver.h" +#include "services.h" +#include "connection.h" +#include "utils.h" +#include "activation.h" +#include "policy.h" +#include "bus.h" +#include "selinux.h" + +struct BusService +{ + int refcount; + + BusRegistry *registry; + char *name; + DBusList *owners; +}; + +struct BusOwner +{ + int refcount; + + BusService *service; + DBusConnection *conn; + + unsigned int allow_replacement : 1; + unsigned int do_not_queue : 1; +}; + +struct BusRegistry +{ + int refcount; + + BusContext *context; + + DBusHashTable *service_hash; + DBusMemPool *service_pool; + DBusMemPool *owner_pool; + + DBusHashTable *service_sid_table; +}; + +BusRegistry* +bus_registry_new (BusContext *context) +{ + BusRegistry *registry; + + registry = dbus_new0 (BusRegistry, 1); + if (registry == NULL) + return NULL; + + registry->refcount = 1; + registry->context = context; + + registry->service_hash = _dbus_hash_table_new (DBUS_HASH_STRING, + NULL, NULL); + if (registry->service_hash == NULL) + goto failed; + + registry->service_pool = _dbus_mem_pool_new (sizeof (BusService), + TRUE); + + if (registry->service_pool == NULL) + goto failed; + + registry->owner_pool = _dbus_mem_pool_new (sizeof (BusOwner), + TRUE); + + if (registry->owner_pool == NULL) + goto failed; + + registry->service_sid_table = NULL; + + return registry; + + failed: + bus_registry_unref (registry); + return NULL; +} + +BusRegistry * +bus_registry_ref (BusRegistry *registry) +{ + _dbus_assert (registry->refcount > 0); + registry->refcount += 1; + + return registry; +} + +void +bus_registry_unref (BusRegistry *registry) +{ + _dbus_assert (registry->refcount > 0); + registry->refcount -= 1; + + if (registry->refcount == 0) + { + if (registry->service_hash) + _dbus_hash_table_unref (registry->service_hash); + if (registry->service_pool) + _dbus_mem_pool_free (registry->service_pool); + if (registry->owner_pool) + _dbus_mem_pool_free (registry->owner_pool); + if (registry->service_sid_table) + _dbus_hash_table_unref (registry->service_sid_table); + + dbus_free (registry); + } +} + +BusService* +bus_registry_lookup (BusRegistry *registry, + const DBusString *service_name) +{ + BusService *service; + + service = _dbus_hash_table_lookup_string (registry->service_hash, + _dbus_string_get_const_data (service_name)); + + return service; +} + +static DBusList * +_bus_service_find_owner_link (BusService *service, + DBusConnection *connection) +{ + DBusList *link; + + link = _dbus_list_get_first_link (&service->owners); + + while (link != NULL) + { + BusOwner *bus_owner; + + bus_owner = (BusOwner *) link->data; + if (bus_owner->conn == connection) + break; + + link = _dbus_list_get_next_link (&service->owners, link); + } + + return link; +} + +static void +bus_owner_set_flags (BusOwner *owner, + dbus_uint32_t flags) +{ + owner->allow_replacement = + (flags & DBUS_NAME_FLAG_ALLOW_REPLACEMENT) != FALSE; + + owner->do_not_queue = + (flags & DBUS_NAME_FLAG_DO_NOT_QUEUE) != FALSE; +} + +static BusOwner * +bus_owner_new (BusService *service, + DBusConnection *conn, + dbus_uint32_t flags) +{ + BusOwner *result; + + result = _dbus_mem_pool_alloc (service->registry->owner_pool); + if (result != NULL) + { + result->refcount = 1; + /* don't ref the connection because we don't want + to block the connection from going away. + transactions take care of reffing the connection + but we need to use refcounting on the owner + so that the owner does not get freed before + we can deref the connection in the transaction + */ + result->conn = conn; + result->service = service; + + if (!bus_connection_add_owned_service (conn, service)) + { + _dbus_mem_pool_dealloc (service->registry->owner_pool, result); + return NULL; + } + + bus_owner_set_flags (result, flags); + } + return result; +} + +static BusOwner * +bus_owner_ref (BusOwner *owner) +{ + _dbus_assert (owner->refcount > 0); + owner->refcount += 1; + + return owner; +} + +static void +bus_owner_unref (BusOwner *owner) +{ + _dbus_assert (owner->refcount > 0); + owner->refcount -= 1; + + if (owner->refcount == 0) + { + bus_connection_remove_owned_service (owner->conn, owner->service); + _dbus_mem_pool_dealloc (owner->service->registry->owner_pool, owner); + } +} + +BusService* +bus_registry_ensure (BusRegistry *registry, + const DBusString *service_name, + DBusConnection *owner_connection_if_created, + dbus_uint32_t flags, + BusTransaction *transaction, + DBusError *error) +{ + BusService *service; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + _dbus_assert (owner_connection_if_created != NULL); + _dbus_assert (transaction != NULL); + + service = _dbus_hash_table_lookup_string (registry->service_hash, + _dbus_string_get_const_data (service_name)); + if (service != NULL) + return service; + + service = _dbus_mem_pool_alloc (registry->service_pool); + if (service == NULL) + { + BUS_SET_OOM (error); + return NULL; + } + + service->registry = registry; + service->refcount = 1; + + _dbus_verbose ("copying string %p '%s' to service->name\n", + service_name, _dbus_string_get_const_data (service_name)); + if (!_dbus_string_copy_data (service_name, &service->name)) + { + _dbus_mem_pool_dealloc (registry->service_pool, service); + BUS_SET_OOM (error); + return NULL; + } + _dbus_verbose ("copied string %p '%s' to '%s'\n", + service_name, _dbus_string_get_const_data (service_name), + service->name); + + if (!bus_driver_send_service_owner_changed (service->name, + NULL, + bus_connection_get_name (owner_connection_if_created), + transaction, error)) + { + bus_service_unref (service); + return NULL; + } + + if (!bus_activation_service_created (bus_context_get_activation (registry->context), + service->name, transaction, error)) + { + bus_service_unref (service); + return NULL; + } + + if (!bus_service_add_owner (service, owner_connection_if_created, flags, + transaction, error)) + { + bus_service_unref (service); + return NULL; + } + + if (!_dbus_hash_table_insert_string (registry->service_hash, + service->name, + service)) + { + /* The add_owner gets reverted on transaction cancel */ + BUS_SET_OOM (error); + return NULL; + } + + return service; +} + +void +bus_registry_foreach (BusRegistry *registry, + BusServiceForeachFunction function, + void *data) +{ + DBusHashIter iter; + + _dbus_hash_iter_init (registry->service_hash, &iter); + while (_dbus_hash_iter_next (&iter)) + { + BusService *service = _dbus_hash_iter_get_value (&iter); + + (* function) (service, data); + } +} + +dbus_bool_t +bus_registry_list_services (BusRegistry *registry, + char ***listp, + int *array_len) +{ + int i, j, len; + char **retval; + DBusHashIter iter; + + len = _dbus_hash_table_get_n_entries (registry->service_hash); + retval = dbus_new (char *, len + 1); + + if (retval == NULL) + return FALSE; + + _dbus_hash_iter_init (registry->service_hash, &iter); + i = 0; + while (_dbus_hash_iter_next (&iter)) + { + BusService *service = _dbus_hash_iter_get_value (&iter); + + retval[i] = _dbus_strdup (service->name); + if (retval[i] == NULL) + goto error; + + i++; + } + + retval[i] = NULL; + + if (array_len) + *array_len = len; + + *listp = retval; + return TRUE; + + error: + for (j = 0; j < i; j++) + dbus_free (retval[i]); + dbus_free (retval); + + return FALSE; +} + +dbus_bool_t +bus_registry_acquire_service (BusRegistry *registry, + DBusConnection *connection, + const DBusString *service_name, + dbus_uint32_t flags, + dbus_uint32_t *result, + BusTransaction *transaction, + DBusError *error) +{ + dbus_bool_t retval; + DBusConnection *old_owner_conn; + DBusConnection *current_owner_conn; + BusClientPolicy *policy; + BusService *service; + BusActivation *activation; + BusSELinuxID *sid; + BusOwner *primary_owner; + + retval = FALSE; + + if (!_dbus_validate_bus_name (service_name, 0, + _dbus_string_get_length (service_name))) + { + dbus_set_error (error, DBUS_ERROR_INVALID_ARGS, + "Requested bus name \"%s\" is not valid", + _dbus_string_get_const_data (service_name)); + + _dbus_verbose ("Attempt to acquire invalid service name\n"); + + goto out; + } + + if (_dbus_string_get_byte (service_name, 0) == ':') + { + /* Not allowed; only base services can start with ':' */ + dbus_set_error (error, DBUS_ERROR_INVALID_ARGS, + "Cannot acquire a service starting with ':' such as \"%s\"", + _dbus_string_get_const_data (service_name)); + + _dbus_verbose ("Attempt to acquire invalid base service name \"%s\"", + _dbus_string_get_const_data (service_name)); + + goto out; + } + + if (_dbus_string_equal_c_str (service_name, DBUS_SERVICE_DBUS)) + { + dbus_set_error (error, DBUS_ERROR_INVALID_ARGS, + "Connection \"%s\" is not allowed to own the service \"%s\"because " + "it is reserved for D-Bus' use only", + bus_connection_is_active (connection) ? + bus_connection_get_name (connection) : + "(inactive)", + DBUS_SERVICE_DBUS); + goto out; + } + + policy = bus_connection_get_policy (connection); + _dbus_assert (policy != NULL); + + /* Note that if sid is #NULL then the bus's own context gets used + * in bus_connection_selinux_allows_acquire_service() + */ + sid = bus_selinux_id_table_lookup (registry->service_sid_table, + service_name); + + if (!bus_selinux_allows_acquire_service (connection, sid, + _dbus_string_get_const_data (service_name), error)) + { + + if (dbus_error_is_set (error) && + dbus_error_has_name (error, DBUS_ERROR_NO_MEMORY)) + { + goto out; + } + + dbus_set_error (error, DBUS_ERROR_ACCESS_DENIED, + "Connection \"%s\" is not allowed to own the service \"%s\" due " + "to SELinux policy", + bus_connection_is_active (connection) ? + bus_connection_get_name (connection) : + "(inactive)", + _dbus_string_get_const_data (service_name)); + goto out; + } + + if (!bus_client_policy_check_can_own (policy, connection, + service_name)) + { + dbus_set_error (error, DBUS_ERROR_ACCESS_DENIED, + "Connection \"%s\" is not allowed to own the service \"%s\" due " + "to security policies in the configuration file", + bus_connection_is_active (connection) ? + bus_connection_get_name (connection) : + "(inactive)", + _dbus_string_get_const_data (service_name)); + goto out; + } + + if (bus_connection_get_n_services_owned (connection) >= + bus_context_get_max_services_per_connection (registry->context)) + { + dbus_set_error (error, DBUS_ERROR_LIMITS_EXCEEDED, + "Connection \"%s\" is not allowed to own more services " + "(increase limits in configuration file if required)", + bus_connection_is_active (connection) ? + bus_connection_get_name (connection) : + "(inactive)"); + goto out; + } + + service = bus_registry_lookup (registry, service_name); + + if (service != NULL) + { + primary_owner = bus_service_get_primary_owner (service); + if (primary_owner != NULL) + old_owner_conn = primary_owner->conn; + else + old_owner_conn = NULL; + } + else + old_owner_conn = NULL; + + if (service == NULL) + { + service = bus_registry_ensure (registry, + service_name, connection, flags, + transaction, error); + if (service == NULL) + goto out; + } + + primary_owner = bus_service_get_primary_owner (service); + if (primary_owner == NULL) + goto out; + + current_owner_conn = primary_owner->conn; + + if (old_owner_conn == NULL) + { + _dbus_assert (current_owner_conn == connection); + + *result = DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER; + } + else if (old_owner_conn == connection) + { + bus_owner_set_flags (primary_owner, flags); + *result = DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER; + } + else if (((flags & DBUS_NAME_FLAG_DO_NOT_QUEUE) && + !(bus_service_get_allow_replacement (service))) || + ((flags & DBUS_NAME_FLAG_DO_NOT_QUEUE) && + !(flags & DBUS_NAME_FLAG_REPLACE_EXISTING))) + { + DBusList *link; + BusOwner *temp_owner; + /* Since we can't be queued if we are already in the queue + remove us */ + + link = _bus_service_find_owner_link (service, connection); + if (link != NULL) + { + _dbus_list_unlink (&service->owners, link); + temp_owner = (BusOwner *)link->data; + bus_owner_unref (temp_owner); + _dbus_list_free_link (link); + } + + *result = DBUS_REQUEST_NAME_REPLY_EXISTS; + } + else if (!(flags & DBUS_NAME_FLAG_DO_NOT_QUEUE) && + (!(flags & DBUS_NAME_FLAG_REPLACE_EXISTING) || + !(bus_service_get_allow_replacement (service)))) + { + /* Queue the connection */ + if (!bus_service_add_owner (service, connection, + flags, + transaction, error)) + goto out; + + *result = DBUS_REQUEST_NAME_REPLY_IN_QUEUE; + } + else + { + /* Replace the current owner */ + + /* We enqueue the new owner and remove the first one because + * that will cause NameAcquired and NameLost messages to + * be sent. + */ + + if (!bus_service_add_owner (service, connection, + flags, + transaction, error)) + goto out; + + if (primary_owner->do_not_queue) + { + if (!bus_service_remove_owner (service, old_owner_conn, + transaction, error)) + goto out; + } + else + { + if (!bus_service_swap_owner (service, old_owner_conn, + transaction, error)) + goto out; + } + + + _dbus_assert (connection == bus_service_get_primary_owner (service)->conn); + *result = DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER; + } + + activation = bus_context_get_activation (registry->context); + retval = bus_activation_send_pending_auto_activation_messages (activation, + service, + transaction, + error); + + out: + return retval; +} + +dbus_bool_t +bus_registry_release_service (BusRegistry *registry, + DBusConnection *connection, + const DBusString *service_name, + dbus_uint32_t *result, + BusTransaction *transaction, + DBusError *error) +{ + dbus_bool_t retval; + BusService *service; + + retval = FALSE; + + if (!_dbus_validate_bus_name (service_name, 0, + _dbus_string_get_length (service_name))) + { + dbus_set_error (error, DBUS_ERROR_INVALID_ARGS, + "Given bus name \"%s\" is not valid", + _dbus_string_get_const_data (service_name)); + + _dbus_verbose ("Attempt to release invalid service name\n"); + + goto out; + } + + if (_dbus_string_get_byte (service_name, 0) == ':') + { + /* Not allowed; the base service name cannot be created or released */ + dbus_set_error (error, DBUS_ERROR_INVALID_ARGS, + "Cannot release a service starting with ':' such as \"%s\"", + _dbus_string_get_const_data (service_name)); + + _dbus_verbose ("Attempt to release invalid base service name \"%s\"", + _dbus_string_get_const_data (service_name)); + + goto out; + } + + if (_dbus_string_equal_c_str (service_name, DBUS_SERVICE_DBUS)) + { + /* Not allowed; the base service name cannot be created or released */ + dbus_set_error (error, DBUS_ERROR_INVALID_ARGS, + "Cannot release the %s service because it is owned by the bus", + DBUS_SERVICE_DBUS); + + _dbus_verbose ("Attempt to release service name \"%s\"", + DBUS_SERVICE_DBUS); + + goto out; + } + + service = bus_registry_lookup (registry, service_name); + + if (service == NULL) + { + *result = DBUS_RELEASE_NAME_REPLY_NON_EXISTENT; + } + else if (!bus_service_has_owner (service, connection)) + { + *result = DBUS_RELEASE_NAME_REPLY_NOT_OWNER; + } + else + { + if (!bus_service_remove_owner (service, connection, + transaction, error)) + goto out; + + _dbus_assert (!bus_service_has_owner (service, connection)); + *result = DBUS_RELEASE_NAME_REPLY_RELEASED; + } + + retval = TRUE; + + out: + return retval; +} + +dbus_bool_t +bus_registry_set_service_context_table (BusRegistry *registry, + DBusHashTable *table) +{ + DBusHashTable *new_table; + DBusHashIter iter; + + new_table = bus_selinux_id_table_new (); + if (!new_table) + return FALSE; + + _dbus_hash_iter_init (table, &iter); + while (_dbus_hash_iter_next (&iter)) + { + const char *service = _dbus_hash_iter_get_string_key (&iter); + const char *context = _dbus_hash_iter_get_value (&iter); + + if (!bus_selinux_id_table_insert (new_table, + service, + context)) + return FALSE; + } + + if (registry->service_sid_table) + _dbus_hash_table_unref (registry->service_sid_table); + registry->service_sid_table = new_table; + return TRUE; +} + +static void +bus_service_unlink_owner (BusService *service, + BusOwner *owner) +{ + _dbus_list_remove_last (&service->owners, owner); + bus_owner_unref (owner); +} + +static void +bus_service_unlink (BusService *service) +{ + _dbus_assert (service->owners == NULL); + + /* the service may not be in the hash, if + * the failure causing transaction cancel + * was in the right place, but that's OK + */ + _dbus_hash_table_remove_string (service->registry->service_hash, + service->name); + + bus_service_unref (service); +} + +static void +bus_service_relink (BusService *service, + DBusPreallocatedHash *preallocated) +{ + _dbus_assert (service->owners == NULL); + _dbus_assert (preallocated != NULL); + + _dbus_hash_table_insert_string_preallocated (service->registry->service_hash, + preallocated, + service->name, + service); + + bus_service_ref (service); +} + +/** + * Data used to represent an ownership cancellation in + * a bus transaction. + */ +typedef struct +{ + BusOwner *owner; /**< the owner */ + BusService *service; /**< service to cancel ownership of */ +} OwnershipCancelData; + +static void +cancel_ownership (void *data) +{ + OwnershipCancelData *d = data; + + /* We don't need to send messages notifying of these + * changes, since we're reverting something that was + * cancelled (effectively never really happened) + */ + bus_service_unlink_owner (d->service, d->owner); + + if (d->service->owners == NULL) + bus_service_unlink (d->service); +} + +static void +free_ownership_cancel_data (void *data) +{ + OwnershipCancelData *d = data; + + dbus_connection_unref (d->owner->conn); + bus_owner_unref (d->owner); + bus_service_unref (d->service); + + dbus_free (d); +} + +static dbus_bool_t +add_cancel_ownership_to_transaction (BusTransaction *transaction, + BusService *service, + BusOwner *owner) +{ + OwnershipCancelData *d; + + d = dbus_new (OwnershipCancelData, 1); + if (d == NULL) + return FALSE; + + d->service = service; + d->owner = owner; + + if (!bus_transaction_add_cancel_hook (transaction, cancel_ownership, d, + free_ownership_cancel_data)) + { + dbus_free (d); + return FALSE; + } + + bus_service_ref (d->service); + bus_owner_ref (owner); + dbus_connection_ref (d->owner->conn); + + return TRUE; +} + +/* this function is self-cancelling if you cancel the transaction */ +dbus_bool_t +bus_service_add_owner (BusService *service, + DBusConnection *connection, + dbus_uint32_t flags, + BusTransaction *transaction, + DBusError *error) +{ + BusOwner *bus_owner; + DBusList *bus_owner_link; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + /* Send service acquired message first, OOM will result + * in cancelling the transaction + */ + if (service->owners == NULL) + { + if (!bus_driver_send_service_acquired (connection, service->name, transaction, error)) + return FALSE; + } + + bus_owner_link = _bus_service_find_owner_link (service, connection); + + if (bus_owner_link == NULL) + { + bus_owner = bus_owner_new (service, connection, flags); + if (bus_owner == NULL) + { + BUS_SET_OOM (error); + return FALSE; + } + + bus_owner_set_flags (bus_owner, flags); + if (!(flags & DBUS_NAME_FLAG_REPLACE_EXISTING) || service->owners == NULL) + { + if (!_dbus_list_append (&service->owners, + bus_owner)) + { + bus_owner_unref (bus_owner); + BUS_SET_OOM (error); + return FALSE; + } + } + else + { + if (!_dbus_list_insert_after (&service->owners, + _dbus_list_get_first_link (&service->owners), + bus_owner)) + { + bus_owner_unref (bus_owner); + BUS_SET_OOM (error); + return FALSE; + } + } + } + else + { + /* Update the link since we are already in the queue + * No need for operations that can produce OOM + */ + + bus_owner = (BusOwner *) bus_owner_link->data; + if (flags & DBUS_NAME_FLAG_REPLACE_EXISTING) + { + DBusList *link; + _dbus_list_unlink (&service->owners, bus_owner_link); + link = _dbus_list_get_first_link (&service->owners); + _dbus_assert (link != NULL); + + _dbus_list_insert_after_link (&service->owners, link, bus_owner_link); + } + + bus_owner_set_flags (bus_owner, flags); + return TRUE; + } + + if (!add_cancel_ownership_to_transaction (transaction, + service, + bus_owner)) + { + bus_service_unlink_owner (service, bus_owner); + BUS_SET_OOM (error); + return FALSE; + } + + return TRUE; +} + +typedef struct +{ + BusOwner *owner; + BusService *service; + BusOwner *before_owner; /* restore to position before this connection in owners list */ + DBusList *owner_link; + DBusList *service_link; + DBusPreallocatedHash *hash_entry; +} OwnershipRestoreData; + +static void +restore_ownership (void *data) +{ + OwnershipRestoreData *d = data; + DBusList *link; + + _dbus_assert (d->service_link != NULL); + _dbus_assert (d->owner_link != NULL); + + if (d->service->owners == NULL) + { + _dbus_assert (d->hash_entry != NULL); + bus_service_relink (d->service, d->hash_entry); + } + else + { + _dbus_assert (d->hash_entry == NULL); + } + + /* We don't need to send messages notifying of these + * changes, since we're reverting something that was + * cancelled (effectively never really happened) + */ + link = _dbus_list_get_first_link (&d->service->owners); + while (link != NULL) + { + if (link->data == d->before_owner) + break; + + link = _dbus_list_get_next_link (&d->service->owners, link); + } + + _dbus_list_insert_before_link (&d->service->owners, link, d->owner_link); + + /* Note that removing then restoring this changes the order in which + * ServiceDeleted messages are sent on destruction of the + * connection. This should be OK as the only guarantee there is + * that the base service is destroyed last, and we never even + * tentatively remove the base service. + */ + bus_connection_add_owned_service_link (d->owner->conn, d->service_link); + + d->hash_entry = NULL; + d->service_link = NULL; + d->owner_link = NULL; +} + +static void +free_ownership_restore_data (void *data) +{ + OwnershipRestoreData *d = data; + + if (d->service_link) + _dbus_list_free_link (d->service_link); + if (d->owner_link) + _dbus_list_free_link (d->owner_link); + if (d->hash_entry) + _dbus_hash_table_free_preallocated_entry (d->service->registry->service_hash, + d->hash_entry); + + dbus_connection_unref (d->owner->conn); + bus_owner_unref (d->owner); + bus_service_unref (d->service); + + dbus_free (d); +} + +static dbus_bool_t +add_restore_ownership_to_transaction (BusTransaction *transaction, + BusService *service, + BusOwner *owner) +{ + OwnershipRestoreData *d; + DBusList *link; + + d = dbus_new (OwnershipRestoreData, 1); + if (d == NULL) + return FALSE; + + d->service = service; + d->owner = owner; + d->service_link = _dbus_list_alloc_link (service); + d->owner_link = _dbus_list_alloc_link (owner); + d->hash_entry = _dbus_hash_table_preallocate_entry (service->registry->service_hash); + + bus_service_ref (d->service); + bus_owner_ref (d->owner); + dbus_connection_ref (d->owner->conn); + + d->before_owner = NULL; + link = _dbus_list_get_first_link (&service->owners); + while (link != NULL) + { + if (link->data == owner) + { + link = _dbus_list_get_next_link (&service->owners, link); + + if (link) + d->before_owner = link->data; + + break; + } + + link = _dbus_list_get_next_link (&service->owners, link); + } + + if (d->service_link == NULL || + d->owner_link == NULL || + d->hash_entry == NULL || + !bus_transaction_add_cancel_hook (transaction, restore_ownership, d, + free_ownership_restore_data)) + { + free_ownership_restore_data (d); + return FALSE; + } + + return TRUE; +} + +dbus_bool_t +bus_service_swap_owner (BusService *service, + DBusConnection *connection, + BusTransaction *transaction, + DBusError *error) +{ + DBusList *swap_link; + BusOwner *primary_owner; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + /* We send out notifications before we do any work we + * might have to undo if the notification-sending failed + */ + + /* Send service lost message */ + primary_owner = bus_service_get_primary_owner (service); + if (primary_owner == NULL || primary_owner->conn != connection) + _dbus_assert_not_reached ("Tried to swap a non primary owner"); + + + if (!bus_driver_send_service_lost (connection, service->name, + transaction, error)) + return FALSE; + + if (service->owners == NULL) + { + _dbus_assert_not_reached ("Tried to swap owner of a service that has no owners"); + } + else if (_dbus_list_length_is_one (&service->owners)) + { + _dbus_assert_not_reached ("Tried to swap owner of a service that has no other owners in the queue"); + } + else + { + DBusList *link; + BusOwner *new_owner; + DBusConnection *new_owner_conn; + link = _dbus_list_get_first_link (&service->owners); + _dbus_assert (link != NULL); + link = _dbus_list_get_next_link (&service->owners, link); + _dbus_assert (link != NULL); + + new_owner = (BusOwner *)link->data; + new_owner_conn = new_owner->conn; + + if (!bus_driver_send_service_owner_changed (service->name, + bus_connection_get_name (connection), + bus_connection_get_name (new_owner_conn), + transaction, error)) + return FALSE; + + /* This will be our new owner */ + if (!bus_driver_send_service_acquired (new_owner_conn, + service->name, + transaction, + error)) + return FALSE; + } + + if (!add_restore_ownership_to_transaction (transaction, service, primary_owner)) + { + BUS_SET_OOM (error); + return FALSE; + } + + /* unlink the primary and make it the second link */ + swap_link = _dbus_list_get_first_link (&service->owners); + _dbus_list_unlink (&service->owners, swap_link); + + _dbus_list_insert_after_link (&service->owners, + _dbus_list_get_first_link (&service->owners), + swap_link); + + return TRUE; +} + +/* this function is self-cancelling if you cancel the transaction */ +dbus_bool_t +bus_service_remove_owner (BusService *service, + DBusConnection *connection, + BusTransaction *transaction, + DBusError *error) +{ + BusOwner *primary_owner; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + /* We send out notifications before we do any work we + * might have to undo if the notification-sending failed + */ + + /* Send service lost message */ + primary_owner = bus_service_get_primary_owner (service); + if (primary_owner != NULL && primary_owner->conn == connection) + { + if (!bus_driver_send_service_lost (connection, service->name, + transaction, error)) + return FALSE; + } + else + { + /* if we are not the primary owner then just remove us from the queue */ + DBusList *link; + BusOwner *temp_owner; + + link = _bus_service_find_owner_link (service, connection); + _dbus_list_unlink (&service->owners, link); + temp_owner = (BusOwner *)link->data; + bus_owner_unref (temp_owner); + _dbus_list_free_link (link); + + return TRUE; + } + + if (service->owners == NULL) + { + _dbus_assert_not_reached ("Tried to remove owner of a service that has no owners"); + } + else if (_dbus_list_length_is_one (&service->owners)) + { + if (!bus_driver_send_service_owner_changed (service->name, + bus_connection_get_name (connection), + NULL, + transaction, error)) + return FALSE; + } + else + { + DBusList *link; + BusOwner *new_owner; + DBusConnection *new_owner_conn; + link = _dbus_list_get_first_link (&service->owners); + _dbus_assert (link != NULL); + link = _dbus_list_get_next_link (&service->owners, link); + _dbus_assert (link != NULL); + + new_owner = (BusOwner *)link->data; + new_owner_conn = new_owner->conn; + + if (!bus_driver_send_service_owner_changed (service->name, + bus_connection_get_name (connection), + bus_connection_get_name (new_owner_conn), + transaction, error)) + return FALSE; + + /* This will be our new owner */ + if (!bus_driver_send_service_acquired (new_owner_conn, + service->name, + transaction, + error)) + return FALSE; + } + + if (!add_restore_ownership_to_transaction (transaction, service, primary_owner)) + { + BUS_SET_OOM (error); + return FALSE; + } + + bus_service_unlink_owner (service, primary_owner); + + if (service->owners == NULL) + bus_service_unlink (service); + + return TRUE; +} + +BusService * +bus_service_ref (BusService *service) +{ + _dbus_assert (service->refcount > 0); + + service->refcount += 1; + + return service; +} + +void +bus_service_unref (BusService *service) +{ + _dbus_assert (service->refcount > 0); + + service->refcount -= 1; + + if (service->refcount == 0) + { + _dbus_assert (service->owners == NULL); + + dbus_free (service->name); + _dbus_mem_pool_dealloc (service->registry->service_pool, service); + } +} + +DBusConnection * +bus_service_get_primary_owners_connection (BusService *service) +{ + BusOwner *owner; + + owner = bus_service_get_primary_owner (service); + + if (owner != NULL) + return owner->conn; + else + return NULL; +} + +BusOwner* +bus_service_get_primary_owner (BusService *service) +{ + return _dbus_list_get_first (&service->owners); +} + +const char* +bus_service_get_name (BusService *service) +{ + return service->name; +} + +dbus_bool_t +bus_service_get_allow_replacement (BusService *service) +{ + BusOwner *owner; + DBusList *link; + + _dbus_assert (service->owners != NULL); + + link = _dbus_list_get_first_link (&service->owners); + owner = (BusOwner *) link->data; + + return owner->allow_replacement; +} + +dbus_bool_t +bus_service_has_owner (BusService *service, + DBusConnection *connection) +{ + DBusList *link; + + link = _bus_service_find_owner_link (service, connection); + + if (link == NULL) + return FALSE; + else + return TRUE; +} + +dbus_bool_t +bus_service_list_queued_owners (BusService *service, + DBusList **return_list, + DBusError *error) +{ + DBusList *link; + + _dbus_assert (*return_list == NULL); + + link = _dbus_list_get_first_link (&service->owners); + _dbus_assert (link != NULL); + + while (link != NULL) + { + BusOwner *owner; + const char *uname; + + owner = (BusOwner *) link->data; + uname = bus_connection_get_name (owner->conn); + + if (!_dbus_list_append (return_list, (char *)uname)) + goto oom; + + link = _dbus_list_get_next_link (&service->owners, link); + } + + return TRUE; + + oom: + _dbus_list_clear (return_list); + BUS_SET_OOM (error); + return FALSE; +} diff --git a/bus/services.h b/bus/services.h new file mode 100644 index 00000000..056dd9fa --- /dev/null +++ b/bus/services.h @@ -0,0 +1,94 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* services.h Service management + * + * Copyright (C) 2003 Red Hat, Inc. + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef BUS_SERVICES_H +#define BUS_SERVICES_H + +#include <dbus/dbus.h> +#include <dbus/dbus-string.h> +#include <dbus/dbus-hash.h> +#include "connection.h" +#include "bus.h" + +typedef void (* BusServiceForeachFunction) (BusService *service, + void *data); + +BusRegistry* bus_registry_new (BusContext *context); +BusRegistry* bus_registry_ref (BusRegistry *registry); +void bus_registry_unref (BusRegistry *registry); +BusService* bus_registry_lookup (BusRegistry *registry, + const DBusString *service_name); +BusService* bus_registry_ensure (BusRegistry *registry, + const DBusString *service_name, + DBusConnection *owner_connection_if_created, + dbus_uint32_t flags, + BusTransaction *transaction, + DBusError *error); +void bus_registry_foreach (BusRegistry *registry, + BusServiceForeachFunction function, + void *data); +dbus_bool_t bus_registry_list_services (BusRegistry *registry, + char ***listp, + int *array_len); +dbus_bool_t bus_registry_acquire_service (BusRegistry *registry, + DBusConnection *connection, + const DBusString *service_name, + dbus_uint32_t flags, + dbus_uint32_t *result, + BusTransaction *transaction, + DBusError *error); +dbus_bool_t bus_registry_release_service (BusRegistry *registry, + DBusConnection *connection, + const DBusString *service_name, + dbus_uint32_t *result, + BusTransaction *transaction, + DBusError *error); +dbus_bool_t bus_registry_set_service_context_table (BusRegistry *registry, + DBusHashTable *table); + +BusService* bus_service_ref (BusService *service); +void bus_service_unref (BusService *service); +dbus_bool_t bus_service_add_owner (BusService *service, + DBusConnection *connection, + dbus_uint32_t flags, + BusTransaction *transaction, + DBusError *error); +dbus_bool_t bus_service_swap_owner (BusService *service, + DBusConnection *connection, + BusTransaction *transaction, + DBusError *error); +dbus_bool_t bus_service_remove_owner (BusService *service, + DBusConnection *connection, + BusTransaction *transaction, + DBusError *error); +dbus_bool_t bus_service_has_owner (BusService *service, + DBusConnection *connection); +BusOwner* bus_service_get_primary_owner (BusService *service); +dbus_bool_t bus_service_get_allow_replacement (BusService *service); +const char* bus_service_get_name (BusService *service); +dbus_bool_t bus_service_list_queued_owners (BusService *service, + DBusList **return_list, + DBusError *error); + +DBusConnection* bus_service_get_primary_owners_connection (BusService *service); +#endif /* BUS_SERVICES_H */ diff --git a/bus/session.conf.in b/bus/session.conf.in new file mode 100644 index 00000000..e7229ad5 --- /dev/null +++ b/bus/session.conf.in @@ -0,0 +1,60 @@ +<!-- This configuration file controls the per-user-login-session message bus. + Add a session-local.conf and edit that rather than changing this + file directly. --> + +<!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> + + <!-- If we fork, keep the user's original umask to avoid affecting + the behavior of child processes. --> + <keep_umask/> + + <listen>unix:tmpdir=@DBUS_SESSION_SOCKET_DIR@</listen> + + <standard_session_servicedirs /> + + <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> + + <!-- Config files are placed here that among other things, + further restrict the above policy for specific services. --> + <includedir>session.d</includedir> + + <!-- This is included last so local configuration can override what's + in this standard file --> + <include ignore_missing="yes">session-local.conf</include> + + <include if_selinux_enabled="yes" selinux_root_relative="yes">contexts/dbus_contexts</include> + + <!-- For the session bus, override the default relatively-low limits + with essentially infinite limits, since the bus is just running + as the user anyway, using up bus resources is not something we need + to worry about. In some cases, we do set the limits lower than + "all available memory" if exceeding the limit is almost certainly a bug, + having the bus enforce a limit is nicer than a huge memory leak. But the + intent is that these limits should never be hit. --> + + <!-- the memory limits are 1G instead of say 4G because they can't exceed 32-bit signed int max --> + <limit name="max_incoming_bytes">1000000000</limit> + <limit name="max_outgoing_bytes">1000000000</limit> + <limit name="max_message_size">1000000000</limit> + <limit name="service_start_timeout">120000</limit> + <limit name="auth_timeout">240000</limit> + <limit name="max_completed_connections">100000</limit> + <limit name="max_incomplete_connections">10000</limit> + <limit name="max_connections_per_user">100000</limit> + <limit name="max_pending_service_starts">10000</limit> + <limit name="max_names_per_connection">50000</limit> + <limit name="max_match_rules_per_connection">50000</limit> + <limit name="max_replies_per_connection">50000</limit> + +</busconfig> diff --git a/bus/signals.c b/bus/signals.c new file mode 100644 index 00000000..b020a76c --- /dev/null +++ b/bus/signals.c @@ -0,0 +1,2030 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* signals.c Bus signal connection implementation + * + * Copyright (C) 2003, 2005 Red Hat, Inc. + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#include "signals.h" +#include "services.h" +#include "utils.h" +#include <dbus/dbus-marshal-validate.h> + +struct BusMatchRule +{ + int refcount; /**< reference count */ + + DBusConnection *matches_go_to; /**< Owner of the rule */ + + unsigned int flags; /**< BusMatchFlags */ + + int message_type; + char *interface; + char *member; + char *sender; + char *destination; + char *path; + + unsigned int *arg_lens; + char **args; + int args_len; +}; + +#define BUS_MATCH_ARG_IS_PATH 0x8000000u + +BusMatchRule* +bus_match_rule_new (DBusConnection *matches_go_to) +{ + BusMatchRule *rule; + + rule = dbus_new0 (BusMatchRule, 1); + if (rule == NULL) + return NULL; + + rule->refcount = 1; + rule->matches_go_to = matches_go_to; + +#ifndef DBUS_BUILD_TESTS + _dbus_assert (rule->matches_go_to != NULL); +#endif + + return rule; +} + +BusMatchRule * +bus_match_rule_ref (BusMatchRule *rule) +{ + _dbus_assert (rule->refcount > 0); + + rule->refcount += 1; + + return rule; +} + +void +bus_match_rule_unref (BusMatchRule *rule) +{ + _dbus_assert (rule->refcount > 0); + + rule->refcount -= 1; + if (rule->refcount == 0) + { + dbus_free (rule->interface); + dbus_free (rule->member); + dbus_free (rule->sender); + dbus_free (rule->destination); + dbus_free (rule->path); + dbus_free (rule->arg_lens); + + /* can't use dbus_free_string_array() since there + * are embedded NULL + */ + if (rule->args) + { + int i; + + i = 0; + while (i < rule->args_len) + { + if (rule->args[i]) + dbus_free (rule->args[i]); + ++i; + } + + dbus_free (rule->args); + } + + dbus_free (rule); + } +} + +#ifdef DBUS_ENABLE_VERBOSE_MODE +/* Note this function does not do escaping, so it's only + * good for debug spew at the moment + */ +static char* +match_rule_to_string (BusMatchRule *rule) +{ + DBusString str; + char *ret; + + if (!_dbus_string_init (&str)) + { + char *s; + while ((s = _dbus_strdup ("nomem")) == NULL) + ; /* only OK for debug spew... */ + return s; + } + + if (rule->flags & BUS_MATCH_MESSAGE_TYPE) + { + /* FIXME make type readable */ + if (!_dbus_string_append_printf (&str, "type='%d'", rule->message_type)) + goto nomem; + } + + if (rule->flags & BUS_MATCH_INTERFACE) + { + if (_dbus_string_get_length (&str) > 0) + { + if (!_dbus_string_append (&str, ",")) + goto nomem; + } + + if (!_dbus_string_append_printf (&str, "interface='%s'", rule->interface)) + goto nomem; + } + + if (rule->flags & BUS_MATCH_MEMBER) + { + if (_dbus_string_get_length (&str) > 0) + { + if (!_dbus_string_append (&str, ",")) + goto nomem; + } + + if (!_dbus_string_append_printf (&str, "member='%s'", rule->member)) + goto nomem; + } + + if (rule->flags & BUS_MATCH_PATH) + { + if (_dbus_string_get_length (&str) > 0) + { + if (!_dbus_string_append (&str, ",")) + goto nomem; + } + + if (!_dbus_string_append_printf (&str, "path='%s'", rule->path)) + goto nomem; + } + + if (rule->flags & BUS_MATCH_SENDER) + { + if (_dbus_string_get_length (&str) > 0) + { + if (!_dbus_string_append (&str, ",")) + goto nomem; + } + + if (!_dbus_string_append_printf (&str, "sender='%s'", rule->sender)) + goto nomem; + } + + if (rule->flags & BUS_MATCH_DESTINATION) + { + if (_dbus_string_get_length (&str) > 0) + { + if (!_dbus_string_append (&str, ",")) + goto nomem; + } + + if (!_dbus_string_append_printf (&str, "destination='%s'", rule->destination)) + goto nomem; + } + + if (rule->flags & BUS_MATCH_ARGS) + { + int i; + + _dbus_assert (rule->args != NULL); + + i = 0; + while (i < rule->args_len) + { + if (rule->args[i] != NULL) + { + dbus_bool_t is_path; + + if (_dbus_string_get_length (&str) > 0) + { + if (!_dbus_string_append (&str, ",")) + goto nomem; + } + + is_path = (rule->arg_lens[i] & BUS_MATCH_ARG_IS_PATH) != 0; + + if (!_dbus_string_append_printf (&str, + "arg%d%s='%s'", + i, is_path ? "path" : "", + rule->args[i])) + goto nomem; + } + + ++i; + } + } + + if (!_dbus_string_steal_data (&str, &ret)) + goto nomem; + + _dbus_string_free (&str); + return ret; + + nomem: + _dbus_string_free (&str); + { + char *s; + while ((s = _dbus_strdup ("nomem")) == NULL) + ; /* only OK for debug spew... */ + return s; + } +} +#endif /* DBUS_ENABLE_VERBOSE_MODE */ + +dbus_bool_t +bus_match_rule_set_message_type (BusMatchRule *rule, + int type) +{ + rule->flags |= BUS_MATCH_MESSAGE_TYPE; + + rule->message_type = type; + + return TRUE; +} + +dbus_bool_t +bus_match_rule_set_interface (BusMatchRule *rule, + const char *interface) +{ + char *new; + + _dbus_assert (interface != NULL); + + new = _dbus_strdup (interface); + if (new == NULL) + return FALSE; + + rule->flags |= BUS_MATCH_INTERFACE; + dbus_free (rule->interface); + rule->interface = new; + + return TRUE; +} + +dbus_bool_t +bus_match_rule_set_member (BusMatchRule *rule, + const char *member) +{ + char *new; + + _dbus_assert (member != NULL); + + new = _dbus_strdup (member); + if (new == NULL) + return FALSE; + + rule->flags |= BUS_MATCH_MEMBER; + dbus_free (rule->member); + rule->member = new; + + return TRUE; +} + +dbus_bool_t +bus_match_rule_set_sender (BusMatchRule *rule, + const char *sender) +{ + char *new; + + _dbus_assert (sender != NULL); + + new = _dbus_strdup (sender); + if (new == NULL) + return FALSE; + + rule->flags |= BUS_MATCH_SENDER; + dbus_free (rule->sender); + rule->sender = new; + + return TRUE; +} + +dbus_bool_t +bus_match_rule_set_destination (BusMatchRule *rule, + const char *destination) +{ + char *new; + + _dbus_assert (destination != NULL); + + new = _dbus_strdup (destination); + if (new == NULL) + return FALSE; + + rule->flags |= BUS_MATCH_DESTINATION; + dbus_free (rule->destination); + rule->destination = new; + + return TRUE; +} + +dbus_bool_t +bus_match_rule_set_path (BusMatchRule *rule, + const char *path) +{ + char *new; + + _dbus_assert (path != NULL); + + new = _dbus_strdup (path); + if (new == NULL) + return FALSE; + + rule->flags |= BUS_MATCH_PATH; + dbus_free (rule->path); + rule->path = new; + + return TRUE; +} + +dbus_bool_t +bus_match_rule_set_arg (BusMatchRule *rule, + int arg, + const DBusString *value, + dbus_bool_t is_path) +{ + int length; + char *new; + + _dbus_assert (value != NULL); + + /* args_len is the number of args not including null termination + * in the char** + */ + if (arg >= rule->args_len) + { + unsigned int *new_arg_lens; + char **new_args; + int new_args_len; + int i; + + new_args_len = arg + 1; + + /* add another + 1 here for null termination */ + new_args = dbus_realloc (rule->args, + sizeof (char *) * (new_args_len + 1)); + if (new_args == NULL) + return FALSE; + + /* NULL the new slots */ + i = rule->args_len; + while (i <= new_args_len) /* <= for null termination */ + { + new_args[i] = NULL; + ++i; + } + + rule->args = new_args; + + /* and now add to the lengths */ + new_arg_lens = dbus_realloc (rule->arg_lens, + sizeof (int) * (new_args_len + 1)); + + if (new_arg_lens == NULL) + return FALSE; + + /* zero the new slots */ + i = rule->args_len; + while (i <= new_args_len) /* <= for null termination */ + { + new_arg_lens[i] = 0; + ++i; + } + + rule->arg_lens = new_arg_lens; + rule->args_len = new_args_len; + } + + length = _dbus_string_get_length (value); + if (!_dbus_string_copy_data (value, &new)) + return FALSE; + + rule->flags |= BUS_MATCH_ARGS; + + dbus_free (rule->args[arg]); + rule->arg_lens[arg] = length; + rule->args[arg] = new; + + if (is_path) + rule->arg_lens[arg] |= BUS_MATCH_ARG_IS_PATH; + + /* NULL termination didn't get busted */ + _dbus_assert (rule->args[rule->args_len] == NULL); + _dbus_assert (rule->arg_lens[rule->args_len] == 0); + + return TRUE; +} + +#define ISWHITE(c) (((c) == ' ') || ((c) == '\t') || ((c) == '\n') || ((c) == '\r')) + +static dbus_bool_t +find_key (const DBusString *str, + int start, + DBusString *key, + int *value_pos, + DBusError *error) +{ + const char *p; + const char *s; + const char *key_start; + const char *key_end; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + s = _dbus_string_get_const_data (str); + + p = s + start; + + while (*p && ISWHITE (*p)) + ++p; + + key_start = p; + + while (*p && *p != '=' && !ISWHITE (*p)) + ++p; + + key_end = p; + + while (*p && ISWHITE (*p)) + ++p; + + if (key_start == key_end) + { + /* Empty match rules or trailing whitespace are OK */ + *value_pos = p - s; + return TRUE; + } + + if (*p != '=') + { + dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID, + "Match rule has a key with no subsequent '=' character"); + return FALSE; + } + ++p; + + if (!_dbus_string_append_len (key, key_start, key_end - key_start)) + { + BUS_SET_OOM (error); + return FALSE; + } + + *value_pos = p - s; + + return TRUE; +} + +static dbus_bool_t +find_value (const DBusString *str, + int start, + const char *key, + DBusString *value, + int *value_end, + DBusError *error) +{ + const char *p; + const char *s; + char quote_char; + int orig_len; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + orig_len = _dbus_string_get_length (value); + + s = _dbus_string_get_const_data (str); + + p = s + start; + + quote_char = '\0'; + + while (*p) + { + if (quote_char == '\0') + { + switch (*p) + { + case '\0': + goto done; + + case '\'': + quote_char = '\''; + goto next; + + case ',': + ++p; + goto done; + + case '\\': + quote_char = '\\'; + goto next; + + default: + if (!_dbus_string_append_byte (value, *p)) + { + BUS_SET_OOM (error); + goto failed; + } + } + } + else if (quote_char == '\\') + { + /* \ only counts as an escape if escaping a quote mark */ + if (*p != '\'') + { + if (!_dbus_string_append_byte (value, '\\')) + { + BUS_SET_OOM (error); + goto failed; + } + } + + if (!_dbus_string_append_byte (value, *p)) + { + BUS_SET_OOM (error); + goto failed; + } + + quote_char = '\0'; + } + else + { + _dbus_assert (quote_char == '\''); + + if (*p == '\'') + { + quote_char = '\0'; + } + else + { + if (!_dbus_string_append_byte (value, *p)) + { + BUS_SET_OOM (error); + goto failed; + } + } + } + + next: + ++p; + } + + done: + + if (quote_char == '\\') + { + if (!_dbus_string_append_byte (value, '\\')) + { + BUS_SET_OOM (error); + goto failed; + } + } + else if (quote_char == '\'') + { + dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID, + "Unbalanced quotation marks in match rule"); + goto failed; + } + else + _dbus_assert (quote_char == '\0'); + + /* Zero-length values are allowed */ + + *value_end = p - s; + + return TRUE; + + failed: + _DBUS_ASSERT_ERROR_IS_SET (error); + _dbus_string_set_length (value, orig_len); + return FALSE; +} + +/* duplicates aren't allowed so the real legitimate max is only 6 or + * so. Leaving extra so we don't have to bother to update it. + * FIXME this is sort of busted now with arg matching, but we let + * you match on up to 10 args for now + */ +#define MAX_RULE_TOKENS 16 + +/* this is slightly too high level to be termed a "token" + * but let's not be pedantic. + */ +typedef struct +{ + char *key; + char *value; +} RuleToken; + +static dbus_bool_t +tokenize_rule (const DBusString *rule_text, + RuleToken tokens[MAX_RULE_TOKENS], + DBusError *error) +{ + int i; + int pos; + DBusString key; + DBusString value; + dbus_bool_t retval; + + retval = FALSE; + + if (!_dbus_string_init (&key)) + { + BUS_SET_OOM (error); + return FALSE; + } + + if (!_dbus_string_init (&value)) + { + _dbus_string_free (&key); + BUS_SET_OOM (error); + return FALSE; + } + + i = 0; + pos = 0; + while (i < MAX_RULE_TOKENS && + pos < _dbus_string_get_length (rule_text)) + { + _dbus_assert (tokens[i].key == NULL); + _dbus_assert (tokens[i].value == NULL); + + if (!find_key (rule_text, pos, &key, &pos, error)) + goto out; + + if (_dbus_string_get_length (&key) == 0) + goto next; + + if (!_dbus_string_steal_data (&key, &tokens[i].key)) + { + BUS_SET_OOM (error); + goto out; + } + + if (!find_value (rule_text, pos, tokens[i].key, &value, &pos, error)) + goto out; + + if (!_dbus_string_steal_data (&value, &tokens[i].value)) + { + BUS_SET_OOM (error); + goto out; + } + + next: + ++i; + } + + retval = TRUE; + + out: + if (!retval) + { + i = 0; + while (tokens[i].key || tokens[i].value) + { + dbus_free (tokens[i].key); + dbus_free (tokens[i].value); + tokens[i].key = NULL; + tokens[i].value = NULL; + ++i; + } + } + + _dbus_string_free (&key); + _dbus_string_free (&value); + + return retval; +} + +static dbus_bool_t +bus_match_rule_parse_arg_match (BusMatchRule *rule, + const char *key, + const DBusString *value, + DBusError *error) +{ + dbus_bool_t is_path; + DBusString key_str; + unsigned long arg; + int length; + int end; + + /* For now, arg0='foo' always implies that 'foo' is a + * DBUS_TYPE_STRING. Someday we could add an arg0type='int32' thing + * if we wanted, which would specify another type, in which case + * arg0='5' would have the 5 parsed as an int rather than string. + */ + + /* First we need to parse arg0 = 0, arg27 = 27 */ + + _dbus_string_init_const (&key_str, key); + length = _dbus_string_get_length (&key_str); + + if (_dbus_string_get_length (&key_str) < 4) + { + dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID, + "Key '%s' in match rule starts with 'arg' but lacks an arg number. Should be 'arg0' or 'arg7' for example.\n", key); + goto failed; + } + + if (!_dbus_string_parse_uint (&key_str, 3, &arg, &end)) + { + dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID, + "Key '%s' in match rule starts with 'arg' but could not parse arg number. Should be 'arg0' or 'arg7' for example.\n", key); + goto failed; + } + + if (end != length && + ((end + 4) != length || + !_dbus_string_ends_with_c_str (&key_str, "path"))) + { + dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID, + "Key '%s' in match rule contains junk after argument number. Only 'path' is optionally valid ('arg0path' for example).\n", key); + goto failed; + } + + is_path = end != length; + + /* If we didn't check this we could allocate a huge amount of RAM */ + if (arg > DBUS_MAXIMUM_MATCH_RULE_ARG_NUMBER) + { + dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID, + "Key '%s' in match rule has arg number %lu but the maximum is %d.\n", key, (unsigned long) arg, DBUS_MAXIMUM_MATCH_RULE_ARG_NUMBER); + goto failed; + } + + if ((rule->flags & BUS_MATCH_ARGS) && + rule->args_len > (int) arg && + rule->args[arg] != NULL) + { + dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID, + "Argument %d matched more than once in match rule\n", key); + goto failed; + } + + if (!bus_match_rule_set_arg (rule, arg, value, is_path)) + { + BUS_SET_OOM (error); + goto failed; + } + + return TRUE; + + failed: + _DBUS_ASSERT_ERROR_IS_SET (error); + return FALSE; +} + +/* + * The format is comma-separated with strings quoted with single quotes + * as for the shell (to escape a literal single quote, use '\''). + * + * type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='Foo', + * path='/bar/foo',destination=':452345.34' + * + */ +BusMatchRule* +bus_match_rule_parse (DBusConnection *matches_go_to, + const DBusString *rule_text, + DBusError *error) +{ + BusMatchRule *rule; + RuleToken tokens[MAX_RULE_TOKENS+1]; /* NULL termination + 1 */ + int i; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + if (_dbus_string_get_length (rule_text) > DBUS_MAXIMUM_MATCH_RULE_LENGTH) + { + dbus_set_error (error, DBUS_ERROR_LIMITS_EXCEEDED, + "Match rule text is %d bytes, maximum is %d", + _dbus_string_get_length (rule_text), + DBUS_MAXIMUM_MATCH_RULE_LENGTH); + return NULL; + } + + memset (tokens, '\0', sizeof (tokens)); + + rule = bus_match_rule_new (matches_go_to); + if (rule == NULL) + { + BUS_SET_OOM (error); + goto failed; + } + + if (!tokenize_rule (rule_text, tokens, error)) + goto failed; + + i = 0; + while (tokens[i].key != NULL) + { + DBusString tmp_str; + int len; + const char *key = tokens[i].key; + const char *value = tokens[i].value; + + _dbus_string_init_const (&tmp_str, value); + len = _dbus_string_get_length (&tmp_str); + + if (strcmp (key, "type") == 0) + { + int t; + + if (rule->flags & BUS_MATCH_MESSAGE_TYPE) + { + dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID, + "Key %s specified twice in match rule\n", key); + goto failed; + } + + t = dbus_message_type_from_string (value); + + if (t == DBUS_MESSAGE_TYPE_INVALID) + { + dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID, + "Invalid message type (%s) in match rule\n", value); + goto failed; + } + + if (!bus_match_rule_set_message_type (rule, t)) + { + BUS_SET_OOM (error); + goto failed; + } + } + else if (strcmp (key, "sender") == 0) + { + if (rule->flags & BUS_MATCH_SENDER) + { + dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID, + "Key %s specified twice in match rule\n", key); + goto failed; + } + + if (!_dbus_validate_bus_name (&tmp_str, 0, len)) + { + dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID, + "Sender name '%s' is invalid\n", value); + goto failed; + } + + if (!bus_match_rule_set_sender (rule, value)) + { + BUS_SET_OOM (error); + goto failed; + } + } + else if (strcmp (key, "interface") == 0) + { + if (rule->flags & BUS_MATCH_INTERFACE) + { + dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID, + "Key %s specified twice in match rule\n", key); + goto failed; + } + + if (!_dbus_validate_interface (&tmp_str, 0, len)) + { + dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID, + "Interface name '%s' is invalid\n", value); + goto failed; + } + + if (!bus_match_rule_set_interface (rule, value)) + { + BUS_SET_OOM (error); + goto failed; + } + } + else if (strcmp (key, "member") == 0) + { + if (rule->flags & BUS_MATCH_MEMBER) + { + dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID, + "Key %s specified twice in match rule\n", key); + goto failed; + } + + if (!_dbus_validate_member (&tmp_str, 0, len)) + { + dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID, + "Member name '%s' is invalid\n", value); + goto failed; + } + + if (!bus_match_rule_set_member (rule, value)) + { + BUS_SET_OOM (error); + goto failed; + } + } + else if (strcmp (key, "path") == 0) + { + if (rule->flags & BUS_MATCH_PATH) + { + dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID, + "Key %s specified twice in match rule\n", key); + goto failed; + } + + if (!_dbus_validate_path (&tmp_str, 0, len)) + { + dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID, + "Path '%s' is invalid\n", value); + goto failed; + } + + if (!bus_match_rule_set_path (rule, value)) + { + BUS_SET_OOM (error); + goto failed; + } + } + else if (strcmp (key, "destination") == 0) + { + if (rule->flags & BUS_MATCH_DESTINATION) + { + dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID, + "Key %s specified twice in match rule\n", key); + goto failed; + } + + if (!_dbus_validate_bus_name (&tmp_str, 0, len)) + { + dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID, + "Destination name '%s' is invalid\n", value); + goto failed; + } + + if (!bus_match_rule_set_destination (rule, value)) + { + BUS_SET_OOM (error); + goto failed; + } + } + else if (strncmp (key, "arg", 3) == 0) + { + if (!bus_match_rule_parse_arg_match (rule, key, &tmp_str, error)) + goto failed; + } + else + { + dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID, + "Unknown key \"%s\" in match rule", + key); + goto failed; + } + + ++i; + } + + + goto out; + + failed: + _DBUS_ASSERT_ERROR_IS_SET (error); + if (rule) + { + bus_match_rule_unref (rule); + rule = NULL; + } + + out: + + i = 0; + while (tokens[i].key || tokens[i].value) + { + _dbus_assert (i < MAX_RULE_TOKENS); + dbus_free (tokens[i].key); + dbus_free (tokens[i].value); + ++i; + } + + return rule; +} + +struct BusMatchmaker +{ + int refcount; + + DBusList *all_rules; +}; + +BusMatchmaker* +bus_matchmaker_new (void) +{ + BusMatchmaker *matchmaker; + + matchmaker = dbus_new0 (BusMatchmaker, 1); + if (matchmaker == NULL) + return NULL; + + matchmaker->refcount = 1; + + return matchmaker; +} + +BusMatchmaker * +bus_matchmaker_ref (BusMatchmaker *matchmaker) +{ + _dbus_assert (matchmaker->refcount > 0); + + matchmaker->refcount += 1; + + return matchmaker; +} + +void +bus_matchmaker_unref (BusMatchmaker *matchmaker) +{ + _dbus_assert (matchmaker->refcount > 0); + + matchmaker->refcount -= 1; + if (matchmaker->refcount == 0) + { + while (matchmaker->all_rules != NULL) + { + BusMatchRule *rule; + + rule = matchmaker->all_rules->data; + bus_match_rule_unref (rule); + _dbus_list_remove_link (&matchmaker->all_rules, + matchmaker->all_rules); + } + + dbus_free (matchmaker); + } +} + +/* The rule can't be modified after it's added. */ +dbus_bool_t +bus_matchmaker_add_rule (BusMatchmaker *matchmaker, + BusMatchRule *rule) +{ + _dbus_assert (bus_connection_is_active (rule->matches_go_to)); + + if (!_dbus_list_append (&matchmaker->all_rules, rule)) + return FALSE; + + if (!bus_connection_add_match_rule (rule->matches_go_to, rule)) + { + _dbus_list_remove_last (&matchmaker->all_rules, rule); + return FALSE; + } + + bus_match_rule_ref (rule); + +#ifdef DBUS_ENABLE_VERBOSE_MODE + { + char *s = match_rule_to_string (rule); + + _dbus_verbose ("Added match rule %s to connection %p\n", + s, rule->matches_go_to); + dbus_free (s); + } +#endif + + return TRUE; +} + +static dbus_bool_t +match_rule_equal (BusMatchRule *a, + BusMatchRule *b) +{ + if (a->flags != b->flags) + return FALSE; + + if (a->matches_go_to != b->matches_go_to) + return FALSE; + + if ((a->flags & BUS_MATCH_MESSAGE_TYPE) && + a->message_type != b->message_type) + return FALSE; + + if ((a->flags & BUS_MATCH_MEMBER) && + strcmp (a->member, b->member) != 0) + return FALSE; + + if ((a->flags & BUS_MATCH_PATH) && + strcmp (a->path, b->path) != 0) + return FALSE; + + if ((a->flags & BUS_MATCH_INTERFACE) && + strcmp (a->interface, b->interface) != 0) + return FALSE; + + if ((a->flags & BUS_MATCH_SENDER) && + strcmp (a->sender, b->sender) != 0) + return FALSE; + + if ((a->flags & BUS_MATCH_DESTINATION) && + strcmp (a->destination, b->destination) != 0) + return FALSE; + + if (a->flags & BUS_MATCH_ARGS) + { + int i; + + if (a->args_len != b->args_len) + return FALSE; + + i = 0; + while (i < a->args_len) + { + int length; + + if ((a->args[i] != NULL) != (b->args[i] != NULL)) + return FALSE; + + if (a->arg_lens[i] != b->arg_lens[i]) + return FALSE; + + length = a->arg_lens[i] & ~BUS_MATCH_ARG_IS_PATH; + + if (a->args[i] != NULL) + { + _dbus_assert (b->args[i] != NULL); + if (memcmp (a->args[i], b->args[i], length) != 0) + return FALSE; + } + + ++i; + } + } + + return TRUE; +} + +static void +bus_matchmaker_remove_rule_link (BusMatchmaker *matchmaker, + DBusList *link) +{ + BusMatchRule *rule = link->data; + + bus_connection_remove_match_rule (rule->matches_go_to, rule); + _dbus_list_remove_link (&matchmaker->all_rules, link); + +#ifdef DBUS_ENABLE_VERBOSE_MODE + { + char *s = match_rule_to_string (rule); + + _dbus_verbose ("Removed match rule %s for connection %p\n", + s, rule->matches_go_to); + dbus_free (s); + } +#endif + + bus_match_rule_unref (rule); +} + +void +bus_matchmaker_remove_rule (BusMatchmaker *matchmaker, + BusMatchRule *rule) +{ + bus_connection_remove_match_rule (rule->matches_go_to, rule); + _dbus_list_remove (&matchmaker->all_rules, rule); + +#ifdef DBUS_ENABLE_VERBOSE_MODE + { + char *s = match_rule_to_string (rule); + + _dbus_verbose ("Removed match rule %s for connection %p\n", + s, rule->matches_go_to); + dbus_free (s); + } +#endif + + bus_match_rule_unref (rule); +} + +/* Remove a single rule which is equal to the given rule by value */ +dbus_bool_t +bus_matchmaker_remove_rule_by_value (BusMatchmaker *matchmaker, + BusMatchRule *value, + DBusError *error) +{ + /* FIXME this is an unoptimized linear scan */ + + DBusList *link; + + /* we traverse backward because bus_connection_remove_match_rule() + * removes the most-recently-added rule + */ + link = _dbus_list_get_last_link (&matchmaker->all_rules); + while (link != NULL) + { + BusMatchRule *rule; + DBusList *prev; + + rule = link->data; + prev = _dbus_list_get_prev_link (&matchmaker->all_rules, link); + + if (match_rule_equal (rule, value)) + { + bus_matchmaker_remove_rule_link (matchmaker, link); + break; + } + + link = prev; + } + + if (link == NULL) + { + dbus_set_error (error, DBUS_ERROR_MATCH_RULE_NOT_FOUND, + "The given match rule wasn't found and can't be removed"); + return FALSE; + } + + return TRUE; +} + +void +bus_matchmaker_disconnected (BusMatchmaker *matchmaker, + DBusConnection *disconnected) +{ + DBusList *link; + + /* FIXME + * + * This scans all match rules on the bus. We could avoid that + * for the rules belonging to the connection, since we keep + * a list of those; but for the rules that just refer to + * the connection we'd need to do something more elaborate. + * + */ + + _dbus_assert (bus_connection_is_active (disconnected)); + + link = _dbus_list_get_first_link (&matchmaker->all_rules); + while (link != NULL) + { + BusMatchRule *rule; + DBusList *next; + + rule = link->data; + next = _dbus_list_get_next_link (&matchmaker->all_rules, link); + + if (rule->matches_go_to == disconnected) + { + bus_matchmaker_remove_rule_link (matchmaker, link); + } + else if (((rule->flags & BUS_MATCH_SENDER) && *rule->sender == ':') || + ((rule->flags & BUS_MATCH_DESTINATION) && *rule->destination == ':')) + { + /* The rule matches to/from a base service, see if it's the + * one being disconnected, since we know this service name + * will never be recycled. + */ + const char *name; + + name = bus_connection_get_name (disconnected); + _dbus_assert (name != NULL); /* because we're an active connection */ + + if (((rule->flags & BUS_MATCH_SENDER) && + strcmp (rule->sender, name) == 0) || + ((rule->flags & BUS_MATCH_DESTINATION) && + strcmp (rule->destination, name) == 0)) + { + bus_matchmaker_remove_rule_link (matchmaker, link); + } + } + + link = next; + } +} + +static dbus_bool_t +connection_is_primary_owner (DBusConnection *connection, + const char *service_name) +{ + BusService *service; + DBusString str; + BusRegistry *registry; + + _dbus_assert (connection != NULL); + + registry = bus_connection_get_registry (connection); + + _dbus_string_init_const (&str, service_name); + service = bus_registry_lookup (registry, &str); + + if (service == NULL) + return FALSE; /* Service doesn't exist so connection can't own it. */ + + return bus_service_get_primary_owners_connection (service) == connection; +} + +static dbus_bool_t +match_rule_matches (BusMatchRule *rule, + DBusConnection *sender, + DBusConnection *addressed_recipient, + DBusMessage *message) +{ + /* All features of the match rule are AND'd together, + * so FALSE if any of them don't match. + */ + + /* sender/addressed_recipient of #NULL may mean bus driver, + * or for addressed_recipient may mean a message with no + * specific recipient (i.e. a signal) + */ + + if (rule->flags & BUS_MATCH_MESSAGE_TYPE) + { + _dbus_assert (rule->message_type != DBUS_MESSAGE_TYPE_INVALID); + + if (rule->message_type != dbus_message_get_type (message)) + return FALSE; + } + + if (rule->flags & BUS_MATCH_INTERFACE) + { + const char *iface; + + _dbus_assert (rule->interface != NULL); + + iface = dbus_message_get_interface (message); + if (iface == NULL) + return FALSE; + + if (strcmp (iface, rule->interface) != 0) + return FALSE; + } + + if (rule->flags & BUS_MATCH_MEMBER) + { + const char *member; + + _dbus_assert (rule->member != NULL); + + member = dbus_message_get_member (message); + if (member == NULL) + return FALSE; + + if (strcmp (member, rule->member) != 0) + return FALSE; + } + + if (rule->flags & BUS_MATCH_SENDER) + { + _dbus_assert (rule->sender != NULL); + + if (sender == NULL) + { + if (strcmp (rule->sender, + DBUS_SERVICE_DBUS) != 0) + return FALSE; + } + else + { + if (!connection_is_primary_owner (sender, rule->sender)) + return FALSE; + } + } + + if (rule->flags & BUS_MATCH_DESTINATION) + { + const char *destination; + + _dbus_assert (rule->destination != NULL); + + destination = dbus_message_get_destination (message); + if (destination == NULL) + return FALSE; + + if (addressed_recipient == NULL) + { + if (strcmp (rule->destination, + DBUS_SERVICE_DBUS) != 0) + return FALSE; + } + else + { + if (!connection_is_primary_owner (addressed_recipient, rule->destination)) + return FALSE; + } + } + + if (rule->flags & BUS_MATCH_PATH) + { + const char *path; + + _dbus_assert (rule->path != NULL); + + path = dbus_message_get_path (message); + if (path == NULL) + return FALSE; + + if (strcmp (path, rule->path) != 0) + return FALSE; + } + + if (rule->flags & BUS_MATCH_ARGS) + { + int i; + DBusMessageIter iter; + + _dbus_assert (rule->args != NULL); + + dbus_message_iter_init (message, &iter); + + i = 0; + while (i < rule->args_len) + { + int current_type; + const char *expected_arg; + int expected_length; + dbus_bool_t is_path; + + expected_arg = rule->args[i]; + expected_length = rule->arg_lens[i] & ~BUS_MATCH_ARG_IS_PATH; + is_path = (rule->arg_lens[i] & BUS_MATCH_ARG_IS_PATH) != 0; + + current_type = dbus_message_iter_get_arg_type (&iter); + + if (expected_arg != NULL) + { + const char *actual_arg; + int actual_length; + + if (current_type != DBUS_TYPE_STRING) + return FALSE; + + actual_arg = NULL; + dbus_message_iter_get_basic (&iter, &actual_arg); + _dbus_assert (actual_arg != NULL); + + actual_length = strlen (actual_arg); + + if (is_path) + { + if (actual_length < expected_length && + actual_arg[actual_length - 1] != '/') + return FALSE; + + if (expected_length < actual_length && + expected_arg[expected_length - 1] != '/') + return FALSE; + + if (memcmp (actual_arg, expected_arg, + MIN (actual_length, expected_length)) != 0) + return FALSE; + } + else + { + if (expected_length != actual_length || + memcmp (expected_arg, actual_arg, expected_length) != 0) + return FALSE; + } + + } + + if (current_type != DBUS_TYPE_INVALID) + dbus_message_iter_next (&iter); + + ++i; + } + } + + return TRUE; +} + +dbus_bool_t +bus_matchmaker_get_recipients (BusMatchmaker *matchmaker, + BusConnections *connections, + DBusConnection *sender, + DBusConnection *addressed_recipient, + DBusMessage *message, + DBusList **recipients_p) +{ + /* FIXME for now this is a wholly unoptimized linear search */ + /* Guessing the important optimization is to skip the signal-related + * match lists when processing method call and exception messages. + * So separate match rule lists for signals? + */ + + DBusList *link; + + _dbus_assert (*recipients_p == NULL); + + /* This avoids sending same message to the same connection twice. + * Purpose of the stamp instead of a bool is to avoid iterating over + * all connections resetting the bool each time. + */ + bus_connections_increment_stamp (connections); + + /* addressed_recipient is already receiving the message, don't add to list. + * NULL addressed_recipient means either bus driver, or this is a signal + * and thus lacks a specific addressed_recipient. + */ + if (addressed_recipient != NULL) + bus_connection_mark_stamp (addressed_recipient); + + link = _dbus_list_get_first_link (&matchmaker->all_rules); + while (link != NULL) + { + BusMatchRule *rule; + + rule = link->data; + +#ifdef DBUS_ENABLE_VERBOSE_MODE + { + char *s = match_rule_to_string (rule); + + _dbus_verbose ("Checking whether message matches rule %s for connection %p\n", + s, rule->matches_go_to); + dbus_free (s); + } +#endif + + if (match_rule_matches (rule, + sender, addressed_recipient, message)) + { + _dbus_verbose ("Rule matched\n"); + + /* Append to the list if we haven't already */ + if (bus_connection_mark_stamp (rule->matches_go_to)) + { + if (!_dbus_list_append (recipients_p, rule->matches_go_to)) + goto nomem; + } +#ifdef DBUS_ENABLE_VERBOSE_MODE + else + { + _dbus_verbose ("Connection already receiving this message, so not adding again\n"); + } +#endif /* DBUS_ENABLE_VERBOSE_MODE */ + } + + link = _dbus_list_get_next_link (&matchmaker->all_rules, link); + } + + return TRUE; + + nomem: + _dbus_list_clear (recipients_p); + return FALSE; +} + +#ifdef DBUS_BUILD_TESTS +#include "test.h" +#include <stdlib.h> + +static BusMatchRule* +check_parse (dbus_bool_t should_succeed, + const char *text) +{ + BusMatchRule *rule; + DBusString str; + DBusError error; + + dbus_error_init (&error); + + _dbus_string_init_const (&str, text); + + rule = bus_match_rule_parse (NULL, &str, &error); + if (dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY)) + { + dbus_error_free (&error); + return NULL; + } + + if (should_succeed && rule == NULL) + { + _dbus_warn ("Failed to parse: %s: %s: \"%s\"\n", + error.name, error.message, + _dbus_string_get_const_data (&str)); + exit (1); + } + + if (!should_succeed && rule != NULL) + { + _dbus_warn ("Failed to fail to parse: \"%s\"\n", + _dbus_string_get_const_data (&str)); + exit (1); + } + + dbus_error_free (&error); + + return rule; +} + +static void +assert_large_rule (BusMatchRule *rule) +{ + _dbus_assert (rule->flags & BUS_MATCH_MESSAGE_TYPE); + _dbus_assert (rule->flags & BUS_MATCH_SENDER); + _dbus_assert (rule->flags & BUS_MATCH_INTERFACE); + _dbus_assert (rule->flags & BUS_MATCH_MEMBER); + _dbus_assert (rule->flags & BUS_MATCH_DESTINATION); + _dbus_assert (rule->flags & BUS_MATCH_PATH); + + _dbus_assert (rule->message_type == DBUS_MESSAGE_TYPE_SIGNAL); + _dbus_assert (rule->interface != NULL); + _dbus_assert (rule->member != NULL); + _dbus_assert (rule->sender != NULL); + _dbus_assert (rule->destination != NULL); + _dbus_assert (rule->path != NULL); + + _dbus_assert (strcmp (rule->interface, "org.freedesktop.DBusInterface") == 0); + _dbus_assert (strcmp (rule->sender, "org.freedesktop.DBusSender") == 0); + _dbus_assert (strcmp (rule->member, "Foo") == 0); + _dbus_assert (strcmp (rule->path, "/bar/foo") == 0); + _dbus_assert (strcmp (rule->destination, ":452345.34") == 0); +} + +static dbus_bool_t +test_parsing (void *data) +{ + BusMatchRule *rule; + + rule = check_parse (TRUE, "type='signal',sender='org.freedesktop.DBusSender',interface='org.freedesktop.DBusInterface',member='Foo',path='/bar/foo',destination=':452345.34'"); + if (rule != NULL) + { + assert_large_rule (rule); + bus_match_rule_unref (rule); + } + + /* With extra whitespace and useless quotes */ + rule = check_parse (TRUE, " type='signal', \tsender='org.freedes''ktop.DBusSender', interface='org.freedesktop.DBusInterface''''', \tmember='Foo',path='/bar/foo',destination=':452345.34'''''"); + if (rule != NULL) + { + assert_large_rule (rule); + bus_match_rule_unref (rule); + } + + + /* A simple signal connection */ + rule = check_parse (TRUE, "type='signal',path='/foo',interface='org.Bar'"); + if (rule != NULL) + { + _dbus_assert (rule->flags & BUS_MATCH_MESSAGE_TYPE); + _dbus_assert (rule->flags & BUS_MATCH_INTERFACE); + _dbus_assert (rule->flags & BUS_MATCH_PATH); + + _dbus_assert (rule->message_type == DBUS_MESSAGE_TYPE_SIGNAL); + _dbus_assert (rule->interface != NULL); + _dbus_assert (rule->path != NULL); + + _dbus_assert (strcmp (rule->interface, "org.Bar") == 0); + _dbus_assert (strcmp (rule->path, "/foo") == 0); + + bus_match_rule_unref (rule); + } + + /* argN */ + rule = check_parse (TRUE, "arg0='foo'"); + if (rule != NULL) + { + _dbus_assert (rule->flags == BUS_MATCH_ARGS); + _dbus_assert (rule->args != NULL); + _dbus_assert (rule->args_len == 1); + _dbus_assert (rule->args[0] != NULL); + _dbus_assert (rule->args[1] == NULL); + _dbus_assert (strcmp (rule->args[0], "foo") == 0); + + bus_match_rule_unref (rule); + } + + rule = check_parse (TRUE, "arg1='foo'"); + if (rule != NULL) + { + _dbus_assert (rule->flags == BUS_MATCH_ARGS); + _dbus_assert (rule->args != NULL); + _dbus_assert (rule->args_len == 2); + _dbus_assert (rule->args[0] == NULL); + _dbus_assert (rule->args[1] != NULL); + _dbus_assert (rule->args[2] == NULL); + _dbus_assert (strcmp (rule->args[1], "foo") == 0); + + bus_match_rule_unref (rule); + } + + rule = check_parse (TRUE, "arg2='foo'"); + if (rule != NULL) + { + _dbus_assert (rule->flags == BUS_MATCH_ARGS); + _dbus_assert (rule->args != NULL); + _dbus_assert (rule->args_len == 3); + _dbus_assert (rule->args[0] == NULL); + _dbus_assert (rule->args[1] == NULL); + _dbus_assert (rule->args[2] != NULL); + _dbus_assert (rule->args[3] == NULL); + _dbus_assert (strcmp (rule->args[2], "foo") == 0); + + bus_match_rule_unref (rule); + } + + rule = check_parse (TRUE, "arg40='foo'"); + if (rule != NULL) + { + _dbus_assert (rule->flags == BUS_MATCH_ARGS); + _dbus_assert (rule->args != NULL); + _dbus_assert (rule->args_len == 41); + _dbus_assert (rule->args[0] == NULL); + _dbus_assert (rule->args[1] == NULL); + _dbus_assert (rule->args[40] != NULL); + _dbus_assert (rule->args[41] == NULL); + _dbus_assert (strcmp (rule->args[40], "foo") == 0); + + bus_match_rule_unref (rule); + } + + rule = check_parse (TRUE, "arg63='foo'"); + if (rule != NULL) + { + _dbus_assert (rule->flags == BUS_MATCH_ARGS); + _dbus_assert (rule->args != NULL); + _dbus_assert (rule->args_len == 64); + _dbus_assert (rule->args[0] == NULL); + _dbus_assert (rule->args[1] == NULL); + _dbus_assert (rule->args[63] != NULL); + _dbus_assert (rule->args[64] == NULL); + _dbus_assert (strcmp (rule->args[63], "foo") == 0); + + bus_match_rule_unref (rule); + } + + /* Too-large argN */ + rule = check_parse (FALSE, "arg300='foo'"); + _dbus_assert (rule == NULL); + rule = check_parse (FALSE, "arg64='foo'"); + _dbus_assert (rule == NULL); + + /* No N in argN */ + rule = check_parse (FALSE, "arg='foo'"); + _dbus_assert (rule == NULL); + rule = check_parse (FALSE, "argv='foo'"); + _dbus_assert (rule == NULL); + rule = check_parse (FALSE, "arg3junk='foo'"); + _dbus_assert (rule == NULL); + rule = check_parse (FALSE, "argument='foo'"); + _dbus_assert (rule == NULL); + + /* Reject duplicates */ + rule = check_parse (FALSE, "type='signal',type='method_call'"); + _dbus_assert (rule == NULL); + + /* Duplicates with the argN code */ + rule = check_parse (FALSE, "arg0='foo',arg0='bar'"); + _dbus_assert (rule == NULL); + rule = check_parse (FALSE, "arg3='foo',arg3='bar'"); + _dbus_assert (rule == NULL); + rule = check_parse (FALSE, "arg30='foo',arg30='bar'"); + _dbus_assert (rule == NULL); + + /* Reject broken keys */ + rule = check_parse (FALSE, "blah='signal'"); + _dbus_assert (rule == NULL); + + /* Reject broken values */ + rule = check_parse (FALSE, "type='chouin'"); + _dbus_assert (rule == NULL); + rule = check_parse (FALSE, "interface='abc@def++'"); + _dbus_assert (rule == NULL); + rule = check_parse (FALSE, "service='youpi'"); + _dbus_assert (rule == NULL); + + /* Allow empty rule */ + rule = check_parse (TRUE, ""); + if (rule != NULL) + { + _dbus_assert (rule->flags == 0); + + bus_match_rule_unref (rule); + } + + /* All-whitespace rule is the same as empty */ + rule = check_parse (TRUE, " \t"); + if (rule != NULL) + { + _dbus_assert (rule->flags == 0); + + bus_match_rule_unref (rule); + } + + /* But with non-whitespace chars and no =value, it's not OK */ + rule = check_parse (FALSE, "type"); + _dbus_assert (rule == NULL); + + return TRUE; +} + +static struct { + const char *first; + const char *second; +} equality_tests[] = { + { "type='signal'", "type='signal'" }, + { "type='signal',interface='foo.bar'", "interface='foo.bar',type='signal'" }, + { "type='signal',member='bar'", "member='bar',type='signal'" }, + { "type='method_call',sender=':1.0'", "sender=':1.0',type='method_call'" }, + { "type='method_call',destination=':1.0'", "destination=':1.0',type='method_call'" }, + { "type='method_call',path='/foo/bar'", "path='/foo/bar',type='method_call'" }, + { "type='method_call',arg0='blah'", "arg0='blah',type='method_call'" }, + { "type='method_call',arg0='boo'", "arg0='boo',type='method_call'" }, + { "type='method_call',arg0='blah',arg1='baz'", "arg0='blah',arg1='baz',type='method_call'" }, + { "type='method_call',arg3='foosh'", "arg3='foosh',type='method_call'" }, + { "arg3='fool'", "arg3='fool'" }, + { "member='food'", "member='food'" } +}; + +static void +test_equality (void) +{ + int i; + + i = 0; + while (i < _DBUS_N_ELEMENTS (equality_tests)) + { + BusMatchRule *first; + BusMatchRule *second; + int j; + + first = check_parse (TRUE, equality_tests[i].first); + _dbus_assert (first != NULL); + second = check_parse (TRUE, equality_tests[i].second); + _dbus_assert (second != NULL); + + if (!match_rule_equal (first, second)) + { + _dbus_warn ("rule %s and %s should have been equal\n", + equality_tests[i].first, + equality_tests[i].second); + exit (1); + } + + bus_match_rule_unref (second); + + /* Check that the rule is not equal to any of the + * others besides its pair match + */ + j = 0; + while (j < _DBUS_N_ELEMENTS (equality_tests)) + { + if (i != j) + { + second = check_parse (TRUE, equality_tests[j].second); + + if (match_rule_equal (first, second)) + { + _dbus_warn ("rule %s and %s should not have been equal\n", + equality_tests[i].first, + equality_tests[j].second); + exit (1); + } + + bus_match_rule_unref (second); + } + + ++j; + } + + bus_match_rule_unref (first); + + ++i; + } +} + +static const char* +should_match_message_1[] = { + "type='signal'", + "member='Frobated'", + "arg0='foobar'", + "type='signal',member='Frobated'", + "type='signal',member='Frobated',arg0='foobar'", + "member='Frobated',arg0='foobar'", + "type='signal',arg0='foobar'", + NULL +}; + +static const char* +should_not_match_message_1[] = { + "type='method_call'", + "type='error'", + "type='method_return'", + "type='signal',member='Oopsed'", + "arg0='blah'", + "arg1='foobar'", + "arg2='foobar'", + "arg3='foobar'", + "arg0='3'", + "arg1='3'", + "arg0='foobar',arg1='abcdef'", + "arg0='foobar',arg1='abcdef',arg2='abcdefghi',arg3='abcdefghi',arg4='abcdefghi'", + "arg0='foobar',arg1='abcdef',arg4='abcdefghi',arg3='abcdefghi',arg2='abcdefghi'", + NULL +}; + +static void +check_matches (dbus_bool_t expected_to_match, + int number, + DBusMessage *message, + const char *rule_text) +{ + BusMatchRule *rule; + dbus_bool_t matched; + + rule = check_parse (TRUE, rule_text); + _dbus_assert (rule != NULL); + + /* We can't test sender/destination rules since we pass NULL here */ + matched = match_rule_matches (rule, NULL, NULL, message); + + if (matched != expected_to_match) + { + _dbus_warn ("Expected rule %s to %s message %d, failed\n", + rule_text, expected_to_match ? + "match" : "not match", number); + exit (1); + } + + bus_match_rule_unref (rule); +} + +static void +check_matching (DBusMessage *message, + int number, + const char **should_match, + const char **should_not_match) +{ + int i; + + i = 0; + while (should_match[i] != NULL) + { + check_matches (TRUE, number, message, should_match[i]); + ++i; + } + + i = 0; + while (should_not_match[i] != NULL) + { + check_matches (FALSE, number, message, should_not_match[i]); + ++i; + } +} + +static void +test_matching (void) +{ + DBusMessage *message1; + const char *v_STRING; + dbus_int32_t v_INT32; + + message1 = dbus_message_new (DBUS_MESSAGE_TYPE_SIGNAL); + _dbus_assert (message1 != NULL); + if (!dbus_message_set_member (message1, "Frobated")) + _dbus_assert_not_reached ("oom"); + + v_STRING = "foobar"; + v_INT32 = 3; + if (!dbus_message_append_args (message1, + DBUS_TYPE_STRING, &v_STRING, + DBUS_TYPE_INT32, &v_INT32, + NULL)) + _dbus_assert_not_reached ("oom"); + + check_matching (message1, 1, + should_match_message_1, + should_not_match_message_1); + + dbus_message_unref (message1); +} + +dbus_bool_t +bus_signals_test (const DBusString *test_data_dir) +{ + BusMatchmaker *matchmaker; + + matchmaker = bus_matchmaker_new (); + bus_matchmaker_ref (matchmaker); + bus_matchmaker_unref (matchmaker); + bus_matchmaker_unref (matchmaker); + + if (!_dbus_test_oom_handling ("parsing match rules", test_parsing, NULL)) + _dbus_assert_not_reached ("Parsing match rules test failed"); + + test_equality (); + + test_matching (); + + return TRUE; +} + +#endif /* DBUS_BUILD_TESTS */ + diff --git a/bus/signals.h b/bus/signals.h new file mode 100644 index 00000000..4ea10755 --- /dev/null +++ b/bus/signals.h @@ -0,0 +1,88 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* signals.h Bus signal connection implementation + * + * Copyright (C) 2003 Red Hat, Inc. + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef BUS_SIGNALS_H +#define BUS_SIGNALS_H + +#include <dbus/dbus.h> +#include <dbus/dbus-string.h> +#include <dbus/dbus-sysdeps.h> +#include "connection.h" + +typedef enum +{ + BUS_MATCH_MESSAGE_TYPE = 1 << 0, + BUS_MATCH_INTERFACE = 1 << 1, + BUS_MATCH_MEMBER = 1 << 2, + BUS_MATCH_SENDER = 1 << 3, + BUS_MATCH_DESTINATION = 1 << 4, + BUS_MATCH_PATH = 1 << 5, + BUS_MATCH_ARGS = 1 << 6 +} BusMatchFlags; + +BusMatchRule* bus_match_rule_new (DBusConnection *matches_go_to); +BusMatchRule* bus_match_rule_ref (BusMatchRule *rule); +void bus_match_rule_unref (BusMatchRule *rule); + +dbus_bool_t bus_match_rule_set_message_type (BusMatchRule *rule, + int type); +dbus_bool_t bus_match_rule_set_interface (BusMatchRule *rule, + const char *interface); +dbus_bool_t bus_match_rule_set_member (BusMatchRule *rule, + const char *member); +dbus_bool_t bus_match_rule_set_sender (BusMatchRule *rule, + const char *sender); +dbus_bool_t bus_match_rule_set_destination (BusMatchRule *rule, + const char *destination); +dbus_bool_t bus_match_rule_set_path (BusMatchRule *rule, + const char *path); +dbus_bool_t bus_match_rule_set_arg (BusMatchRule *rule, + int arg, + const DBusString *value, + dbus_bool_t is_path); + +BusMatchRule* bus_match_rule_parse (DBusConnection *matches_go_to, + const DBusString *rule_text, + DBusError *error); + +BusMatchmaker* bus_matchmaker_new (void); +BusMatchmaker* bus_matchmaker_ref (BusMatchmaker *matchmaker); +void bus_matchmaker_unref (BusMatchmaker *matchmaker); + +dbus_bool_t bus_matchmaker_add_rule (BusMatchmaker *matchmaker, + BusMatchRule *rule); +dbus_bool_t bus_matchmaker_remove_rule_by_value (BusMatchmaker *matchmaker, + BusMatchRule *value, + DBusError *error); +void bus_matchmaker_remove_rule (BusMatchmaker *matchmaker, + BusMatchRule *rule); +void bus_matchmaker_disconnected (BusMatchmaker *matchmaker, + DBusConnection *disconnected); +dbus_bool_t bus_matchmaker_get_recipients (BusMatchmaker *matchmaker, + BusConnections *connections, + DBusConnection *sender, + DBusConnection *addressed_recipient, + DBusMessage *message, + DBusList **recipients_p); + +#endif /* BUS_SIGNALS_H */ diff --git a/bus/system.conf.in b/bus/system.conf.in new file mode 100644 index 00000000..92f4cc42 --- /dev/null +++ b/bus/system.conf.in @@ -0,0 +1,83 @@ +<!-- This configuration file controls the systemwide message bus. + Add a system-local.conf and edit that rather than changing this + file directly. --> + +<!-- Note that there are any number of ways you can hose yourself + security-wise by screwing up this file; in particular, you + probably don't want to listen on any more addresses, add any more + auth mechanisms, run as a different user, etc. --> + +<!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, do not change this --> + <type>system</type> + + <!-- Run as special user --> + <user>@DBUS_USER@</user> + + <!-- Fork into daemon mode --> + <fork/> + + <!-- We use system service launching using a helper --> + <standard_system_servicedirs/> + + <!-- This is a setuid helper that is used to launch system services --> + <servicehelper>@DBUS_LIBEXECDIR@/dbus-daemon-launch-helper</servicehelper> + + <!-- Write a pid file --> + <pidfile>@DBUS_SYSTEM_PID_FILE@</pidfile> + + <!-- Enable logging to syslog --> + <syslog/> + + <!-- Only allow socket-credentials-based authentication --> + <auth>EXTERNAL</auth> + + <!-- Only listen on a local socket. (abstract=/path/to/socket + means use abstract namespace, don't really create filesystem + file; only Linux supports this. Use path=/whatever on other + systems.) --> + <listen>@DBUS_SYSTEM_BUS_DEFAULT_ADDRESS@</listen> + + <policy context="default"> + <!-- All users can connect to system bus --> + <allow user="*"/> + + <!-- Holes must be punched in service configuration files for + name ownership and sending method calls --> + <deny own="*"/> + <deny send_type="method_call"/> + + <!-- Signals and reply messages (method returns, errors) are allowed + by default --> + <allow send_type="signal"/> + <allow send_requested_reply="true" send_type="method_return"/> + <allow send_requested_reply="true" send_type="error"/> + + <!-- All messages may be received by default --> + <allow receive_type="method_call"/> + <allow receive_type="method_return"/> + <allow receive_type="error"/> + <allow receive_type="signal"/> + + <!-- Allow anyone to talk to the message bus --> + <allow send_destination="org.freedesktop.DBus"/> + <!-- But disallow some specific bus services --> + <deny send_destination="org.freedesktop.DBus" + send_interface="org.freedesktop.DBus" + send_member="UpdateActivationEnvironment"/> + </policy> + + <!-- Config files are placed here that among other things, punch + holes in the above policy for specific services. --> + <includedir>system.d</includedir> + + <!-- This is included last so local configuration can override what's + in this standard file --> + <include ignore_missing="yes">system-local.conf</include> + + <include if_selinux_enabled="yes" selinux_root_relative="yes">contexts/dbus_contexts</include> + +</busconfig> diff --git a/bus/test-launch-helper.c b/bus/test-launch-helper.c new file mode 100644 index 00000000..d78ca519 --- /dev/null +++ b/bus/test-launch-helper.c @@ -0,0 +1,146 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* test-main.c main() for the OOM check of the launch helper + * + * Copyright (C) 2007 Red Hat, Inc. + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "test.h" +#include "activation-helper.h" + +#include <stdio.h> +#include <stdlib.h> +#include <dbus/dbus-internals.h> + +#ifdef DBUS_BUILD_TESTS +static void +die (const char *failure) +{ + fprintf (stderr, "Unit test failed: %s\n", failure); + exit (1); +} + +static void +check_memleaks (const char *name) +{ + dbus_shutdown (); + + printf ("%s: checking for memleaks\n", name); + if (_dbus_get_malloc_blocks_outstanding () != 0) + { + _dbus_warn ("%d dbus_malloc blocks were not freed\n", + _dbus_get_malloc_blocks_outstanding ()); + die ("memleaks"); + } +} + +static void +test_post_hook (const char *name) +{ + check_memleaks (name); +} +#endif /* DBUS_BUILD_TESTS */ + + +#ifdef ACTIVATION_LAUNCHER_DO_OOM + +/* returns true if good things happen, or if we get OOM */ +static dbus_bool_t +bus_activation_helper_oom_test (void *data) +{ + const char *service; + DBusError error; + dbus_bool_t retval; + + service = (const char *) data; + retval = TRUE; + + dbus_error_init (&error); + if (!run_launch_helper (service, &error)) + { + _DBUS_ASSERT_ERROR_IS_SET (&error); + /* we failed, but a OOM is good */ + if (!dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY)) + { + _dbus_warn ("FAILED SELF TEST: Error: %s\n", error.message); + retval = FALSE; + } + dbus_error_free (&error); + } + else + { + /* we succeeded, yay! */ + _DBUS_ASSERT_ERROR_IS_CLEAR (&error); + } + return retval; +} + +#endif + +int +main (int argc, char **argv) +{ +#ifdef DBUS_BUILD_TESTS + const char *dir; + DBusString config_file; + + if (argc > 1) + dir = argv[1]; + else + dir = _dbus_getenv ("DBUS_TEST_DATA"); + + if (dir == NULL) + { + fprintf (stderr, "Must specify test data directory as argv[1] or in DBUS_TEST_DATA env variable\n"); + return 1; + } + + printf ("%s: Running launch helper OOM checks\n", argv[0]); + + if (!_dbus_string_init (&config_file)) + return 1; + if (!_dbus_string_append (&config_file, dir)) + return 1; + if (!_dbus_string_append (&config_file, "/valid-config-files-system/debug-allow-all-pass.conf")) + return 1; + + /* use a config file that will actually work... */ + _dbus_setenv ("TEST_LAUNCH_HELPER_CONFIG", + _dbus_string_get_const_data (&config_file)); + + _dbus_string_free (&config_file); + + if (!_dbus_test_oom_handling ("dbus-daemon-launch-helper", + bus_activation_helper_oom_test, + "org.freedesktop.DBus.TestSuiteEchoService")) + die ("OOM failed"); + + test_post_hook (argv[0]); + + printf ("%s: Success\n", argv[0]); + + return 0; +#else /* DBUS_BUILD_TESTS */ + + printf ("Not compiled with test support\n"); + + return 0; +#endif +} + diff --git a/bus/test-main.c b/bus/test-main.c new file mode 100644 index 00000000..f19d0d55 --- /dev/null +++ b/bus/test-main.c @@ -0,0 +1,151 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* test-main.c main() for make check + * + * Copyright (C) 2003 Red Hat, Inc. + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "test.h" +#include <stdio.h> +#include <stdlib.h> +#include <dbus/dbus-string.h> +#include <dbus/dbus-sysdeps.h> +#include <dbus/dbus-internals.h> +#include "selinux.h" + +#ifdef DBUS_BUILD_TESTS +static void +die (const char *failure) +{ + fprintf (stderr, "Unit test failed: %s\n", failure); + exit (1); +} + +static void +check_memleaks (const char *name) +{ + dbus_shutdown (); + + printf ("%s: checking for memleaks\n", name); + if (_dbus_get_malloc_blocks_outstanding () != 0) + { + _dbus_warn ("%d dbus_malloc blocks were not freed\n", + _dbus_get_malloc_blocks_outstanding ()); + die ("memleaks"); + } +} +#endif /* DBUS_BUILD_TESTS */ + +static void +test_pre_hook (void) +{ + + if (_dbus_getenv ("DBUS_TEST_SELINUX") + && (!bus_selinux_pre_init () + || !bus_selinux_full_init ())) + die ("could not init selinux support"); +} + +static char *progname = ""; +static void +test_post_hook (void) +{ + if (_dbus_getenv ("DBUS_TEST_SELINUX")) + bus_selinux_shutdown (); + check_memleaks (progname); +} + +int +main (int argc, char **argv) +{ +#ifdef DBUS_BUILD_TESTS + const char *dir; + DBusString test_data_dir; + + progname = argv[0]; + + if (argc > 1) + dir = argv[1]; + else + dir = _dbus_getenv ("DBUS_TEST_DATA"); + + if (dir == NULL) + { + fprintf (stderr, "Must specify test data directory as argv[1] or in DBUS_TEST_DATA env variable\n"); + return 1; + } + + _dbus_string_init_const (&test_data_dir, dir); + + if (!_dbus_threads_init_debug ()) + die ("initializing debug threads"); + + test_pre_hook (); + printf ("%s: Running expire list test\n", argv[0]); + if (!bus_expire_list_test (&test_data_dir)) + die ("expire list"); + test_post_hook (); + + test_pre_hook (); + printf ("%s: Running config file parser test\n", argv[0]); + if (!bus_config_parser_test (&test_data_dir)) + die ("parser"); + test_post_hook (); + + test_pre_hook (); + printf ("%s: Running policy test\n", argv[0]); + if (!bus_policy_test (&test_data_dir)) + die ("policy"); + test_post_hook (); + + test_pre_hook (); + printf ("%s: Running signals test\n", argv[0]); + if (!bus_signals_test (&test_data_dir)) + die ("signals"); + test_post_hook (); + + test_pre_hook (); + printf ("%s: Running SHA1 connection test\n", argv[0]); + if (!bus_dispatch_sha1_test (&test_data_dir)) + die ("sha1"); + test_post_hook (); + + test_pre_hook (); + printf ("%s: Running message dispatch test\n", argv[0]); + if (!bus_dispatch_test (&test_data_dir)) + die ("dispatch"); + test_post_hook (); + + test_pre_hook (); + printf ("%s: Running service files reloading test\n", argv[0]); + if (!bus_activation_service_reload_test (&test_data_dir)) + die ("service reload"); + test_post_hook (); + + printf ("%s: Success\n", argv[0]); + + + return 0; +#else /* DBUS_BUILD_TESTS */ + + printf ("Not compiled with test support\n"); + + return 0; +#endif +} diff --git a/bus/test-system.c b/bus/test-system.c new file mode 100644 index 00000000..6224ab5e --- /dev/null +++ b/bus/test-system.c @@ -0,0 +1,106 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* test-main.c main() for make check + * + * Copyright (C) 2003 Red Hat, Inc. + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "test.h" +#include <stdio.h> +#include <stdlib.h> +#include <dbus/dbus-string.h> +#include <dbus/dbus-sysdeps.h> +#include <dbus/dbus-internals.h> + +#ifdef DBUS_BUILD_TESTS +static void +die (const char *failure) +{ + fprintf (stderr, "Unit test failed: %s\n", failure); + exit (1); +} + +static void +check_memleaks (const char *name) +{ + dbus_shutdown (); + + printf ("%s: checking for memleaks\n", name); + if (_dbus_get_malloc_blocks_outstanding () != 0) + { + _dbus_warn ("%d dbus_malloc blocks were not freed\n", + _dbus_get_malloc_blocks_outstanding ()); + die ("memleaks"); + } +} +#endif /* DBUS_BUILD_TESTS */ + +static void +test_pre_hook (void) +{ +} + +static char *progname = ""; +static void +test_post_hook (void) +{ + check_memleaks (progname); +} + +int +main (int argc, char **argv) +{ +#ifdef DBUS_BUILD_TESTS + const char *dir; + DBusString test_data_dir; + + progname = argv[0]; + + if (argc > 1) + dir = argv[1]; + else + dir = _dbus_getenv ("DBUS_TEST_DATA"); + + if (dir == NULL) + { + fprintf (stderr, "Must specify test data directory as argv[1] or in DBUS_TEST_DATA env variable\n"); + return 1; + } + + _dbus_string_init_const (&test_data_dir, dir); + + if (!_dbus_threads_init_debug ()) + die ("initializing debug threads"); + + test_pre_hook (); + printf ("%s: Running config file parser (trivial) test\n", argv[0]); + if (!bus_config_parser_trivial_test (&test_data_dir)) + die ("parser"); + test_post_hook (); + + printf ("%s: Success\n", argv[0]); + + return 0; +#else /* DBUS_BUILD_TESTS */ + + printf ("Not compiled with test support\n"); + + return 0; +#endif +} diff --git a/bus/test.c b/bus/test.c new file mode 100644 index 00000000..8dfe098c --- /dev/null +++ b/bus/test.c @@ -0,0 +1,346 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* test.c unit test routines + * + * Copyright (C) 2003 Red Hat, Inc. + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> + +#ifdef DBUS_BUILD_TESTS +#include "test.h" +#include <dbus/dbus-internals.h> +#include <dbus/dbus-list.h> +#include <dbus/dbus-sysdeps.h> + +/* The "debug client" watch/timeout handlers don't dispatch messages, + * as we manually pull them in order to verify them. This is why they + * are different from the real handlers in connection.c + */ +static DBusList *clients = NULL; +static DBusLoop *client_loop = NULL; + +static dbus_bool_t +client_watch_callback (DBusWatch *watch, + unsigned int condition, + void *data) +{ + /* FIXME this can be done in dbus-mainloop.c + * if the code in activation.c for the babysitter + * watch handler is fixed. + */ + + return dbus_watch_handle (watch, condition); +} + +static dbus_bool_t +add_client_watch (DBusWatch *watch, + void *data) +{ + DBusConnection *connection = data; + + return _dbus_loop_add_watch (client_loop, + watch, client_watch_callback, connection, + NULL); +} + +static void +remove_client_watch (DBusWatch *watch, + void *data) +{ + DBusConnection *connection = data; + + _dbus_loop_remove_watch (client_loop, + watch, client_watch_callback, connection); +} + +static void +client_timeout_callback (DBusTimeout *timeout, + void *data) +{ + DBusConnection *connection = data; + + dbus_connection_ref (connection); + + /* can return FALSE on OOM but we just let it fire again later */ + dbus_timeout_handle (timeout); + + dbus_connection_unref (connection); +} + +static dbus_bool_t +add_client_timeout (DBusTimeout *timeout, + void *data) +{ + DBusConnection *connection = data; + + return _dbus_loop_add_timeout (client_loop, timeout, client_timeout_callback, connection, NULL); +} + +static void +remove_client_timeout (DBusTimeout *timeout, + void *data) +{ + DBusConnection *connection = data; + + _dbus_loop_remove_timeout (client_loop, timeout, client_timeout_callback, connection); +} + +static DBusHandlerResult +client_disconnect_filter (DBusConnection *connection, + DBusMessage *message, + void *user_data) +{ + if (!dbus_message_is_signal (message, + DBUS_INTERFACE_LOCAL, + "Disconnected")) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + _dbus_verbose ("Removing client %p in disconnect handler\n", + connection); + + _dbus_list_remove (&clients, connection); + + dbus_connection_unref (connection); + + if (clients == NULL) + { + _dbus_loop_unref (client_loop); + client_loop = NULL; + } + + return DBUS_HANDLER_RESULT_HANDLED; +} + +dbus_bool_t +bus_setup_debug_client (DBusConnection *connection) +{ + dbus_bool_t retval; + + if (!dbus_connection_add_filter (connection, + client_disconnect_filter, + NULL, NULL)) + return FALSE; + + retval = FALSE; + + if (client_loop == NULL) + { + client_loop = _dbus_loop_new (); + if (client_loop == NULL) + goto out; + } + + if (!dbus_connection_set_watch_functions (connection, + add_client_watch, + remove_client_watch, + NULL, + connection, + NULL)) + goto out; + + if (!dbus_connection_set_timeout_functions (connection, + add_client_timeout, + remove_client_timeout, + NULL, + connection, NULL)) + goto out; + + if (!_dbus_list_append (&clients, connection)) + goto out; + + retval = TRUE; + + out: + if (!retval) + { + dbus_connection_remove_filter (connection, + client_disconnect_filter, + NULL); + + dbus_connection_set_watch_functions (connection, + NULL, NULL, NULL, NULL, NULL); + dbus_connection_set_timeout_functions (connection, + NULL, NULL, NULL, NULL, NULL); + + _dbus_list_remove_last (&clients, connection); + + if (clients == NULL) + { + _dbus_loop_unref (client_loop); + client_loop = NULL; + } + } + + return retval; +} + +void +bus_test_clients_foreach (BusConnectionForeachFunction function, + void *data) +{ + DBusList *link; + + link = _dbus_list_get_first_link (&clients); + while (link != NULL) + { + DBusConnection *connection = link->data; + DBusList *next = _dbus_list_get_next_link (&clients, link); + + if (!(* function) (connection, data)) + break; + + link = next; + } +} + +dbus_bool_t +bus_test_client_listed (DBusConnection *connection) +{ + DBusList *link; + + link = _dbus_list_get_first_link (&clients); + while (link != NULL) + { + DBusConnection *c = link->data; + DBusList *next = _dbus_list_get_next_link (&clients, link); + + if (c == connection) + return TRUE; + + link = next; + } + + return FALSE; +} + +void +bus_test_run_clients_loop (dbus_bool_t block_once) +{ + if (client_loop == NULL) + return; + + _dbus_verbose ("---> Dispatching on \"client side\"\n"); + + /* dispatch before we block so pending dispatches + * won't make our block return early + */ + _dbus_loop_dispatch (client_loop); + + /* Do one blocking wait, since we're expecting data */ + if (block_once) + { + _dbus_verbose ("---> blocking on \"client side\"\n"); + _dbus_loop_iterate (client_loop, TRUE); + } + + /* Then mop everything up */ + while (_dbus_loop_iterate (client_loop, FALSE)) + ; + + _dbus_verbose ("---> Done dispatching on \"client side\"\n"); +} + +void +bus_test_run_bus_loop (BusContext *context, + dbus_bool_t block_once) +{ + _dbus_verbose ("---> Dispatching on \"server side\"\n"); + + /* dispatch before we block so pending dispatches + * won't make our block return early + */ + _dbus_loop_dispatch (bus_context_get_loop (context)); + + /* Do one blocking wait, since we're expecting data */ + if (block_once) + { + _dbus_verbose ("---> blocking on \"server side\"\n"); + _dbus_loop_iterate (bus_context_get_loop (context), TRUE); + } + + /* Then mop everything up */ + while (_dbus_loop_iterate (bus_context_get_loop (context), FALSE)) + ; + + _dbus_verbose ("---> Done dispatching on \"server side\"\n"); +} + +void +bus_test_run_everything (BusContext *context) +{ + while (_dbus_loop_iterate (bus_context_get_loop (context), FALSE) || + (client_loop == NULL || _dbus_loop_iterate (client_loop, FALSE))) + ; +} + +BusContext* +bus_context_new_test (const DBusString *test_data_dir, + const char *filename) +{ + DBusError error; + DBusString config_file; + DBusString relative; + BusContext *context; + + if (!_dbus_string_init (&config_file)) + { + _dbus_warn ("No memory\n"); + return NULL; + } + + if (!_dbus_string_copy (test_data_dir, 0, + &config_file, 0)) + { + _dbus_warn ("No memory\n"); + _dbus_string_free (&config_file); + return NULL; + } + + _dbus_string_init_const (&relative, filename); + + if (!_dbus_concat_dir_and_file (&config_file, &relative)) + { + _dbus_warn ("No memory\n"); + _dbus_string_free (&config_file); + return NULL; + } + + dbus_error_init (&error); + context = bus_context_new (&config_file, FALSE, NULL, NULL, &error); + if (context == NULL) + { + _DBUS_ASSERT_ERROR_IS_SET (&error); + + _dbus_warn ("Failed to create debug bus context from configuration file %s: %s\n", + filename, error.message); + + dbus_error_free (&error); + + _dbus_string_free (&config_file); + + return NULL; + } + + _dbus_string_free (&config_file); + + return context; +} + +#endif diff --git a/bus/test.h b/bus/test.h new file mode 100644 index 00000000..72d68b09 --- /dev/null +++ b/bus/test.h @@ -0,0 +1,58 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* test.h unit test routines + * + * Copyright (C) 2003 Red Hat, Inc. + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef BUS_TEST_H +#define BUS_TEST_H + +#include <config.h> + +#ifdef DBUS_BUILD_TESTS + +#include <dbus/dbus.h> +#include <dbus/dbus-string.h> +#include "connection.h" + +dbus_bool_t bus_dispatch_test (const DBusString *test_data_dir); +dbus_bool_t bus_dispatch_sha1_test (const DBusString *test_data_dir); +dbus_bool_t bus_policy_test (const DBusString *test_data_dir); +dbus_bool_t bus_config_parser_test (const DBusString *test_data_dir); +dbus_bool_t bus_config_parser_trivial_test (const DBusString *test_data_dir); +dbus_bool_t bus_signals_test (const DBusString *test_data_dir); +dbus_bool_t bus_expire_list_test (const DBusString *test_data_dir); +dbus_bool_t bus_activation_service_reload_test (const DBusString *test_data_dir); +dbus_bool_t bus_setup_debug_client (DBusConnection *connection); +void bus_test_clients_foreach (BusConnectionForeachFunction function, + void *data); +dbus_bool_t bus_test_client_listed (DBusConnection *connection); +void bus_test_run_bus_loop (BusContext *context, + dbus_bool_t block); +void bus_test_run_clients_loop (dbus_bool_t block); +void bus_test_run_everything (BusContext *context); +BusContext* bus_context_new_test (const DBusString *test_data_dir, + const char *filename); + + + +#endif + +#endif /* BUS_TEST_H */ diff --git a/bus/utils.c b/bus/utils.c new file mode 100644 index 00000000..7d248727 --- /dev/null +++ b/bus/utils.c @@ -0,0 +1,48 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* utils.c General utility functions + * + * Copyright (C) 2003 CodeFactory AB + * Copyright (C) 2003 Red Hat, Inc. + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> +#include "utils.h" +#include <dbus/dbus-sysdeps.h> +#include <dbus/dbus-mainloop.h> + +const char bus_no_memory_message[] = "Memory allocation failure in message bus"; + +void +bus_connection_dispatch_all_messages (DBusConnection *connection) +{ + while (bus_connection_dispatch_one_message (connection)) + ; +} + +dbus_bool_t +bus_connection_dispatch_one_message (DBusConnection *connection) +{ + DBusDispatchStatus status; + + while ((status = dbus_connection_dispatch (connection)) == DBUS_DISPATCH_NEED_MEMORY) + _dbus_wait_for_memory (); + + return status == DBUS_DISPATCH_DATA_REMAINS; +} diff --git a/bus/utils.h b/bus/utils.h new file mode 100644 index 00000000..f533895f --- /dev/null +++ b/bus/utils.h @@ -0,0 +1,36 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* utils.h General utility functions + * + * Copyright (C) 2003 CodeFactory AB + * Copyright (C) 2003 Red Hat, Inc. + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef BUS_UTILS_H +#define BUS_UTILS_H + +#include <dbus/dbus.h> + +extern const char bus_no_memory_message[]; +#define BUS_SET_OOM(error) dbus_set_error_const ((error), DBUS_ERROR_NO_MEMORY, bus_no_memory_message) + +void bus_connection_dispatch_all_messages (DBusConnection *connection); +dbus_bool_t bus_connection_dispatch_one_message (DBusConnection *connection); + +#endif /* BUS_UTILS_H */ |