diff options
author | Thomas Haller <thaller@redhat.com> | 2016-10-12 11:56:33 +0200 |
---|---|---|
committer | Thomas Haller <thaller@redhat.com> | 2016-10-13 21:33:33 +0200 |
commit | f42466215aa6de79f14a00f256286e49e235914b (patch) | |
tree | f196ca5a3d876776d9ecc606095929f950c58dc1 /dispatcher | |
parent | 8b660f245667c33e89d883ec71cc3df54b6c655f (diff) | |
download | NetworkManager-f42466215aa6de79f14a00f256286e49e235914b.tar.gz |
callouts/dispatcher: rename directory callouts
Originally, the "callouts" directory contained various programs
that NetworkManager would call, for example the dhcp helper.
For a while, it only contains nm-dispatcher. Thus rename the directory
to indicate that it's for dispatcher.
Diffstat (limited to 'dispatcher')
-rw-r--r-- | dispatcher/Makefile.am | 105 | ||||
-rw-r--r-- | dispatcher/nm-dispatcher-utils.c | 536 | ||||
-rw-r--r-- | dispatcher/nm-dispatcher-utils.h | 43 | ||||
-rw-r--r-- | dispatcher/nm-dispatcher.c | 977 | ||||
-rw-r--r-- | dispatcher/nm-dispatcher.conf | 14 | ||||
-rw-r--r-- | dispatcher/nm-dispatcher.xml | 46 | ||||
-rw-r--r-- | dispatcher/org.freedesktop.nm_dispatcher.service.in | 6 | ||||
-rw-r--r-- | dispatcher/tests/Makefile.am | 41 | ||||
-rw-r--r-- | dispatcher/tests/dispatcher-connectivity-full | 23 | ||||
-rw-r--r-- | dispatcher/tests/dispatcher-connectivity-unknown | 22 | ||||
-rw-r--r-- | dispatcher/tests/dispatcher-down | 22 | ||||
-rw-r--r-- | dispatcher/tests/dispatcher-external | 39 | ||||
-rw-r--r-- | dispatcher/tests/dispatcher-up | 65 | ||||
-rw-r--r-- | dispatcher/tests/dispatcher-vpn-down | 64 | ||||
-rw-r--r-- | dispatcher/tests/dispatcher-vpn-up | 64 | ||||
-rw-r--r-- | dispatcher/tests/test-dispatcher-envp.c | 671 |
16 files changed, 2738 insertions, 0 deletions
diff --git a/dispatcher/Makefile.am b/dispatcher/Makefile.am new file mode 100644 index 0000000000..1f65b47a6a --- /dev/null +++ b/dispatcher/Makefile.am @@ -0,0 +1,105 @@ +SUBDIRS = . tests + +libexec_PROGRAMS = \ + nm-dispatcher + +noinst_LTLIBRARIES = \ + libnm-dispatcher-core.la \ + libnmdbus-dispatcher.la + +AM_CPPFLAGS = \ + -I${top_srcdir}/shared \ + -I${top_builddir}/shared \ + -I${top_srcdir}/libnm-core \ + -I${top_builddir}/libnm-core \ + $(GLIB_CFLAGS) \ + -DNETWORKMANAGER_COMPILATION \ + -DNMCONFDIR=\"$(nmconfdir)\" \ + -DSYSCONFDIR=\"$(sysconfdir)\" \ + -DLIBEXECDIR=\"$(libexecdir)\" + +############################################################################### + +dbusservicedir = $(DBUS_SYS_DIR) +dbusservice_DATA = \ + nm-dispatcher.conf + +############################################################################### + +libnm_dispatcher_core_la_SOURCES = \ + $(top_srcdir)/shared/nm-dispatcher-api.h \ + nm-dispatcher-utils.c \ + nm-dispatcher-utils.h + +libnm_dispatcher_core_la_LIBADD = \ + $(top_builddir)/libnm/libnm.la \ + $(GLIB_LIBS) + +############################################################################### + +nm_dispatcher_SOURCES = \ + $(top_srcdir)/shared/nm-dispatcher-api.h \ + nm-dispatcher.c + +nm_dispatcher_LDFLAGS = \ + -Wl,--version-script="$(top_srcdir)/linker-script-binary.ver" + +nm_dispatcher_LDADD = \ + $(top_builddir)/libnm/libnm.la \ + libnm-dispatcher-core.la \ + libnmdbus-dispatcher.la \ + $(GLIB_LIBS) + +############################################################################### + +nodist_libnmdbus_dispatcher_la_SOURCES = \ + nmdbus-dispatcher.c \ + nmdbus-dispatcher.h + +libnmdbus_dispatcher_la_CPPFLAGS = $(filter-out -DGLIB_VERSION_MAX_ALLOWED%,$(AM_CPPFLAGS)) + +nmdbus-dispatcher.h: nm-dispatcher.xml + $(AM_V_GEN) gdbus-codegen \ + --generate-c-code $(basename $@) \ + --c-namespace NMDBus \ + --interface-prefix org.freedesktop \ + $< + +nmdbus-dispatcher.c: nmdbus-dispatcher.h + @true + +BUILT_SOURCES = nmdbus-dispatcher.h nmdbus-dispatcher.c + +############################################################################### + +dbusactivationdir = $(datadir)/dbus-1/system-services +dbusactivation_in_files = org.freedesktop.nm_dispatcher.service.in +dbusactivation_DATA = $(dbusactivation_in_files:.service.in=.service) + +%.service: %.service.in + $(edit) $< >$@ + +edit = @sed \ + -e 's|@sbindir[@]|$(sbindir)|g' \ + -e 's|@sysconfdir[@]|$(sysconfdir)|g' \ + -e 's|@localstatedir[@]|$(localstatedir)|g' \ + -e 's|@libexecdir[@]|$(libexecdir)|g' + +dispatcherdir=$(sysconfdir)/NetworkManager/dispatcher.d +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 = \ + $(BUILT_SOURCES) \ + $(dbusactivation_DATA) + +EXTRA_DIST = \ + $(dbusservice_DATA) \ + $(dbusactivation_in_files) \ + nm-dispatcher.xml + diff --git a/dispatcher/nm-dispatcher-utils.c b/dispatcher/nm-dispatcher-utils.c new file mode 100644 index 0000000000..64a4b9ef6c --- /dev/null +++ b/dispatcher/nm-dispatcher-utils.c @@ -0,0 +1,536 @@ +/* -*- 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 "nm-default.h" + +#include <string.h> + +#include "nm-dbus-interface.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-api.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 *_list_append_val_strv (GSList *items, char **values, const char *format, ...) G_GNUC_PRINTF(3, 4); + +static GSList * +_list_append_val_strv (GSList *items, char **values, const char *format, ...) +{ + if (!values) + g_return_val_if_reached (items); + + /* Only add an item if the list of @values is not empty */ + if (values[0]) { + va_list args; + guint i; + GString *str = g_string_new (NULL); + + va_start (args, format); + g_string_append_vprintf (str, format, args); + va_end (args); + + g_string_append (str, values[0]); + for (i = 1; values[i]; i++) { + g_string_append_c (str, ' '); + g_string_append (str, values[i]); + } + items = g_slist_prepend (items, g_string_free (str, FALSE)); + } + + /* we take ownership of the values array and free it. */ + g_strfreev (values); + return items; +} + +static GSList * +add_domains (GSList *items, + GVariant *dict, + const char *prefix, + const char four_or_six) +{ + GVariant *val; + + /* Search domains */ + val = g_variant_lookup_value (dict, "domains", G_VARIANT_TYPE_STRING_ARRAY); + if (val) { + items = _list_append_val_strv (items, g_variant_dup_strv (val, NULL), + "%sIP%c_DOMAINS=", prefix, four_or_six); + g_variant_unref (val); + } + return items; +} + +static GSList * +construct_proxy_items (GSList *items, GVariant *proxy_config, const char *prefix) +{ + GVariant *val; + + if (proxy_config == NULL) + return items; + + if (prefix == NULL) + prefix = ""; + + /* PAC Url */ + val = g_variant_lookup_value (proxy_config, "pac-url", G_VARIANT_TYPE_STRING); + if (val) { + char *str; + + str = g_strdup_printf ("%sPROXY_PAC_URL=%s", + prefix, + g_variant_get_string (val, NULL)); + + items = g_slist_prepend (items, str); + g_variant_unref (val); + } + + /* PAC Script */ + val = g_variant_lookup_value (proxy_config, "pac-script", G_VARIANT_TYPE_STRING); + if (val) { + char *str; + + str = g_strdup_printf ("%sPROXY_PAC_SCRIPT=%s", + prefix, + g_variant_get_string (val, NULL)); + + items = g_slist_prepend (items, str); + g_variant_unref (val); + } + + return items; +} + +static GSList * +construct_ip4_items (GSList *items, GVariant *ip4_config, const char *prefix) +{ + GPtrArray *addresses, *routes; + char *gateway; + GVariant *val; + int i; + + if (ip4_config == NULL) + return items; + + if (prefix == NULL) + prefix = ""; + + /* IP addresses */ + val = g_variant_lookup_value (ip4_config, "addresses", G_VARIANT_TYPE ("aau")); + if (val) { + addresses = nm_utils_ip4_addresses_from_variant (val, &gateway); + if (!gateway) + gateway = g_strdup ("0.0.0.0"); + + for (i = 0; i < addresses->len; i++) { + NMIPAddress *addr = addresses->pdata[i]; + char *addrtmp; + + addrtmp = g_strdup_printf ("%sIP4_ADDRESS_%d=%s/%d %s", prefix, i, + nm_ip_address_get_address (addr), + nm_ip_address_get_prefix (addr), + gateway); + items = g_slist_prepend (items, addrtmp); + } + if (addresses->len) + items = g_slist_prepend (items, g_strdup_printf ("%sIP4_NUM_ADDRESSES=%d", prefix, addresses->len)); + + /* Write gateway to a separate variable, too. */ + items = g_slist_prepend (items, g_strdup_printf ("%sIP4_GATEWAY=%s", prefix, gateway)); + + g_ptr_array_unref (addresses); + g_free (gateway); + g_variant_unref (val); + } + + /* DNS servers */ + val = g_variant_lookup_value (ip4_config, "nameservers", G_VARIANT_TYPE ("au")); + if (val) { + items = _list_append_val_strv (items, nm_utils_ip4_dns_from_variant (val), + "%sIP4_NAMESERVERS=", prefix); + g_variant_unref (val); + } + + /* Search domains */ + items = add_domains (items, ip4_config, prefix, '4'); + + /* WINS servers */ + val = g_variant_lookup_value (ip4_config, "wins-servers", G_VARIANT_TYPE ("au")); + if (val) { + items = _list_append_val_strv (items, nm_utils_ip4_dns_from_variant (val), + "%sIP4_WINS_SERVERS=", prefix); + g_variant_unref (val); + } + + /* Static routes */ + val = g_variant_lookup_value (ip4_config, "routes", G_VARIANT_TYPE ("aau")); + if (val) { + routes = nm_utils_ip4_routes_from_variant (val); + + for (i = 0; i < routes->len; i++) { + NMIPRoute *route = routes->pdata[i]; + const char *next_hop; + char *routetmp; + + next_hop = nm_ip_route_get_next_hop (route); + if (!next_hop) + next_hop = "0.0.0.0"; + + routetmp = g_strdup_printf ("%sIP4_ROUTE_%d=%s/%d %s %u", prefix, i, + nm_ip_route_get_dest (route), + nm_ip_route_get_prefix (route), + next_hop, + (guint32) MAX (0, nm_ip_route_get_metric (route))); + items = g_slist_prepend (items, routetmp); + } + items = g_slist_prepend (items, g_strdup_printf ("%sIP4_NUM_ROUTES=%d", prefix, routes->len)); + g_ptr_array_unref (routes); + g_variant_unref (val); + } else + items = g_slist_prepend (items, g_strdup_printf ("%sIP4_NUM_ROUTES=0", prefix)); + + return items; +} + +static GSList * +construct_device_dhcp4_items (GSList *items, GVariant *dhcp4_config) +{ + GVariantIter iter; + const char *key, *tmp; + GVariant *val; + char *ucased; + + if (dhcp4_config == NULL) + return items; + + g_variant_iter_init (&iter, dhcp4_config); + while (g_variant_iter_next (&iter, "{&sv}", &key, &val)) { + ucased = g_ascii_strup (key, -1); + tmp = g_variant_get_string (val, NULL); + items = g_slist_prepend (items, g_strdup_printf ("DHCP4_%s=%s", ucased, tmp)); + g_free (ucased); + g_variant_unref (val); + } + return items; +} + +static GSList * +construct_ip6_items (GSList *items, GVariant *ip6_config, const char *prefix) +{ + GPtrArray *addresses, *routes; + char *gateway = NULL; + GVariant *val; + int i; + + if (ip6_config == NULL) + return items; + + if (prefix == NULL) + prefix = ""; + + /* IP addresses */ + val = g_variant_lookup_value (ip6_config, "addresses", G_VARIANT_TYPE ("a(ayuay)")); + if (val) { + addresses = nm_utils_ip6_addresses_from_variant (val, &gateway); + if (!gateway) + gateway = g_strdup ("::"); + + for (i = 0; i < addresses->len; i++) { + NMIPAddress *addr = addresses->pdata[i]; + char *addrtmp; + + addrtmp = g_strdup_printf ("%sIP6_ADDRESS_%d=%s/%d %s", prefix, i, + nm_ip_address_get_address (addr), + nm_ip_address_get_prefix (addr), + gateway); + items = g_slist_prepend (items, addrtmp); + } + if (addresses->len) + items = g_slist_prepend (items, g_strdup_printf ("%sIP6_NUM_ADDRESSES=%d", prefix, addresses->len)); + + /* Write gateway to a separate variable, too. */ + items = g_slist_prepend (items, g_strdup_printf ("%sIP6_GATEWAY=%s", prefix, gateway)); + + g_ptr_array_unref (addresses); + g_free (gateway); + g_variant_unref (val); + } + + /* DNS servers */ + val = g_variant_lookup_value (ip6_config, "nameservers", G_VARIANT_TYPE ("aay")); + if (val) { + items = _list_append_val_strv (items, nm_utils_ip6_dns_from_variant (val), + "%sIP6_NAMESERVERS=", prefix); + g_variant_unref (val); + } + + /* Search domains */ + items = add_domains (items, ip6_config, prefix, '6'); + + /* Static routes */ + val = g_variant_lookup_value (ip6_config, "routes", G_VARIANT_TYPE ("a(ayuayu)")); + if (val) { + routes = nm_utils_ip6_routes_from_variant (val); + + for (i = 0; i < routes->len; i++) { + NMIPRoute *route = routes->pdata[i]; + const char *next_hop; + char *routetmp; + + next_hop = nm_ip_route_get_next_hop (route); + if (!next_hop) + next_hop = "::"; + + routetmp = g_strdup_printf ("%sIP6_ROUTE_%d=%s/%d %s %u", prefix, i, + nm_ip_route_get_dest (route), + nm_ip_route_get_prefix (route), + next_hop, + (guint32) MAX (0, nm_ip_route_get_metric (route))); + items = g_slist_prepend (items, routetmp); + } + if (routes->len) + items = g_slist_prepend (items, g_strdup_printf ("%sIP6_NUM_ROUTES=%d", prefix, routes->len)); + g_ptr_array_unref (routes); + g_variant_unref (val); + } + + return items; +} + +static GSList * +construct_device_dhcp6_items (GSList *items, GVariant *dhcp6_config) +{ + GVariantIter iter; + const char *key, *tmp; + GVariant *val; + char *ucased; + + if (dhcp6_config == NULL) + return items; + + g_variant_iter_init (&iter, dhcp6_config); + while (g_variant_iter_next (&iter, "{&sv}", &key, &val)) { + ucased = g_ascii_strup (key, -1); + tmp = g_variant_get_string (val, NULL); + items = g_slist_prepend (items, g_strdup_printf ("DHCP6_%s=%s", ucased, tmp)); + g_free (ucased); + g_variant_unref (val); + } + return items; +} + +char ** +nm_dispatcher_utils_construct_envp (const char *action, + GVariant *connection_dict, + GVariant *connection_props, + GVariant *device_props, + GVariant *device_proxy_props, + GVariant *device_ip4_props, + GVariant *device_ip6_props, + GVariant *device_dhcp4_props, + GVariant *device_dhcp6_props, + const char *connectivity_state, + const char *vpn_ip_iface, + GVariant *vpn_proxy_props, + GVariant *vpn_ip4_props, + GVariant *vpn_ip6_props, + char **out_iface, + const char **out_error_message) +{ + const char *iface = NULL, *ip_iface = NULL; + 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 and connectivity changes don't require a device nor contain a connection */ + if ( !strcmp (action, NMD_ACTION_HOSTNAME) + || !strcmp (action, NMD_ACTION_CONNECTIVITY_CHANGE)) { + goto done; + } + + /* 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. + */ + if (vpn_ip_iface && !strlen (vpn_ip_iface)) + vpn_ip_iface = NULL; + + /* interface name */ + if (!g_variant_lookup (device_props, NMD_DEVICE_PROPS_INTERFACE, "&s", &iface)) { + *out_error_message = "Missing or invalid required value " NMD_DEVICE_PROPS_INTERFACE "!"; + return NULL; + } + if (!*iface) + iface = NULL; + + /* IP interface name */ + 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)) { + *out_error_message = "Invalid value " NMD_DEVICE_PROPS_IP_INTERFACE "!"; + return NULL; + } + g_variant_unref (value); + (void) g_variant_lookup (device_props, NMD_DEVICE_PROPS_IP_INTERFACE, "&s", &ip_iface); + } + + /* Device type */ + if (!g_variant_lookup (device_props, NMD_DEVICE_PROPS_TYPE, "u", NULL)) { + *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) { + *out_error_message = "Missing or invalid required value " NMD_DEVICE_PROPS_STATE "!"; + return NULL; + } + dev_state = g_variant_get_uint32 (value); + g_variant_unref (value); + + /* device itself */ + if (!g_variant_lookup (device_props, NMD_DEVICE_PROPS_PATH, "o", NULL)) { + *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) { + *out_error_message = "Failed to read connection setting"; + return NULL; + } + + if (!g_variant_lookup (con_setting, NM_SETTING_CONNECTION_UUID, "&s", &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)) { + *out_error_message = "Connection hash did not contain the ID"; + g_variant_unref (con_setting); + return NULL; + } + + items = construct_basic_items (items, uuid, id, iface, ip_iface); + g_variant_unref (con_setting); + + /* Device it's aren't valid if the device isn't activated */ + if (iface && (dev_state == NM_DEVICE_STATE_ACTIVATED)) { + items = construct_proxy_items (items, device_proxy_props, NULL); + 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_proxy_items (items, vpn_proxy_props, "VPN_"); + items = construct_ip4_items (items, vpn_ip4_props, "VPN_"); + items = construct_ip6_items (items, vpn_ip6_props, "VPN_"); + } + + /* 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); + + done: + /* The connectivity_state value will only be meaningful for 'connectivity-change' events + * (otherwise it will be "UNKNOWN"), so we only set the environment variable in those cases. + */ + if (connectivity_state && strcmp(connectivity_state, "UNKNOWN")) + items = g_slist_prepend (items, g_strdup_printf ("CONNECTIVITY_STATE=%s", connectivity_state)); + + path = g_getenv ("PATH"); + if (path) { + path_item = g_strdup_printf ("PATH=%s", path); + items = g_slist_prepend (items, path_item); + } + + /* 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); + + *out_error_message = NULL; + return envp; +} + diff --git a/dispatcher/nm-dispatcher-utils.h b/dispatcher/nm-dispatcher-utils.h new file mode 100644 index 0000000000..a603432668 --- /dev/null +++ b/dispatcher/nm-dispatcher-utils.h @@ -0,0 +1,43 @@ +/* -*- 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 __NETWORKMANAGER_DISPATCHER_UTILS_H__ +#define __NETWORKMANAGER_DISPATCHER_UTILS_H__ + +char ** +nm_dispatcher_utils_construct_envp (const char *action, + GVariant *connection_dict, + GVariant *connection_props, + GVariant *device_props, + GVariant *device_proxy_props, + GVariant *device_ip4_props, + GVariant *device_ip6_props, + GVariant *device_dhcp4_props, + GVariant *device_dhcp6_props, + const char *connectivity_state, + const char *vpn_ip_iface, + GVariant *vpn_proxy_props, + GVariant *vpn_ip4_props, + GVariant *vpn_ip6_props, + char **out_iface, + const char **out_error_message); + +#endif /* __NETWORKMANAGER_DISPATCHER_UTILS_H__ */ + diff --git a/dispatcher/nm-dispatcher.c b/dispatcher/nm-dispatcher.c new file mode 100644 index 0000000000..6bb5221858 --- /dev/null +++ b/dispatcher/nm-dispatcher.c @@ -0,0 +1,977 @@ +/* -*- 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 - 2012 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include <syslog.h> +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <signal.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <errno.h> +#include <arpa/inet.h> +#include <glib-unix.h> + +#include "nm-dispatcher-api.h" +#include "nm-dispatcher-utils.h" + +#include "nmdbus-dispatcher.h" + +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; + +typedef struct { + GObject parent; + + /* Private data */ + NMDBusDispatcher *dbus_dispatcher; + + Request *current_request; + GQueue *requests_waiting; + gint num_requests_pending; +} Handler; + +typedef struct { + GObjectClass parent; +} HandlerClass; + +GType handler_get_type (void); + +#define HANDLER_TYPE (handler_get_type ()) +#define HANDLER(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), HANDLER_TYPE, Handler)) +#define HANDLER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), HANDLER_TYPE, HandlerClass)) + +G_DEFINE_TYPE(Handler, handler, G_TYPE_OBJECT) + +static gboolean +handle_action (NMDBusDispatcher *dbus_dispatcher, + GDBusMethodInvocation *context, + const char *str_action, + GVariant *connection_dict, + GVariant *connection_props, + GVariant *device_props, + GVariant *device_proxy_props, + GVariant *device_ip4_props, + GVariant *device_ip6_props, + GVariant *device_dhcp4_props, + GVariant *device_dhcp6_props, + const char *connectivity_state, + const char *vpn_ip_iface, + GVariant *vpn_proxy_props, + GVariant *vpn_ip4_props, + GVariant *vpn_ip6_props, + gboolean request_debug, + gpointer user_data); + +static void +handler_init (Handler *h) +{ + 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); +} + +static void +handler_class_init (HandlerClass *h_class) +{ +} + +static gboolean dispatch_one_script (Request *request); + +typedef struct { + Request *request; + + char *script; + 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; + char **envp; + gboolean debug; + + GPtrArray *scripts; /* list of ScriptInfo */ + guint idx; + 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 ("req:%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) || _LOG_R_D_enabled (__request)) { \ + 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_debug, __VA_ARGS__) +#define _LOG_R_I(_request, ...) _LOG(_request, NULL, TRUE, g_info, __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_debug, __VA_ARGS__) +#define _LOG_S_I(_script, ...) _LOG(NULL, _script, TRUE, g_info, __VA_ARGS__) +#define _LOG_S_W(_script, ...) _LOG(NULL, _script, TRUE, g_warning, __VA_ARGS__) + +/*****************************************************************************/ + +static void +script_info_free (gpointer ptr) +{ + ScriptInfo *info = ptr; + + g_free (info->script); + g_free (info->error); + 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); + g_ptr_array_free (request->scripts, TRUE); + + g_slice_free (Request, request); +} + +static gboolean +quit_timeout_cb (gpointer user_data) +{ + g_main_loop_quit (loop); + return FALSE; +} + +static void +quit_timeout_reschedule (void) +{ + if (!persist) { + nm_clear_g_source (&quit_id); + quit_id = g_timeout_add_seconds (10, quit_timeout_cb, NULL); + } +} + +/** + * 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) { + 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 = g_queue_pop_head (h->requests_waiting); + if (!request) + return FALSE; + } + + _LOG_R_I (request, "start running ordered scripts..."); + + h->current_request = request; + + return TRUE; +} + +/** + * 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) +{ + GVariantBuilder results; + GVariant *ret; + guint i; + Handler *handler = request->handler; + + nm_assert (request); + + /* Are there still pending scripts? Then do nothing (for now). */ + if (request->num_scripts_done < request->scripts->len) + return; + + 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); + + g_variant_builder_add (&results, "(sus)", + script->script, + script->result, + script->error ? script->error : ""); + } + + ret = g_variant_new ("(a(sus))", &results); + g_dbus_method_invocation_return_value (request->context, ret); + + _LOG_R_D (request, "completed (%u scripts)", request->scripts->len); + + if (handler->current_request == request) + handler->current_request = NULL; + + request_free (request); + + 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 +script_watch_cb (GPid pid, gint status, gpointer user_data) +{ + ScriptInfo *script = user_data; + guint err; + + g_assert (pid == script->pid); + + 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); + if (err == 0) + script->result = DISPATCH_RESULT_SUCCESS; + else { + script->error = g_strdup_printf ("Script '%s' exited with error status %d.", + script->script, err); + } + } else if (WIFSTOPPED (status)) { + script->error = g_strdup_printf ("Script '%s' stopped unexpectedly with signal %d.", + script->script, WSTOPSIG (status)); + } else if (WIFSIGNALED (status)) { + script->error = g_strdup_printf ("Script '%s' died with signal %d", + script->script, WTERMSIG (status)); + } else { + script->error = g_strdup_printf ("Script '%s' died from an unknown cause", + script->script); + } + + if (script->result == DISPATCH_RESULT_SUCCESS) { + _LOG_S_D (script, "complete"); + } else { + script->result = DISPATCH_RESULT_FAILED; + _LOG_S_W (script, "complete: failed with %s", script->error); + } + + g_spawn_close_pid (script->pid); + + complete_script (script); +} + +static gboolean +script_timeout_cb (gpointer user_data) +{ + ScriptInfo *script = user_data; + + script->timeout_id = 0; + nm_clear_g_source (&script->watch_id); + script->request->num_scripts_done++; + if (!script->wait) + script->request->num_scripts_nowait--; + + _LOG_S_W (script, "complete: timeout (kill script)"); + + kill (script->pid, SIGKILL); +again: + if (waitpid (script->pid, NULL, 0) == -1) { + if (errno == EINTR) + goto again; + } + + script->error = g_strdup_printf ("Script '%s' timed out.", script->script); + script->result = DISPATCH_RESULT_TIMEOUT; + + g_spawn_close_pid (script->pid); + + complete_script (script); + + return FALSE; +} + +static inline gboolean +check_permissions (struct stat *s, const char **out_error_msg) +{ + g_return_val_if_fail (s != NULL, FALSE); + g_return_val_if_fail (out_error_msg != NULL, FALSE); + g_return_val_if_fail (*out_error_msg == NULL, FALSE); + + /* Only accept regular files */ + if (!S_ISREG (s->st_mode)) { + *out_error_msg = "not a regular file."; + return FALSE; + } + + /* Only accept files owned by root */ + if (s->st_uid != 0) { + *out_error_msg = "not owned by root."; + return FALSE; + } + + /* Only accept files not writable by group or other, and not SUID */ + if (s->st_mode & (S_IWGRP | S_IWOTH | S_ISUID)) { + *out_error_msg = "writable by group or other, or set-UID."; + return FALSE; + } + + /* Only accept files executable by the owner */ + if (!(s->st_mode & S_IXUSR)) { + *out_error_msg = "not executable by owner."; + return FALSE; + } + + return TRUE; +} + +static gboolean +check_filename (const char *file_name) +{ + static const char *bad_suffixes[] = { + "~", + ".rpmsave", + ".rpmorig", + ".rpmnew", + ".swp", + }; + char *tmp; + guint i; + + /* File must not be a backup file, package management file, or start with '.' */ + + if (file_name[0] == '.') + return FALSE; + for (i = 0; i < G_N_ELEMENTS (bad_suffixes); i++) { + if (g_str_has_suffix (file_name, bad_suffixes[i])) + return FALSE; + } + tmp = g_strrstr (file_name, ".dpkg-"); + if (tmp && !strchr (&tmp[1], '.')) + return FALSE; + return TRUE; +} + +#define SCRIPT_TIMEOUT 600 /* 10 minutes */ + +static gboolean +script_dispatch (ScriptInfo *script) +{ + GError *error = NULL; + gchar *argv[4]; + Request *request = script->request; + + if (script->dispatched) + return FALSE; + + script->dispatched = TRUE; + + argv[0] = script->script; + argv[1] = request->iface + ? request->iface + : (!strcmp (request->action, NMD_ACTION_HOSTNAME) ? "none" : ""); + argv[2] = request->action; + argv[3] = NULL; + + _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, 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 { + _LOG_S_W (script, "complete: failed to execute script: %s", error->message); + script->result = DISPATCH_RESULT_EXEC_FAILED; + script->error = g_strdup (error->message); + request->num_scripts_done++; + g_clear_error (&error); + return FALSE; + } +} + +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 * +find_scripts (const char *str_action) +{ + GDir *dir; + const char *filename; + GSList *sorted = NULL; + GError *error = NULL; + const char *dirname; + + if ( strcmp (str_action, NMD_ACTION_PRE_UP) == 0 + || strcmp (str_action, NMD_ACTION_VPN_PRE_UP) == 0) + dirname = NMD_SCRIPT_DIR_PRE_UP; + else if ( strcmp (str_action, NMD_ACTION_PRE_DOWN) == 0 + || strcmp (str_action, NMD_ACTION_VPN_PRE_DOWN) == 0) + dirname = NMD_SCRIPT_DIR_PRE_DOWN; + else + dirname = NMD_SCRIPT_DIR_DEFAULT; + + if (!(dir = g_dir_open (dirname, 0, &error))) { + g_message ("find-scripts: Failed to open dispatcher directory '%s': %s", + dirname, error->message); + g_error_free (error); + return NULL; + } + + while ((filename = g_dir_read_name (dir))) { + char *path; + struct stat st; + int err; + const char *err_msg = NULL; + + if (!check_filename (filename)) + continue; + + path = g_build_filename (dirname, filename, NULL); + + err = stat (path, &st); + if (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 ("find-scripts: Cannot execute '%s': %s", path, err_msg); + else { + /* success */ + sorted = g_slist_insert_sorted (sorted, path, (GCompareFunc) g_strcmp0); + path = NULL; + } + g_free (path); + } + g_dir_close (dir); + + return sorted; +} + +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, + GVariant *connection_dict, + GVariant *connection_props, + GVariant *device_props, + GVariant *device_proxy_props, + GVariant *device_ip4_props, + GVariant *device_ip6_props, + GVariant *device_dhcp4_props, + GVariant *device_dhcp6_props, + const char *connectivity_state, + const char *vpn_ip_iface, + GVariant *vpn_proxy_props, + GVariant *vpn_ip4_props, + GVariant *vpn_ip6_props, + gboolean request_debug, + gpointer user_data) +{ + Handler *h = user_data; + GSList *sorted_scripts = NULL; + GSList *iter; + Request *request; + char **p; + guint i, num_nowait = 0; + const char *error_message = NULL; + + sorted_scripts = find_scripts (str_action); + + request = g_slice_new0 (Request); + request->request_id = ++request_id_counter; + request->handler = h; + request->debug = request_debug || debug; + request->context = context; + request->action = g_strdup (str_action); + + request->envp = nm_dispatcher_utils_construct_envp (str_action, + connection_dict, + connection_props, + device_props, + device_proxy_props, + device_ip4_props, + device_ip6_props, + device_dhcp4_props, + device_dhcp6_props, + connectivity_state, + vpn_ip_iface, + vpn_proxy_props, + vpn_ip4_props, + vpn_ip6_props, + &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; + + 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); + + _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; +} + +static gboolean ever_acquired_name = FALSE; + +static void +on_name_acquired (GDBusConnection *connection, + const char *name, + gpointer user_data) +{ + ever_acquired_name = TRUE; +} + +static void +on_name_lost (GDBusConnection *connection, + const char *name, + gpointer user_data) +{ + if (!connection) { + if (!ever_acquired_name) { + g_warning ("Could not get the system bus. Make sure the message bus daemon is running!"); + exit (1); + } else { + g_message ("System bus stopped. Exiting"); + exit (0); + } + } else if (!ever_acquired_name) { + g_warning ("Could not acquire the " NM_DISPATCHER_DBUS_SERVICE " service."); + exit (1); + } else { + g_message ("Lost the " NM_DISPATCHER_DBUS_SERVICE " name. Exiting"); + exit (0); + } +} + +static void +log_handler (const gchar *log_domain, + GLogLevelFlags log_level, + const gchar *message, + gpointer ignored) +{ + int syslog_priority; + + switch (log_level) { + case G_LOG_LEVEL_ERROR: + syslog_priority = LOG_CRIT; + break; + case G_LOG_LEVEL_CRITICAL: + syslog_priority = LOG_ERR; + break; + case G_LOG_LEVEL_WARNING: + syslog_priority = LOG_WARNING; + break; + case G_LOG_LEVEL_MESSAGE: + syslog_priority = LOG_NOTICE; + break; + case G_LOG_LEVEL_DEBUG: + syslog_priority = LOG_DEBUG; + break; + case G_LOG_LEVEL_INFO: + default: + syslog_priority = LOG_INFO; + break; + } + + syslog (syslog_priority, "%s", message); +} + + +static void +logging_setup (void) +{ + openlog (G_LOG_DOMAIN, LOG_CONS, LOG_DAEMON); + g_log_set_handler (G_LOG_DOMAIN, + G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION, + log_handler, + NULL); +} + +static void +logging_shutdown (void) +{ + closelog (); +} + +static gboolean +signal_handler (gpointer user_data) +{ + int signo = GPOINTER_TO_INT (user_data); + + g_message ("Caught signal %d, shutting down...", signo); + g_main_loop_quit (loop); + + return G_SOURCE_REMOVE; +} + +int +main (int argc, char **argv) +{ + GOptionContext *opt_ctx; + GError *error = NULL; + GDBusConnection *bus; + Handler *handler; + + GOptionEntry entries[] = { + { "debug", 0, 0, G_OPTION_ARG_NONE, &debug, "Output to console rather than syslog", NULL }, + { "persist", 0, 0, G_OPTION_ARG_NONE, &persist, "Don't quit after a short timeout", NULL }, + { NULL } + }; + + opt_ctx = g_option_context_new (NULL); + g_option_context_set_summary (opt_ctx, "Executes scripts upon actions by NetworkManager."); + g_option_context_add_main_entries (opt_ctx, entries, NULL); + + if (!g_option_context_parse (opt_ctx, &argc, &argv, &error)) { + g_warning ("Error parsing command line arguments: %s", error->message); + g_error_free (error); + return 1; + } + + g_option_context_free (opt_ctx); + + 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)); + + + if (debug) { + if (!g_getenv ("G_MESSAGES_DEBUG")) { + /* we log our regular messages using g_debug() and g_info(). + * When we redirect glib logging to syslog, there is no problem. + * But in "debug" mode, glib will no print these messages unless + * we set G_MESSAGES_DEBUG. */ + g_setenv ("G_MESSAGES_DEBUG", "all", TRUE); + } + } else + logging_setup (); + + loop = g_main_loop_new (NULL, FALSE); + + bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); + if (!bus) { + g_warning ("Could not get the system bus (%s). Make sure the message bus daemon is running!", + error->message); + g_error_free (error); + return 1; + } + + handler = g_object_new (HANDLER_TYPE, NULL); + g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (handler->dbus_dispatcher), + bus, + NM_DISPATCHER_DBUS_PATH, + &error); + if (error) { + g_warning ("Could not export Dispatcher D-Bus interface: %s", error->message); + g_error_free (error); + return 1; + } + + g_bus_own_name_on_connection (bus, + NM_DISPATCHER_DBUS_SERVICE, + G_BUS_NAME_OWNER_FLAGS_NONE, + on_name_acquired, + on_name_lost, + NULL, NULL); + g_object_unref (bus); + + quit_timeout_reschedule (); + + g_main_loop_run (loop); + + g_queue_free (handler->requests_waiting); + g_object_unref (handler); + + if (!debug) + logging_shutdown (); + + return 0; +} + diff --git a/dispatcher/nm-dispatcher.conf b/dispatcher/nm-dispatcher.conf new file mode 100644 index 0000000000..fd2f0e5a42 --- /dev/null +++ b/dispatcher/nm-dispatcher.conf @@ -0,0 +1,14 @@ +<!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_dispatcher"/> + <allow send_destination="org.freedesktop.nm_dispatcher"/> + </policy> + <policy context="default"> + <deny own="org.freedesktop.nm_dispatcher"/> + <deny send_destination="org.freedesktop.nm_dispatcher"/> + </policy> +</busconfig> + diff --git a/dispatcher/nm-dispatcher.xml b/dispatcher/nm-dispatcher.xml new file mode 100644 index 0000000000..0d9d28e2c2 --- /dev/null +++ b/dispatcher/nm-dispatcher.xml @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="UTF-8"?> +<node name="/"> + <interface name="org.freedesktop.nm_dispatcher"> + <annotation name="org.gtk.GDBus.C.Name" value="Dispatcher"/> + + <!-- + Action: + @action: The action being performed. + @connection: The connection for which this action was triggered. + @connection_properties: Properties of the connection, including service and path. + @device_properties: Properties of the device, including type, path, interface, and state. + @device_proxy_properties: Properties of the device's proxy configuration. + @device_ip4_config: Properties of the device's IPv4 configuration. + @device_ip6_config: Properties of the device's IPv6 configuration. + @device_dhcp4_config: Properties of the device's DHCPv4 configuration. + @device_dhcp6_config: Properties of the device's DHCPv6 configuration. + @connectivity_state: Current connectivity state: unknown, none, limited, portal or full. + @vpn_ip_iface: VPN interface name. + @vpn_proxy_properties: Properties of the VPN's proxy configuration. + @vpn_ip4_config: Properties of the VPN's IPv4 configuration. + @vpn_ip6_config: Properties of the VPN's IPv6 configuration. + @debug: Whether to log debug output. + @results: Results of dispatching operations. Each element of the returned array is a struct containing the path of an executed script (s), the result of running that script (u), and a description of the result (s). + + INTERNAL; not public API. Perform an action. + --> + <method name="Action"> + <arg name="action" type="s" direction="in"/> + <arg name="connection" type="a{sa{sv}}" direction="in"/> + <arg name="connection_properties" type="a{sv}" direction="in"/> + <arg name="device_properties" type="a{sv}" direction="in"/> + <arg name="device_proxy_properties" type="a{sv}" direction="in"/> + <arg name="device_ip4_config" type="a{sv}" direction="in"/> + <arg name="device_ip6_config" type="a{sv}" direction="in"/> + <arg name="device_dhcp4_config" type="a{sv}" direction="in"/> + <arg name="device_dhcp6_config" type="a{sv}" direction="in"/> + <arg name="connectivity_state" type="s" direction="in"/> + <arg name="vpn_ip_iface" type="s" direction="in"/> + <arg name="vpn_proxy_properties" type="a{sv}" direction="in"/> + <arg name="vpn_ip4_config" type="a{sv}" direction="in"/> + <arg name="vpn_ip6_config" type="a{sv}" direction="in"/> + <arg name="debug" type="b" direction="in"/> + <arg name="results" type="a(sus)" direction="out"/> + </method> + </interface> +</node> diff --git a/dispatcher/org.freedesktop.nm_dispatcher.service.in b/dispatcher/org.freedesktop.nm_dispatcher.service.in new file mode 100644 index 0000000000..ff037cca9f --- /dev/null +++ b/dispatcher/org.freedesktop.nm_dispatcher.service.in @@ -0,0 +1,6 @@ +[D-BUS Service] +Name=org.freedesktop.nm_dispatcher +Exec=@libexecdir@/nm-dispatcher +User=root +SystemdService=dbus-org.freedesktop.nm-dispatcher.service + diff --git a/dispatcher/tests/Makefile.am b/dispatcher/tests/Makefile.am new file mode 100644 index 0000000000..e7e50adc03 --- /dev/null +++ b/dispatcher/tests/Makefile.am @@ -0,0 +1,41 @@ +if ENABLE_TESTS + +AM_CPPFLAGS = \ + -I$(top_srcdir)/shared \ + -I$(top_builddir)/shared \ + -I$(top_srcdir)/libnm-core \ + -I$(top_builddir)/libnm-core \ + -I$(top_srcdir)/dispatcher \ + -I$(top_builddir)/dispatcher \ + -DNETWORKMANAGER_COMPILATION \ + -DSRCDIR=\"$(abs_srcdir)\" \ + $(GLIB_CFLAGS) + +noinst_PROGRAMS = \ + test-dispatcher-envp + +############################################################################### + +test_dispatcher_envp_SOURCES = \ + test-dispatcher-envp.c + +test_dispatcher_envp_LDADD = \ + $(top_builddir)/libnm/libnm.la \ + $(top_builddir)/dispatcher/libnm-dispatcher-core.la \ + $(GLIB_LIBS) + +############################################################################### + +@VALGRIND_RULES@ +TESTS = test-dispatcher-envp + +endif + +EXTRA_DIST= \ + dispatcher-connectivity-full \ + dispatcher-connectivity-unknown \ + dispatcher-down \ + dispatcher-external \ + dispatcher-up \ + dispatcher-vpn-down \ + dispatcher-vpn-up diff --git a/dispatcher/tests/dispatcher-connectivity-full b/dispatcher/tests/dispatcher-connectivity-full new file mode 100644 index 0000000000..0b2796d12d --- /dev/null +++ b/dispatcher/tests/dispatcher-connectivity-full @@ -0,0 +1,23 @@ +[main] +action=connectiviy-change +expected-iface=wlan0 +uuid=3fd2a33a-d81b-423f-ae99-e6baba742311 +id=Random Connection +connectivity-state=FULL + +[device] +state=30 +ip-interface=wlan0 +type=2 +interface=wlan0 +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=/dispatcher/tests/dispatcher-connectivity-full +DEVICE_IFACE=wlan0 +DEVICE_IP_IFACE=wlan0 +CONNECTIVITY_STATE=FULL diff --git a/dispatcher/tests/dispatcher-connectivity-unknown b/dispatcher/tests/dispatcher-connectivity-unknown new file mode 100644 index 0000000000..4d797712a4 --- /dev/null +++ b/dispatcher/tests/dispatcher-connectivity-unknown @@ -0,0 +1,22 @@ +[main] +action=connectiviy-change +expected-iface=wlan0 +uuid=3fd2a33a-d81b-423f-ae99-e6baba742311 +id=Random Connection +connectivity-state=UNKNOWN + +[device] +state=30 +ip-interface=wlan0 +type=2 +interface=wlan0 +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=/dispatcher/tests/dispatcher-connectivity-unknown +DEVICE_IFACE=wlan0 +DEVICE_IP_IFACE=wlan0 diff --git a/dispatcher/tests/dispatcher-down b/dispatcher/tests/dispatcher-down new file mode 100644 index 0000000000..e0e44a7234 --- /dev/null +++ b/dispatcher/tests/dispatcher-down @@ -0,0 +1,22 @@ +[main] +action=down +expected-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] +PATH= +CONNECTION_UUID=3fd2a33a-d81b-423f-ae99-e6baba742311 +CONNECTION_DBUS_PATH=/org/freedesktop/NetworkManager/Connections/5 +CONNECTION_ID=Random Connection +CONNECTION_FILENAME=/dispatcher/tests/dispatcher-down +DEVICE_IFACE=wlan0 +DEVICE_IP_IFACE=wlan0 + diff --git a/dispatcher/tests/dispatcher-external b/dispatcher/tests/dispatcher-external new file mode 100644 index 0000000000..41f01d4636 --- /dev/null +++ b/dispatcher/tests/dispatcher-external @@ -0,0 +1,39 @@ +[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 + +[proxy] +pac-url=http://networkmanager.com/proxy.pac +pac-script="function FindProxyForURL (url, host) {}" + +[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=/dispatcher/tests/dispatcher-external +CONNECTION_ID=virbr0 +CONNECTION_EXTERNAL=1 +DEVICE_IFACE=virbr0 +DEVICE_IP_IFACE=virbr0 +PROXY_PAC_URL=http://networkmanager.com/proxy.pac +PROXY_PAC_SCRIPT="function FindProxyForURL (url, host) {}" +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/dispatcher/tests/dispatcher-up b/dispatcher/tests/dispatcher-up new file mode 100644 index 0000000000..ad7e87d3b1 --- /dev/null +++ b/dispatcher/tests/dispatcher-up @@ -0,0 +1,65 @@ +[main] +action=up +expected-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 + +[proxy] +pac-url=http://networkmanager.com/proxy.pac +pac-script="function FindProxyForURL (url, host) {}" + +[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] +PATH= +CONNECTION_UUID=3fd2a33a-d81b-423f-ae99-e6baba742311 +CONNECTION_DBUS_PATH=/org/freedesktop/NetworkManager/Connections/5 +CONNECTION_ID=Random Connection +CONNECTION_FILENAME=/dispatcher/tests/dispatcher-up +DEVICE_IFACE=wlan0 +DEVICE_IP_IFACE=wlan0 +PROXY_PAC_URL=http://networkmanager.com/proxy.pac +PROXY_PAC_SCRIPT="function FindProxyForURL (url, host) {}" +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_GATEWAY=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/dispatcher/tests/dispatcher-vpn-down b/dispatcher/tests/dispatcher-vpn-down new file mode 100644 index 0000000000..c921f105d5 --- /dev/null +++ b/dispatcher/tests/dispatcher-vpn-down @@ -0,0 +1,64 @@ +[main] +action=vpn-down +expected-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 + +[proxy] +pac-url=http://networkmanager.com/proxy.pac +pac-script="function FindProxyForURL (url, host) {}" + +[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] +PATH= +CONNECTION_UUID=355653c0-34d3-4777-ad25-f9a498b7ef8e +CONNECTION_DBUS_PATH=/org/freedesktop/NetworkManager/Connections/5 +CONNECTION_ID=Random Connection +CONNECTION_FILENAME=/dispatcher/tests/dispatcher-vpn-down +DEVICE_IFACE=wlan0 +DEVICE_IP_IFACE=tun0 +PROXY_PAC_URL=http://networkmanager.com/proxy.pac +PROXY_PAC_SCRIPT="function FindProxyForURL (url, host) {}" +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_GATEWAY=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/dispatcher/tests/dispatcher-vpn-up b/dispatcher/tests/dispatcher-vpn-up new file mode 100644 index 0000000000..cbafc03131 --- /dev/null +++ b/dispatcher/tests/dispatcher-vpn-up @@ -0,0 +1,64 @@ +[main] +action=vpn-up +expected-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 + +[proxy] +pac-url=http://networkmanager.com/proxy.pac +pac-script="function FindProxyForURL (url, host) {}" + +[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] +PATH= +CONNECTION_UUID=355653c0-34d3-4777-ad25-f9a498b7ef8e +CONNECTION_DBUS_PATH=/org/freedesktop/NetworkManager/Connections/5 +CONNECTION_ID=Random Connection +CONNECTION_FILENAME=/dispatcher/tests/dispatcher-vpn-up +DEVICE_IFACE=wlan0 +DEVICE_IP_IFACE=tun0 +PROXY_PAC_URL=http://networkmanager.com/proxy.pac +PROXY_PAC_SCRIPT="function FindProxyForURL (url, host) {}" +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_GATEWAY=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/dispatcher/tests/test-dispatcher-envp.c b/dispatcher/tests/test-dispatcher-envp.c new file mode 100644 index 0000000000..6dd4db07b2 --- /dev/null +++ b/dispatcher/tests/test-dispatcher-envp.c @@ -0,0 +1,671 @@ +/* -*- 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 "nm-default.h" + +#include <arpa/inet.h> +#include <stdlib.h> +#include <string.h> + +#include "nm-core-internal.h" +#include "nm-dispatcher-utils.h" +#include "nm-dispatcher-api.h" + +#include "nm-utils/nm-test-utils.h" + +/*****************************************************************************/ + +static gboolean +parse_main (GKeyFile *kf, + const char *filename, + GVariant **out_con_dict, + GVariant **out_con_props, + char **out_expected_iface, + char **out_action, + char **out_connectivity_state, + char **out_vpn_ip_iface, + GError **error) +{ + char *uuid, *id; + NMConnection *connection; + NMSettingConnection *s_con; + GVariantBuilder props; + + *out_expected_iface = g_key_file_get_string (kf, "main", "expected-iface", error); + if (*out_expected_iface == NULL) + return FALSE; + + *out_connectivity_state = g_key_file_get_string (kf, "main", "connectivity-state", NULL); + *out_vpn_ip_iface = g_key_file_get_string (kf, "main", "vpn-ip-iface", NULL); + + *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_simple_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_dict = nm_connection_to_dbus (connection, NM_CONNECTION_SERIALIZE_ALL); + g_object_unref (connection); + + g_variant_builder_init (&props, G_VARIANT_TYPE ("a{sv}")); + g_variant_builder_add (&props, "{sv}", + 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, "/dispatcher"); + 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; +} + +static gboolean +parse_device (GKeyFile *kf, GVariant **out_device_props, GError **error) +{ + GVariantBuilder props; + char *tmp; + gint i; + + g_variant_builder_init (&props, G_VARIANT_TYPE ("a{sv}")); + + i = g_key_file_get_integer (kf, "device", "state", error); + if (i == 0) + return FALSE; + g_variant_builder_add (&props, "{sv}", + NMD_DEVICE_PROPS_STATE, + g_variant_new_uint32 (i)); + + i = g_key_file_get_integer (kf, "device", "type", error); + if (i == 0) + return FALSE; + g_variant_builder_add (&props, "{sv}", + NMD_DEVICE_PROPS_TYPE, + g_variant_new_uint32 (i)); + + tmp = g_key_file_get_string (kf, "device", "interface", error); + if (tmp == NULL) + return FALSE; + g_variant_builder_add (&props, "{sv}", + NMD_DEVICE_PROPS_INTERFACE, + g_variant_new_string (tmp)); + g_free (tmp); + + tmp = g_key_file_get_string (kf, "device", "ip-interface", error); + if (tmp == NULL) + return FALSE; + g_variant_builder_add (&props, "{sv}", + NMD_DEVICE_PROPS_IP_INTERFACE, + g_variant_new_string (tmp)); + g_free (tmp); + + tmp = g_key_file_get_string (kf, "device", "path", error); + if (tmp == NULL) + return FALSE; + g_variant_builder_add (&props, "{sv}", + NMD_DEVICE_PROPS_PATH, + g_variant_new_object_path (tmp)); + g_free (tmp); + + *out_device_props = g_variant_builder_end (&props); + return TRUE; +} + +static gboolean +add_uint_array (GKeyFile *kf, + GVariantBuilder *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))) { + guint32 addr; + + g_assert_cmpint (inet_pton (AF_INET, *iter, &addr), ==, 1); + g_array_append_val (items, addr); + } + } + g_variant_builder_add (props, "{sv}", key, + g_variant_new_fixed_array (G_VARIANT_TYPE_UINT32, + items->data, items->len, + sizeof (guint32))); + g_array_unref (items); + } + g_strfreev (split); + return TRUE; +} + +static gboolean +parse_proxy (GKeyFile *kf, GVariant **out_props, const char *section, GError **error) +{ + GVariantBuilder props; + char *tmp; + + g_variant_builder_init (&props, G_VARIANT_TYPE ("a{sv}")); + + tmp = g_key_file_get_string (kf, section, "pac-url", error); + if (tmp == NULL) + return FALSE; + g_variant_builder_add (&props, "{sv}", + "pac-url", + g_variant_new_string (tmp)); + g_free (tmp); + + tmp = g_key_file_get_string (kf, section, "pac-script", error); + if (tmp == NULL) + return FALSE; + g_variant_builder_add (&props, "{sv}", + "pac-script", + g_variant_new_string (tmp)); + g_free (tmp); + *out_props = g_variant_builder_end (&props); + return TRUE; +} + +static gboolean +parse_ip4 (GKeyFile *kf, GVariant **out_props, const char *section, GError **error) +{ + GVariantBuilder props; + char *tmp; + char **split, **iter; + GPtrArray *addresses, *routes; + const char *gateway = NULL; + + g_variant_builder_init (&props, G_VARIANT_TYPE ("a{sv}")); + + /* search domains */ + /* Use char** for domains. (DBUS_TYPE_G_ARRAY_OF_STRING of NMIP4Config + * becomes G_TYPE_STRV when sending the value over D-Bus) + */ + 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 (split && g_strv_length (split) > 0) { + for (iter = split; iter && *iter; iter++) + g_strstrip (*iter); + g_variant_builder_add (&props, "{sv}", "domains", g_variant_new_strv ((gpointer) split, -1)); + } + g_strfreev (split); + + /* nameservers */ + if (!add_uint_array (kf, &props, "ip4", "nameservers", error)) + return FALSE; + /* wins-servers */ + if (!add_uint_array (kf, &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 (split && g_strv_length (split) > 0) { + addresses = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_ip_address_unref); + for (iter = split; iter && *iter; iter++) { + NMIPAddress *addr; + char *ip, *prefix; + + if (strlen (g_strstrip (*iter)) == 0) + continue; + + ip = *iter; + + prefix = strchr (ip, '/'); + g_assert (prefix); + *prefix++ = '\0'; + + if (addresses->len == 0) { + gateway = strchr (prefix, ' '); + g_assert (gateway); + gateway++; + } + + addr = nm_ip_address_new (AF_INET, ip, (guint) atoi (prefix), error); + if (!addr) { + g_ptr_array_unref (addresses); + return FALSE; + } + g_ptr_array_add (addresses, addr); + } + + g_variant_builder_add (&props, "{sv}", "addresses", + nm_utils_ip4_addresses_to_variant (addresses, gateway)); + g_ptr_array_unref (addresses); + } + 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 (split && g_strv_length (split) > 0) { + routes = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_ip_route_unref); + for (iter = split; iter && *iter; iter++) { + NMIPRoute *route; + char *dest, *prefix, *next_hop, *metric; + + if (strlen (g_strstrip (*iter)) == 0) + continue; + + dest = *iter; + + prefix = strchr (dest, '/'); + g_assert (prefix); + *prefix++ = '\0'; + + next_hop = strchr (prefix, ' '); + g_assert (next_hop); + next_hop++; + + metric = strchr (next_hop, ' '); + g_assert (metric); + metric++; + + route = nm_ip_route_new (AF_INET, + dest, (guint) atoi (prefix), + next_hop, (guint) atoi (metric), + error); + if (!route) { + g_ptr_array_unref (routes); + return FALSE; + } + g_ptr_array_add (routes, route); + } + + g_variant_builder_add (&props, "{sv}", "routes", + nm_utils_ip4_routes_to_variant (routes)); + g_ptr_array_unref (routes); + } + g_strfreev (split); + } + + *out_props = g_variant_builder_end (&props); + return TRUE; +} + +static gboolean +parse_dhcp (GKeyFile *kf, + const char *group_name, + GVariant **out_props, + GError **error) +{ + char **keys, **iter, *val; + GVariantBuilder props; + + keys = g_key_file_get_keys (kf, group_name, NULL, error); + if (!keys) + return FALSE; + + g_variant_builder_init (&props, G_VARIANT_TYPE ("a{sv}")); + for (iter = keys; iter && *iter; iter++) { + val = g_key_file_get_string (kf, group_name, *iter, error); + if (!val) { + g_strfreev (keys); + g_variant_builder_clear (&props); + return FALSE; + } + g_variant_builder_add (&props, "{sv}", *iter, g_variant_new_string (val)); + g_free (val); + } + g_strfreev (keys); + + *out_props = g_variant_builder_end (&props); + return TRUE; +} + +static gboolean +get_dispatcher_file (const char *file, + GVariant **out_con_dict, + GVariant **out_con_props, + GVariant **out_device_props, + GVariant **out_device_proxy_props, + GVariant **out_device_ip4_props, + GVariant **out_device_ip6_props, + GVariant **out_device_dhcp4_props, + GVariant **out_device_dhcp6_props, + char **out_connectivity_state, + char **out_vpn_ip_iface, + GVariant **out_vpn_proxy_props, + GVariant **out_vpn_ip4_props, + GVariant **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; + + g_assert (!error || !*error); + g_assert (out_con_dict && !*out_con_dict); + g_assert (out_con_props && !*out_con_props); + g_assert (out_device_props && !*out_device_props); + g_assert (out_device_proxy_props && !*out_device_proxy_props); + g_assert (out_device_ip4_props && !*out_device_ip4_props); + g_assert (out_device_ip6_props && !*out_device_ip6_props); + g_assert (out_device_dhcp4_props && !*out_device_dhcp4_props); + g_assert (out_device_dhcp6_props && !*out_device_dhcp6_props); + g_assert (out_connectivity_state && !*out_connectivity_state); + g_assert (out_vpn_ip_iface && !*out_vpn_ip_iface); + g_assert (out_vpn_proxy_props && !*out_vpn_proxy_props); + g_assert (out_vpn_ip4_props && !*out_vpn_ip4_props); + g_assert (out_vpn_ip6_props && !*out_vpn_ip6_props); + g_assert (out_expected_iface && !*out_expected_iface); + g_assert (out_action && !*out_action); + g_assert (out_env && !*out_env); + + 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, + file, + out_con_dict, + out_con_props, + out_expected_iface, + out_action, + out_connectivity_state, + out_vpn_ip_iface, + error)) + goto out; + + if (!parse_device (kf, out_device_props, error)) + goto out; + + if (g_key_file_has_group (kf, "proxy")) { + if (!parse_proxy (kf, out_device_proxy_props, "proxy", 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_dhcp6_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 *file, const char *override_vpn_ip_iface) +{ + gs_unref_variant GVariant *con_dict = NULL; + gs_unref_variant GVariant *con_props = NULL; + gs_unref_variant GVariant *device_props = NULL; + gs_unref_variant GVariant *device_proxy_props = NULL; + gs_unref_variant GVariant *device_ip4_props = NULL; + gs_unref_variant GVariant *device_ip6_props = NULL; + gs_unref_variant GVariant *device_dhcp4_props = NULL; + gs_unref_variant GVariant *device_dhcp6_props = NULL; + gs_free char *connectivity_change = NULL; + gs_free char *vpn_ip_iface = NULL; + gs_unref_variant GVariant *vpn_proxy_props = NULL; + gs_unref_variant GVariant *vpn_ip4_props = NULL; + gs_unref_variant GVariant *vpn_ip6_props = NULL; + gs_free char *expected_iface = NULL; + gs_free char *action = NULL; + gs_free char *out_iface = NULL; + const char *error_message = NULL; + gs_unref_hashtable GHashTable *expected_env = NULL; + GError *error = NULL; + gboolean success; + char *p; + gs_strfreev char **denv = NULL; + char **iter; + + /* Read in the test file */ + p = g_build_filename (SRCDIR, file, NULL); + success = get_dispatcher_file (p, + &con_dict, + &con_props, + &device_props, + &device_proxy_props, + &device_ip4_props, + &device_ip6_props, + &device_dhcp4_props, + &device_dhcp6_props, + &connectivity_change, + &vpn_ip_iface, + &vpn_proxy_props, + &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_dict, + con_props, + device_props, + device_proxy_props, + device_ip4_props, + device_ip6_props, + device_dhcp4_props, + device_dhcp6_props, + connectivity_change, + override_vpn_ip_iface ? override_vpn_ip_iface : vpn_ip_iface, + vpn_proxy_props, + vpn_ip4_props, + vpn_ip6_props, + &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 + g_message ("\n******* Generated environment:"); + for (iter = denv; iter && *iter; iter++) + g_message (" %s", *iter); +#endif + +#ifdef DEBUG + { + GHashTableIter k; + const char *key; + + g_message ("\n******* Expected environment:"); + g_hash_table_iter_init (&k, expected_env); + while (g_hash_table_iter_next (&k, (gpointer) &key, NULL)) + g_message (" %s", key); + } +#endif + + g_assert_cmpint (g_strv_length (denv), ==, g_hash_table_size (expected_env)); + + /* Compare dispatcher generated env and expected env */ + for (iter = denv; iter && *iter; iter++) { + gpointer foo; + const char *i_value = *iter; + + if (strstr (i_value, "PATH=") == i_value) { + g_assert_cmpstr (&i_value[strlen("PATH=")], ==, g_getenv ("PATH")); + + /* The path is constructed dynamically. Ignore the actual value. */ + i_value = "PATH="; + } + + foo = g_hash_table_lookup (expected_env, i_value); + if (!foo) + g_warning ("Failed to find %s in environment", i_value); + g_assert (foo); + } + + g_assert_cmpstr (expected_iface, ==, out_iface); +} + +/*****************************************************************************/ + +static void +test_up (void) +{ + test_generic ("dispatcher-up", NULL); +} + +static void +test_down (void) +{ + test_generic ("dispatcher-down", NULL); +} + +static void +test_vpn_up (void) +{ + test_generic ("dispatcher-vpn-up", NULL); +} + +static void +test_vpn_down (void) +{ + test_generic ("dispatcher-vpn-down", NULL); +} + +static void +test_external (void) +{ + test_generic ("dispatcher-external", NULL); +} + +static void +test_connectivity_changed (void) +{ + /* These tests will check that the CONNECTIVITY_STATE environment + * variable is only defined for known states, such as 'full'. */ + test_generic ("dispatcher-connectivity-unknown", NULL); + test_generic ("dispatcher-connectivity-full", NULL); +} + +static void +test_up_empty_vpn_iface (void) +{ + /* Test that an empty VPN iface variable, like is passed through D-Bus + * from NM, is ignored by the dispatcher environment construction code. + */ + test_generic ("dispatcher-up", ""); +} + +/*****************************************************************************/ + +NMTST_DEFINE (); + +int +main (int argc, char **argv) +{ + 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/connectivity_changed", test_connectivity_changed); + + g_test_add_func ("/dispatcher/up_empty_vpn_iface", test_up_empty_vpn_iface); + + return g_test_run (); +} + |