summary refs log tree commit diff
path: root/callouts
diff options
context:
space:
mode:
Diffstat (limited to 'callouts')
-rw-r--r--callouts/Makefile.am20
-rw-r--r--callouts/Makefile.in53
-rw-r--r--callouts/nm-avahi-autoipd-action.c126
-rw-r--r--callouts/nm-avahi-autoipd.conf13
-rw-r--r--callouts/nm-dispatcher-api.h6
-rw-r--r--callouts/nm-dispatcher-utils.c43
-rw-r--r--callouts/nm-dispatcher-utils.h5
-rw-r--r--callouts/nm-dispatcher.c498
-rw-r--r--callouts/tests/Makefile.am11
-rw-r--r--callouts/tests/Makefile.in31
-rw-r--r--callouts/tests/dispatcher-down1
-rw-r--r--callouts/tests/dispatcher-external33
-rw-r--r--callouts/tests/dispatcher-up1
-rw-r--r--callouts/tests/dispatcher-vpn-down1
-rw-r--r--callouts/tests/dispatcher-vpn-up1
-rw-r--r--callouts/tests/test-dispatcher-envp.c39
16 files changed, 518 insertions, 364 deletions
diff --git a/callouts/Makefile.am b/callouts/Makefile.am
index 2f9fc71d..6440a4de 100644
--- a/callouts/Makefile.am
+++ b/callouts/Makefile.am
@@ -1,12 +1,11 @@
 SUBDIRS = . tests
 
 AM_CPPFLAGS = \
-	-I${top_srcdir}/include \
-	-I${top_builddir}/include \
+	-I${top_srcdir}/shared \
+	-I${top_builddir}/shared \
 	-I${top_srcdir}/libnm-core \
 	-I${top_builddir}/libnm-core \
 	$(GLIB_CFLAGS) \
-	$(DBUS_CFLAGS) \
 	-DNETWORKMANAGER_COMPILATION \
 	-DNM_VERSION_MAX_ALLOWED=NM_VERSION_NEXT_STABLE \
 	-DNMCONFDIR=\"$(nmconfdir)\" \
@@ -23,20 +22,10 @@ noinst_LTLIBRARIES = \
 
 dbusservicedir = $(DBUS_SYS_DIR)
 dbusservice_DATA = \
-	nm-dispatcher.conf \
-	nm-avahi-autoipd.conf
+	nm-dispatcher.conf
 
 libexec_PROGRAMS = \
-	nm-dispatcher \
-	nm-avahi-autoipd.action
-
-
-nm_avahi_autoipd_action_SOURCES = \
-	nm-avahi-autoipd-action.c
-
-nm_avahi_autoipd_action_LDADD = \
-	$(DBUS_LIBS) \
-	$(GLIB_LIBS)
+	nm-dispatcher
 
 
 nm_dispatcher_SOURCES = \
@@ -106,6 +95,7 @@ install-data-hook:
 	   $(mkinstalldirs) -m 0755 $(DESTDIR)$(dispatcherdir)
 	   $(mkinstalldirs) -m 0755 $(DESTDIR)$(dispatcherdir)/pre-down.d
 	   $(mkinstalldirs) -m 0755 $(DESTDIR)$(dispatcherdir)/pre-up.d
+	   $(mkinstalldirs) -m 0755 $(DESTDIR)$(dispatcherdir)/no-wait.d
 
 CLEANFILES = $(nodist_libnmdbus_dispatcher_la_SOURCES) $(dbusactivation_DATA)
 
diff --git a/callouts/Makefile.in b/callouts/Makefile.in
index 5a4458b4..2fa3d996 100644
--- a/callouts/Makefile.in
+++ b/callouts/Makefile.in
@@ -90,8 +90,7 @@ PRE_UNINSTALL = :
 POST_UNINSTALL = :
 build_triplet = @build@
 host_triplet = @host@
-libexec_PROGRAMS = nm-dispatcher$(EXEEXT) \
-	nm-avahi-autoipd.action$(EXEEXT)
+libexec_PROGRAMS = nm-dispatcher$(EXEEXT)
 subdir = callouts
 ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
 am__aclocal_m4_deps = $(top_srcdir)/m4/ax_lib_readline.m4 \
@@ -134,12 +133,6 @@ libtest_dispatcher_envp_la_OBJECTS =  \
 am__installdirs = "$(DESTDIR)$(libexecdir)" \
 	"$(DESTDIR)$(dbusactivationdir)" "$(DESTDIR)$(dbusservicedir)"
 PROGRAMS = $(libexec_PROGRAMS)
-am_nm_avahi_autoipd_action_OBJECTS =  \
-	nm-avahi-autoipd-action.$(OBJEXT)
-nm_avahi_autoipd_action_OBJECTS =  \
-	$(am_nm_avahi_autoipd_action_OBJECTS)
-nm_avahi_autoipd_action_DEPENDENCIES = $(am__DEPENDENCIES_1) \
-	$(am__DEPENDENCIES_1)
 am_nm_dispatcher_OBJECTS = nm-dispatcher.$(OBJEXT) \
 	nm-dispatcher-utils.$(OBJEXT)
 nm_dispatcher_OBJECTS = $(am_nm_dispatcher_OBJECTS)
@@ -180,10 +173,9 @@ am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
 am__v_CCLD_0 = @echo "  CCLD    " $@;
 am__v_CCLD_1 = 
 SOURCES = $(nodist_libnmdbus_dispatcher_la_SOURCES) \
-	$(libtest_dispatcher_envp_la_SOURCES) \
-	$(nm_avahi_autoipd_action_SOURCES) $(nm_dispatcher_SOURCES)
+	$(libtest_dispatcher_envp_la_SOURCES) $(nm_dispatcher_SOURCES)
 DIST_SOURCES = $(libtest_dispatcher_envp_la_SOURCES) \
-	$(nm_avahi_autoipd_action_SOURCES) $(nm_dispatcher_SOURCES)
+	$(nm_dispatcher_SOURCES)
 RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
 	ctags-recursive dvi-recursive html-recursive info-recursive \
 	install-data-recursive install-dvi-recursive \
@@ -296,7 +288,6 @@ BLUEZ5_LIBS = @BLUEZ5_LIBS@
 CC = @CC@
 CCDEPMODE = @CCDEPMODE@
 CFLAGS = @CFLAGS@
-CKDB_PATH = @CKDB_PATH@
 CODE_COVERAGE_CFLAGS = @CODE_COVERAGE_CFLAGS@
 CODE_COVERAGE_ENABLED = @CODE_COVERAGE_ENABLED@
 CODE_COVERAGE_LDFLAGS = @CODE_COVERAGE_LDFLAGS@
@@ -308,8 +299,6 @@ CXXDEPMODE = @CXXDEPMODE@
 CXXFLAGS = @CXXFLAGS@
 CYGPATH_W = @CYGPATH_W@
 DBUS_CFLAGS = @DBUS_CFLAGS@
-DBUS_GLIB_100_CFLAGS = @DBUS_GLIB_100_CFLAGS@
-DBUS_GLIB_100_LIBS = @DBUS_GLIB_100_LIBS@
 DBUS_LIBS = @DBUS_LIBS@
 DBUS_SYS_DIR = @DBUS_SYS_DIR@
 DEFS = @DEFS@
@@ -319,6 +308,7 @@ DHCPCD_PATH = @DHCPCD_PATH@
 DISTRO_NETWORK_SERVICE = @DISTRO_NETWORK_SERVICE@
 DLLTOOL = @DLLTOOL@
 DNSMASQ_PATH = @DNSMASQ_PATH@
+DNSSEC_TRIGGER_SCRIPT = @DNSSEC_TRIGGER_SCRIPT@
 DSYMUTIL = @DSYMUTIL@
 DUMPBIN = @DUMPBIN@
 ECHO_C = @ECHO_C@
@@ -373,16 +363,13 @@ INTROSPECTION_MAKEFILE = @INTROSPECTION_MAKEFILE@
 INTROSPECTION_SCANNER = @INTROSPECTION_SCANNER@
 INTROSPECTION_TYPELIBDIR = @INTROSPECTION_TYPELIBDIR@
 IPTABLES_PATH = @IPTABLES_PATH@
-IWMX_SDK_CFLAGS = @IWMX_SDK_CFLAGS@
-IWMX_SDK_LIBS = @IWMX_SDK_LIBS@
 KERNEL_FIRMWARE_DIR = @KERNEL_FIRMWARE_DIR@
 LCOV = @LCOV@
 LD = @LD@
 LDFLAGS = @LDFLAGS@
+LIBAUDIT_CFLAGS = @LIBAUDIT_CFLAGS@
+LIBAUDIT_LIBS = @LIBAUDIT_LIBS@
 LIBDL = @LIBDL@
-LIBGCRYPT_CFLAGS = @LIBGCRYPT_CFLAGS@
-LIBGCRYPT_CONFIG = @LIBGCRYPT_CONFIG@
-LIBGCRYPT_LIBS = @LIBGCRYPT_LIBS@
 LIBICONV = @LIBICONV@
 LIBINTL = @LIBINTL@
 LIBM = @LIBM@
@@ -419,6 +406,8 @@ NEWT_LIBS = @NEWT_LIBS@
 NM = @NM@
 NMEDIT = @NMEDIT@
 NM_CONFIG_DEFAULT_AUTH_POLKIT_TEXT = @NM_CONFIG_DEFAULT_AUTH_POLKIT_TEXT@
