NetworkManager/dispatcher/nm-dispatcher-utils.c

598 lines
17 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2008 - 2011 Red Hat, Inc.
*/
#include "nm-default.h"
#include "nm-dispatcher-utils.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-libnm-core-aux/nm-dispatcher-api.h"
#include "nm-utils.h"
/*****************************************************************************/
static gboolean
_is_valid_key (const char *line, gssize len)
{
gsize i, l;
char ch;
if (!line)
return FALSE;
if (len < 0)
len = strlen (line);
if (len == 0)
return FALSE;
ch = line[0];
if ( !(ch >= 'A' && ch <= 'Z')
&& !NM_IN_SET (ch, '_'))
return FALSE;
l = (gsize) len;
for (i = 1; i < l; i++) {
ch = line[i];
if ( !(ch >= 'A' && ch <= 'Z')
&& !(ch >= '0' && ch <= '9')
&& !NM_IN_SET (ch, '_'))
return FALSE;
}
return TRUE;
}
static gboolean
_is_valid_line (const char *line)
{
const char *d;
if (!line)
return FALSE;
d = strchr (line, '=');
if (!d || d == line)
return FALSE;
return _is_valid_key (line, d - line);
}
static char *
_sanitize_var_name (const char *key)
{
char *sanitized;
nm_assert (key);
if (!key[0])
return NULL;
sanitized = g_ascii_strup (key, -1);
if (!NM_STRCHAR_ALL (sanitized, ch, (ch >= 'A' && ch <= 'Z')
|| (ch >= '0' && ch <= '9')
|| NM_IN_SET (ch, '_'))) {
g_free (sanitized);
return NULL;
}
nm_assert (_is_valid_key (sanitized, -1));
return sanitized;
}
static void
_items_add_str_take (GPtrArray *items, char *line)
{
nm_assert (items);
nm_assert (_is_valid_line (line));
g_ptr_array_add (items, line);
}
static void
_items_add_str (GPtrArray *items, const char *line)
{
_items_add_str_take (items, g_strdup (line));
}
static void
_items_add_key (GPtrArray *items, const char *prefix, const char *key, const char *value)
{
nm_assert (items);
nm_assert (_is_valid_key (key, -1));
nm_assert (value);
_items_add_str_take (items, g_strconcat (prefix ?: "", key, "=", value, NULL));
}
static void
_items_add_key0 (GPtrArray *items, const char *prefix, const char *key, const char *value)
{
nm_assert (items);
nm_assert (_is_valid_key (key, -1));
if (!value) {
/* for convenience, allow NULL values to indicate to skip the line. */
return;
}
_items_add_str_take (items, g_strconcat (prefix ?: "", key, "=", value, NULL));
}
G_GNUC_PRINTF (2, 3)
static void
_items_add_printf (GPtrArray *items, const char *fmt, ...)
{
va_list ap;
char *line;
nm_assert (items);
nm_assert (fmt);
va_start (ap, fmt);
line = g_strdup_vprintf (fmt, ap);
va_end (ap);
_items_add_str_take (items, line);
}
static void
_items_add_strv (GPtrArray *items, const char *prefix, const char *key, const char *const*values)
{
gboolean has;
guint i;
GString *str;
nm_assert (items);
nm_assert (_is_valid_key (key, -1));
if (!values || !values[0]) {
/* Only add an item if the list of @values is not empty */
return;
}
str = g_string_new (NULL);
if (prefix)
g_string_append (str, prefix);
g_string_append (str, key);
g_string_append_c (str, '=');
has = FALSE;
for (i = 0; values[i]; i++) {
if (!values[i][0])
continue;
if (has)
g_string_append_c (str, ' ');
else
has = TRUE;
g_string_append (str, values[i]);
}
_items_add_str_take (items, g_string_free (str, FALSE));
}
/*****************************************************************************/
static void
construct_proxy_items (GPtrArray *items, GVariant *proxy_config, const char *prefix)
{
GVariant *variant;
nm_assert (items);
if (!proxy_config)
return;
variant = g_variant_lookup_value (proxy_config, "pac-url", G_VARIANT_TYPE_STRING);
if (variant) {
_items_add_key (items, prefix, "PROXY_PAC_URL",
g_variant_get_string (variant, NULL));
g_variant_unref (variant);
}
variant = g_variant_lookup_value (proxy_config, "pac-script", G_VARIANT_TYPE_STRING);
if (variant) {
_items_add_key (items, prefix, "PROXY_PAC_SCRIPT",
g_variant_get_string (variant, NULL));
g_variant_unref (variant);
}
}
static void
construct_ip_items (GPtrArray *items, int addr_family, GVariant *ip_config, const char *prefix)
{
GVariant *val;
guint i;
guint nroutes = 0;
char four_or_six;
if (!ip_config)
return;
if (!prefix)
prefix = "";
four_or_six = nm_utils_addr_family_to_char (addr_family);
val = g_variant_lookup_value (ip_config,
"addresses",
addr_family == AF_INET
? G_VARIANT_TYPE ("aau")
: G_VARIANT_TYPE ("a(ayuay)"));
if (val) {
gs_unref_ptrarray GPtrArray *addresses = NULL;
gs_free char *gateway_free = NULL;
const char *gateway;
if (addr_family == AF_INET)
addresses = nm_utils_ip4_addresses_from_variant (val, &gateway_free);
else
addresses = nm_utils_ip6_addresses_from_variant (val, &gateway_free);
gateway = gateway_free ?: "0.0.0.0";
if (addresses && addresses->len) {
for (i = 0; i < addresses->len; i++) {
NMIPAddress *addr = addresses->pdata[i];
_items_add_printf (items,
"%sIP%c_ADDRESS_%d=%s/%d %s",
prefix,
four_or_six,
i,
nm_ip_address_get_address (addr),
nm_ip_address_get_prefix (addr),
gateway);
}
_items_add_printf (items,
"%sIP%c_NUM_ADDRESSES=%u",
prefix,
four_or_six,
addresses->len);
}
_items_add_key (items,
prefix,
addr_family == AF_INET
? "IP4_GATEWAY"
: "IP6_GATEWAY",
gateway);
g_variant_unref (val);
}
val = g_variant_lookup_value (ip_config,
"nameservers",
addr_family == AF_INET
? G_VARIANT_TYPE ("au")
: G_VARIANT_TYPE ("aay"));
if (val) {
gs_strfreev char **v = NULL;
if (addr_family == AF_INET)
v = nm_utils_ip4_dns_from_variant (val);
else
v = nm_utils_ip6_dns_from_variant (val);
_items_add_strv (items,
prefix,
addr_family == AF_INET
? "IP4_NAMESERVERS"
: "IP6_NAMESERVERS",
NM_CAST_STRV_CC (v));
g_variant_unref (val);
}
val = g_variant_lookup_value (ip_config, "domains", G_VARIANT_TYPE_STRING_ARRAY);
if (val) {
gs_free const char **v = NULL;
v = g_variant_get_strv (val, NULL);
_items_add_strv (items, prefix,
addr_family == AF_INET
? "IP4_DOMAINS"
: "IP6_DOMAINS",
v);
g_variant_unref (val);
}
if (addr_family == AF_INET) {
val = g_variant_lookup_value (ip_config, "wins-servers", G_VARIANT_TYPE ("au"));
if (val) {
gs_strfreev char **v = NULL;
v = nm_utils_ip4_dns_from_variant (val);
_items_add_strv (items, prefix, "IP4_WINS_SERVERS", NM_CAST_STRV_CC (v));
g_variant_unref (val);
}
}
val = g_variant_lookup_value (ip_config,
"routes",
addr_family == AF_INET
? G_VARIANT_TYPE ("aau")
: G_VARIANT_TYPE ("a(ayuayu)"));
if (val) {
gs_unref_ptrarray GPtrArray *routes = NULL;
if (addr_family == AF_INET)
routes = nm_utils_ip4_routes_from_variant (val);
else
routes = nm_utils_ip6_routes_from_variant (val);
if ( routes
&& routes->len > 0) {
const char *const DEFAULT_GW = addr_family == AF_INET ? "0.0.0.0" : "::";
nroutes = routes->len;
for (i = 0; i < routes->len; i++) {
NMIPRoute *route = routes->pdata[i];
_items_add_printf (items,
"%sIP%c_ROUTE_%u=%s/%d %s %u",
prefix,
four_or_six,
i,
nm_ip_route_get_dest (route),
nm_ip_route_get_prefix (route),
nm_ip_route_get_next_hop (route) ?: DEFAULT_GW,
(guint) NM_MAX ((gint64) 0, nm_ip_route_get_metric (route)));
}
}
g_variant_unref (val);
}
if (nroutes > 0 || addr_family == AF_INET) {
/* we also set IP4_NUM_ROUTES=0, but don't do so for addresses and IPv6 routes.
* Historic reasons. */
_items_add_printf (items, "%sIP%c_NUM_ROUTES=%u", prefix, four_or_six, nroutes);
}
}
static void
construct_device_dhcp_items (GPtrArray *items, int addr_family, GVariant *dhcp_config)
{
GVariantIter iter;
const char *key;
GVariant *val;
char four_or_six;
gboolean found_unknown_245 = FALSE;
gs_unref_variant GVariant *private_245_val = NULL;
if (!dhcp_config)
return;
if (!g_variant_is_of_type (dhcp_config, G_VARIANT_TYPE_VARDICT))
return;
four_or_six = nm_utils_addr_family_to_char (addr_family);
g_variant_iter_init (&iter, dhcp_config);
while (g_variant_iter_next (&iter, "{&sv}", &key, &val)) {
if (g_variant_is_of_type (val, G_VARIANT_TYPE_STRING)) {
gs_free char *ucased = NULL;
ucased = _sanitize_var_name (key);
if (ucased) {
_items_add_printf (items,
"DHCP%c_%s=%s",
four_or_six,
ucased,
g_variant_get_string (val, NULL));
/* MS Azure sends the server endpoint in the dhcp private
* option 245. cloud-init searches the Azure server endpoint
* value looking for the standard dhclient label used for
* that option, which is "unknown_245".
* The 11-dhclient script shipped with Fedora and RHEL dhcp
* package converts our dispatcher environment vars to the
* dhclient ones (new_<some_option>) and calls dhclient hook
* scripts.
* Let's make cloud-init happy and let's duplicate the dhcp
* option 245 with the legacy name of the default dhclient
* label also when using the internal client.
* Note however that the dhclient plugin will have unknown_
* labels represented as ascii string when possible, falling
* back to hex string otherwise.
* private_ labels instead are always in hex string format.
* This shouldn't affect the MS Azure server endpoint value,
* as it usually belongs to the 240.0.0.0/4 network and so
* is always represented as an hex string. Moreover, cloudinit
* code checks just for an hex value in unknown_245.
*/
if (addr_family == AF_INET) {
if (nm_streq (key, "private_245"))
private_245_val = g_variant_ref (val);
else if (nm_streq (key, "unknown_245"))
found_unknown_245 = true;
}
}
}
g_variant_unref (val);
}
if (private_245_val != NULL && !found_unknown_245) {
_items_add_printf (items,
"DHCP4_UNKNOWN_245=%s",
g_variant_get_string (private_245_val, NULL));
}
}
/*****************************************************************************/
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;
const char *ip_iface = NULL;
const char *uuid = NULL;
const char *id = NULL;
const char *path = NULL;
const char *filename = NULL;
gboolean external;
NMDeviceState dev_state = NM_DEVICE_STATE_UNKNOWN;
GVariant *variant;
gs_unref_ptrarray GPtrArray *items = NULL;
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);
items = g_ptr_array_new_with_free_func (g_free);
/* Hostname and connectivity changes don't require a device nor contain a connection */
if (NM_IN_STRSET (action, NMD_ACTION_HOSTNAME,
NMD_ACTION_CONNECTIVITY_CHANGE))
goto done;
/* Connection properties */
if (g_variant_lookup (connection_props, NMD_CONNECTION_PROPS_PATH, "&o", &path))
_items_add_key (items, NULL, "CONNECTION_DBUS_PATH", path);
if (g_variant_lookup (connection_props, NMD_CONNECTION_PROPS_EXTERNAL, "b", &external) && external)
_items_add_str (items, "CONNECTION_EXTERNAL=1");
if (g_variant_lookup (connection_props, NMD_CONNECTION_PROPS_FILENAME, "&s", &filename))
_items_add_key (items, NULL, "CONNECTION_FILENAME", 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 && !vpn_ip_iface[0])
vpn_ip_iface = NULL;
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;
variant = g_variant_lookup_value (device_props, NMD_DEVICE_PROPS_IP_INTERFACE, NULL);
if (variant) {
if (!g_variant_is_of_type (variant, G_VARIANT_TYPE_STRING)) {
*out_error_message = "Invalid value " NMD_DEVICE_PROPS_IP_INTERFACE "!";
return NULL;
}
g_variant_unref (variant);
(void) g_variant_lookup (device_props, NMD_DEVICE_PROPS_IP_INTERFACE, "&s", &ip_iface);
}
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;
}
variant = g_variant_lookup_value (device_props, NMD_DEVICE_PROPS_STATE, G_VARIANT_TYPE_UINT32);
if (!variant) {
*out_error_message = "Missing or invalid required value " NMD_DEVICE_PROPS_STATE "!";
return NULL;
}
dev_state = g_variant_get_uint32 (variant);
g_variant_unref (variant);
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;
}
{
gs_unref_variant GVariant *con_setting = NULL;
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";
return NULL;
}
if (!g_variant_lookup (con_setting, NM_SETTING_CONNECTION_ID, "&s", &id)) {
*out_error_message = "Connection hash did not contain the ID";
return NULL;
}
_items_add_key0 (items, NULL, "CONNECTION_UUID", uuid);
_items_add_key0 (items, NULL, "CONNECTION_ID", id);
_items_add_key0 (items, NULL, "DEVICE_IFACE", iface);
_items_add_key0 (items, NULL, "DEVICE_IP_IFACE", ip_iface);
}
/* Device items aren't valid if the device isn't activated */
if ( iface
&& dev_state == NM_DEVICE_STATE_ACTIVATED) {
construct_proxy_items (items, device_proxy_props, NULL);
construct_ip_items (items, AF_INET, device_ip4_props, NULL);
construct_ip_items (items, AF_INET6, device_ip6_props, NULL);
construct_device_dhcp_items (items, AF_INET, device_dhcp4_props);
construct_device_dhcp_items (items, AF_INET6, device_dhcp6_props);
}
if (vpn_ip_iface) {
_items_add_key (items, NULL, "VPN_IP_IFACE", vpn_ip_iface);
construct_proxy_items (items, vpn_proxy_props, "VPN_");
construct_ip_items (items, AF_INET, vpn_ip4_props, "VPN_");
construct_ip_items (items, AF_INET6, 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 (!NM_IN_STRSET (connectivity_state, NULL, "UNKNOWN"))
_items_add_key (items, NULL, "CONNECTIVITY_STATE", connectivity_state);
_items_add_key0 (items, NULL, "PATH", g_getenv ("PATH"));
_items_add_key (items, NULL, "NM_DISPATCHER_ACTION", action);
*out_error_message = NULL;
g_ptr_array_add (items, NULL);
return (char **) g_ptr_array_free (g_steal_pointer (&items), FALSE);
}