/* -*- 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) 2004 - 2017 Red Hat, Inc. * Copyright (C) 2005 - 2008 Novell, Inc. */ #include "nm-default.h" #include "nm-dispatcher.h" #include #include #include "nm-dispatcher-api.h" #include "NetworkManagerUtils.h" #include "nm-utils.h" #include "nm-connectivity.h" #include "nm-act-request.h" #include "devices/nm-device.h" #include "nm-dhcp4-config.h" #include "nm-dhcp6-config.h" #include "nm-proxy-config.h" #include "nm-ip4-config.h" #include "nm-ip6-config.h" #include "nm-manager.h" #include "settings/nm-settings-connection.h" #include "platform/nm-platform.h" #include "nm-core-internal.h" #define CALL_TIMEOUT (1000 * 60 * 10) /* 10 minutes for all scripts */ #define _NMLOG_DOMAIN LOGD_DISPATCH #define _NMLOG(level, ...) __NMLOG_DEFAULT (level, _NMLOG_DOMAIN, "dispatcher", __VA_ARGS__) static GDBusProxy *dispatcher_proxy; static GHashTable *requests = NULL; typedef struct { GFileMonitor *monitor; const char *const description; const char *const dir; const guint16 dir_len; char has_scripts; } Monitor; enum { MONITOR_INDEX_DEFAULT, MONITOR_INDEX_PRE_UP, MONITOR_INDEX_PRE_DOWN, }; static Monitor monitors[3] = { #define MONITORS_INIT_SET(INDEX, USE, SCRIPT_DIR) [INDEX] = { .dir_len = NM_STRLEN (SCRIPT_DIR), .dir = SCRIPT_DIR, .description = ("" USE), .has_scripts = TRUE } MONITORS_INIT_SET (MONITOR_INDEX_DEFAULT, "default", NMD_SCRIPT_DIR_DEFAULT), MONITORS_INIT_SET (MONITOR_INDEX_PRE_UP, "pre-up", NMD_SCRIPT_DIR_PRE_UP), MONITORS_INIT_SET (MONITOR_INDEX_PRE_DOWN, "pre-down", NMD_SCRIPT_DIR_PRE_DOWN), }; static const Monitor* _get_monitor_by_action (NMDispatcherAction action) { switch (action) { case NM_DISPATCHER_ACTION_PRE_UP: case NM_DISPATCHER_ACTION_VPN_PRE_UP: return &monitors[MONITOR_INDEX_PRE_UP]; case NM_DISPATCHER_ACTION_PRE_DOWN: case NM_DISPATCHER_ACTION_VPN_PRE_DOWN: return &monitors[MONITOR_INDEX_PRE_DOWN]; default: return &monitors[MONITOR_INDEX_DEFAULT]; } } static void dump_proxy_to_props (NMProxyConfig *proxy, GVariantBuilder *builder) { const char *pac_url = NULL, *pac_script = NULL; if (nm_proxy_config_get_method (proxy) == NM_PROXY_CONFIG_METHOD_NONE) return; pac_url = nm_proxy_config_get_pac_url (proxy); if (pac_url) { g_variant_builder_add (builder, "{sv}", "pac-url", g_variant_new_string (pac_url)); } pac_script = nm_proxy_config_get_pac_script (proxy); if (pac_script) { g_variant_builder_add (builder, "{sv}", "pac-script", g_variant_new_string (pac_script)); } } static void dump_ip4_to_props (NMIP4Config *ip4, GVariantBuilder *builder) { GVariantBuilder int_builder; NMDedupMultiIter ipconf_iter; gboolean first; guint n, i; const NMPlatformIP4Address *addr; const NMPlatformIP4Route *route; guint32 array[4]; /* Addresses */ g_variant_builder_init (&int_builder, G_VARIANT_TYPE ("aau")); first = TRUE; nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, ip4, &addr) { const NMPObject *default_route; array[0] = addr->address; array[1] = addr->plen; array[2] = ( first && (default_route = nm_ip4_config_best_default_route_get (ip4))) ? NMP_OBJECT_CAST_IP4_ROUTE (default_route)->gateway : (guint32) 0; g_variant_builder_add (&int_builder, "@au", g_variant_new_fixed_array (G_VARIANT_TYPE_UINT32, array, 3, sizeof (guint32))); first = FALSE; } g_variant_builder_add (builder, "{sv}", "addresses", g_variant_builder_end (&int_builder)); /* DNS servers */ g_variant_builder_init (&int_builder, G_VARIANT_TYPE ("au")); n = nm_ip4_config_get_num_nameservers (ip4); for (i = 0; i < n; i++) g_variant_builder_add (&int_builder, "u", nm_ip4_config_get_nameserver (ip4, i)); g_variant_builder_add (builder, "{sv}", "nameservers", g_variant_builder_end (&int_builder)); /* Search domains */ g_variant_builder_init (&int_builder, G_VARIANT_TYPE ("as")); n = nm_ip4_config_get_num_domains (ip4); for (i = 0; i < n; i++) g_variant_builder_add (&int_builder, "s", nm_ip4_config_get_domain (ip4, i)); g_variant_builder_add (builder, "{sv}", "domains", g_variant_builder_end (&int_builder)); /* WINS servers */ g_variant_builder_init (&int_builder, G_VARIANT_TYPE ("au")); n = nm_ip4_config_get_num_wins (ip4); for (i = 0; i < n; i++) g_variant_builder_add (&int_builder, "u", nm_ip4_config_get_wins (ip4, i)); g_variant_builder_add (builder, "{sv}", "wins-servers", g_variant_builder_end (&int_builder)); /* Static routes */ g_variant_builder_init (&int_builder, G_VARIANT_TYPE ("aau")); nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, ip4, &route) { if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT (route)) continue; array[0] = route->network; array[1] = route->plen; array[2] = route->gateway; array[3] = route->metric; g_variant_builder_add (&int_builder, "@au", g_variant_new_fixed_array (G_VARIANT_TYPE_UINT32, array, 4, sizeof (guint32))); } g_variant_builder_add (builder, "{sv}", "routes", g_variant_builder_end (&int_builder)); } static void dump_ip6_to_props (NMIP6Config *ip6, GVariantBuilder *builder) { GVariantBuilder int_builder; NMDedupMultiIter ipconf_iter; guint n, i; gboolean first; const NMPlatformIP6Address *addr; const NMPlatformIP6Route *route; GVariant *ip, *gw; /* Addresses */ g_variant_builder_init (&int_builder, G_VARIANT_TYPE ("a(ayuay)")); first = TRUE; nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, ip6, &addr) { const NMPObject *default_route; ip = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, &addr->address, sizeof (struct in6_addr), 1); gw = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, ( first && (default_route = nm_ip6_config_best_default_route_get (ip6))) ? &NMP_OBJECT_CAST_IP6_ROUTE (default_route)->gateway : &in6addr_any, sizeof (struct in6_addr), 1); g_variant_builder_add (&int_builder, "(@ayu@ay)", ip, addr->plen, gw); first = FALSE; } g_variant_builder_add (builder, "{sv}", "addresses", g_variant_builder_end (&int_builder)); /* DNS servers */ g_variant_builder_init (&int_builder, G_VARIANT_TYPE ("aay")); n = nm_ip6_config_get_num_nameservers (ip6); for (i = 0; i < n; i++) { ip = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, nm_ip6_config_get_nameserver (ip6, i), sizeof (struct in6_addr), 1); g_variant_builder_add (&int_builder, "@ay", ip); } g_variant_builder_add (builder, "{sv}", "nameservers", g_variant_builder_end (&int_builder)); /* Search domains */ g_variant_builder_init (&int_builder, G_VARIANT_TYPE ("as")); n = nm_ip6_config_get_num_domains (ip6); for (i = 0; i < n; i++) g_variant_builder_add (&int_builder, "s", nm_ip6_config_get_domain (ip6, i)); g_variant_builder_add (builder, "{sv}", "domains", g_variant_builder_end (&int_builder)); /* Static routes */ g_variant_builder_init (&int_builder, G_VARIANT_TYPE ("a(ayuayu)")); nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, ip6, &route) { if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT (route)) continue; ip = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, &route->network, sizeof (struct in6_addr), 1); gw = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, &route->gateway, sizeof (struct in6_addr), 1); g_variant_builder_add (&int_builder, "(@ayu@ayu)", ip, route->plen, gw, route->metric); } g_variant_builder_add (builder, "{sv}", "routes", g_variant_builder_end (&int_builder)); } static void fill_device_props (NMDevice *device, GVariantBuilder *dev_builder, GVariantBuilder *proxy_builder, GVariantBuilder *ip4_builder, GVariantBuilder *ip6_builder, GVariant **dhcp4_props, GVariant **dhcp6_props) { NMProxyConfig *proxy_config; NMIP4Config *ip4_config; NMIP6Config *ip6_config; NMDhcp4Config *dhcp4_config; NMDhcp6Config *dhcp6_config; /* If the action is for a VPN, send the VPN's IP interface instead of the device's */ g_variant_builder_add (dev_builder, "{sv}", NMD_DEVICE_PROPS_IP_INTERFACE, g_variant_new_string (nm_device_get_ip_iface (device))); g_variant_builder_add (dev_builder, "{sv}", NMD_DEVICE_PROPS_INTERFACE, g_variant_new_string (nm_device_get_iface (device))); g_variant_builder_add (dev_builder, "{sv}", NMD_DEVICE_PROPS_TYPE, g_variant_new_uint32 (nm_device_get_device_type (device))); g_variant_builder_add (dev_builder, "{sv}", NMD_DEVICE_PROPS_STATE, g_variant_new_uint32 (nm_device_get_state (device))); if (nm_exported_object_is_exported (NM_EXPORTED_OBJECT (device))) g_variant_builder_add (dev_builder, "{sv}", NMD_DEVICE_PROPS_PATH, g_variant_new_object_path (nm_exported_object_get_path (NM_EXPORTED_OBJECT (device)))); proxy_config = nm_device_get_proxy_config (device); if (proxy_config) dump_proxy_to_props (proxy_config, proxy_builder); ip4_config = nm_device_get_ip4_config (device); if (ip4_config) dump_ip4_to_props (ip4_config, ip4_builder); ip6_config = nm_device_get_ip6_config (device); if (ip6_config) dump_ip6_to_props (ip6_config, ip6_builder); dhcp4_config = nm_device_get_dhcp4_config (device); if (dhcp4_config) *dhcp4_props = nm_dhcp4_config_get_options (dhcp4_config); dhcp6_config = nm_device_get_dhcp6_config (device); if (dhcp6_config) *dhcp6_props = nm_dhcp6_config_get_options (dhcp6_config); } static void fill_vpn_props (NMProxyConfig *proxy_config, NMIP4Config *ip4_config, NMIP6Config *ip6_config, GVariantBuilder *proxy_builder, GVariantBuilder *ip4_builder, GVariantBuilder *ip6_builder) { if (proxy_config) dump_proxy_to_props (proxy_config, proxy_builder); if (ip4_config) dump_ip4_to_props (ip4_config, ip4_builder); if (ip6_config) dump_ip6_to_props (ip6_config, ip6_builder); } typedef struct { NMDispatcherAction action; guint request_id; NMDispatcherFunc callback; gpointer user_data; guint idle_id; } DispatchInfo; static void dispatcher_info_free (DispatchInfo *info) { if (info->idle_id) g_source_remove (info->idle_id); g_free (info); } static void _ensure_requests (void) { if (G_UNLIKELY (requests == NULL)) { requests = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) dispatcher_info_free); } } static void dispatcher_info_cleanup (DispatchInfo *info) { g_hash_table_remove (requests, GUINT_TO_POINTER (info->request_id)); } static const char * dispatch_result_to_string (DispatchResult result) { switch (result) { case DISPATCH_RESULT_UNKNOWN: return "unknown"; case DISPATCH_RESULT_SUCCESS: return "success"; case DISPATCH_RESULT_EXEC_FAILED: return "exec failed"; case DISPATCH_RESULT_FAILED: return "failed"; case DISPATCH_RESULT_TIMEOUT: return "timed out"; } g_assert_not_reached (); } static void dispatcher_results_process (guint request_id, NMDispatcherAction action, GVariantIter *results) { const char *script, *err; guint32 result; const Monitor *monitor = _get_monitor_by_action (action); g_return_if_fail (results != NULL); if (g_variant_iter_n_children (results) == 0) { _LOGD ("(%u) succeeded but no scripts invoked", request_id); return; } while (g_variant_iter_next (results, "(&su&s)", &script, &result, &err)) { const char *script_validation_msg = ""; if (!*script) { script_validation_msg = " (path is NULL)"; script = "(unknown)"; } else if (!strncmp (script, monitor->dir, monitor->dir_len) /* check: prefixed by script directory */ && script[monitor->dir_len] == '/' && script[monitor->dir_len+1] /* check: with additional "/?" */ && !strchr (&script[monitor->dir_len+1], '/')) { /* check: and no further '/' */ /* we expect the script to lie inside monitor->dir. If it does, * strip the directory name. Otherwise show the full path and a warning. */ script += monitor->dir_len + 1; } else script_validation_msg = " (unexpected path)"; if (result == DISPATCH_RESULT_SUCCESS) { _LOGD ("(%u) %s succeeded%s", request_id, script, script_validation_msg); } else { _LOGW ("(%u) %s failed (%s): %s%s", request_id, script, dispatch_result_to_string (result), err, script_validation_msg); } } } static void dispatcher_done_cb (GObject *proxy, GAsyncResult *result, gpointer user_data) { DispatchInfo *info = user_data; GVariant *ret; GVariantIter *results; GError *error = NULL; ret = _nm_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), result, G_VARIANT_TYPE ("(a(sus))"), &error); if (ret) { g_variant_get (ret, "(a(sus))", &results); dispatcher_results_process (info->request_id, info->action, results); g_variant_iter_free (results); g_variant_unref (ret); } else { if (_nm_dbus_error_has_name (error, "org.freedesktop.systemd1.LoadFailed")) { g_dbus_error_strip_remote_error (error); _LOGW ("(%u) failed to call dispatcher scripts: %s", info->request_id, error->message); } else { _LOGD ("(%u) failed to call dispatcher scripts: %s", info->request_id, error->message); } g_clear_error (&error); } if (info->callback) info->callback (info->request_id, info->user_data); dispatcher_info_cleanup (info); } static const char *action_table[] = { [NM_DISPATCHER_ACTION_HOSTNAME] = NMD_ACTION_HOSTNAME, [NM_DISPATCHER_ACTION_PRE_UP] = NMD_ACTION_PRE_UP, [NM_DISPATCHER_ACTION_UP] = NMD_ACTION_UP, [NM_DISPATCHER_ACTION_PRE_DOWN] = NMD_ACTION_PRE_DOWN, [NM_DISPATCHER_ACTION_DOWN] = NMD_ACTION_DOWN, [NM_DISPATCHER_ACTION_VPN_PRE_UP] = NMD_ACTION_VPN_PRE_UP, [NM_DISPATCHER_ACTION_VPN_UP] = NMD_ACTION_VPN_UP, [NM_DISPATCHER_ACTION_VPN_PRE_DOWN] = NMD_ACTION_VPN_PRE_DOWN, [NM_DISPATCHER_ACTION_VPN_DOWN] = NMD_ACTION_VPN_DOWN, [NM_DISPATCHER_ACTION_DHCP4_CHANGE] = NMD_ACTION_DHCP4_CHANGE, [NM_DISPATCHER_ACTION_DHCP6_CHANGE] = NMD_ACTION_DHCP6_CHANGE, [NM_DISPATCHER_ACTION_CONNECTIVITY_CHANGE] = NMD_ACTION_CONNECTIVITY_CHANGE }; static const char * action_to_string (NMDispatcherAction action) { g_assert ((gsize) action < G_N_ELEMENTS (action_table)); return action_table[action]; } static gboolean dispatcher_idle_cb (gpointer user_data) { DispatchInfo *info = user_data; info->idle_id = 0; if (info->callback) info->callback (info->request_id, info->user_data); dispatcher_info_cleanup (info); return G_SOURCE_REMOVE; } static gboolean _dispatcher_call (NMDispatcherAction action, gboolean blocking, NMDevice *device, NMSettingsConnection *settings_connection, NMConnection *applied_connection, gboolean activation_type_external, NMConnectivityState connectivity_state, const char *vpn_iface, NMProxyConfig *vpn_proxy_config, NMIP4Config *vpn_ip4_config, NMIP6Config *vpn_ip6_config, NMDispatcherFunc callback, gpointer user_data, guint *out_call_id) { GVariant *connection_dict; GVariantBuilder connection_props; GVariantBuilder device_props; GVariantBuilder device_proxy_props; GVariantBuilder device_ip4_props; GVariantBuilder device_ip6_props; GVariant *device_dhcp4_props = NULL; GVariant *device_dhcp6_props = NULL; GVariantBuilder vpn_proxy_props; GVariantBuilder vpn_ip4_props; GVariantBuilder vpn_ip6_props; DispatchInfo *info = NULL; gboolean success = FALSE; GError *error = NULL; static guint request_counter = 0; guint reqid = ++request_counter; const char *connectivity_state_string = "UNKNOWN"; if (!dispatcher_proxy) return FALSE; /* Wrapping protection */ if (G_UNLIKELY (!reqid)) reqid = ++request_counter; g_assert (!blocking || (!callback && !user_data)); _ensure_requests (); /* All actions except 'hostname' and 'connectivity-change' require a device */ if ( action == NM_DISPATCHER_ACTION_HOSTNAME || action == NM_DISPATCHER_ACTION_CONNECTIVITY_CHANGE) { _LOGD ("(%u) dispatching action '%s'%s", reqid, action_to_string (action), blocking ? " (blocking)" : (callback ? " (with callback)" : "")); } else { g_return_val_if_fail (NM_IS_DEVICE (device), FALSE); _LOGD ("(%u) (%s) dispatching action '%s'%s", reqid, vpn_iface ? vpn_iface : nm_device_get_iface (device), action_to_string (action), blocking ? " (blocking)" : (callback ? " (with callback)" : "")); } if (!_get_monitor_by_action(action)->has_scripts) { if (blocking == FALSE && (out_call_id || callback)) { info = g_malloc0 (sizeof (*info)); info->action = action; info->request_id = reqid; info->callback = callback; info->user_data = user_data; info->idle_id = g_idle_add (dispatcher_idle_cb, info); _LOGD ("(%u) simulate request; no scripts in %s", reqid, _get_monitor_by_action(action)->dir); } else _LOGD ("(%u) ignoring request; no scripts in %s", reqid, _get_monitor_by_action(action)->dir); success = TRUE; goto done; } if (applied_connection) connection_dict = nm_connection_to_dbus (applied_connection, NM_CONNECTION_SERIALIZE_NO_SECRETS); else connection_dict = g_variant_new_array (G_VARIANT_TYPE ("{sa{sv}}"), NULL, 0); g_variant_builder_init (&connection_props, G_VARIANT_TYPE_VARDICT); if (settings_connection) { const char *connection_path; const char *filename; connection_path = nm_connection_get_path (NM_CONNECTION (settings_connection)); if (connection_path) { g_variant_builder_add (&connection_props, "{sv}", NMD_CONNECTION_PROPS_PATH, g_variant_new_object_path (connection_path)); } filename = nm_settings_connection_get_filename (settings_connection); if (filename) { g_variant_builder_add (&connection_props, "{sv}", NMD_CONNECTION_PROPS_FILENAME, g_variant_new_string (filename)); } if (activation_type_external) { g_variant_builder_add (&connection_props, "{sv}", NMD_CONNECTION_PROPS_EXTERNAL, g_variant_new_boolean (TRUE)); } } g_variant_builder_init (&device_props, G_VARIANT_TYPE_VARDICT); g_variant_builder_init (&device_proxy_props, G_VARIANT_TYPE_VARDICT); g_variant_builder_init (&device_ip4_props, G_VARIANT_TYPE_VARDICT); g_variant_builder_init (&device_ip6_props, G_VARIANT_TYPE_VARDICT); g_variant_builder_init (&vpn_proxy_props, G_VARIANT_TYPE_VARDICT); g_variant_builder_init (&vpn_ip4_props, G_VARIANT_TYPE_VARDICT); g_variant_builder_init (&vpn_ip6_props, G_VARIANT_TYPE_VARDICT); /* hostname and connectivity-change actions don't send device data */ if ( action != NM_DISPATCHER_ACTION_HOSTNAME && action != NM_DISPATCHER_ACTION_CONNECTIVITY_CHANGE) { fill_device_props (device, &device_props, &device_proxy_props, &device_ip4_props, &device_ip6_props, &device_dhcp4_props, &device_dhcp6_props); if (vpn_ip4_config || vpn_ip6_config) { fill_vpn_props (vpn_proxy_config, vpn_ip4_config, vpn_ip6_config, &vpn_proxy_props, &vpn_ip4_props, &vpn_ip6_props); } } if (!device_dhcp4_props) device_dhcp4_props = g_variant_ref_sink (g_variant_new_array (G_VARIANT_TYPE ("{sv}"), NULL, 0)); if (!device_dhcp6_props) device_dhcp6_props = g_variant_ref_sink (g_variant_new_array (G_VARIANT_TYPE ("{sv}"), NULL, 0)); #if WITH_CONCHECK connectivity_state_string = nm_connectivity_state_to_string (connectivity_state); #endif /* Send the action to the dispatcher */ if (blocking) { GVariant *ret; GVariantIter *results; ret = _nm_dbus_proxy_call_sync (dispatcher_proxy, "Action", g_variant_new ("(s@a{sa{sv}}a{sv}a{sv}a{sv}a{sv}a{sv}@a{sv}@a{sv}ssa{sv}a{sv}a{sv}b)", action_to_string (action), connection_dict, &connection_props, &device_props, &device_proxy_props, &device_ip4_props, &device_ip6_props, device_dhcp4_props, device_dhcp6_props, connectivity_state_string, vpn_iface ? vpn_iface : "", &vpn_proxy_props, &vpn_ip4_props, &vpn_ip6_props, nm_logging_enabled (LOGL_DEBUG, LOGD_DISPATCH)), G_VARIANT_TYPE ("(a(sus))"), G_DBUS_CALL_FLAGS_NONE, CALL_TIMEOUT, NULL, &error); if (ret) { g_variant_get (ret, "(a(sus))", &results); dispatcher_results_process (reqid, action, results); g_variant_iter_free (results); g_variant_unref (ret); success = TRUE; } else { g_dbus_error_strip_remote_error (error); _LOGW ("(%u) failed: %s", reqid, error->message); g_clear_error (&error); success = FALSE; } } else { info = g_malloc0 (sizeof (*info)); info->action = action; info->request_id = reqid; info->callback = callback; info->user_data = user_data; g_dbus_proxy_call (dispatcher_proxy, "Action", g_variant_new ("(s@a{sa{sv}}a{sv}a{sv}a{sv}a{sv}a{sv}@a{sv}@a{sv}ssa{sv}a{sv}a{sv}b)", action_to_string (action), connection_dict, &connection_props, &device_props, &device_proxy_props, &device_ip4_props, &device_ip6_props, device_dhcp4_props, device_dhcp6_props, connectivity_state_string, vpn_iface ? vpn_iface : "", &vpn_proxy_props, &vpn_ip4_props, &vpn_ip6_props, nm_logging_enabled (LOGL_DEBUG, LOGD_DISPATCH)), G_DBUS_CALL_FLAGS_NONE, CALL_TIMEOUT, NULL, dispatcher_done_cb, info); success = TRUE; } g_variant_unref (device_dhcp4_props); g_variant_unref (device_dhcp6_props); done: if (success && info) { /* Track the request in case of cancelation */ g_hash_table_insert (requests, GUINT_TO_POINTER (info->request_id), info); if (out_call_id) *out_call_id = info->request_id; } else if (out_call_id) *out_call_id = 0; return success; } /** * nm_dispatcher_call_hostname: * @callback: a caller-supplied callback to execute when done * @user_data: caller-supplied pointer passed to @callback * @out_call_id: on success, a call identifier which can be passed to * nm_dispatcher_call_cancel() * * This method always invokes the dispatcher action asynchronously. * * Returns: %TRUE if the action was dispatched, %FALSE on failure */ gboolean nm_dispatcher_call_hostname (NMDispatcherFunc callback, gpointer user_data, guint *out_call_id) { return _dispatcher_call (NM_DISPATCHER_ACTION_HOSTNAME, FALSE, NULL, NULL, NULL, FALSE, NM_CONNECTIVITY_UNKNOWN, NULL, NULL, NULL, NULL, callback, user_data, out_call_id); } /** * nm_dispatcher_call_device: * @action: the %NMDispatcherAction * @device: the #NMDevice the action applies to * @act_request: the #NMActRequest for the action. If %NULL, use the * current request of the device. * @callback: a caller-supplied callback to execute when done * @user_data: caller-supplied pointer passed to @callback * @out_call_id: on success, a call identifier which can be passed to * nm_dispatcher_call_cancel() * * This method always invokes the device dispatcher action asynchronously. To ignore * the result, pass %NULL to @callback. * * Returns: %TRUE if the action was dispatched, %FALSE on failure */ gboolean nm_dispatcher_call_device (NMDispatcherAction action, NMDevice *device, NMActRequest *act_request, NMDispatcherFunc callback, gpointer user_data, guint *out_call_id) { nm_assert (NM_IS_DEVICE (device)); if (!act_request) { act_request = nm_device_get_act_request (device); if (!act_request) return FALSE; } nm_assert (NM_IN_SET (nm_active_connection_get_device (NM_ACTIVE_CONNECTION (act_request)), NULL, device)); return _dispatcher_call (action, FALSE, device, nm_act_request_get_settings_connection (act_request), nm_act_request_get_applied_connection (act_request), nm_active_connection_get_activation_type (NM_ACTIVE_CONNECTION (act_request)) == NM_ACTIVATION_TYPE_EXTERNAL, NM_CONNECTIVITY_UNKNOWN, NULL, NULL, NULL, NULL, callback, user_data, out_call_id); } /** * nm_dispatcher_call_device_sync(): * @action: the %NMDispatcherAction * @device: the #NMDevice the action applies to * @act_request: the #NMActRequest for the action. If %NULL, use the * current request of the device. * * This method always invokes the dispatcher action synchronously and it may * take a long time to return. * * Returns: %TRUE if the action was dispatched, %FALSE on failure */ gboolean nm_dispatcher_call_device_sync (NMDispatcherAction action, NMDevice *device, NMActRequest *act_request) { nm_assert (NM_IS_DEVICE (device)); if (!act_request) { act_request = nm_device_get_act_request (device); if (!act_request) return FALSE; } nm_assert (NM_IN_SET (nm_active_connection_get_device (NM_ACTIVE_CONNECTION (act_request)), NULL, device)); return _dispatcher_call (action, TRUE, device, nm_act_request_get_settings_connection (act_request), nm_act_request_get_applied_connection (act_request), nm_active_connection_get_activation_type (NM_ACTIVE_CONNECTION (act_request)) == NM_ACTIVATION_TYPE_EXTERNAL, NM_CONNECTIVITY_UNKNOWN, NULL, NULL, NULL, NULL, NULL, NULL, NULL); } /** * nm_dispatcher_call_vpn(): * @action: the %NMDispatcherAction * @settings_connection: the #NMSettingsConnection the action applies to * @applied_connection: the currently applied connection * @parent_device: the parent #NMDevice of the VPN connection * @vpn_iface: the IP interface of the VPN tunnel, if any * @vpn_proxy_config: the #NMProxyConfig of the VPN connection * @vpn_ip4_config: the #NMIP4Config of the VPN connection * @vpn_ip6_config: the #NMIP6Config of the VPN connection * @callback: a caller-supplied callback to execute when done * @user_data: caller-supplied pointer passed to @callback * @out_call_id: on success, a call identifier which can be passed to * nm_dispatcher_call_cancel() * * This method always invokes the dispatcher action asynchronously. To ignore * the result, pass %NULL to @callback. * * Returns: %TRUE if the action was dispatched, %FALSE on failure */ gboolean nm_dispatcher_call_vpn (NMDispatcherAction action, NMSettingsConnection *settings_connection, NMConnection *applied_connection, NMDevice *parent_device, const char *vpn_iface, NMProxyConfig *vpn_proxy_config, NMIP4Config *vpn_ip4_config, NMIP6Config *vpn_ip6_config, NMDispatcherFunc callback, gpointer user_data, guint *out_call_id) { return _dispatcher_call (action, FALSE, parent_device, settings_connection, applied_connection, FALSE, NM_CONNECTIVITY_UNKNOWN, vpn_iface, vpn_proxy_config, vpn_ip4_config, vpn_ip6_config, callback, user_data, out_call_id); } /** * nm_dispatcher_call_vpn_sync(): * @action: the %NMDispatcherAction * @settings_connection: the #NMSettingsConnection the action applies to * @applied_connection: the currently applied connection * @parent_device: the parent #NMDevice of the VPN connection * @vpn_iface: the IP interface of the VPN tunnel, if any * @vpn_proxy_config: the #NMProxyConfig of the VPN connection * @vpn_ip4_config: the #NMIP4Config of the VPN connection * @vpn_ip6_config: the #NMIP6Config of the VPN connection * * This method always invokes the dispatcher action synchronously and it may * take a long time to return. * * Returns: %TRUE if the action was dispatched, %FALSE on failure */ gboolean nm_dispatcher_call_vpn_sync (NMDispatcherAction action, NMSettingsConnection *settings_connection, NMConnection *applied_connection, NMDevice *parent_device, const char *vpn_iface, NMProxyConfig *vpn_proxy_config, NMIP4Config *vpn_ip4_config, NMIP6Config *vpn_ip6_config) { return _dispatcher_call (action, TRUE, parent_device, settings_connection, applied_connection, FALSE, NM_CONNECTIVITY_UNKNOWN, vpn_iface, vpn_proxy_config, vpn_ip4_config, vpn_ip6_config, NULL, NULL, NULL); } /** * nm_dispatcher_call_connectivity(): * @connectivity_state: the #NMConnectivityState value * @callback: a caller-supplied callback to execute when done * @user_data: caller-supplied pointer passed to @callback * @out_call_id: on success, a call identifier which can be passed to * nm_dispatcher_call_cancel() * * This method does not block the caller. * * Returns: %TRUE if the action was dispatched, %FALSE on failure */ gboolean nm_dispatcher_call_connectivity (NMConnectivityState connectivity_state, NMDispatcherFunc callback, gpointer user_data, guint *out_call_id) { return _dispatcher_call (NM_DISPATCHER_ACTION_CONNECTIVITY_CHANGE, FALSE, NULL, NULL, NULL, FALSE, connectivity_state, NULL, NULL, NULL, NULL, callback, user_data, out_call_id); } void nm_dispatcher_call_cancel (guint call_id) { DispatchInfo *info; _ensure_requests (); /* Canceling just means the callback doesn't get called, so set the * DispatcherInfo's callback to NULL. */ info = g_hash_table_lookup (requests, GUINT_TO_POINTER (call_id)); g_return_if_fail (info); if (info && info->callback) { _LOGD ("(%u) cancelling dispatcher callback action", call_id); info->callback = NULL; } } static void dispatcher_dir_changed (GFileMonitor *monitor, GFile *file, GFile *other_file, GFileMonitorEvent event_type, Monitor *item) { const char *name; char *full_name; GDir *dir; GError *error = NULL; dir = g_dir_open (item->dir, 0, &error); if (dir) { int errsv = 0; item->has_scripts = FALSE; errno = 0; while (!item->has_scripts && (name = g_dir_read_name (dir))) { full_name = g_build_filename (item->dir, name, NULL); item->has_scripts = g_file_test (full_name, G_FILE_TEST_IS_EXECUTABLE); g_free (full_name); } errsv = errno; g_dir_close (dir); if (item->has_scripts) _LOGD ("%s script directory '%s' has scripts", item->description, item->dir); else if (errsv == 0) _LOGD ("%s script directory '%s' has no scripts", item->description, item->dir); else { _LOGD ("%s script directory '%s' error reading (%s)", item->description, item->dir, strerror (errsv)); item->has_scripts = TRUE; } } else { if (g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT)) { _LOGD ("%s script directory '%s' does not exist", item->description, item->dir); item->has_scripts = FALSE; } else { _LOGD ("%s script directory '%s' error (%s)", item->description, item->dir, error->message); item->has_scripts = TRUE; } g_error_free (error); } } void nm_dispatcher_init (void) { GFile *file; guint i; GError *error = NULL; for (i = 0; i < G_N_ELEMENTS (monitors); i++) { file = g_file_new_for_path (monitors[i].dir); monitors[i].monitor = g_file_monitor_directory (file, G_FILE_MONITOR_NONE, NULL, NULL); if (monitors[i].monitor) { g_signal_connect (monitors[i].monitor, "changed", G_CALLBACK (dispatcher_dir_changed), &monitors[i]); dispatcher_dir_changed (monitors[i].monitor, file, NULL, 0, &monitors[i]); } g_object_unref (file); } dispatcher_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, NULL, NM_DISPATCHER_DBUS_SERVICE, NM_DISPATCHER_DBUS_PATH, NM_DISPATCHER_DBUS_INTERFACE, NULL, &error); if (!dispatcher_proxy) { _LOGE ("could not get dispatcher proxy! %s", error->message); g_clear_error (&error); } }