+NM_CONFIG_DEFAULT_LOGGING_AUDIT_TEXT = @NM_CONFIG_DEFAULT_LOGGING_AUDIT_TEXT@
+NM_CONFIG_LOGGING_BACKEND_DEFAULT_TEXT = @NM_CONFIG_LOGGING_BACKEND_DEFAULT_TEXT@
 NM_MAJOR_VERSION = @NM_MAJOR_VERSION@
 NM_MICRO_VERSION = @NM_MICRO_VERSION@
 NM_MINOR_VERSION = @NM_MINOR_VERSION@
@@ -447,7 +436,6 @@ POLKIT_LIBS = @POLKIT_LIBS@
 POSUB = @POSUB@
 PPPD_PATH = @PPPD_PATH@
 PPPD_PLUGIN_DIR = @PPPD_PLUGIN_DIR@
-PPPOE_PATH = @PPPOE_PATH@
 QT_CFLAGS = @QT_CFLAGS@
 QT_LIBS = @QT_LIBS@
 RANLIB = @RANLIB@
@@ -462,6 +450,8 @@ SYSTEMD_200_CFLAGS = @SYSTEMD_200_CFLAGS@
 SYSTEMD_200_LIBS = @SYSTEMD_200_LIBS@
 SYSTEMD_INHIBIT_CFLAGS = @SYSTEMD_INHIBIT_CFLAGS@
 SYSTEMD_INHIBIT_LIBS = @SYSTEMD_INHIBIT_LIBS@
+SYSTEMD_JOURNAL_CFLAGS = @SYSTEMD_JOURNAL_CFLAGS@
+SYSTEMD_JOURNAL_LIBS = @SYSTEMD_JOURNAL_LIBS@
 SYSTEMD_LOGIN_CFLAGS = @SYSTEMD_LOGIN_CFLAGS@
 SYSTEMD_LOGIN_LIBS = @SYSTEMD_LOGIN_LIBS@
 SYSTEM_CA_PATH = @SYSTEM_CA_PATH@
@@ -522,6 +512,7 @@ mkdir_p = @mkdir_p@
 nmbinary = @nmbinary@
 nmconfdir = @nmconfdir@
 nmdatadir = @nmdatadir@
+nmlibdir = @nmlibdir@
 nmrundir = @nmrundir@
 nmstatedir = @nmstatedir@
 oldincludedir = @oldincludedir@
@@ -529,6 +520,7 @@ pdfdir = @pdfdir@
 prefix = @prefix@
 program_transform_name = @program_transform_name@
 psdir = @psdir@
+runstatedir = @runstatedir@
 sbindir = @sbindir@
 sharedstatedir = @sharedstatedir@
 srcdir = @srcdir@
@@ -546,12 +538,11 @@ with_resolvconf = @with_resolvconf@
 with_valgrind = @with_valgrind@
 SUBDIRS = . tests
 AM_CPPFLAGS = \
-	-I${top_srcdir}/include \
-	-I${top_builddir}/include \
+	-I${top_srcdir}/shared \
+	-I${top_builddir}/shared \
 	-I${top_srcdir}/libnm-core \
 	-I${top_builddir}/libnm-core \
 	$(GLIB_CFLAGS) \
-	$(DBUS_CFLAGS) \
 	-DNETWORKMANAGER_COMPILATION \
 	-DNM_VERSION_MAX_ALLOWED=NM_VERSION_NEXT_STABLE \
 	-DNMCONFDIR=\"$(nmconfdir)\" \
@@ -568,15 +559,7 @@ noinst_LTLIBRARIES = libtest-dispatcher-envp.la \
 	libnmdbus-dispatcher.la
 dbusservicedir = $(DBUS_SYS_DIR)
 dbusservice_DATA = \
-	nm-dispatcher.conf \
-	nm-avahi-autoipd.conf
-
-nm_avahi_autoipd_action_SOURCES = \
-	nm-avahi-autoipd-action.c
-
-nm_avahi_autoipd_action_LDADD = \
-	$(DBUS_LIBS) \
-	$(GLIB_LIBS)
+	nm-dispatcher.conf
 
 nm_dispatcher_SOURCES = \
 	nm-dispatcher.c \
@@ -727,10 +710,6 @@ clean-libexecPROGRAMS:
 	echo " rm -f" $$list; \
 	rm -f $$list
 
-nm-avahi-autoipd.action$(EXEEXT): $(nm_avahi_autoipd_action_OBJECTS) $(nm_avahi_autoipd_action_DEPENDENCIES) $(EXTRA_nm_avahi_autoipd_action_DEPENDENCIES) 
-	@rm -f nm-avahi-autoipd.action$(EXEEXT)
-	$(AM_V_CCLD)$(LINK) $(nm_avahi_autoipd_action_OBJECTS) $(nm_avahi_autoipd_action_LDADD) $(LIBS)
-
 nm-dispatcher$(EXEEXT): $(nm_dispatcher_OBJECTS) $(nm_dispatcher_DEPENDENCIES) $(EXTRA_nm_dispatcher_DEPENDENCIES) 
 	@rm -f nm-dispatcher$(EXEEXT)
 	$(AM_V_CCLD)$(LINK) $(nm_dispatcher_OBJECTS) $(nm_dispatcher_LDADD) $(LIBS)
@@ -743,7 +722,6 @@ distclean-compile:
 
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libnmdbus_dispatcher_la-nmdbus-dispatcher.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtest_dispatcher_envp_la-nm-dispatcher-utils.Plo@am__quote@
-@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-avahi-autoipd-action.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-dispatcher-utils.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-dispatcher.Po@am__quote@
 
@@ -1140,6 +1118,7 @@ install-data-hook:
 	   $(mkinstalldirs) -m 0755 $(DESTDIR)$(dispatcherdir)
 	   $(mkinstalldirs) -m 0755 $(DESTDIR)$(dispatcherdir)/pre-down.d
 	   $(mkinstalldirs) -m 0755 $(DESTDIR)$(dispatcherdir)/pre-up.d
+	   $(mkinstalldirs) -m 0755 $(DESTDIR)$(dispatcherdir)/no-wait.d
 
 # 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.
diff --git a/callouts/nm-avahi-autoipd-action.c b/callouts/nm-avahi-autoipd-action.c
deleted file mode 100644
index b28dae5d..00000000
--- a/callouts/nm-avahi-autoipd-action.c
+++ /dev/null
@@ -1,126 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/* NetworkManager -- Network link manager
- *
- * 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.
- *
- * Copyright 2008, 2014 Red Hat, Inc.
- */
-
-#include "config.h"
-
-#include <stdlib.h>
-#include <string.h>
-
-#include <gio/gio.h>
-
-#define NM_AVAHI_AUTOIPD_DBUS_SERVICE   "org.freedesktop.nm_avahi_autoipd"
-#define NM_AVAHI_AUTOIPD_DBUS_INTERFACE "org.freedesktop.nm_avahi_autoipd"
-
-static void
-on_name_acquired (GDBusConnection *connection,
-                  const gchar     *name,
-                  gpointer         loop)
-{
-	g_main_loop_quit (loop);
-}
-
-static void
-on_name_lost (GDBusConnection *connection,
-              const gchar     *name,
-              gpointer         user_data)
-{
-	g_printerr ("Error: Could not acquire the NM autoipd service.");
-	exit (1);
-}
-
-int
-main (int argc, char *argv[])
-{
-	GDBusConnection *connection;
-	char *event, *iface, *address;
-	GMainLoop *loop;
-	GError *error = NULL;
-
-#if !GLIB_CHECK_VERSION (2, 35, 0)
-	g_type_init ();
-#endif
-
-	if (argc != 4) {
-		g_printerr ("Error: expected 3 arguments (event, interface, address).\n");
-		exit (1);
-	}
-
-	event = argv[1];
-	iface = argv[2];
-	address = argv[3] ? argv[3] : "";
-
-	if (!event || !iface || !strlen (event) || !strlen (iface)) {
-		g_printerr ("Error: unexpected arguments received from avahi-autoipd.\n");
-		exit (1);
-	}
-
-	/* Get a connection to the system bus */
-	connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
-	if (error) {
-		char *remote_error = g_dbus_error_get_remote_error (error);
-
-		g_dbus_error_strip_remote_error (error);
-		g_printerr ("Error: could not get the system bus.  Make sure "
-		            "the message bus daemon is running!  Message: (%s) %s\n",
-		            remote_error, error->message);
-		g_free (remote_error);
-		g_error_free (error);
-		return 1;
-	}
-
-	/* Acquire the bus name */
-	loop = g_main_loop_new (NULL, FALSE);
-	g_bus_own_name_on_connection (connection,
-	                              NM_AVAHI_AUTOIPD_DBUS_SERVICE,
-	                              0,
-	                              on_name_acquired,
-	                              on_name_lost,
-	                              loop, NULL);
-	g_main_loop_run (loop);
-	g_main_loop_unref (loop);
-
-	/* Send the signal */
-	if (!g_dbus_connection_emit_signal (connection,
-	                                    NULL,
-	                                    "/",
-	                                    NM_AVAHI_AUTOIPD_DBUS_INTERFACE,
-	                                    "Event",
-	                                    g_variant_new ("(sss)",
-	                                                   event,
-	                                                   iface,
-	                                                   address),
-	                                    &error)) {
-		g_dbus_error_strip_remote_error (error);
-		g_printerr ("Error: Could not send autoipd Event signal: %s\n", error->message);
-		g_error_free (error);
-		return 1;
-	}
-
-	if (!g_dbus_connection_flush_sync (connection, NULL, &error)) {
-		g_dbus_error_strip_remote_error (error);
-		g_printerr ("Error: Could not flush D-Bus connection: %s\n", error->message);
-		g_error_free (error);
-		return 1;
-	}
-
-	g_object_unref (connection);
-	return 0;
-}
-
diff --git a/callouts/nm-avahi-autoipd.conf b/callouts/nm-avahi-autoipd.conf
deleted file mode 100644
index 485ab429..00000000
--- a/callouts/nm-avahi-autoipd.conf
+++ /dev/null
@@ -1,13 +0,0 @@
-<!DOCTYPE busconfig PUBLIC
- "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
- "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
-<busconfig>
-        <policy user="root">
-                <allow own="org.freedesktop.nm_avahi_autoipd"/>
-        </policy>
-        <policy context="default">
-                <deny own="org.freedesktop.nm_avahi_autoipd"/>
-                <deny send_destination="org.freedesktop.nm_avahi_autoipd"/>
-        </policy>
-</busconfig>
-
diff --git a/callouts/nm-dispatcher-api.h b/callouts/nm-dispatcher-api.h
index 9b2c3b32..d702ba68 100644
--- a/callouts/nm-dispatcher-api.h
+++ b/callouts/nm-dispatcher-api.h
@@ -21,10 +21,7 @@
 #define NMD_SCRIPT_DIR_DEFAULT  NMCONFDIR "/dispatcher.d"
 #define NMD_SCRIPT_DIR_PRE_UP   NMD_SCRIPT_DIR_DEFAULT "/pre-up.d"
 #define NMD_SCRIPT_DIR_PRE_DOWN NMD_SCRIPT_DIR_DEFAULT "/pre-down.d"
