summaryrefslogtreecommitdiff
path: root/callouts
diff options
context:
space:
mode:
authorDan Williams <dcbw@redhat.com>2011-05-02 22:38:51 -0500
committerDan Williams <dcbw@redhat.com>2011-05-02 22:54:28 -0500
commitf898dbf1a9cb657c3970fc9044d99dc46455f690 (patch)
tree7648cc3224e6cb77aa0e6bc1ccf746f8b688a8f6 /callouts
parenta2fc80b0ea1e33224725f541098cd72d2f26b815 (diff)
downloadNetworkManager-f898dbf1a9cb657c3970fc9044d99dc46455f690.tar.gz
dispatcher: enhance dispatcher script environment (bgo #648382)
For VPN connections, the interface name would be that of the VPN's IP interface, but the script environment would be the that of the VPN's parent device. Enhance the environment by adding any VPN specific details as additional environment variables prefixed by "VPN_". Leave the existing environment setup intact for backwards compatiblity. Additionally, the dispatcher never got updated for IPv6 support, so push IPv6 configuration and DHCPv6 configuration into the environment too. Even better, push everything the dispatcher needs to it instead of making the dispatcher make D-Bus requests back to NM, which sometimes fails if NM has already torn down the device or the connection which the device was using. And add some testcases to ensure that we don't break backwards compat, the testcases here were grabbed from a 0.8.4 machine with a hacked up dispatcher to dump everything it was given from NM.
Diffstat (limited to 'callouts')
-rw-r--r--callouts/Makefile.am35
-rw-r--r--callouts/nm-dispatcher-action.c327
-rw-r--r--callouts/nm-dispatcher-utils.c509
-rw-r--r--callouts/nm-dispatcher-utils.h41
-rw-r--r--callouts/nm-dispatcher.xml40
-rw-r--r--callouts/tests/Makefile.am38
-rw-r--r--callouts/tests/dispatcher-old-down19
-rw-r--r--callouts/tests/dispatcher-old-up55
-rw-r--r--callouts/tests/dispatcher-old-vpn-down54
-rw-r--r--callouts/tests/dispatcher-old-vpn-up54
-rw-r--r--callouts/tests/test-dispatcher-envp.c615
11 files changed, 1498 insertions, 289 deletions
diff --git a/callouts/Makefile.am b/callouts/Makefile.am
index 9fcdd6c7c0..4a1ea7ca55 100644
--- a/callouts/Makefile.am
+++ b/callouts/Makefile.am
@@ -1,3 +1,13 @@
+SUBDIRS=. tests
+
+###########################################
+# Test libraries
+###########################################
+
+noinst_LTLIBRARIES = \
+ libtest-dispatcher-envp.la
+
+
dbusservicedir = $(DBUS_SYS_DIR)
dbusservice_DATA = \
nm-dhcp-client.conf \
@@ -41,7 +51,9 @@ nm_avahi_autoipd_action_LDADD = \
nm_dispatcher_action_SOURCES = \
nm-dispatcher-action.c \
- nm-dispatcher-action.h
+ nm-dispatcher-action.h \
+ nm-dispatcher-utils.c \
+ nm-dispatcher-utils.h
nm_dispatcher_action_CPPFLAGS = \
-I${top_srcdir} \
@@ -54,7 +66,6 @@ nm_dispatcher_action_CPPFLAGS = \
-DLIBEXECDIR=\"$(libexecdir)\"
nm_dispatcher_action_LDADD = \
- $(top_builddir)/libnm-glib/libnm-glib.la \
$(top_builddir)/libnm-util/libnm-util.la \
$(DBUS_LIBS) \
$(GLIB_LIBS)
@@ -62,6 +73,26 @@ nm_dispatcher_action_LDADD = \
nm-dispatcher-glue.h: nm-dispatcher.xml
$(AM_V_GEN) dbus-binding-tool --prefix=nm_dispatcher --mode=glib-server --output=$@ $<
+###########################################
+# dispatcher envp
+###########################################
+
+libtest_dispatcher_envp_la_SOURCES = \
+ nm-dispatcher-utils.c \
+ nm-dispatcher-utils.h
+
+libtest_dispatcher_envp_la_CPPFLAGS = \
+ -I${top_srcdir}/include \
+ -I${top_srcdir}/libnm-util \
+ $(GLIB_CFLAGS) \
+ $(DBUS_CFLAGS)
+
+libtest_dispatcher_envp_la_LIBADD = \
+ $(top_builddir)/libnm-util/libnm-util.la \
+ $(GLIB_LIBS) \
+ $(DBUS_LIBS)
+
+
udevrulesdir = $(UDEV_BASE_DIR)/rules.d
udevrules_DATA = 77-nm-olpc-mesh.rules
diff --git a/callouts/nm-dispatcher-action.c b/callouts/nm-dispatcher-action.c
index ed9cb00c1e..54186a98a1 100644
--- a/callouts/nm-dispatcher-action.c
+++ b/callouts/nm-dispatcher-action.c
@@ -35,15 +35,9 @@
#include <dbus/dbus-glib-lowlevel.h>
#include <dbus/dbus-glib.h>
-#include <NetworkManager.h>
-#include "nm-glib-compat.h"
-#include <libnm-util/nm-connection.h>
-#include <libnm-util/nm-setting-ip4-config.h>
-#include <libnm-util/nm-setting-connection.h>
-#include <libnm-glib/nm-dhcp4-config.h>
-#include <libnm-glib/nm-device.h>
#include "nm-dispatcher-action.h"
+#include "nm-dispatcher-utils.h"
#define NMD_SCRIPT_DIR SYSCONFDIR "/NetworkManager/dispatcher.d"
@@ -84,11 +78,18 @@ typedef struct {
} Dispatcher;
static gboolean
-nm_dispatcher_action (Handler *obj,
+nm_dispatcher_action (Handler *h,
const char *action,
- GHashTable *connection,
+ GHashTable *connection_hash,
GHashTable *connection_props,
GHashTable *device_props,
+ GHashTable *device_ip4_props,
+ GHashTable *device_ip6_props,
+ GHashTable *device_dhcp4_props,
+ GHashTable *device_dhcp6_props,
+ const char *vpn_ip_iface,
+ GHashTable *vpn_ip4_props,
+ GHashTable *vpn_ip6_props,
GError **error);
#include "nm-dispatcher-glue.h"
@@ -220,207 +221,13 @@ child_setup (gpointer user_data G_GNUC_UNUSED)
setpgid (pid, pid);
}
-typedef struct {
- char **envp;
- guint32 i;
-} EnvAddInfo;
-
-static void
-add_one_option_to_envp (gpointer key, gpointer value, gpointer user_data)
-{
- EnvAddInfo *info = (EnvAddInfo *) user_data;
- char *ucased;
-
- ucased = g_ascii_strup (key, -1);
- info->envp[info->i++] = g_strdup_printf ("DHCP4_%s=%s", ucased, (char *) value);
- g_free (ucased);
-}
-
-static char **
-construct_envp (const char *uuid,
- const char *id,
- NMIP4Config *ip4_config,
- NMDHCP4Config *dhcp4_config)
-{
- guint32 env_size = 0;
- char **envp;
- guint32 envp_idx = 0;
- GHashTable *options = NULL;
- GSList *addresses, *routes, *iter;
- GArray *nameservers, *wins;
- GPtrArray *domains;
- guint32 num, i;
- GString *tmp;
-
- if (!ip4_config)
- return g_new0 (char *, 1);
-
- addresses = (GSList *) nm_ip4_config_get_addresses (ip4_config);
- nameservers = (GArray *) nm_ip4_config_get_nameservers (ip4_config);
- domains = (GPtrArray *) nm_ip4_config_get_domains (ip4_config);
- wins = (GArray *) nm_ip4_config_get_wins_servers (ip4_config);
- routes = (GSList *) nm_ip4_config_get_routes (ip4_config);
-
- env_size = g_slist_length (addresses)
- + 1 /* addresses length */
- + 1 /* nameservers */
- + 1 /* domains */
- + 1 /* WINS servers */
- + g_slist_length (routes)
- + 1 /* routes length */
- + 1 /* connection UUID */
- + 1 /* connection ID */;
-
- if (dhcp4_config) {
- options = nm_dhcp4_config_get_options (dhcp4_config);
- env_size += g_hash_table_size (options);
- }
-
- envp = g_new0 (char *, env_size + 1);
-
- if (uuid)
- envp[envp_idx++] = g_strdup_printf ("CONNECTION_UUID=%s", uuid);
- if (id)
- envp[envp_idx++] = g_strdup_printf ("CONNECTION_ID=%s", id);
-
- /* IP4 config stuff */
- for (iter = addresses, num = 0; iter; iter = g_slist_next (iter)) {
- NMIP4Address *addr = (NMIP4Address *) iter->data;
- char str_addr[INET_ADDRSTRLEN + 1];
- char str_gw[INET_ADDRSTRLEN + 1];
- struct in_addr tmp_addr;
- guint32 prefix = nm_ip4_address_get_prefix (addr);
-
- memset (str_addr, 0, sizeof (str_addr));
- tmp_addr.s_addr = nm_ip4_address_get_address (addr);
- if (!inet_ntop (AF_INET, &tmp_addr, str_addr, sizeof (str_addr)))
- continue;
-
- memset (str_gw, 0, sizeof (str_gw));
- tmp_addr.s_addr = nm_ip4_address_get_gateway (addr);
- inet_ntop (AF_INET, &tmp_addr, str_gw, sizeof (str_gw));
-
- tmp = g_string_sized_new (25 + strlen (str_addr) + strlen (str_gw));
- g_string_append_printf (tmp, "IP4_ADDRESS_%d=%s/%d %s", num++, str_addr, prefix, str_gw);
- envp[envp_idx++] = tmp->str;
- g_string_free (tmp, FALSE);
- }
- if (num)
- envp[envp_idx++] = g_strdup_printf ("IP4_NUM_ADDRESSES=%d", num);
-
- if (nameservers && nameservers->len) {
- gboolean first = TRUE;
-
- tmp = g_string_new ("IP4_NAMESERVERS=");
- for (i = 0; i < nameservers->len; i++) {
- struct in_addr addr;
- char buf[INET_ADDRSTRLEN + 1];
-
- addr.s_addr = g_array_index (nameservers, guint32, i);
- memset (buf, 0, sizeof (buf));
- if (inet_ntop (AF_INET, &addr, buf, sizeof (buf))) {
- if (!first)
- g_string_append_c (tmp, ' ');
- g_string_append (tmp, buf);
- first = FALSE;
- }
- }
- envp[envp_idx++] = tmp->str;
- g_string_free (tmp, FALSE);
- }
-
- if (domains && domains->len) {
- tmp = g_string_new ("IP4_DOMAINS=");
- for (i = 0; i < domains->len; i++) {
- if (i > 0)
- g_string_append_c (tmp, ' ');
- g_string_append (tmp, (char *) g_ptr_array_index (domains, i));
- }
- envp[envp_idx++] = tmp->str;
- g_string_free (tmp, FALSE);
- }
-
- if (wins && wins->len) {
- gboolean first = TRUE;
-
- tmp = g_string_new ("IP4_WINS_SERVERS=");
- for (i = 0; i < wins->len; i++) {
- struct in_addr addr;
- char buf[INET_ADDRSTRLEN + 1];
-
- addr.s_addr = g_array_index (wins, guint32, i);
- memset (buf, 0, sizeof (buf));
- if (inet_ntop (AF_INET, &addr, buf, sizeof (buf))) {
- if (!first)
- g_string_append_c (tmp, ' ');
- g_string_append (tmp, buf);
- first = FALSE;
- }
- }
- envp[envp_idx++] = tmp->str;
- g_string_free (tmp, FALSE);
- }
-
- for (iter = routes, num = 0; iter; iter = g_slist_next (iter)) {
- NMIP4Route *route = (NMIP4Route *) iter->data;
- char str_addr[INET_ADDRSTRLEN + 1];
- char str_nh[INET_ADDRSTRLEN + 1];
- struct in_addr tmp_addr;
- guint32 prefix = nm_ip4_route_get_prefix (route);
- guint32 metric = nm_ip4_route_get_metric (route);
-
- memset (str_addr, 0, sizeof (str_addr));
- tmp_addr.s_addr = nm_ip4_route_get_dest (route);
- if (!inet_ntop (AF_INET, &tmp_addr, str_addr, sizeof (str_addr)))
- continue;
-
- memset (str_nh, 0, sizeof (str_nh));
- tmp_addr.s_addr = nm_ip4_route_get_next_hop (route);
- inet_ntop (AF_INET, &tmp_addr, str_nh, sizeof (str_nh));
-
- tmp = g_string_sized_new (30 + strlen (str_addr) + strlen (str_nh));
- g_string_append_printf (tmp, "IP4_ROUTE_%d=%s/%d %s %d", num++, str_addr, prefix, str_nh, metric);
- envp[envp_idx++] = tmp->str;
- g_string_free (tmp, FALSE);
- }
- envp[envp_idx++] = g_strdup_printf ("IP4_NUM_ROUTES=%d", num);
-
- /* DHCP stuff */
- if (dhcp4_config && options) {
- EnvAddInfo info;
-
- info.envp = envp;
- info.i = envp_idx;
- g_hash_table_foreach (options, add_one_option_to_envp, &info);
- }
-
- if (debug) {
- char **p;
-
- g_message ("------------ Script Environment ------------");
- for (p = envp; *p; p++)
- g_message (" %s", *p);
- g_message ("\n");
- }
-
- return envp;
-}
-
static void
-dispatch_scripts (const char *action,
- const char *iface,
- const char *parent_iface,
- NMDeviceType type,
- const char *uuid,
- const char *id,
- NMIP4Config *ip4_config,
- NMDHCP4Config *dhcp4_config)
+dispatch_scripts (const char *action, const char *iface, char **envp)
{
GDir *dir;
const char *filename;
GSList *scripts = NULL, *iter;
GError *error = NULL;
- char **envp = NULL;
if (!(dir = g_dir_open (NMD_SCRIPT_DIR, 0, &error))) {
g_warning ("g_dir_open() could not open '" NMD_SCRIPT_DIR "'. '%s'",
@@ -458,8 +265,6 @@ dispatch_scripts (const char *action,
}
g_dir_close (dir);
- envp = construct_envp (uuid, id, ip4_config, dhcp4_config);
-
for (iter = scripts; iter; iter = g_slist_next (iter)) {
gchar *argv[4];
gint status = -1;
@@ -487,8 +292,6 @@ dispatch_scripts (const char *action,
}
}
- g_strfreev (envp);
-
g_slist_foreach (scripts, (GFunc) g_free, NULL);
g_slist_free (scripts);
}
@@ -499,18 +302,18 @@ nm_dispatcher_action (Handler *h,
GHashTable *connection_hash,
GHashTable *connection_props,
GHashTable *device_props,
+ GHashTable *device_ip4_props,
+ GHashTable *device_ip6_props,
+ GHashTable *device_dhcp4_props,
+ GHashTable *device_dhcp6_props,
+ const char *vpn_ip_iface,
+ GHashTable *vpn_ip4_props,
+ GHashTable *vpn_ip6_props,
GError **error)
{
Dispatcher *d = g_object_get_data (G_OBJECT (h), "dispatcher");
- NMConnection *connection;
- char *iface = NULL, *parent_iface = NULL;
- const char *uuid = NULL, *id = NULL;
- NMDeviceType type = NM_DEVICE_TYPE_UNKNOWN;
- NMDeviceState dev_state = NM_DEVICE_STATE_UNKNOWN;
- NMDevice *device = NULL;
- NMDHCP4Config *dhcp4_config = NULL;
- NMIP4Config *ip4_config = NULL;
- GValue *value;
+ char **envp, **p;
+ char *iface = NULL;
/* Back off the quit timeout */
if (d->quit_timeout)
@@ -518,80 +321,30 @@ nm_dispatcher_action (Handler *h,
if (!d->persist)
d->quit_timeout = g_timeout_add_seconds (10, quit_timeout_cb, NULL);
- /* Hostname changes don't require a device nor contain a connection */
- if (!strcmp (action, "hostname"))
- goto dispatch;
-
- connection = nm_connection_new_from_hash (connection_hash, error);
- if (connection) {
- uuid = nm_connection_get_uuid (connection);
- id = nm_connection_get_id (connection);
- } else {
- g_warning ("%s: Invalid connection: '%s' / '%s' invalid: %d",
- __func__,
- g_type_name (nm_connection_lookup_setting_type_by_quark ((*error)->domain)),
- (*error)->message, (*error)->code);
- /* Don't fail on this error yet */
- g_error_free (*error);
- *error = NULL;
- }
-
- /* interface name */
- value = g_hash_table_lookup (device_props, NMD_DEVICE_PROPS_INTERFACE);
- if (!value || !G_VALUE_HOLDS_STRING (value)) {
- g_warning ("Missing or invalid required value " NMD_DEVICE_PROPS_INTERFACE "!");
- goto out;
- }
- iface = (char *) g_value_get_string (value);
-
- /* IP interface name */
- value = g_hash_table_lookup (device_props, NMD_DEVICE_PROPS_IP_INTERFACE);
- if (value) {
- if (!G_VALUE_HOLDS_STRING (value)) {
- g_warning ("Invalid required value " NMD_DEVICE_PROPS_IP_INTERFACE "!");
- goto out;
- }
- parent_iface = iface;
- iface = (char *) g_value_get_string (value);
- }
-
- /* Device type */
- value = g_hash_table_lookup (device_props, NMD_DEVICE_PROPS_TYPE);
- if (!value || !G_VALUE_HOLDS_UINT (value)) {
- g_warning ("Missing or invalid required value " NMD_DEVICE_PROPS_TYPE "!");
- goto out;
- }
- type = g_value_get_uint (value);
-
- /* Device state */
- value = g_hash_table_lookup (device_props, NMD_DEVICE_PROPS_STATE);
- if (!value || !G_VALUE_HOLDS_UINT (value)) {
- g_warning ("Missing or invalid required value " NMD_DEVICE_PROPS_STATE "!");
- goto out;
- }
- dev_state = g_value_get_uint (value);
-
- /* device itself */
- value = g_hash_table_lookup (device_props, NMD_DEVICE_PROPS_PATH);
- if (!value || (G_VALUE_TYPE (value) != DBUS_TYPE_G_OBJECT_PATH)) {
- g_warning ("Missing or invalid required value " NMD_DEVICE_PROPS_PATH "!");
- goto out;
- }
- device = NM_DEVICE (nm_device_new (d->g_connection, (const char *) g_value_get_boxed (value)));
+ envp = nm_dispatcher_utils_construct_envp (action,
+ connection_hash,
+ connection_props,
+ device_props,
+ device_ip4_props,
+ device_ip6_props,
+ device_dhcp4_props,
+ device_dhcp6_props,
+ vpn_ip_iface,
+ vpn_ip4_props,
+ vpn_ip6_props,
+ &iface);
- /* Get the DHCP4 config */
- if (device && (dev_state == NM_DEVICE_STATE_ACTIVATED)) {
- dhcp4_config = nm_device_get_dhcp4_config (device);
- ip4_config = nm_device_get_ip4_config (device);
+ if (debug) {
+ g_message ("------------ Script Environment ------------");
+ for (p = envp; *p; p++)
+ g_message (" %s", *p);
+ g_message ("\n");
}
-dispatch:
- dispatch_scripts (action, iface, parent_iface, type, uuid, id, ip4_config, dhcp4_config);
-
- if (device)
- g_object_unref (device);
+ dispatch_scripts (action, iface, envp);
+ g_strfreev (envp);
+ g_free (iface);
-out:
return TRUE;
}
diff --git a/callouts/nm-dispatcher-utils.c b/callouts/nm-dispatcher-utils.c
new file mode 100644
index 0000000000..97ee1a4bef
--- /dev/null
+++ b/callouts/nm-dispatcher-utils.c
@@ -0,0 +1,509 @@
+/* -*- 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 (C) 2008 - 2011 Red Hat, Inc.
+ */
+
+#include <config.h>
+#include <string.h>
+
+#include <glib-object.h>
+
+#include <NetworkManager.h>
+#include <nm-dbus-glib-types.h>
+#include <nm-connection.h>
+#include <nm-setting-ip4-config.h>
+#include <nm-setting-ip6-config.h>
+#include <nm-setting-connection.h>
+
+#include "nm-dispatcher-action.h"
+#include "nm-utils.h"
+
+#include "nm-dispatcher-utils.h"
+
+static GSList *
+construct_basic_items (GSList *list,
+ const char *uuid,
+ const char *id,
+ const char *iface,
+ const char *ip_iface)
+{
+ if (uuid)
+ list = g_slist_prepend (list, g_strdup_printf ("CONNECTION_UUID=%s", uuid));
+ if (id)
+ list = g_slist_prepend (list, g_strdup_printf ("CONNECTION_ID=%s", id));
+ if (iface)
+ list = g_slist_prepend (list, g_strdup_printf ("DEVICE_IFACE=%s", iface));
+ if (ip_iface)
+ list = g_slist_prepend (list, g_strdup_printf ("DEVICE_IP_IFACE=%s", ip_iface));
+ return list;
+}
+
+static GSList *
+add_domains (GSList *items,
+ GHashTable *hash,
+ const char *prefix,
+ const char four_or_six)
+{
+ GValue *val;
+ GPtrArray *domains = NULL;
+ GString *tmp;
+ guint i;
+
+ /* Search domains */
+ val = g_hash_table_lookup (hash, "domains");
+ if (!val || !G_VALUE_HOLDS (val, DBUS_TYPE_G_ARRAY_OF_STRING))
+ return items;
+
+ domains = (GPtrArray *) g_value_get_boxed (val);
+ if (!domains || (domains->len == 0))
+ return items;
+
+ tmp = g_string_new (NULL);
+ g_string_append_printf (tmp, "%sIP%c_DOMAINS=", prefix, four_or_six);
+ for (i = 0; i < domains->len; i++) {
+ if (i > 0)
+ g_string_append_c (tmp, ' ');
+ g_string_append (tmp, (char *) g_ptr_array_index (domains, i));
+ }
+ items = g_slist_prepend (items, tmp->str);
+ g_string_free (tmp, FALSE);
+
+ return items;
+}
+
+static GSList *
+construct_ip4_items (GSList *items, GHashTable *ip4_config, const char *prefix)
+{
+ GSList *addresses = NULL, *routes = NULL, *iter;
+ GArray *dns = NULL, *wins = NULL;
+ guint32 num, i;
+ GString *tmp;
+ GValue *val;
+
+ if (ip4_config == NULL)
+ return items;
+
+ if (prefix == NULL)
+ prefix = "";
+
+ /* IP addresses */
+ val = g_hash_table_lookup (ip4_config, "addresses");
+ if (val)
+ addresses = nm_utils_ip4_addresses_from_gvalue (val);
+
+ for (iter = addresses, num = 0; iter; iter = g_slist_next (iter)) {
+ NMIP4Address *addr = (NMIP4Address *) iter->data;
+ char str_addr[INET_ADDRSTRLEN + 1];
+ char str_gw[INET_ADDRSTRLEN + 1];
+ struct in_addr tmp_addr;
+ guint32 ip_prefix = nm_ip4_address_get_prefix (addr);
+ char *addrtmp;
+
+ memset (str_addr, 0, sizeof (str_addr));
+ tmp_addr.s_addr = nm_ip4_address_get_address (addr);
+ if (!inet_ntop (AF_INET, &tmp_addr, str_addr, sizeof (str_addr)))
+ continue;
+
+ memset (str_gw, 0, sizeof (str_gw));
+ tmp_addr.s_addr = nm_ip4_address_get_gateway (addr);
+ inet_ntop (AF_INET, &tmp_addr, str_gw, sizeof (str_gw));
+
+ addrtmp = g_strdup_printf ("%sIP4_ADDRESS_%d=%s/%d %s", prefix, num++, str_addr, ip_prefix, str_gw);
+ items = g_slist_prepend (items, addrtmp);
+ }
+ if (num)
+ items = g_slist_prepend (items, g_strdup_printf ("%sIP4_NUM_ADDRESSES=%d", prefix, num));
+ if (addresses) {
+ g_slist_foreach (addresses, (GFunc) nm_ip4_address_unref, NULL);
+ g_slist_free (addresses);
+ }
+
+ /* DNS servers */
+ val = g_hash_table_lookup (ip4_config, "nameservers");
+ if (val && G_VALUE_HOLDS (val, DBUS_TYPE_G_UINT_ARRAY))
+ dns = (GArray *) g_value_get_boxed (val);
+
+ if (dns && (dns->len > 0)) {
+ gboolean first = TRUE;
+
+ tmp = g_string_new (NULL);
+ g_string_append_printf (tmp, "%sIP4_NAMESERVERS=", prefix);
+ for (i = 0; i < dns->len; i++) {
+ struct in_addr addr;
+ char buf[INET_ADDRSTRLEN + 1];
+
+ addr.s_addr = g_array_index (dns, guint32, i);
+ memset (buf, 0, sizeof (buf));
+ if (inet_ntop (AF_INET, &addr, buf, sizeof (buf))) {
+ if (!first)
+ g_string_append_c (tmp, ' ');
+ g_string_append (tmp, buf);
+ first = FALSE;
+ }
+ }
+ items = g_slist_prepend (items, tmp->str);
+ g_string_free (tmp, FALSE);
+ }
+
+ /* Search domains */
+ items = add_domains (items, ip4_config, prefix, '4');
+
+ /* WINS servers */
+ val = g_hash_table_lookup (ip4_config, "wins-servers");
+ if (val && G_VALUE_HOLDS (val, DBUS_TYPE_G_UINT_ARRAY))
+ wins = (GArray *) g_value_get_boxed (val);
+
+ if (wins && wins->len) {
+ gboolean first = TRUE;
+
+ tmp = g_string_new (NULL);
+ g_string_append_printf (tmp, "%sIP4_WINS_SERVERS=", prefix);
+ for (i = 0; i < wins->len; i++) {
+ struct in_addr addr;
+ char buf[INET_ADDRSTRLEN + 1];
+
+ addr.s_addr = g_array_index (wins, guint32, i);
+ memset (buf, 0, sizeof (buf));
+ if (inet_ntop (AF_INET, &addr, buf, sizeof (buf))) {
+ if (!first)
+ g_string_append_c (tmp, ' ');
+ g_string_append (tmp, buf);
+ first = FALSE;
+ }
+ }
+ items = g_slist_prepend (items, tmp->str);
+ g_string_free (tmp, FALSE);
+ }
+
+ /* Static routes */
+ val = g_hash_table_lookup (ip4_config, "routes");
+ if (val)
+ routes = nm_utils_ip4_routes_from_gvalue (val);
+
+ for (iter = routes, num = 0; iter; iter = g_slist_next (iter)) {
+ NMIP4Route *route = (NMIP4Route *) iter->data;
+ char str_addr[INET_ADDRSTRLEN + 1];
+ char str_nh[INET_ADDRSTRLEN + 1];
+ struct in_addr tmp_addr;
+ guint32 ip_prefix = nm_ip4_route_get_prefix (route);
+ guint32 metric = nm_ip4_route_get_metric (route);
+ char *routetmp;
+
+ memset (str_addr, 0, sizeof (str_addr));
+ tmp_addr.s_addr = nm_ip4_route_get_dest (route);
+ if (!inet_ntop (AF_INET, &tmp_addr, str_addr, sizeof (str_addr)))
+ continue;
+
+ memset (str_nh, 0, sizeof (str_nh));
+ tmp_addr.s_addr = nm_ip4_route_get_next_hop (route);
+ inet_ntop (AF_INET, &tmp_addr, str_nh, sizeof (str_nh));
+
+ routetmp = g_strdup_printf ("%sIP4_ROUTE_%d=%s/%d %s %d", prefix, num++, str_addr, ip_prefix, str_nh, metric);
+ items = g_slist_prepend (items, routetmp);
+ }
+ items = g_slist_prepend (items, g_strdup_printf ("%sIP4_NUM_ROUTES=%d", prefix, num));
+ if (routes) {
+ g_slist_foreach (routes, (GFunc) nm_ip4_route_unref, NULL);
+ g_slist_free (routes);
+ }
+
+ return items;
+}
+
+static GSList *
+construct_device_dhcp4_items (GSList *items, GHashTable *dhcp4_config)
+{
+ GHashTableIter iter;
+ const char *key, *tmp;
+ GValue *val;
+ char *ucased;
+
+ if (dhcp4_config == NULL)
+ return items;
+
+ g_hash_table_iter_init (&iter, dhcp4_config);
+ while (g_hash_table_iter_next (&iter, (gpointer) &key, (gpointer) &val)) {
+ ucased = g_ascii_strup (key, -1);
+ tmp = g_value_get_string (val);
+ items = g_slist_prepend (items, g_strdup_printf ("DHCP4_%s=%s", ucased, tmp));
+ g_free (ucased);
+ }
+ return items;
+}
+
+static GSList *
+construct_ip6_items (GSList *items, GHashTable *ip6_config, const char *prefix)
+{
+ GSList *addresses = NULL, *routes = NULL, *dns = NULL, *iter;
+ guint32 num;
+ GString *tmp;
+ GValue *val;
+
+ if (ip6_config == NULL)
+ return items;
+
+ if (prefix == NULL)
+ prefix = "";
+
+ /* IP addresses */
+ val = g_hash_table_lookup (ip6_config, "addresses");
+ if (val)
+ addresses = nm_utils_ip6_addresses_from_gvalue (val);
+
+ for (iter = addresses, num = 0; iter; iter = g_slist_next (iter)) {
+ NMIP6Address *addr = (NMIP6Address *) iter->data;
+ char str_addr[INET6_ADDRSTRLEN + 1];
+ char str_gw[INET6_ADDRSTRLEN + 1];
+ const struct in6_addr *tmp_addr;
+ guint32 ip_prefix = nm_ip6_address_get_prefix (addr);
+ char *addrtmp;
+
+ memset (str_addr, 0, sizeof (str_addr));
+ tmp_addr = nm_ip6_address_get_address (addr);
+ if (!inet_ntop (AF_INET6, &tmp_addr, str_addr, sizeof (str_addr)))
+ continue;
+
+ memset (str_gw, 0, sizeof (str_gw));
+ tmp_addr = nm_ip6_address_get_gateway (addr);
+ inet_ntop (AF_INET6, &tmp_addr, str_gw, sizeof (str_gw));
+
+ addrtmp = g_strdup_printf ("%sIP6_ADDRESS_%d=%s/%d %s", prefix, num++, str_addr, ip_prefix, str_gw);
+ items = g_slist_prepend (items, addrtmp);
+ }
+ if (num)
+ items = g_slist_prepend (items, g_strdup_printf ("%sIP6_NUM_ADDRESSES=%d", prefix, num));
+ if (addresses) {
+ g_slist_foreach (addresses, (GFunc) nm_ip6_address_unref, NULL);
+ g_slist_free (addresses);
+ }
+
+ /* DNS servers */
+ val = g_hash_table_lookup (ip6_config, "nameservers");
+ if (val)
+ dns = nm_utils_ip6_dns_from_gvalue (val);
+
+ if (g_slist_length (dns)) {
+ tmp = g_string_new (NULL);
+ g_string_append_printf (tmp, "%sIP6_NAMESERVERS=", prefix);
+
+ for (iter = dns; iter; iter = g_slist_next (iter)) {
+ const struct in6_addr *addr = iter->data;
+ gboolean first = TRUE;
+ char buf[INET6_ADDRSTRLEN + 1];
+
+ memset (buf, 0, sizeof (buf));
+ if (inet_ntop (AF_INET6, addr, buf, sizeof (buf))) {
+ if (!first)
+ g_string_append_c (tmp, ' ');
+ g_string_append (tmp, buf);
+ first = FALSE;
+ }
+ }
+
+ items = g_slist_prepend (items, tmp->str);
+ g_string_free (tmp, FALSE);
+ }
+
+ /* Search domains */
+ items = add_domains (items, ip6_config, prefix, '6');
+
+ /* Static routes */
+ val = g_hash_table_lookup (ip6_config, "routes");
+ if (val)
+ routes = nm_utils_ip6_routes_from_gvalue (val);
+
+ for (iter = routes, num = 0; iter; iter = g_slist_next (iter)) {
+ NMIP6Route *route = (NMIP6Route *) iter->data;
+ char str_addr[INET6_ADDRSTRLEN + 1];
+ char str_nh[INET6_ADDRSTRLEN + 1];
+ const struct in6_addr *tmp_addr;
+ guint32 ip_prefix = nm_ip6_route_get_prefix (route);
+ guint32 metric = nm_ip6_route_get_metric (route);
+ char *routetmp;
+
+ memset (str_addr, 0, sizeof (str_addr));
+ tmp_addr = nm_ip6_route_get_dest (route);
+ if (!inet_ntop (AF_INET6, &tmp_addr, str_addr, sizeof (str_addr)))
+ continue;
+
+ memset (str_nh, 0, sizeof (str_nh));
+ tmp_addr = nm_ip6_route_get_next_hop (route);
+ inet_ntop (AF_INET6, &tmp_addr, str_nh, sizeof (str_nh));
+
+ routetmp = g_strdup_printf ("%sIP6_ROUTE_%d=%s/%d %s %d", prefix, num++, str_addr, ip_prefix, str_nh, metric);
+ items = g_slist_prepend (items, routetmp);
+ }
+ if (num)
+ items = g_slist_prepend (items, g_strdup_printf ("%sIP6_NUM_ROUTES=%d", prefix, num));
+ if (routes) {
+ g_slist_foreach (routes, (GFunc) nm_ip6_route_unref, NULL);
+ g_slist_free (routes);
+ }
+
+ return items;
+}
+
+static GSList *
+construct_device_dhcp6_items (GSList *items, GHashTable *dhcp6_config)
+{
+ GHashTableIter iter;
+ const char *key, *tmp;
+ GValue *val;
+ char *ucased;
+
+ if (dhcp6_config == NULL)
+ return items;
+
+ g_hash_table_iter_init (&iter, dhcp6_config);
+ while (g_hash_table_iter_next (&iter, (gpointer) &key, (gpointer) &val)) {
+ ucased = g_ascii_strup (key, -1);
+ tmp = g_value_get_string (val);
+ items = g_slist_prepend (items, g_strdup_printf ("DHCP6_%s=%s", ucased, tmp));
+ g_free (ucased);
+ }
+ return items;
+}
+
+char **
+nm_dispatcher_utils_construct_envp (const char *action,
+ GHashTable *connection_hash,
+ GHashTable *connection_props,
+ GHashTable *device_props,
+ GHashTable *device_ip4_props,
+ GHashTable *device_ip6_props,
+ GHashTable *device_dhcp4_props,
+ GHashTable *device_dhcp6_props,
+ const char *vpn_ip_iface,
+ GHashTable *vpn_ip4_props,
+ GHashTable *vpn_ip6_props,
+ char **out_iface)
+{
+ const char *iface = NULL, *ip_iface = NULL;
+ const char *uuid = NULL, *id = NULL;
+ NMDeviceState dev_state = NM_DEVICE_STATE_UNKNOWN;
+ GValue *value;
+ char **envp = NULL;
+ GSList *items = NULL, *iter;
+ guint i;
+ GHashTable *con_setting_hash;
+
+ 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"))
+ return g_new0 (char *, 1);
+
+ con_setting_hash = g_hash_table_lookup (connection_hash, NM_SETTING_CONNECTION_SETTING_NAME);
+ if (!con_setting_hash) {
+ g_warning ("Failed to read connection setting");
+ return NULL;
+ }
+
+ value = g_hash_table_lookup (con_setting_hash, NM_SETTING_CONNECTION_UUID);
+ if (!value || !G_VALUE_HOLDS (value, G_TYPE_STRING)) {
+ g_warning ("Connection hash did not contain the UUID");
+ return NULL;
+ }
+ uuid = g_value_get_string (value);
+
+ value = g_hash_table_lookup (con_setting_hash, NM_SETTING_CONNECTION_ID);
+ if (!value || !G_VALUE_HOLDS (value, G_TYPE_STRING)) {
+ g_warning ("Connection hash did not contain the ID");
+ return NULL;
+ }
+ id = g_value_get_string (value);
+
+ /* interface name */
+ value = g_hash_table_lookup (device_props, NMD_DEVICE_PROPS_INTERFACE);
+ if (!value || !G_VALUE_HOLDS_STRING (value)) {
+ g_warning ("Missing or invalid required value " NMD_DEVICE_PROPS_INTERFACE "!");
+ return NULL;
+ }
+ iface = g_value_get_string (value);
+
+ /* IP interface name */
+ value = g_hash_table_lookup (device_props, NMD_DEVICE_PROPS_IP_INTERFACE);
+ if (value) {
+ if (!G_VALUE_HOLDS_STRING (value)) {
+ g_warning ("Invalid required value " NMD_DEVICE_PROPS_IP_INTERFACE "!");
+ return NULL;
+ }
+ ip_iface = g_value_get_string (value);
+ }
+
+ /* Device type */
+ value = g_hash_table_lookup (device_props, NMD_DEVICE_PROPS_TYPE);
+ if (!value || !G_VALUE_HOLDS_UINT (value)) {
+ g_warning ("Missing or invalid required value " NMD_DEVICE_PROPS_TYPE "!");
+ return NULL;
+ }
+
+ /* Device state */
+ value = g_hash_table_lookup (device_props, NMD_DEVICE_PROPS_STATE);
+ if (!value || !G_VALUE_HOLDS_UINT (value)) {
+ g_warning ("Missing or invalid required value " NMD_DEVICE_PROPS_STATE "!");
+ return NULL;
+ }
+ dev_state = g_value_get_uint (value);
+
+ /* device itself */
+ value = g_hash_table_lookup (device_props, NMD_DEVICE_PROPS_PATH);
+ if (!value || (G_VALUE_TYPE (value) != DBUS_TYPE_G_OBJECT_PATH)) {
+ g_warning ("Missing or invalid required value " NMD_DEVICE_PROPS_PATH "!");
+ return NULL;
+ }
+
+ items = construct_basic_items (items, uuid, id, iface, ip_iface);
+
+ /* Device it's aren't valid if the device isn't activated */
+ if (iface && (dev_state == NM_DEVICE_STATE_ACTIVATED)) {
+ items = construct_ip4_items (items, device_ip4_props, NULL);
+ items = construct_ip6_items (items, device_ip6_props, NULL);
+ items = construct_device_dhcp4_items (items, device_dhcp4_props);
+ items = construct_device_dhcp6_items (items, device_dhcp6_props);
+ }
+
+ if (vpn_ip_iface) {
+ items = g_slist_prepend (items, g_strdup_printf ("VPN_IP_IFACE=%s", vpn_ip_iface));
+ items = construct_ip4_items (items, vpn_ip4_props, "VPN_");
+ items = construct_ip6_items (items, vpn_ip6_props, "VPN_");
+ }
+
+ /* Convert the list to an environment pointer */
+ envp = g_new0 (char *, g_slist_length (items) + 1);
+ for (iter = items, i = 0; iter; iter = g_slist_next (iter), i++)
+ envp[i] = (char *) iter->data;
+ g_slist_free (items);
+
+ /* Backwards compat: 'iface' is set in this order:
+ * 1) VPN interface name
+ * 2) Device IP interface name
+ * 3) Device interface anme
+ */
+ if (vpn_ip_iface)
+ *out_iface = g_strdup (vpn_ip_iface);
+ else if (ip_iface)
+ *out_iface = g_strdup (ip_iface);
+ else
+ *out_iface = g_strdup (iface);
+
+ return envp;
+}
+
diff --git a/callouts/nm-dispatcher-utils.h b/callouts/nm-dispatcher-utils.h
new file mode 100644
index 0000000000..33202e0ebe
--- /dev/null
+++ b/callouts/nm-dispatcher-utils.h
@@ -0,0 +1,41 @@
+/* -*- 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 (C) 2008 - 2011 Red Hat, Inc.
+ */
+
+#ifndef NM_DISPATCHER_UTILS_H
+#define NM_DISPATCHER_UTILS_H
+
+#include <glib.h>
+
+char **
+nm_dispatcher_utils_construct_envp (const char *action,
+ GHashTable *connection_hash,
+ GHashTable *connection_props,
+ GHashTable *device_props,
+ GHashTable *device_ip4_props,
+ GHashTable *device_ip6_props,
+ GHashTable *device_dhcp4_props,
+ GHashTable *device_dhcp6_props,
+ const char *vpn_ip_iface,
+ GHashTable *vpn_ip4_props,
+ GHashTable *vpn_ip6_props,
+ char **out_iface);
+
+#endif /* NM_DISPATCHER_UTILS_H */
+
diff --git a/callouts/nm-dispatcher.xml b/callouts/nm-dispatcher.xml
index 532ae9f313..92780b1790 100644
--- a/callouts/nm-dispatcher.xml
+++ b/callouts/nm-dispatcher.xml
@@ -32,6 +32,46 @@
</tp:docstring>
</arg>
+ <arg name="device_ip4_config" type="a{sv}" direction="in">
+ <tp:docstring>
+ Properties of the device's IPv4 configuration.
+ </tp:docstring>
+ </arg>
+
+ <arg name="device_ip6_config" type="a{sv}" direction="in">
+ <tp:docstring>
+ Properties of the device's IPv6 configuration.
+ </tp:docstring>
+ </arg>
+
+ <arg name="device_dhcp4_config" type="a{sv}" direction="in">
+ <tp:docstring>
+ Properties of the device's DHCPv4 configuration.
+ </tp:docstring>
+ </arg>
+
+ <arg name="device_dhcp6_config" type="a{sv}" direction="in">
+ <tp:docstring>
+ Properties of the device's DHCPv6 configuration.
+ </tp:docstring>
+ </arg>
+
+ <arg name="vpn_ip_iface" type="s" direction="in">
+ <tp:docstring>VPN interface name.</tp:docstring>
+ </arg>
+
+ <arg name="vpn_ip4_config" type="a{sv}" direction="in">
+ <tp:docstring>
+ Properties of the VPN's IPv4 configuration.
+ </tp:docstring>
+ </arg>
+
+ <arg name="vpn_ip6_config" type="a{sv}" direction="in">
+ <tp:docstring>
+ Properties of the VPN's IPv6 configuration.
+ </tp:docstring>
+ </arg>
+
</method>
</interface>
</node>
diff --git a/callouts/tests/Makefile.am b/callouts/tests/Makefile.am
new file mode 100644
index 0000000000..6a46b629f1
--- /dev/null
+++ b/callouts/tests/Makefile.am
@@ -0,0 +1,38 @@
+INCLUDES = \
+ -I$(top_srcdir)/include \
+ -I$(top_srcdir)/libnm-util \
+ -I$(top_srcdir)/callouts
+
+noinst_PROGRAMS = \
+ test-dispatcher-envp
+
+####### dispatcher envp #######
+
+test_dispatcher_envp_SOURCES = \
+ test-dispatcher-envp.c
+
+test_dispatcher_envp_CPPFLAGS = \
+ $(GLIB_CFLAGS) \
+ $(DBUS_CFLAGS)
+
+test_dispatcher_envp_LDADD = \
+ $(top_builddir)/libnm-util/libnm-util.la \
+ $(top_builddir)/callouts/libtest-dispatcher-envp.la \
+ $(GLIB_LIBS) \
+ $(DBUS_LIBS)
+
+###########################################
+
+if WITH_TESTS
+
+check-local: test-dispatcher-envp
+ $(abs_builddir)/test-dispatcher-envp $(abs_srcdir)
+
+endif
+
+EXTRA_DIST= \
+ dispatcher-old-down \
+ dispatcher-old-up \
+ dispatcher-old-vpn-down \
+ dispatcher-old-vpn-up
+
diff --git a/callouts/tests/dispatcher-old-down b/callouts/tests/dispatcher-old-down
new file mode 100644
index 0000000000..4210921d38
--- /dev/null
+++ b/callouts/tests/dispatcher-old-down
@@ -0,0 +1,19 @@
+[main]
+action=down
+iface=wlan0
+uuid=3fd2a33a-d81b-423f-ae99-e6baba742311
+id=Random Connection
+
+[device]
+state=30
+ip-interface=wlan0
+type=2
+interface=wlan0
+path=/org/freedesktop/NetworkManager/Devices/0
+
+[env]
+CONNECTION_UUID=3fd2a33a-d81b-423f-ae99-e6baba742311
+CONNECTION_ID=Random Connection
+DEVICE_IFACE=wlan0
+DEVICE_IP_IFACE=wlan0
+
diff --git a/callouts/tests/dispatcher-old-up b/callouts/tests/dispatcher-old-up
new file mode 100644
index 0000000000..ed15fef541
--- /dev/null
+++ b/callouts/tests/dispatcher-old-up
@@ -0,0 +1,55 @@
+[main]
+action=up
+iface=wlan0
+uuid=3fd2a33a-d81b-423f-ae99-e6baba742311
+id=Random Connection
+
+[device]
+state=100
+ip-interface=wlan0
+type=2
+interface=wlan0
+path=/org/freedesktop/NetworkManager/Devices/0
+
+[dhcp4]
+netbios_name_servers=0.0.0.0
+domain_name_servers=68.87.77.134 68.87.72.134 192.168.1.1
+dhcp_lease_time=86400
+network_number=192.168.1.0
+domain_name=hsd1.mn.comcast.net.
+ip_address=192.168.1.119
+dhcp_message_type=5
+dhcp_server_identifier=192.168.1.1
+routers=192.168.1.1
+broadcast_address=192.168.1.255
+subnet_mask=255.255.255.0
+expiry=1304300446
+
+[ip4]
+addresses=192.168.1.119/24 192.168.1.1
+nameservers=68.87.77.134 68.87.72.134 192.168.1.1
+domains=hsd1.mn.comcast.net.
+
+[env]
+CONNECTION_UUID=3fd2a33a-d81b-423f-ae99-e6baba742311
+CONNECTION_ID=Random Connection
+DEVICE_IFACE=wlan0
+DEVICE_IP_IFACE=wlan0
+IP4_ADDRESS_0=192.168.1.119/24 192.168.1.1
+IP4_NUM_ADDRESSES=1
+IP4_NAMESERVERS=68.87.77.134 68.87.72.134 192.168.1.1
+IP4_DOMAINS=hsd1.mn.comcast.net.
+IP4_NUM_ROUTES=0
+DHCP4_NETBIOS_NAME_SERVERS=0.0.0.0
+DHCP4_DOMAIN_NAME_SERVERS=68.87.77.134 68.87.72.134 192.168.1.1
+DHCP4_DHCP_LEASE_TIME=86400
+DHCP4_NETWORK_NUMBER=192.168.1.0
+DHCP4_DOMAIN_NAME=hsd1.mn.comcast.net.
+DHCP4_IP_ADDRESS=192.168.1.119
+DHCP4_DHCP_MESSAGE_TYPE=5
+DHCP4_DHCP_SERVER_IDENTIFIER=192.168.1.1
+DHCP4_ROUTERS=192.168.1.1
+DHCP4_BROADCAST_ADDRESS=192.168.1.255
+DHCP4_SUBNET_MASK=255.255.255.0
+DHCP4_EXPIRY=1304300446
+
diff --git a/callouts/tests/dispatcher-old-vpn-down b/callouts/tests/dispatcher-old-vpn-down
new file mode 100644
index 0000000000..b45d8ea85a
--- /dev/null
+++ b/callouts/tests/dispatcher-old-vpn-down
@@ -0,0 +1,54 @@
+[main]
+action=vpn-down
+iface=tun0
+uuid=355653c0-34d3-4777-ad25-f9a498b7ef8e
+id=Random Connection
+
+[device]
+state=100
+ip-interface=tun0
+type=2
+interface=wlan0
+path=/org/freedesktop/NetworkManager/Devices/0
+
+[dhcp4]
+netbios_name_servers=0.0.0.0
+domain_name_servers=68.87.77.134 68.87.72.134 192.168.1.1
+dhcp_lease_time=86400
+network_number=192.168.1.0
+domain_name=hsd1.mn.comcast.net.
+ip_address=192.168.1.119
+dhcp_message_type=5
+dhcp_server_identifier=192.168.1.1
+routers=192.168.1.1
+broadcast_address=192.168.1.255
+subnet_mask=255.255.255.0
+expiry=1304349405
+
+[ip4]
+addresses=192.168.1.119/24 192.168.1.1
+nameservers=68.87.77.134 68.87.72.134 192.168.1.1
+domains=hsd1.mn.comcast.net.
+
+[env]
+CONNECTION_UUID=355653c0-34d3-4777-ad25-f9a498b7ef8e
+CONNECTION_ID=Random Connection
+DEVICE_IFACE=wlan0
+DEVICE_IP_IFACE=tun0
+IP4_ADDRESS_0=192.168.1.119/24 192.168.1.1
+IP4_NUM_ADDRESSES=1
+IP4_NAMESERVERS=68.87.77.134 68.87.72.134 192.168.1.1
+IP4_DOMAINS=hsd1.mn.comcast.net.
+IP4_NUM_ROUTES=0
+DHCP4_NETBIOS_NAME_SERVERS=0.0.0.0
+DHCP4_DOMAIN_NAME_SERVERS=68.87.77.134 68.87.72.134 192.168.1.1
+DHCP4_DHCP_LEASE_TIME=86400
+DHCP4_NETWORK_NUMBER=192.168.1.0
+DHCP4_DOMAIN_NAME=hsd1.mn.comcast.net.
+DHCP4_IP_ADDRESS=192.168.1.119
+DHCP4_DHCP_MESSAGE_TYPE=5
+DHCP4_DHCP_SERVER_IDENTIFIER=192.168.1.1
+DHCP4_ROUTERS=192.168.1.1
+DHCP4_BROADCAST_ADDRESS=192.168.1.255
+DHCP4_SUBNET_MASK=255.255.255.0
+DHCP4_EXPIRY=1304349405
diff --git a/callouts/tests/dispatcher-old-vpn-up b/callouts/tests/dispatcher-old-vpn-up
new file mode 100644
index 0000000000..f3b502defe
--- /dev/null
+++ b/callouts/tests/dispatcher-old-vpn-up
@@ -0,0 +1,54 @@
+[main]
+action=vpn-up
+iface=tun0
+uuid=355653c0-34d3-4777-ad25-f9a498b7ef8e
+id=Random Connection
+
+[device]
+state=100
+ip-interface=tun0
+type=2
+interface=wlan0
+path=/org/freedesktop/NetworkManager/Devices/0
+
+[dhcp4]
+netbios_name_servers=0.0.0.0
+domain_name_servers=68.87.77.134 68.87.72.134 192.168.1.1
+dhcp_lease_time=86400
+network_number=192.168.1.0
+domain_name=hsd1.mn.comcast.net.
+ip_address=192.168.1.119
+dhcp_message_type=5
+dhcp_server_identifier=192.168.1.1
+routers=192.168.1.1
+broadcast_address=192.168.1.255
+subnet_mask=255.255.255.0
+expiry=1304349405
+
+[ip4]
+addresses=192.168.1.119/24 192.168.1.1
+nameservers=68.87.77.134 68.87.72.134 192.168.1.1
+domains=hsd1.mn.comcast.net.
+
+[env]
+CONNECTION_UUID=355653c0-34d3-4777-ad25-f9a498b7ef8e
+CONNECTION_ID=Random Connection
+DEVICE_IFACE=wlan0
+DEVICE_IP_IFACE=tun0
+IP4_ADDRESS_0=192.168.1.119/24 192.168.1.1
+IP4_NUM_ADDRESSES=1
+IP4_NAMESERVERS=68.87.77.134 68.87.72.134 192.168.1.1
+IP4_DOMAINS=hsd1.mn.comcast.net.
+IP4_NUM_ROUTES=0
+DHCP4_NETBIOS_NAME_SERVERS=0.0.0.0
+DHCP4_DOMAIN_NAME_SERVERS=68.87.77.134 68.87.72.134 192.168.1.1
+DHCP4_DHCP_LEASE_TIME=86400
+DHCP4_NETWORK_NUMBER=192.168.1.0
+DHCP4_DOMAIN_NAME=hsd1.mn.comcast.net.
+DHCP4_IP_ADDRESS=192.168.1.119
+DHCP4_DHCP_MESSAGE_TYPE=5
+DHCP4_DHCP_SERVER_IDENTIFIER=192.168.1.1
+DHCP4_ROUTERS=192.168.1.1
+DHCP4_BROADCAST_ADDRESS=192.168.1.255
+DHCP4_SUBNET_MASK=255.255.255.0
+DHCP4_EXPIRY=1304349405
diff --git a/callouts/tests/test-dispatcher-envp.c b/callouts/tests/test-dispatcher-envp.c
new file mode 100644
index 0000000000..b4dbd784a9
--- /dev/null
+++ b/callouts/tests/test-dispatcher-envp.c
@@ -0,0 +1,615 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/*
+ * 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, 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 (C) 2011 Red Hat, Inc.
+ *
+ */
+
+#include <config.h>
+#include <arpa/inet.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <glib.h>
+#include <glib-object.h>
+
+#include "nm-connection.h"
+#include "nm-setting-connection.h"
+#include "nm-dispatcher-utils.h"
+#include "nm-dbus-glib-types.h"
+#include "nm-dispatcher-action.h"
+#include "nm-utils.h"
+
+/*******************************************/
+
+static void
+value_destroy (gpointer data)
+{
+ GValue *value = (GValue *) data;
+
+ g_value_unset (value);
+ g_slice_free (GValue, value);
+}
+
+static GHashTable *
+value_hash_create (void)
+{
+ return g_hash_table_new_full (g_str_hash, g_str_equal, g_free, value_destroy);
+}
+
+static void
+value_hash_add (GHashTable *hash,
+ const char *key,
+ GValue *value)
+{
+ g_hash_table_insert (hash, g_strdup (key), value);
+}
+
+static void
+value_hash_add_string (GHashTable *hash,
+ const char *key,
+ const char *str)
+{
+ GValue *value;
+
+ value = g_slice_new0 (GValue);
+ g_value_init (value, G_TYPE_STRING);
+ g_value_set_string (value, str);
+
+ value_hash_add (hash, key, value);
+}
+
+static void
+value_hash_add_object_path (GHashTable *hash,
+ const char *key,
+ const char *op)
+{
+ GValue *value;
+
+ value = g_slice_new0 (GValue);
+ g_value_init (value, DBUS_TYPE_G_OBJECT_PATH);
+ g_value_set_boxed (value, op);
+
+ value_hash_add (hash, key, value);
+}
+
+static void
+value_hash_add_uint (GHashTable *hash,
+ const char *key,
+ guint32 val)
+{
+ GValue *value;
+
+ value = g_slice_new0 (GValue);
+ g_value_init (value, G_TYPE_UINT);
+ g_value_set_uint (value, val);
+
+ value_hash_add (hash, key, value);
+}
+
+static void
+value_hash_add_strv (GHashTable *hash,
+ const char *key,
+ GPtrArray *array)
+{
+ GValue *value;
+
+ value = g_slice_new0 (GValue);
+ g_value_init (value, DBUS_TYPE_G_ARRAY_OF_STRING);
+ g_value_take_boxed (value, array);
+ value_hash_add (hash, key, value);
+}
+
+static void
+value_hash_add_uint_array (GHashTable *hash,
+ const char *key,
+ GArray *array)
+{
+ GValue *value;
+
+ value = g_slice_new0 (GValue);
+ g_value_init (value, DBUS_TYPE_G_UINT_ARRAY);
+ g_value_take_boxed (value, array);
+ value_hash_add (hash, key, value);
+}
+
+static gboolean
+parse_main (GKeyFile *kf,
+ GHashTable **out_con_hash,
+ GHashTable **out_con_props,
+ char **out_iface,
+ char **out_action,
+ GError **error)
+{
+ char *uuid, *id;
+ NMConnection *connection;
+ NMSettingConnection *s_con;
+
+ *out_iface = g_key_file_get_string (kf, "main", "iface", error);
+ if (*out_iface == NULL)
+ return FALSE;
+
+ *out_action = g_key_file_get_string (kf, "main", "action", error);
+ if (*out_action == NULL)
+ return FALSE;
+
+ uuid = g_key_file_get_string (kf, "main", "uuid", error);
+ if (uuid == NULL)
+ return FALSE;
+ id = g_key_file_get_string (kf, "main", "id", error);
+ if (id == NULL)
+ return FALSE;
+
+ connection = nm_connection_new ();
+ g_assert (connection);
+ s_con = (NMSettingConnection *) nm_setting_connection_new ();
+ g_assert (s_con);
+ g_object_set (s_con,
+ NM_SETTING_CONNECTION_UUID, uuid,
+ NM_SETTING_CONNECTION_ID, id,
+ NULL);
+ g_free (uuid);
+ g_free (id);
+ nm_connection_add_setting (connection, NM_SETTING (s_con));
+
+ *out_con_hash = nm_connection_to_hash (connection, NM_SETTING_HASH_FLAG_ALL);
+ g_object_unref (connection);
+
+ *out_con_props = value_hash_create ();
+ value_hash_add_object_path (*out_con_props, "connection-path", "/org/freedesktop/NetworkManager/Connections/5");
+
+ return TRUE;
+}
+
+static gboolean
+parse_device (GKeyFile *kf, GHashTable **out_device_props, GError **error)
+{
+ char *tmp;
+ gint i;
+
+ *out_device_props = value_hash_create ();
+
+ i = g_key_file_get_integer (kf, "device", "state", error);
+ if (i == 0)
+ return FALSE;
+ value_hash_add_uint (*out_device_props, NMD_DEVICE_PROPS_STATE, (guint) i);
+
+ i = g_key_file_get_integer (kf, "device", "type", error);
+ if (i == 0)
+ return FALSE;
+ value_hash_add_uint (*out_device_props, NMD_DEVICE_PROPS_TYPE, (guint) i);
+
+ tmp = g_key_file_get_string (kf, "device", "interface", error);
+ if (tmp == NULL)
+ return FALSE;
+ value_hash_add_string (*out_device_props, NMD_DEVICE_PROPS_INTERFACE, tmp);
+ g_free (tmp);
+
+ tmp = g_key_file_get_string (kf, "device", "ip-interface", error);
+ if (tmp == NULL)
+ return FALSE;
+ value_hash_add_string (*out_device_props, NMD_DEVICE_PROPS_IP_INTERFACE, tmp);
+ g_free (tmp);
+
+ tmp = g_key_file_get_string (kf, "device", "path", error);
+ if (tmp == NULL)
+ return FALSE;
+ value_hash_add_object_path (*out_device_props, NMD_DEVICE_PROPS_PATH, tmp);
+ g_free (tmp);
+
+ return TRUE;
+}
+
+static gboolean
+add_uint_array (GKeyFile *kf,
+ GHashTable *props,
+ const char *section,
+ const char *key,
+ GError **error)
+{
+ char *tmp;
+ char **split, **iter;
+ GArray *items;
+
+ tmp = g_key_file_get_string (kf, section, key, error);
+ if (tmp == NULL) {
+ g_clear_error (error);
+ return TRUE;
+ }
+ split = g_strsplit_set (tmp, " ", -1);
+ g_free (tmp);
+
+ if (g_strv_length (split) > 0) {
+ items = g_array_sized_new (FALSE, TRUE, sizeof (guint32), g_strv_length (split));
+ for (iter = split; iter && *iter; iter++) {
+ if (strlen (g_strstrip (*iter))) {
+ struct in_addr addr;
+
+ g_assert_cmpint (inet_pton (AF_INET, *iter, &addr), ==, 1);
+ g_array_append_val (items, addr.s_addr);
+ }
+ }
+ value_hash_add_uint_array (props, key, items);
+ }
+ g_strfreev (split);
+ return TRUE;
+}
+
+static gboolean
+parse_ip4 (GKeyFile *kf, GHashTable **out_props, const char *section, GError **error)
+{
+ char *tmp;
+ char **split, **iter;
+ GPtrArray *domains;
+ GSList *list;
+ GValue *val;
+
+ *out_props = value_hash_create ();
+
+ /* search domains */
+ tmp = g_key_file_get_string (kf, section, "domains", error);
+ if (tmp == NULL)
+ return FALSE;
+ split = g_strsplit_set (tmp, " ", -1);
+ g_free (tmp);
+
+ if (g_strv_length (split) > 0) {
+ domains = g_ptr_array_sized_new (g_strv_length (split));
+ for (iter = split; iter && *iter; iter++) {
+ if (strlen (g_strstrip (*iter)))
+ g_ptr_array_add (domains, g_strdup (*iter));
+ }
+ value_hash_add_strv (*out_props, "domains", domains);
+ }
+ g_strfreev (split);
+
+ /* nameservers */
+ if (!add_uint_array (kf, *out_props, "ip4", "nameservers", error))
+ return FALSE;
+ /* wins-servers */
+ if (!add_uint_array (kf, *out_props, "ip4", "wins-servers", error))
+ return FALSE;
+
+ /* Addresses */
+ tmp = g_key_file_get_string (kf, section, "addresses", error);
+ if (tmp == NULL)
+ return FALSE;
+ split = g_strsplit_set (tmp, ",", -1);
+ g_free (tmp);
+
+ if (g_strv_length (split) > 0) {
+ list = NULL;
+ for (iter = split; iter && *iter; iter++) {
+ NMIP4Address *addr;
+ struct in_addr a;
+ char *p;
+
+ if (strlen (g_strstrip (*iter)) == 0)
+ continue;
+
+ addr = nm_ip4_address_new ();
+
+ p = strchr (*iter, '/');
+ g_assert (p);
+ *p++ = '\0';
+
+ g_assert_cmpint (inet_pton (AF_INET, *iter, &a), ==, 1);
+ nm_ip4_address_set_address (addr, a.s_addr);
+ nm_ip4_address_set_prefix (addr, (guint) atoi (p));
+
+ p = strchr (p, ' ');
+ g_assert (p);
+ p++;
+
+ g_assert_cmpint (inet_pton (AF_INET, p, &a), ==, 1);
+ nm_ip4_address_set_gateway (addr, a.s_addr);
+
+ list = g_slist_append (list, addr);
+ }
+
+ val = g_slice_new0 (GValue);
+ g_value_init (val, DBUS_TYPE_G_ARRAY_OF_ARRAY_OF_UINT);
+ nm_utils_ip4_addresses_to_gvalue (list, val);
+ value_hash_add (*out_props, "addresses", val);
+ }
+ g_strfreev (split);
+
+ /* Routes */
+ tmp = g_key_file_get_string (kf, section, "routes", error);
+ g_clear_error (error);
+ if (tmp) {
+ split = g_strsplit_set (tmp, ",", -1);
+ g_free (tmp);
+
+ if (g_strv_length (split) > 0) {
+ list = NULL;
+ for (iter = split; iter && *iter; iter++) {
+ NMIP4Route *route;
+ struct in_addr a;
+ char *p;
+
+ if (strlen (g_strstrip (*iter)) == 0)
+ continue;
+
+ route = nm_ip4_route_new ();
+
+ p = strchr (*iter, '/');
+ g_assert (p);
+ *p++ = '\0';
+
+ g_assert_cmpint (inet_pton (AF_INET, *iter, &a), ==, 1);
+ nm_ip4_route_set_dest (route, a.s_addr);
+ nm_ip4_route_set_prefix (route, (guint) atoi (p));
+
+ p = strchr (p, ' ');
+ g_assert (p);
+ p++;
+
+ g_assert_cmpint (inet_pton (AF_INET, p, &a), ==, 1);
+ nm_ip4_route_set_next_hop (route, a.s_addr);
+
+ p = strchr (p, ' ');
+ g_assert (p);
+ p++;
+ nm_ip4_route_set_metric (route, (guint) atoi (p));
+
+ list = g_slist_append (list, route);
+ }
+
+ val = g_slice_new0 (GValue);
+ g_value_init (val, DBUS_TYPE_G_ARRAY_OF_ARRAY_OF_UINT);
+ nm_utils_ip4_routes_to_gvalue (list, val);
+ value_hash_add (*out_props, "routes", val);
+ }
+ g_strfreev (split);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+parse_dhcp (GKeyFile *kf,
+ const char *group_name,
+ GHashTable **out_props,
+ GError **error)
+{
+ char **keys, **iter, *val;
+
+ keys = g_key_file_get_keys (kf, group_name, NULL, error);
+ if (!keys)
+ return FALSE;
+
+ *out_props = value_hash_create ();
+ for (iter = keys; iter && *iter; iter++) {
+ val = g_key_file_get_string (kf, group_name, *iter, error);
+ if (!val)
+ return FALSE;
+ value_hash_add_string (*out_props, *iter, val);
+ g_free (val);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+get_dispatcher_file (const char *file,
+ GHashTable **out_con_hash,
+ GHashTable **out_con_props,
+ GHashTable **out_device_props,
+ GHashTable **out_device_ip4_props,
+ GHashTable **out_device_ip6_props,
+ GHashTable **out_device_dhcp4_props,
+ GHashTable **out_device_dhcp6_props,
+ const char **out_vpn_ip_iface,
+ GHashTable **out_vpn_ip4_props,
+ GHashTable **out_vpn_ip6_props,
+ char **out_expected_iface,
+ char **out_action,
+ GHashTable **out_env,
+ GError **error)
+{
+ GKeyFile *kf;
+ gboolean success = FALSE;
+ char **keys, **iter, *val;
+
+ kf = g_key_file_new ();
+ if (!g_key_file_load_from_file (kf, file, G_KEY_FILE_NONE, error))
+ return FALSE;
+
+ if (!parse_main (kf, out_con_hash, out_con_props, out_expected_iface, out_action, error))
+ goto out;
+
+ if (!parse_device (kf, out_device_props, error))
+ goto out;
+
+ if (g_key_file_has_group (kf, "ip4")) {
+ if (!parse_ip4 (kf, out_device_ip4_props, "ip4", error))
+ goto out;
+ }
+
+ if (g_key_file_has_group (kf, "dhcp4")) {
+ if (!parse_dhcp (kf, "dhcp4", out_device_dhcp4_props, error))
+ goto out;
+ }
+
+ if (g_key_file_has_group (kf, "dhcp6")) {
+ if (!parse_dhcp (kf, "dhcp6", out_device_dhcp4_props, error))
+ goto out;
+ }
+
+ g_assert (g_key_file_has_group (kf, "env"));
+ keys = g_key_file_get_keys (kf, "env", NULL, error);
+ *out_env = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+ for (iter = keys; iter && *iter; iter++) {
+ val = g_key_file_get_string (kf, "env", *iter, error);
+ if (!val)
+ goto out;
+ g_hash_table_insert (*out_env,
+ g_strdup_printf ("%s=%s", *iter, val),
+ GUINT_TO_POINTER (1));
+ g_free (val);
+ }
+ g_strfreev (keys);
+
+ success = TRUE;
+
+out:
+ g_key_file_free (kf);
+ return success;
+}
+
+/*******************************************/
+
+static void
+test_generic (const char *path, const char *file)
+{
+ GHashTable *con_hash = NULL;
+ GHashTable *con_props = NULL;
+ GHashTable *device_props = NULL;
+ GHashTable *device_ip4_props = NULL;
+ GHashTable *device_ip6_props = NULL;
+ GHashTable *device_dhcp4_props = NULL;
+ GHashTable *device_dhcp6_props = NULL;
+ const char *vpn_ip_iface = NULL;
+ GHashTable *vpn_ip4_props = NULL;
+ GHashTable *vpn_ip6_props = NULL;
+ char *expected_iface = NULL;
+ char *action = NULL;
+ char *out_iface = NULL;
+ GHashTable *expected_env = NULL;
+ GError *error = NULL;
+ gboolean success;
+ char *p;
+ char **denv, **iter;
+
+ /* Read in the test file */
+ p = g_strdup_printf ("%s/%s", path, file);
+ success = get_dispatcher_file (p,
+ &con_hash,
+ &con_props,
+ &device_props,
+ &device_ip4_props,
+ &device_ip6_props,
+ &device_dhcp4_props,
+ &device_dhcp6_props,
+ &vpn_ip_iface,
+ &vpn_ip4_props,
+ &vpn_ip6_props,
+ &expected_iface,
+ &action,
+ &expected_env,
+ &error);
+ g_free (p);
+ g_assert_no_error (error);
+ g_assert (success);
+
+ /* Get the environment from the dispatcher code */
+ denv = nm_dispatcher_utils_construct_envp (action,
+ con_hash,
+ con_props,
+ device_props,
+ device_ip4_props,
+ device_ip6_props,
+ device_dhcp4_props,
+ device_dhcp6_props,
+ vpn_ip_iface,
+ vpn_ip4_props,
+ vpn_ip6_props,
+ &out_iface);
+
+ /* Print out environment for now */
+ g_message ("\n");
+ for (iter = denv; iter && *iter; iter++)
+ g_message (" %s", *iter);
+
+ g_assert_cmpint (g_strv_length (denv), ==, g_hash_table_size (expected_env));
+
+ {
+ GHashTableIter k;
+ const char *key;
+ g_hash_table_iter_init (&k, expected_env);
+ while (g_hash_table_iter_next (&k, (gpointer) &key, NULL))
+ g_message (" %s", key);
+ }
+
+ /* Compare dispatcher generated env and expected env */
+ for (iter = denv; iter && *iter; iter++) {
+ gpointer foo;
+
+ foo = g_hash_table_lookup (expected_env, *iter);
+ if (!foo)
+ g_warning ("Failed to find %s in environment", *iter);
+ g_assert (foo);
+ }
+
+ g_assert_cmpstr (expected_iface, ==, out_iface);
+}
+
+/*******************************************/
+
+static void
+test_old_up (const char *path)
+{
+ test_generic (path, "dispatcher-old-up");
+}
+
+static void
+test_old_down (const char *path)
+{
+ test_generic (path, "dispatcher-old-down");
+}
+
+static void
+test_old_vpn_up (const char *path)
+{
+ test_generic (path, "dispatcher-old-vpn-up");
+}
+
+static void
+test_old_vpn_down (const char *path)
+{
+ test_generic (path, "dispatcher-old-vpn-down");
+}
+
+/*******************************************/
+
+#if GLIB_CHECK_VERSION(2,25,12)
+typedef GTestFixtureFunc TCFunc;
+#else
+typedef void (*TCFunc)(void);
+#endif
+
+#define TESTCASE(t, d) g_test_create_case (#t, 0, d, NULL, (TCFunc) t, NULL)
+
+int main (int argc, char **argv)
+{
+ GTestSuite *suite;
+
+ g_assert (argc > 1);
+
+ g_test_init (&argc, &argv, NULL);
+ g_type_init ();
+
+ suite = g_test_get_root ();
+
+ g_test_suite_add (suite, TESTCASE (test_old_up, argv[1]));
+ g_test_suite_add (suite, TESTCASE (test_old_down, argv[1]));
+ g_test_suite_add (suite, TESTCASE (test_old_vpn_up, argv[1]));
+ g_test_suite_add (suite, TESTCASE (test_old_vpn_down, argv[1]));
+
+ return g_test_run ();
+}
+