-
-/* dbus-glib types for dispatcher call return value (used by src/nm-dispatcher.c) */
-#define DISPATCHER_TYPE_RESULT       (dbus_g_type_get_struct ("GValueArray", G_TYPE_STRING, G_TYPE_UINT, G_TYPE_STRING, G_TYPE_INVALID))
-#define DISPATCHER_TYPE_RESULT_ARRAY (dbus_g_type_get_collection ("GPtrArray", DISPATCHER_TYPE_RESULT))
+#define NMD_SCRIPT_DIR_NO_WAIT  NMD_SCRIPT_DIR_DEFAULT "/no-wait.d"
 
 #define NM_DISPATCHER_DBUS_SERVICE   "org.freedesktop.nm_dispatcher"
 #define NM_DISPATCHER_DBUS_INTERFACE "org.freedesktop.nm_dispatcher"
@@ -32,6 +29,7 @@
 
 #define NMD_CONNECTION_PROPS_PATH         "path"
 #define NMD_CONNECTION_PROPS_FILENAME     "filename"
+#define NMD_CONNECTION_PROPS_EXTERNAL     "external"
 
 #define NMD_DEVICE_PROPS_INTERFACE        "interface"
 #define NMD_DEVICE_PROPS_IP_INTERFACE     "ip-interface"
diff --git a/callouts/nm-dispatcher-utils.c b/callouts/nm-dispatcher-utils.c
index e7a6f6a2..6079d019 100644
--- a/callouts/nm-dispatcher-utils.c
+++ b/callouts/nm-dispatcher-utils.c
@@ -22,7 +22,6 @@
 
 #include <string.h>
 
-#include <glib-object.h>
 
 #include <nm-dbus-interface.h>
 #include <nm-connection.h>
@@ -30,6 +29,7 @@
 #include <nm-setting-ip6-config.h>
 #include <nm-setting-connection.h>
 
+#include "nm-default.h"
 #include "nm-dispatcher-api.h"
 #include "nm-utils.h"
 
@@ -331,30 +331,46 @@ nm_dispatcher_utils_construct_envp (const char *action,
                                     const char *vpn_ip_iface,
                                     GVariant *vpn_ip4_props,
                                     GVariant *vpn_ip6_props,
-                                    char **out_iface)
+                                    char **out_iface,
+                                    const char **out_error_message)
 {
 	const char *iface = NULL, *ip_iface = NULL;
-	const char *uuid = NULL, *id = NULL, *path;
+	const char *uuid = NULL, *id = NULL, *path = NULL;
 	const char *filename = NULL;
+	gboolean external;
 	NMDeviceState dev_state = NM_DEVICE_STATE_UNKNOWN;
 	GVariant *value;
 	char **envp = NULL, *path_item;
 	GSList *items = NULL, *iter;
 	guint i;
 	GVariant *con_setting;
+	const char *error_message_backup;
+
+	if (!out_error_message)
+		out_error_message = &error_message_backup;
 
 	g_return_val_if_fail (action != NULL, NULL);
 	g_return_val_if_fail (out_iface != NULL, NULL);
 	g_return_val_if_fail (*out_iface == NULL, NULL);
 
 	/* Hostname changes don't require a device nor contain a connection */
-	if (!strcmp (action, "hostname"))
+	if (!strcmp (action, NMD_ACTION_HOSTNAME))
 		goto done;
 
-	/* config filename */
+	/* Connection properties */
+	if (!g_variant_lookup (connection_props, NMD_CONNECTION_PROPS_PATH, "&o", &path)) {
+		*out_error_message = "Missing or invalid required value " NMD_CONNECTION_PROPS_PATH "!";
+		return NULL;
+	}
+	items = g_slist_prepend (items, g_strdup_printf ("CONNECTION_DBUS_PATH=%s", path));
+
+	if (g_variant_lookup (connection_props, NMD_CONNECTION_PROPS_EXTERNAL, "b", &external) && external)
+		items = g_slist_prepend (items, g_strdup ("CONNECTION_EXTERNAL=1"));
+
 	if (g_variant_lookup (connection_props, NMD_CONNECTION_PROPS_FILENAME, "&s", &filename))
 		items = g_slist_prepend (items, g_strdup_printf ("CONNECTION_FILENAME=%s", filename));
 
+
 	/* Canonicalize the VPN interface name; "" is used when passing it through
 	 * D-Bus so make sure that's fixed up here.
 	 */
@@ -363,7 +379,7 @@ nm_dispatcher_utils_construct_envp (const char *action,
 
 	/* interface name */
 	if (!g_variant_lookup (device_props, NMD_DEVICE_PROPS_INTERFACE, "&s", &iface)) {
-		g_warning ("Missing or invalid required value " NMD_DEVICE_PROPS_INTERFACE "!");
+		*out_error_message = "Missing or invalid required value " NMD_DEVICE_PROPS_INTERFACE "!";
 		return NULL;
 	}
 	if (!*iface)
@@ -373,7 +389,7 @@ nm_dispatcher_utils_construct_envp (const char *action,
 	value = g_variant_lookup_value (device_props, NMD_DEVICE_PROPS_IP_INTERFACE, NULL);
 	if (value) {
 		if (!g_variant_is_of_type (value, G_VARIANT_TYPE_STRING)) {
-			g_warning ("Invalid value " NMD_DEVICE_PROPS_IP_INTERFACE "!");
+			*out_error_message = "Invalid value " NMD_DEVICE_PROPS_IP_INTERFACE "!";
 			return NULL;
 		}
 		g_variant_unref (value);
@@ -382,14 +398,14 @@ nm_dispatcher_utils_construct_envp (const char *action,
 
 	/* Device type */
 	if (!g_variant_lookup (device_props, NMD_DEVICE_PROPS_TYPE, "u", NULL)) {
-		g_warning ("Missing or invalid required value " NMD_DEVICE_PROPS_TYPE "!");
+		*out_error_message = "Missing or invalid required value " NMD_DEVICE_PROPS_TYPE "!";
 		return NULL;
 	}
 
 	/* Device state */
 	value = g_variant_lookup_value (device_props, NMD_DEVICE_PROPS_STATE, G_VARIANT_TYPE_UINT32);
 	if (!value) {
-		g_warning ("Missing or invalid required value " NMD_DEVICE_PROPS_STATE "!");
+		*out_error_message = "Missing or invalid required value " NMD_DEVICE_PROPS_STATE "!";
 		return NULL;
 	}
 	dev_state = g_variant_get_uint32 (value);
@@ -397,25 +413,25 @@ nm_dispatcher_utils_construct_envp (const char *action,
 
 	/* device itself */
 	if (!g_variant_lookup (device_props, NMD_DEVICE_PROPS_PATH, "o", NULL)) {
-		g_warning ("Missing or invalid required value " NMD_DEVICE_PROPS_PATH "!");
+		*out_error_message = "Missing or invalid required value " NMD_DEVICE_PROPS_PATH "!";
 		return NULL;
 	}
 
 	/* UUID and ID */
 	con_setting = g_variant_lookup_value (connection_dict, NM_SETTING_CONNECTION_SETTING_NAME, NM_VARIANT_TYPE_SETTING);
 	if (!con_setting) {
-		g_warning ("Failed to read connection setting");
+		*out_error_message = "Failed to read connection setting";
 		return NULL;
 	}
 
 	if (!g_variant_lookup (con_setting, NM_SETTING_CONNECTION_UUID, "&s", &uuid)) {
-		g_warning ("Connection hash did not contain the UUID");
+		*out_error_message = "Connection hash did not contain the UUID";
 		g_variant_unref (con_setting);
 		return NULL;
 	}
 
 	if (!g_variant_lookup (con_setting, NM_SETTING_CONNECTION_ID, "&s", &id)) {
-		g_warning ("Connection hash did not contain the ID");
+		*out_error_message = "Connection hash did not contain the ID";
 		g_variant_unref (con_setting);
 		return NULL;
 	}
@@ -462,6 +478,7 @@ nm_dispatcher_utils_construct_envp (const char *action,
 		envp[i] = (char *) iter->data;
 	g_slist_free (items);
 
+	*out_error_message = NULL;
 	return envp;
 }
 
diff --git a/callouts/nm-dispatcher-utils.h b/callouts/nm-dispatcher-utils.h
index 665a0d4e..40a0d41f 100644
--- a/callouts/nm-dispatcher-utils.h
+++ b/callouts/nm-dispatcher-utils.h
@@ -21,7 +21,7 @@
 #ifndef __NETWORKMANAGER_DISPATCHER_UTILS_H__
 #define __NETWORKMANAGER_DISPATCHER_UTILS_H__
 
-#include <glib.h>
+#include "nm-default.h"
 
 char **
 nm_dispatcher_utils_construct_envp (const char *action,
@@ -35,7 +35,8 @@ nm_dispatcher_utils_construct_envp (const char *action,
                                     const char *vpn_ip_iface,
                                     GVariant *vpn_ip4_props,
                                     GVariant *vpn_ip6_props,
-                                    char **out_iface);
+                                    char **out_iface,
+                                    const char **out_error_message);
 
 #endif  /* __NETWORKMANAGER_DISPATCHER_UTILS_H__ */
 
diff --git a/callouts/nm-dispatcher.c b/callouts/nm-dispatcher.c
index 09eb0d3a..a635e216 100644
--- a/callouts/nm-dispatcher.c
+++ b/callouts/nm-dispatcher.c
@@ -32,13 +32,13 @@
 #include <errno.h>
 #include <arpa/inet.h>
 
-#include <glib.h>
 #include <glib-unix.h>
 
-
+#include "nm-default.h"
 #include "nm-dispatcher-api.h"
 #include "nm-dispatcher-utils.h"
-#include "nm-glib-compat.h"
+#include "nm-macros-internal.h"
+#include "gsystem-local-alloc.h"
 
 #include "nmdbus-dispatcher.h"
 
@@ -46,6 +46,7 @@ static GMainLoop *loop = NULL;
 static gboolean debug = FALSE;
 static gboolean persist = FALSE;
 static guint quit_id;
+static guint request_id_counter = 0;
 
 typedef struct Request Request;
 
@@ -56,7 +57,8 @@ typedef struct {
 	NMDBusDispatcher *dbus_dispatcher;
 
 	Request *current_request;
-	GQueue *pending_requests;
+	GQueue *requests_waiting;
+	gint num_requests_pending;
 } Handler;
 
 typedef struct {
@@ -91,7 +93,7 @@ handle_action (NMDBusDispatcher *dbus_dispatcher,
 static void
 handler_init (Handler *h)
 {
-	h->pending_requests = g_queue_new ();
+	h->requests_waiting = g_queue_new ();
 	h->dbus_dispatcher = nmdbus_dispatcher_skeleton_new ();
 	g_signal_connect (h->dbus_dispatcher, "handle-action",
 	                  G_CALLBACK (handle_action), h);
@@ -102,7 +104,7 @@ handler_class_init (HandlerClass *h_class)
 {
 }
 
-static void dispatch_one_script (Request *request);
+static gboolean dispatch_one_script (Request *request);
 
 typedef struct {
 	Request *request;
@@ -111,11 +113,17 @@ typedef struct {
 	GPid pid;
 	DispatchResult result;
 	char *error;
+	gboolean wait;
+	gboolean dispatched;
+	guint watch_id;
+	guint timeout_id;
 } ScriptInfo;
 
 struct Request {
 	Handler *handler;
 
+	guint request_id;
+
 	GDBusMethodInvocation *context;
 	char *action;
 	char *iface;
@@ -124,11 +132,61 @@ struct Request {
 
 	GPtrArray *scripts;  /* list of ScriptInfo */
 	guint idx;
-
-	guint script_watch_id;
-	guint script_timeout_id;
+	gint num_scripts_done;
+	gint num_scripts_nowait;
 };
 
+/*****************************************************************************/
+
+#define __LOG_print(print_cmd, _request, _script, ...) \
+	G_STMT_START { \
+		nm_assert ((_request) && (!(_script) || (_script)->request == (_request))); \
+		print_cmd ("#%u '%s'%s%s%s%s%s%s: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
+		           (_request)->request_id, \
+		           (_request)->action, \
+		           (_request)->iface ? " [" : "", \
+		           (_request)->iface ? (_request)->iface : "", \
+		           (_request)->iface ? "]" : "", \
+		           (_script) ? ", \"" : "", \
+		           (_script) ? (_script)->script : "", \
+		           (_script) ? "\"" : "" \
+		           _NM_UTILS_MACRO_REST (__VA_ARGS__)); \
+	} G_STMT_END
+
+#define _LOG(_request, _script, log_always, print_cmd, ...) \
+	G_STMT_START { \
+		const Request *__request = (_request); \
+		const ScriptInfo *__script = (_script); \
+		\
+		if (!__request) \
+			__request = __script->request; \
+		nm_assert (__request && (!__script || __script->request == __request)); \
+		if ((log_always) || __request->debug) { \
+			if (FALSE) { \
+				/* g_message() alone does not warn about invalid format. Add a dummy printf() statement to
+				 * get a compiler warning about wrong format. */ \
+				__LOG_print (printf, __request, __script, __VA_ARGS__); \
+			} \
+			__LOG_print (print_cmd, __request, __script, __VA_ARGS__); \
+		} \
+	} G_STMT_END
+
+static gboolean
+_LOG_R_D_enabled (const Request *request)
+{
+	return request->debug;
+}
+
+#define _LOG_R_D(_request, ...) _LOG(_request, NULL, FALSE, g_message, __VA_ARGS__)
+#define _LOG_R_I(_request, ...) _LOG(_request, NULL, TRUE,  g_message, __VA_ARGS__)
+#define _LOG_R_W(_request, ...) _LOG(_request, NULL, TRUE,  g_warning, __VA_ARGS__)
+
+#define _LOG_S_D(_script, ...)  _LOG(NULL, _script,  FALSE, g_message, __VA_ARGS__)
+#define _LOG_S_I(_script, ...)  _LOG(NULL, _script,  TRUE,  g_message, __VA_ARGS__)
+#define _LOG_S_W(_script, ...)  _LOG(NULL, _script,  TRUE,  g_warning, __VA_ARGS__)
+
+/*****************************************************************************/
+
 static void
 script_info_free (gpointer ptr)
 {
@@ -136,17 +194,22 @@ script_info_free (gpointer ptr)
 
 	g_free (info->script);
 	g_free (info->error);
-	g_free (info);
+	g_slice_free (ScriptInfo, info);
 }
 
 static void
 request_free (Request *request)
 {
+	g_assert_cmpuint (request->num_scripts_done, ==, request->scripts->len);
+	g_assert_cmpuint (request->num_scripts_nowait, ==, 0);
+
 	g_free (request->action);
 	g_free (request->iface);
 	g_strfreev (request->envp);
 	if (request->scripts)
 		g_ptr_array_free (request->scripts, TRUE);
+
+	g_slice_free (Request, request);
 }
 
 static gboolean
@@ -157,64 +220,78 @@ quit_timeout_cb (gpointer user_data)
 }
 
 static void
-quit_timeout_cancel (void)
-{
-	if (quit_id) {
-		g_source_remove (quit_id);
-		quit_id = 0;
-	}
-}
-
-static void
 quit_timeout_reschedule (void)
 {
-	quit_timeout_cancel ();
-	if (!persist)
+	if (!persist) {
+		nm_clear_g_source (&quit_id);
 		quit_id = g_timeout_add_seconds (10, quit_timeout_cb, NULL);
+	}
 }
 
-static void
-start_request (Request *request)
+/**
+ * next_request:
+ *
+ * @h: the handler
+ * @request: (allow-none): the request to set as next. If %NULL, dequeue the next
+ * waiting request. Otherwise, try to set the given request.
+ *
+ * Sets the currently active request (@current_request). The current request
+ * is a request that has at least on "wait" script, because requests that only
+ * consist of "no-wait" scripts are handled right away and not enqueued to
+ * @requests_waiting nor set as @current_request.
+ *
+ * Returns: %TRUE, if there was currently not request in process and it set
+ * a new request as current.
+ */
+static gboolean
+next_request (Handler *h, Request *request)
 {
-	if (request->iface)
-		g_message ("Dispatching action '%s' for %s", request->action, request->iface);
-	else
-		g_message ("Dispatching action '%s'", request->action);
+	if (request) {
+		if (h->current_request) {
+			g_queue_push_tail (h->requests_waiting, request);
+			return FALSE;
+		}
+	} else {
+		/* when calling next_request() without explicit @request, we always
+		 * forcefully clear @current_request. That one is certainly
+		 * handled already. */
+		h->current_request = NULL;
 
-	request->handler->current_request = request;
-	dispatch_one_script (request);
-}
+		request = g_queue_pop_head (h->requests_waiting);
+		if (!request)
+			return FALSE;
+	}
 
-static void
-next_request (Handler *h)
-{
-	Request *request = g_queue_pop_head (h->pending_requests);
+	_LOG_R_I (request, "start running ordered scripts...");
 
-	if (request) {
-		start_request (request);
-		return;
-	}
+	h->current_request = request;
 
-	h->current_request = NULL;
-	quit_timeout_reschedule ();
+	return TRUE;
 }
 
-static gboolean
-next_script (gpointer user_data)
+/**
+ * complete_request:
+ * @request: the request
+ *
+ * Checks if all the scripts for the request have terminated and in such case
+ * it sends the D-Bus response and releases the request resources.
+ *
+ * It also decreases @num_requests_pending and possibly does quit_timeout_reschedule().
+ */
+static void
+complete_request (Request *request)
 {
-	Request *request = user_data;
-	Handler *h = request->handler;
 	GVariantBuilder results;
 	GVariant *ret;
 	guint i;
+	Handler *handler = request->handler;
 
-	request->idx++;
-	if (request->idx < request->scripts->len) {
-		dispatch_one_script (request);
-		return FALSE;
-	}
+	nm_assert (request);
+
+	/* Are there still pending scripts? Then do nothing (for now). */
+	if (request->num_scripts_done < request->scripts->len)
+		return;
 
-	/* All done */
 	g_variant_builder_init (&results, G_VARIANT_TYPE ("a(sus)"));
 	for (i = 0; i < request->scripts->len; i++) {
 		ScriptInfo *script = g_ptr_array_index (request->scripts, i);
@@ -228,16 +305,87 @@ next_script (gpointer user_data)
 	ret = g_variant_new ("(a(sus))", &results);
 	g_dbus_method_invocation_return_value (request->context, ret);
 
-	if (request->debug) {
-		if (request->iface)
-			g_message ("Dispatch '%s' on %s complete", request->action, request->iface);
-		else
-			g_message ("Dispatch '%s' complete", request->action);
-	}
+	_LOG_R_D (request, "completed (%u scripts)", request->scripts->len);
+
+	if (handler->current_request == request)
+		handler->current_request = NULL;
+
 	request_free (request);
 
-	next_request (h);
-	return FALSE;
+	g_assert_cmpuint (handler->num_requests_pending, >, 0);
+	if (--handler->num_requests_pending <= 0) {
+		nm_assert (!handler->current_request && !g_queue_peek_head (handler->requests_waiting));
+		quit_timeout_reschedule ();
+	}
+}
+
+static void
+complete_script (ScriptInfo *script)
+{
+	Handler *handler;
+	Request *request;
+	gboolean wait = script->wait;
+
+	request = script->request;
+
+	if (wait) {
+		/* for "wait" scripts, try to schedule the next blocking script.
+		 * If that is successful, return (as we must wait for its completion). */
+		if (dispatch_one_script (request))
+			return;
+	}
+
+	handler = request->handler;
+
+	nm_assert (!wait || handler->current_request == request);
+
+	/* Try to complete the request. @request will be possibly free'd,
+	 * making @script and @request a dangling pointer. */
+	complete_request (request);
+
+	if (!wait) {
+		/* this was a "no-wait" script. We either completed the request,
+		 * or there is nothing to do. Especially, there is no need to
+		 * queue the next_request() -- because no-wait scripts don't block
+		 * requests. However, if this was the last "no-wait" script and
+		 * there are "wait" scripts ready to run, launch them.
+		 */
+		if (   handler->current_request == request
+		    && handler->current_request->num_scripts_nowait == 0) {
+
+			if (dispatch_one_script (handler->current_request))
+				return;
+
+			complete_request (handler->current_request);
+		} else
+			return;
+	} else {
+		/* if the script is a "wait" script, we already tried above to
+		 * dispatch the next script. As we didn't do that, it means we
+		 * just completed the last script of @request and we can continue
+		 * with the next request...
+		 *
+		 * Also, it cannot be that there is another request currently being
+		 * processed because only requests with "wait" scripts can become
+		 * @current_request. As there can only be one "wait" script running
+		 * at any time, it means complete_request() above completed @request. */
+		nm_assert (!handler->current_request);
+	}
+
+	while (next_request (handler, NULL)) {
+		request = handler->current_request;
+
+		if (dispatch_one_script (request))
+			return;
+
+		/* Try to complete the request. It will be either completed
+		 * now, or when all pending "no-wait" scripts return. */
+		complete_request (request);
+
+		/* We can immediately start next_request(), because our current
+		 * @request has obviously no more "wait" scripts either.
+		 * Repeat... */
+	}
 }
 
 static void
@@ -248,9 +396,11 @@ script_watch_cb (GPid pid, gint status, gpointer user_data)
 
 	g_assert (pid == script->pid);
 
-	script->request->script_watch_id = 0;
-	g_source_remove (script->request->script_timeout_id);
-	script->request->script_timeout_id = 0;
+	script->watch_id = 0;
+	nm_clear_g_source (&script->timeout_id);
+	script->request->num_scripts_done++;
+	if (!script->wait)
+		script->request->num_scripts_nowait--;
 
 	if (WIFEXITED (status)) {
 		err = WEXITSTATUS (status);
@@ -272,15 +422,15 @@ script_watch_cb (GPid pid, gint status, gpointer user_data)
 	}
 
 	if (script->result == DISPATCH_RESULT_SUCCESS) {
-		if (script->request->debug)
-			g_message ("Script '%s' complete", script->script);
+		_LOG_S_D (script, "complete");
 	} else {
 		script->result = DISPATCH_RESULT_FAILED;
-		g_warning ("%s", script->error);
+		_LOG_S_W (script, "complete: failed with %s", script->error);
 	}
 
 	g_spawn_close_pid (script->pid);
-	next_script (script->request);
+
+	complete_script (script);
 }
 
 static gboolean
@@ -288,11 +438,13 @@ script_timeout_cb (gpointer user_data)
 {
 	ScriptInfo *script = user_data;
 
-	g_source_remove (script->request->script_watch_id);
-	script->request->script_watch_id = 0;
-	script->request->script_timeout_id = 0;
+	script->timeout_id = 0;
+	nm_clear_g_source (&script->watch_id);
+	script->request->num_scripts_done++;
+	if (!script->wait)
+		script->request->num_scripts_nowait--;
 
-	g_warning ("Script '%s' took too long; killing it.", script->script);
+	_LOG_S_W (script, "complete: timeout (kill script)");
 
 	kill (script->pid, SIGKILL);
 again:
@@ -305,7 +457,9 @@ again:
 	script->result = DISPATCH_RESULT_TIMEOUT;
 
 	g_spawn_close_pid (script->pid);
-	g_idle_add (next_script, script->request);
+
+	complete_script (script);
+
 	return FALSE;
 }
 
@@ -366,34 +520,58 @@ check_filename (const char *file_name)
 
 #define SCRIPT_TIMEOUT 600  /* 10 minutes */
 
-static void
-dispatch_one_script (Request *request)
+static gboolean
+script_dispatch (ScriptInfo *script)
 {
 	GError *error = NULL;
 	gchar *argv[4];
-	ScriptInfo *script = g_ptr_array_index (request->scripts, request->idx);
+	Request *request = script->request;
+
+	if (script->dispatched)
+		return FALSE;
+
+	script->dispatched = TRUE;
 
 	argv[0] = script->script;
-	argv[1] = request->iface ? request->iface : "none";
+	argv[1] = request->iface
+	          ? request->iface
+	          : (!strcmp (request->action, NMD_ACTION_HOSTNAME) ? "none" : "");
 	argv[2] = request->action;
 	argv[3] = NULL;
 
-	if (request->debug)
-		g_message ("Running script '%s'", script->script);
+	_LOG_S_D (script, "run script%s", script->wait ? "" : " (no-wait)");
 
-	if (g_spawn_async ("/", argv, request->envp, G_SPAWN_DO_NOT_REAP_CHILD, NULL, request, &script->pid, &error)) {
-		request->script_watch_id = g_child_watch_add (script->pid, (GChildWatchFunc) script_watch_cb, script);
-		request->script_timeout_id = g_timeout_add_seconds (SCRIPT_TIMEOUT, script_timeout_cb, script);
+	if (g_spawn_async ("/", argv, request->envp, G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &script->pid, &error)) {
+		script->watch_id = g_child_watch_add (script->pid, (GChildWatchFunc) script_watch_cb, script);
+		script->timeout_id = g_timeout_add_seconds (SCRIPT_TIMEOUT, script_timeout_cb, script);
+		if (!script->wait)
+			request->num_scripts_nowait++;
+		return TRUE;
 	} else {
-		g_warning ("Failed to execute script '%s': (%d) %s",
-		           script->script, error->code, error->message);
+		_LOG_S_W (script, "complete: failed to execute script: %s (%d)",
+		          error->message, error->code);
 		script->result = DISPATCH_RESULT_EXEC_FAILED;
 		script->error = g_strdup (error->message);
+		request->num_scripts_done++;
 		g_clear_error (&error);
+		return FALSE;
+	}
+}
 
-		/* Try the next script */
-		g_idle_add (next_script, request);
+static gboolean
+dispatch_one_script (Request *request)
+{
+	if (request->num_scripts_nowait > 0)
+		return TRUE;
+
+	while (request->idx < request->scripts->len) {
+		ScriptInfo *script;
+
+		script = g_ptr_array_index (request->scripts, request->idx++);
+		if (script_dispatch (script))
+			return TRUE;
 	}
+	return FALSE;
 }
 
 static GSList *
@@ -415,7 +593,7 @@ find_scripts (const char *str_action)
 		dirname = NMD_SCRIPT_DIR_DEFAULT;
 
 	if (!(dir = g_dir_open (dirname, 0, &error))) {
-		g_message ("Failed to open dispatcher directory '%s': (%d) %s",
+		g_message ("find-scripts: Failed to open dispatcher directory '%s': (%d) %s",
 		           dirname, error->code, error->message);
 		g_error_free (error);
 		return NULL;
@@ -434,11 +612,11 @@ find_scripts (const char *str_action)
 
 		err = stat (path, &st);
 		if (err)
-			g_warning ("Failed to stat '%s': %d", path, err);
+			g_warning ("find-scripts: Failed to stat '%s': %d", path, err);
 		else if (S_ISDIR (st.st_mode))
 			; /* silently skip. */
 		else if (!check_permissions (&st, &err_msg))
-			g_warning ("Cannot execute '%s': %s", path, err_msg);
+			g_warning ("find-scripts: Cannot execute '%s': %s", path, err_msg);
 		else {
 			/* success */
 			sorted = g_slist_insert_sorted (sorted, path, (GCompareFunc) g_strcmp0);
@@ -452,6 +630,34 @@ find_scripts (const char *str_action)
 }
 
 static gboolean
+script_must_wait (const char *path)
+{
+	gs_free char *link = NULL;
+	gs_free char *dir = NULL;
+	gs_free char *real = NULL;
+	char *tmp;
+
+	link = g_file_read_link (path, NULL);
+	if (link) {
+		if (!g_path_is_absolute (link)) {
+			dir = g_path_get_dirname (path);
+			tmp = g_build_path ("/", dir, link, NULL);
+			g_free (link);
+			g_free (dir);
+			link = tmp;
+		}
+
+		dir = g_path_get_dirname (link);
+		real = realpath (dir, NULL);
+
+		if (real && !strcmp (real, NMD_SCRIPT_DIR_NO_WAIT))
+			return FALSE;
+	}
+
+	return TRUE;
+}
+
+static gboolean
 handle_action (NMDBusDispatcher *dbus_dispatcher,
                GDBusMethodInvocation *context,
                const char *str_action,
@@ -473,21 +679,13 @@ handle_action (NMDBusDispatcher *dbus_dispatcher,
 	GSList *iter;
 	Request *request;
 	char **p;
-	char *iface = NULL;
+	guint i, num_nowait = 0;
+	const char *error_message = NULL;
 
 	sorted_scripts = find_scripts (str_action);
 
-	if (!sorted_scripts) {
-		GVariant *results;
-
-		results = g_variant_new_array (G_VARIANT_TYPE ("(sus)"), NULL, 0);
-		g_dbus_method_invocation_return_value (context, g_variant_new ("(@a(sus))", results));
-		return TRUE;
-	}
-
-	quit_timeout_cancel ();
-
-	request = g_malloc0 (sizeof (*request));
+	request = g_slice_new0 (Request);
+	request->request_id = ++request_id_counter;
 	request->handler = h;
 	request->debug = request_debug || debug;
 	request->context = context;
@@ -504,31 +702,88 @@ handle_action (NMDBusDispatcher *dbus_dispatcher,
 	                                                    vpn_ip_iface,
 	                                                    vpn_ip4_props,
 	                                                    vpn_ip6_props,
-	                                                    &iface);
-
-	if (request->debug) {
-		g_message ("------------ Action ID %p '%s' Interface %s Environment ------------",
-		           context, str_action, iface ? iface : "(none)");
-		for (p = request->envp; *p; p++)
-			g_message ("  %s", *p);
-		g_message ("\n");
-	}
-
-	request->iface = g_strdup (iface);
+	                                                    &request->iface,
+	                                                    &error_message);
 
 	request->scripts = g_ptr_array_new_full (5, script_info_free);
 	for (iter = sorted_scripts; iter; iter = g_slist_next (iter)) {
-		ScriptInfo *s = g_malloc0 (sizeof (*s));
+		ScriptInfo *s;
+
+		s = g_slice_new0 (ScriptInfo);
 		s->request = request;
 		s->script = iter->data;
+		s->wait = script_must_wait (s->script);
 		g_ptr_array_add (request->scripts, s);
 	}
 	g_slist_free (sorted_scripts);
 
-	if (h->current_request)
-		g_queue_push_tail (h->pending_requests, request);
-	else
-		start_request (request);
+	_LOG_R_I (request, "new request (%u scripts)", request->scripts->len);
+	if (   _LOG_R_D_enabled (request)
+	    && request->envp) {
+		for (p = request->envp; *p; p++)
+			_LOG_R_D (request, "environment: %s", *p);
+	}
+
+	if (error_message || request->scripts->len == 0) {
+		GVariant *results;
+
+		if (error_message)
+			_LOG_R_W (request, "completed: invalid request: %s", error_message);
+		else
+			_LOG_R_I (request, "completed: no scripts");
+
+		results = g_variant_new_array (G_VARIANT_TYPE ("(sus)"), NULL, 0);
+		g_dbus_method_invocation_return_value (context, g_variant_new ("(@a(sus))", results));
+		request->num_scripts_done = request->scripts->len;
+		request_free (request);
+		return TRUE;
+	}
+
+	nm_clear_g_source (&quit_id);
+
+	h->num_requests_pending++;
+
+	for (i = 0; i < request->scripts->len; i++) {
+		ScriptInfo *s = g_ptr_array_index (request->scripts, i);
+
+		if (!s->wait) {
+			script_dispatch (s);
+			num_nowait++;
+		}
+	}
+
+	if (num_nowait < request->scripts->len) {
+		/* The request has at least one wait script.
+		 * Try next_request() to schedule the request for
+		 * execution. This either enqueues the request or
+		 * sets it as h->current_request. */
+		if (next_request (h, request)) {
+			/* @request is now @current_request. Go ahead and
+			 * schedule the first wait script. */
+			if (!dispatch_one_script (request)) {
+				/* If that fails, we might be already finished with the
+				 * request. Try complete_request(). */
+				complete_request (request);
+
+				if (next_request (h, NULL)) {
+					/* As @request was successfully scheduled as next_request(), there is no
+					 * other request in queue that can be scheduled afterwards. Assert against
+					 * that, but call next_request() to clear current_request. */
+					g_assert_not_reached ();
+				}
+			}
+		}
+	} else {
+		/* The request contains only no-wait scripts. Try to complete
+		 * the request right away (we might have failed to schedule any
+		 * of the scripts). It will be either completed now, or later
+		 * when the pending scripts return.
+		 * We don't enqueue it to h->requests_waiting.
+		 * There is no need to handle next_request(), because @request is
+		 * not the current request anyway and does not interfere with requests
+		 * that have any "wait" scripts. */
+		complete_request (request);
+	}
 
 	return TRUE;
 }
@@ -571,7 +826,7 @@ log_handler (const gchar *log_domain,
              const gchar *message,
              gpointer ignored)
 {
-	int syslog_priority;	
+	int syslog_priority;
 
 	switch (log_level) {
 	case G_LOG_LEVEL_ERROR:
@@ -603,7 +858,7 @@ static void
 logging_setup (void)
 {
 	openlog (G_LOG_DOMAIN, LOG_CONS, LOG_DAEMON);
-	g_log_set_handler (G_LOG_DOMAIN, 
+	g_log_set_handler (G_LOG_DOMAIN,
 	                   G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION,
 	                   log_handler,
 	                   NULL);
@@ -645,16 +900,14 @@ main (int argc, char **argv)
 	g_option_context_add_main_entries (opt_ctx, entries, NULL);
 
 	if (!g_option_context_parse (opt_ctx, &argc, &argv, &error)) {
-		g_warning ("%s\n", error->message);
+		g_warning ("Error parsing command line arguments: %s", error->message);
 		g_error_free (error);
 		return 1;
 	}
 
 	g_option_context_free (opt_ctx);
 
-#if !GLIB_CHECK_VERSION (2, 35, 0)
-	g_type_init ();
-#endif
+	nm_g_type_init ();
 
 	g_unix_signal_add (SIGTERM, signal_handler, GINT_TO_POINTER (SIGTERM));
 	g_unix_signal_add (SIGINT, signal_handler, GINT_TO_POINTER (SIGINT));
@@ -691,12 +944,11 @@ main (int argc, char **argv)
 	                              NULL, NULL);
 	g_object_unref (bus);
 
-	if (!persist)
-		quit_id = g_timeout_add_seconds (10, quit_timeout_cb, NULL);
+	quit_timeout_reschedule ();
 
 	g_main_loop_run (loop);
 
-	g_queue_free (handler->pending_requests);
+	g_queue_free (handler->requests_waiting);
 	g_object_unref (handler);
 
 	if (!debug)
diff --git a/callouts/tests/Makefile.am b/callouts/tests/Makefile.am
index 394fedab..ee3f647d 100644
--- a/callouts/tests/Makefile.am
+++ b/callouts/tests/Makefile.am
@@ -1,8 +1,8 @@
 if ENABLE_TESTS
 
 AM_CPPFLAGS = \
-	-I$(top_srcdir)/include \
-	-I$(top_builddir)/include \
+	-I$(top_srcdir)/shared \
+	-I$(top_builddir)/shared \
 	-I$(top_srcdir)/libnm-core \
 	-I$(top_builddir)/libnm-core \
 	-I$(top_srcdir)/callouts \
@@ -10,8 +10,7 @@ AM_CPPFLAGS = \
 	-DNETWORKMANAGER_COMPILATION \
 	-DNM_VERSION_MAX_ALLOWED=NM_VERSION_NEXT_STABLE \
 	-DSRCDIR=\"$(abs_srcdir)\" \
-	$(GLIB_CFLAGS) \
-	$(DBUS_CFLAGS)
+	$(GLIB_CFLAGS)
 
 noinst_PROGRAMS = \
 	test-dispatcher-envp
@@ -24,8 +23,7 @@ test_dispatcher_envp_SOURCES = \
 test_dispatcher_envp_LDADD = \
 	$(top_builddir)/libnm/libnm.la \
 	$(top_builddir)/callouts/libtest-dispatcher-envp.la \
-	$(GLIB_LIBS) \
-	$(DBUS_LIBS)
+	$(GLIB_LIBS)
 
 ###########################################
 
@@ -36,6 +34,7 @@ endif
 
 EXTRA_DIST= \
 	dispatcher-down \
+	dispatcher-external \
 	dispatcher-up \
 	dispatcher-vpn-down \
 	dispatcher-vpn-up
diff --git a/callouts/tests/Makefile.in b/callouts/tests/Makefile.in
index f3cb7386..eeaf70d6 100644
--- a/callouts/tests/Makefile.in
+++ b/callouts/tests/Makefile.in
@@ -121,7 +121,7 @@ am__DEPENDENCIES_1 =
 @ENABLE_TESTS_TRUE@test_dispatcher_envp_DEPENDENCIES =  \
 @ENABLE_TESTS_TRUE@	$(top_builddir)/libnm/libnm.la \
 @ENABLE_TESTS_TRUE@	$(top_builddir)/callouts/libtest-dispatcher-envp.la \
-@ENABLE_TESTS_TRUE@	$(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+@ENABLE_TESTS_TRUE@	$(am__DEPENDENCIES_1)
 AM_V_lt = $(am__v_lt_@AM_V@)
 am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
 am__v_lt_0 = --silent
@@ -408,7 +408,6 @@ BLUEZ5_LIBS = @BLUEZ5_LIBS@
 CC = @CC@
 CCDEPMODE = @CCDEPMODE@
 CFLAGS = @CFLAGS@
-CKDB_PATH = @CKDB_PATH@
 CODE_COVERAGE_CFLAGS = @CODE_COVERAGE_CFLAGS@
 CODE_COVERAGE_ENABLED = @CODE_COVERAGE_ENABLED@
 CODE_COVERAGE_LDFLAGS = @CODE_COVERAGE_LDFLAGS@
@@ -420,8 +419,6 @@ CXXDEPMODE = @CXXDEPMODE@
 CXXFLAGS = @CXXFLAGS@
 CYGPATH_W = @CYGPATH_W@
 DBUS_CFLAGS = @DBUS_CFLAGS@
-DBUS_GLIB_100_CFLAGS = @DBUS_GLIB_100_CFLAGS@
-DBUS_GLIB_100_LIBS = @DBUS_GLIB_100_LIBS@
 DBUS_LIBS = @DBUS_LIBS@
 DBUS_SYS_DIR = @DBUS_SYS_DIR@
 DEFS = @DEFS@
@@ -431,6 +428,7 @@ DHCPCD_PATH = @DHCPCD_PATH@
 DISTRO_NETWORK_SERVICE = @DISTRO_NETWORK_SERVICE@
 DLLTOOL = @DLLTOOL@
 DNSMASQ_PATH = @DNSMASQ_PATH@
+DNSSEC_TRIGGER_SCRIPT = @DNSSEC_TRIGGER_SCRIPT@
 DSYMUTIL = @DSYMUTIL@
 DUMPBIN = @DUMPBIN@
 ECHO_C = @ECHO_C@
@@ -485,16 +483,13 @@ INTROSPECTION_MAKEFILE = @INTROSPECTION_MAKEFILE@
 INTROSPECTION_SCANNER = @INTROSPECTION_SCANNER@
 INTROSPECTION_TYPELIBDIR = @INTROSPECTION_TYPELIBDIR@
 IPTABLES_PATH = @IPTABLES_PATH@
-IWMX_SDK_CFLAGS = @IWMX_SDK_CFLAGS@
-IWMX_SDK_LIBS = @IWMX_SDK_LIBS@
 KERNEL_FIRMWARE_DIR = @KERNEL_FIRMWARE_DIR@
 LCOV = @LCOV@
 LD = @LD@
 LDFLAGS = @LDFLAGS@
+LIBAUDIT_CFLAGS = @LIBAUDIT_CFLAGS@
+LIBAUDIT_LIBS = @LIBAUDIT_LIBS@
 LIBDL = @LIBDL@
-LIBGCRYPT_CFLAGS = @LIBGCRYPT_CFLAGS@
-LIBGCRYPT_CONFIG = @LIBGCRYPT_CONFIG@
-LIBGCRYPT_LIBS = @LIBGCRYPT_LIBS@
 LIBICONV = @LIBICONV@
 LIBINTL = @LIBINTL@
 LIBM = @LIBM@
@@ -531,6 +526,8 @@ NEWT_LIBS = @NEWT_LIBS@
 NM = @NM@
 NMEDIT = @NMEDIT@
 NM_CONFIG_DEFAULT_AUTH_POLKIT_TEXT = @NM_CONFIG_DEFAULT_AUTH_POLKIT_TEXT@
+NM_CONFIG_DEFAULT_LOGGING_AUDIT_TEXT = @NM_CONFIG_DEFAULT_LOGGING_AUDIT_TEXT@
+NM_CONFIG_LOGGING_BACKEND_DEFAULT_TEXT = @NM_CONFIG_LOGGING_BACKEND_DEFAULT_TEXT@
 NM_MAJOR_VERSION = @NM_MAJOR_VERSION@
 NM_MICRO_VERSION = @NM_MICRO_VERSION@
 NM_MINOR_VERSION = @NM_MINOR_VERSION@
@@ -559,7 +556,6 @@ POLKIT_LIBS = @POLKIT_LIBS@
 POSUB = @POSUB@
 PPPD_PATH = @PPPD_PATH@
 PPPD_PLUGIN_DIR = @PPPD_PLUGIN_DIR@
-PPPOE_PATH = @PPPOE_PATH@
 QT_CFLAGS = @QT_CFLAGS@
 QT_LIBS = @QT_LIBS@
 RANLIB = @RANLIB@
@@ -574,6 +570,8 @@ SYSTEMD_200_CFLAGS = @SYSTEMD_200_CFLAGS@
 SYSTEMD_200_LIBS = @SYSTEMD_200_LIBS@
 SYSTEMD_INHIBIT_CFLAGS = @SYSTEMD_INHIBIT_CFLAGS@
 SYSTEMD_INHIBIT_LIBS = @SYSTEMD_INHIBIT_LIBS@
+SYSTEMD_JOURNAL_CFLAGS = @SYSTEMD_JOURNAL_CFLAGS@
+SYSTEMD_JOURNAL_LIBS = @SYSTEMD_JOURNAL_LIBS@
 SYSTEMD_LOGIN_CFLAGS = @SYSTEMD_LOGIN_CFLAGS@
 SYSTEMD_LOGIN_LIBS = @SYSTEMD_LOGIN_LIBS@
 SYSTEM_CA_PATH = @SYSTEM_CA_PATH@
@@ -634,6 +632,7 @@ mkdir_p = @mkdir_p@
 nmbinary = @nmbinary@
 nmconfdir = @nmconfdir@
 nmdatadir = @nmdatadir@
+nmlibdir = @nmlibdir@
 nmrundir = @nmrundir@
 nmstatedir = @nmstatedir@
 oldincludedir = @oldincludedir@
@@ -641,6 +640,7 @@ pdfdir = @pdfdir@
 prefix = @prefix@
 program_transform_name = @program_transform_name@
 psdir = @psdir@
+runstatedir = @runstatedir@
 sbindir = @sbindir@
 sharedstatedir = @sharedstatedir@
 srcdir = @srcdir@
@@ -657,8 +657,8 @@ with_netconfig = @with_netconfig@
 with_resolvconf = @with_resolvconf@
 with_valgrind = @with_valgrind@
 @ENABLE_TESTS_TRUE@AM_CPPFLAGS = \
-@ENABLE_TESTS_TRUE@	-I$(top_srcdir)/include \
-@ENABLE_TESTS_TRUE@	-I$(top_builddir)/include \
+@ENABLE_TESTS_TRUE@	-I$(top_srcdir)/shared \
+@ENABLE_TESTS_TRUE@	-I$(top_builddir)/shared \
 @ENABLE_TESTS_TRUE@	-I$(top_srcdir)/libnm-core \
 @ENABLE_TESTS_TRUE@	-I$(top_builddir)/libnm-core \
 @ENABLE_TESTS_TRUE@	-I$(top_srcdir)/callouts \
@@ -666,8 +666,7 @@ with_valgrind = @with_valgrind@
 @ENABLE_TESTS_TRUE@	-DNETWORKMANAGER_COMPILATION \
 @ENABLE_TESTS_TRUE@	-DNM_VERSION_MAX_ALLOWED=NM_VERSION_NEXT_STABLE \
 @ENABLE_TESTS_TRUE@	-DSRCDIR=\"$(abs_srcdir)\" \
-@ENABLE_TESTS_TRUE@	$(GLIB_CFLAGS) \
-@ENABLE_TESTS_TRUE@	$(DBUS_CFLAGS)
+@ENABLE_TESTS_TRUE@	$(GLIB_CFLAGS)
 
 
 ####### dispatcher envp #######
@@ -677,11 +676,11 @@ with_valgrind = @with_valgrind@
 @ENABLE_TESTS_TRUE@test_dispatcher_envp_LDADD = \
 @ENABLE_TESTS_TRUE@	$(top_builddir)/libnm/libnm.la \
 @ENABLE_TESTS_TRUE@	$(top_builddir)/callouts/libtest-dispatcher-envp.la \
-@ENABLE_TESTS_TRUE@	$(GLIB_LIBS) \
-@ENABLE_TESTS_TRUE@	$(DBUS_LIBS)
+@ENABLE_TESTS_TRUE@	$(GLIB_LIBS)
 
 EXTRA_DIST = \
 	dispatcher-down \
+	dispatcher-external \
 	dispatcher-up \
 	dispatcher-vpn-down \
 	dispatcher-vpn-up
diff --git a/callouts/tests/dispatcher-down b/callouts/tests/dispatcher-down
index da97d3f6..f766595c 100644
--- a/callouts/tests/dispatcher-down
+++ b/callouts/tests/dispatcher-down
@@ -14,6 +14,7 @@ path=/org/freedesktop/NetworkManager/Devices/0
 [env]
 PATH=
 CONNECTION_UUID=3fd2a33a-d81b-423f-ae99-e6baba742311
+CONNECTION_DBUS_PATH=/org/freedesktop/NetworkManager/Connections/5
 CONNECTION_ID=Random Connection
 CONNECTION_FILENAME=/callouts/tests/dispatcher-down
 DEVICE_IFACE=wlan0
diff --git a/callouts/tests/dispatcher-external b/callouts/tests/dispatcher-external
new file mode 100644
index 00000000..5430bec2
--- /dev/null
+++ b/callouts/tests/dispatcher-external
@@ -0,0 +1,33 @@
+[main]
+action=up
+expected-iface=virbr0
+uuid=92bbc2fb-7304-46be-8ebb-6093dbe19a6a
+id=virbr0
+external=1
+
+[device]
+state=100
+ip-interface=virbr0
+type=13
+interface=virbr0
+path=/org/freedesktop/NetworkManager/Devices/0
+
+[ip4]
+addresses=192.168.122.1/24 0.0.0.0
+domains=
+gateway=0.0.0.0
+
+[env]
+PATH=
+CONNECTION_UUID=92bbc2fb-7304-46be-8ebb-6093dbe19a6a
+CONNECTION_DBUS_PATH=/org/freedesktop/NetworkManager/Connections/5
+CONNECTION_FILENAME=/callouts/tests/dispatcher-external
+CONNECTION_ID=virbr0
+CONNECTION_EXTERNAL=1
+DEVICE_IFACE=virbr0
+DEVICE_IP_IFACE=virbr0
+IP4_NUM_ADDRESSES=1
+IP4_ADDRESS_0=192.168.122.1/24 0.0.0.0
+IP4_GATEWAY=0.0.0.0
+IP4_NUM_ROUTES=0
+
diff --git a/callouts/tests/dispatcher-up b/callouts/tests/dispatcher-up
index 78cfadad..463409e2 100644
--- a/callouts/tests/dispatcher-up
+++ b/callouts/tests/dispatcher-up
@@ -33,6 +33,7 @@ domains=hsd1.mn.comcast.net.
 [env]
 PATH=
 CONNECTION_UUID=3fd2a33a-d81b-423f-ae99-e6baba742311
+CONNECTION_DBUS_PATH=/org/freedesktop/NetworkManager/Connections/5
 CONNECTION_ID=Random Connection
 CONNECTION_FILENAME=/callouts/tests/dispatcher-up
 DEVICE_IFACE=wlan0
diff --git a/callouts/tests/dispatcher-vpn-down b/callouts/tests/dispatcher-vpn-down
index eb4dc2ca..18267f84 100644
--- a/callouts/tests/dispatcher-vpn-down
+++ b/callouts/tests/dispatcher-vpn-down
@@ -33,6 +33,7 @@ domains=hsd1.mn.comcast.net.
 [env]
 PATH=
 CONNECTION_UUID=355653c0-34d3-4777-ad25-f9a498b7ef8e
+CONNECTION_DBUS_PATH=/org/freedesktop/NetworkManager/Connections/5
 CONNECTION_ID=Random Connection
 CONNECTION_FILENAME=/callouts/tests/dispatcher-vpn-down
 DEVICE_IFACE=wlan0
diff --git a/callouts/tests/dispatcher-vpn-up b/callouts/tests/dispatcher-vpn-up
index 8ae78a31..181ecb53 100644
--- a/callouts/tests/dispatcher-vpn-up
+++ b/callouts/tests/dispatcher-vpn-up
@@ -33,6 +33,7 @@ domains=hsd1.mn.comcast.net.
 [env]
 PATH=
 CONNECTION_UUID=355653c0-34d3-4777-ad25-f9a498b7ef8e
+CONNECTION_DBUS_PATH=/org/freedesktop/NetworkManager/Connections/5
 CONNECTION_ID=Random Connection
 CONNECTION_FILENAME=/callouts/tests/dispatcher-vpn-up
 DEVICE_IFACE=wlan0
diff --git a/callouts/tests/test-dispatcher-envp.c b/callouts/tests/test-dispatcher-envp.c
index 6ae3fab0..27f088f6 100644
--- a/callouts/tests/test-dispatcher-envp.c
+++ b/callouts/tests/test-dispatcher-envp.c
@@ -23,13 +23,14 @@
 #include <arpa/inet.h>
 #include <stdlib.h>
 #include <string.h>
-#include <glib.h>
-#include <glib-object.h>
 
+#include "nm-default.h"
 #include "nm-core-internal.h"
 #include "nm-dispatcher-utils.h"
 #include "nm-dispatcher-api.h"
 
+#include "nm-test-utils.h"
+
 /*******************************************/
 
 static gboolean
@@ -81,13 +82,21 @@ parse_main (GKeyFile *kf,
 
 	g_variant_builder_init (&props, G_VARIANT_TYPE ("a{sv}"));
 	g_variant_builder_add (&props, "{sv}",
-	                       "connection-path",
+	                       NMD_CONNECTION_PROPS_PATH,
 	                       g_variant_new_object_path ("/org/freedesktop/NetworkManager/Connections/5"));
+
 	/* Strip out the non-fixed portion of the filename */
 	filename = strstr (filename, "/callouts");
 	g_variant_builder_add (&props, "{sv}",
 	                       "filename",
 	                       g_variant_new_string (filename));
+
+	if (g_key_file_get_boolean (kf, "main", "external", NULL)) {
+		g_variant_builder_add (&props, "{sv}",
+		                       "external",
+		                       g_variant_new_boolean (TRUE));
+	}
+
 	*out_con_props = g_variant_builder_end (&props);
 
 	return TRUE;
@@ -449,6 +458,7 @@ test_generic (const char *file, const char *override_vpn_ip_iface)
 	char *expected_iface = NULL;
 	char *action = NULL;
 	char *out_iface = NULL;
+	const char *error_message = NULL;
 	GHashTable *expected_env = NULL;
 	GError *error = NULL;
 	gboolean success;
@@ -488,7 +498,13 @@ test_generic (const char *file, const char *override_vpn_ip_iface)
 	                                           override_vpn_ip_iface ? override_vpn_ip_iface : vpn_ip_iface,
 	                                           vpn_ip4_props,
 	                                           vpn_ip6_props,
-	                                           &out_iface);
+	                                           &out_iface,
+	                                           &error_message);
+
+	g_assert ((!denv && error_message) || (denv && !error_message));
+
+	if (error_message)
+		g_warning ("%s", error_message);
 
 	/* Print out environment for now */
 #ifdef DEBUG
@@ -581,6 +597,12 @@ test_vpn_down (void)
 }
 
 static void
+test_external (void)
+{
+	test_generic ("dispatcher-external", NULL);
+}
+
+static void
 test_up_empty_vpn_iface (void)
 {
 	/* Test that an empty VPN iface variable, like is passed through D-Bus
@@ -591,19 +613,18 @@ test_up_empty_vpn_iface (void)
 
 /*******************************************/
 
+NMTST_DEFINE ();
+
 int
 main (int argc, char **argv)
 {
-	g_test_init (&argc, &argv, NULL);
-
-#if !GLIB_CHECK_VERSION (2, 35, 0)
-	g_type_init ();
-#endif
+	nmtst_init (&argc, &argv, TRUE);
 
 	g_test_add_func ("/dispatcher/up", test_up);
 	g_test_add_func ("/dispatcher/down", test_down);
 	g_test_add_func ("/dispatcher/vpn_up", test_vpn_up);
 	g_test_add_func ("/dispatcher/vpn_down", test_vpn_down);
+	g_test_add_func ("/dispatcher/external", test_external);
 
 	g_test_add_func ("/dispatcher/up_empty_vpn_iface", test_up_empty_vpn_iface);