nm-dispatcher-utils.c 16.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
/* -*- 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.
 */

21
#include "nm-default.h"
22

23 24
#include <string.h>

25 26 27 28 29
#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"
30

31
#include "nm-dispatcher-api.h"
32 33 34 35
#include "nm-utils.h"

#include "nm-dispatcher-utils.h"

36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
/*****************************************************************************/

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);
}

87
static char *
88
_sanitize_var_name (const char *key)
89
{
90 91
	char *sanitized;

92 93 94 95 96 97 98 99 100 101 102 103 104
	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;
	}

105
	nm_assert (_is_valid_key (sanitized, -1));
106 107 108
	return sanitized;
}

109 110
static void
_items_add_str_take (GPtrArray *items, char *line)
111
{
112 113 114 115
	nm_assert (items);
	nm_assert (_is_valid_line (line));

	g_ptr_array_add (items, line);
116 117
}

118 119 120 121 122
static void
_items_add_str (GPtrArray *items, const char *line)
{
	_items_add_str_take (items, g_strdup (line));
}
123

124 125
static void
_items_add_key (GPtrArray *items, const char *prefix, const char *key, const char *value)
126
{
127 128 129
	nm_assert (items);
	nm_assert (_is_valid_key (key, -1));
	nm_assert (value);
130

131 132
	_items_add_str_take (items, g_strconcat (prefix ?: "", key, "=", value, NULL));
}
133

134 135 136 137 138
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));
139

140 141 142
	if (!value) {
		/* for convenience, allow NULL values to indicate to skip the line. */
		return;
143 144
	}

145
	_items_add_str_take (items, g_strconcat (prefix ?: "", key, "=", value, NULL));
146 147
}

148 149 150
G_GNUC_PRINTF (2, 3)
static void
_items_add_printf (GPtrArray *items, const char *fmt, ...)
151
{
152 153
	va_list ap;
	char *line;
154

155 156 157 158 159 160 161
	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);
162 163
}

164 165
static void
_items_add_strv (GPtrArray *items, const char *prefix, const char *key, const char *const*values)
166
{
167 168 169
	gboolean has;
	guint i;
	GString *str;
170

171 172
	nm_assert (items);
	nm_assert (_is_valid_key (key, -1));
173

174 175 176 177
	if (!values || !values[0]) {
		/* Only add an item if the list of @values is not empty */
		return;
	}
178

179
	str = g_string_new (NULL);
180

181 182 183 184
	if (prefix)
		g_string_append (str, prefix);
	g_string_append (str, key);
	g_string_append_c (str, '=');
185

186 187 188 189 190 191 192 193 194
	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]);
195 196
	}

197 198
	_items_add_str_take (items, g_string_free (str, FALSE));
}
199

200
/*****************************************************************************/
201

202 203 204 205 206 207 208 209 210 211 212 213 214 215 216
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);
217 218
	}

219 220 221 222 223 224
	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);
	}
225 226
}

227 228
static void
construct_ip_items (GPtrArray *items, int addr_family, GVariant *ip_config, const char *prefix)
229
{
Dan Winship's avatar
Dan Winship committed
230
	GVariant *val;
231 232 233
	guint i;
	guint nroutes = 0;
	char four_or_six;
234

235 236
	if (!ip_config)
		return;
237

238
	if (!prefix)
239 240
		prefix = "";

241 242 243 244 245 246 247
	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)"));
248
	if (val) {
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
		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);
279
		}
280

281 282 283 284 285 286
		_items_add_key (items,
		                prefix,
		                  addr_family == AF_INET
		                ? "IP4_GATEWAY"
		                : "IP6_GATEWAY",
		                gateway);
287

Dan Winship's avatar
Dan Winship committed
288
		g_variant_unref (val);
289
	}
290

291 292 293 294 295
	val = g_variant_lookup_value (ip_config,
	                              "nameservers",
	                                addr_family == AF_INET
	                              ? G_VARIANT_TYPE ("au")
	                              : G_VARIANT_TYPE ("aay"));
Dan Winship's avatar
Dan Winship committed
296
	if (val) {
297 298 299 300 301 302 303 304 305 306 307 308
		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));
Dan Winship's avatar
Dan Winship committed
309
		g_variant_unref (val);
310 311
	}

312
	val = g_variant_lookup_value (ip_config, "domains", G_VARIANT_TYPE_STRING_ARRAY);
Dan Winship's avatar
Dan Winship committed
313
	if (val) {
314 315 316 317 318 319 320 321
		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);
Dan Winship's avatar
Dan Winship committed
322
		g_variant_unref (val);
323 324 325
	}


326 327 328 329
	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;
330

331 332 333
			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);
334
		}
335 336
	}

337 338 339 340 341
	val = g_variant_lookup_value (ip_config,
	                              "routes",
	                                addr_family == AF_INET
	                              ? G_VARIANT_TYPE ("aau")
	                              : G_VARIANT_TYPE ("a(ayuayu)"));
342
	if (val) {
343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368
		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)));
			}
369
		}
370

Dan Winship's avatar
Dan Winship committed
371
		g_variant_unref (val);
372
	}
373 374 375 376
	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);
377
	}
378 379
}

380 381
static void
construct_device_dhcp_items (GPtrArray *items, int addr_family, GVariant *dhcp_config)
382
{
Dan Winship's avatar
Dan Winship committed
383
	GVariantIter iter;
384
	const char *key;
Dan Winship's avatar
Dan Winship committed
385
	GVariant *val;
386 387 388 389
	char four_or_six;

	if (!dhcp_config)
		return;
390

391 392
	if (!g_variant_is_of_type (dhcp_config, G_VARIANT_TYPE_VARDICT))
		return;
393

394 395 396
	four_or_six = nm_utils_addr_family_to_char (addr_family);

	g_variant_iter_init (&iter, dhcp_config);
Dan Winship's avatar
Dan Winship committed
397
	while (g_variant_iter_next (&iter, "{&sv}", &key, &val)) {
398 399 400 401 402 403 404 405 406 407 408
		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));
			}
409
		}
410
		g_variant_unref (val);
411 412 413
	}
}

414 415
/*****************************************************************************/

416 417
char **
nm_dispatcher_utils_construct_envp (const char *action,
Dan Winship's avatar
Dan Winship committed
418 419 420
                                    GVariant *connection_dict,
                                    GVariant *connection_props,
                                    GVariant *device_props,
421
                                    GVariant *device_proxy_props,
Dan Winship's avatar
Dan Winship committed
422 423 424 425
                                    GVariant *device_ip4_props,
                                    GVariant *device_ip6_props,
                                    GVariant *device_dhcp4_props,
                                    GVariant *device_dhcp6_props,
426
                                    const char *connectivity_state,
427
                                    const char *vpn_ip_iface,
428
                                    GVariant *vpn_proxy_props,
Dan Winship's avatar
Dan Winship committed
429 430
                                    GVariant *vpn_ip4_props,
                                    GVariant *vpn_ip6_props,
431 432
                                    char **out_iface,
                                    const char **out_error_message)
433
{
434 435 436 437 438
	const char *iface = NULL;
	const char *ip_iface = NULL;
	const char *uuid = NULL;
	const char *id = NULL;
	const char *path = NULL;
439
	const char *filename = NULL;
440
	gboolean external;
441
	NMDeviceState dev_state = NM_DEVICE_STATE_UNKNOWN;
442 443
	GVariant *variant;
	gs_unref_ptrarray GPtrArray *items = NULL;
444 445 446 447
	const char *error_message_backup;

	if (!out_error_message)
		out_error_message = &error_message_backup;
448 449 450 451 452

	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);

453 454
	items = g_ptr_array_new_with_free_func (g_free);

455
	/* Hostname and connectivity changes don't require a device nor contain a connection */
456 457
	if (NM_IN_STRSET (action, NMD_ACTION_HOSTNAME,
	                          NMD_ACTION_CONNECTIVITY_CHANGE))
458
		goto done;
459

460 461
	/* Connection properties */
	if (!g_variant_lookup (connection_props, NMD_CONNECTION_PROPS_PATH, "&o", &path)) {
462
		*out_error_message = "Missing or invalid required value " NMD_CONNECTION_PROPS_PATH "!";
463 464
		return NULL;
	}
465 466

	_items_add_key (items, NULL, "CONNECTION_DBUS_PATH", path);
467

468
	if (g_variant_lookup (connection_props, NMD_CONNECTION_PROPS_EXTERNAL, "b", &external) && external)
469
		_items_add_str (items, "CONNECTION_EXTERNAL=1");
470

471
	if (g_variant_lookup (connection_props, NMD_CONNECTION_PROPS_FILENAME, "&s", &filename))
472
		_items_add_key (items, NULL, "CONNECTION_FILENAME", filename);
473

474 475 476
	/* Canonicalize the VPN interface name; "" is used when passing it through
	 * D-Bus so make sure that's fixed up here.
	 */
477
	if (vpn_ip_iface && !vpn_ip_iface[0])
478 479
		vpn_ip_iface = NULL;

Dan Winship's avatar
Dan Winship committed
480
	if (!g_variant_lookup (device_props, NMD_DEVICE_PROPS_INTERFACE, "&s", &iface)) {
481
		*out_error_message = "Missing or invalid required value " NMD_DEVICE_PROPS_INTERFACE "!";
482 483
		return NULL;
	}
Dan Winship's avatar
Dan Winship committed
484
	if (!*iface)
485
		iface = NULL;
486

487 488 489
	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)) {
490
			*out_error_message = "Invalid value " NMD_DEVICE_PROPS_IP_INTERFACE "!";
491 492
			return NULL;
		}
493
		g_variant_unref (variant);
494
		(void) g_variant_lookup (device_props, NMD_DEVICE_PROPS_IP_INTERFACE, "&s", &ip_iface);
Thomas Haller's avatar
Thomas Haller committed
495
	}
496

Dan Winship's avatar
Dan Winship committed
497
	if (!g_variant_lookup (device_props, NMD_DEVICE_PROPS_TYPE, "u", NULL)) {
498
		*out_error_message = "Missing or invalid required value " NMD_DEVICE_PROPS_TYPE "!";
499 500 501
		return NULL;
	}

502 503
	variant = g_variant_lookup_value (device_props, NMD_DEVICE_PROPS_STATE, G_VARIANT_TYPE_UINT32);
	if (!variant) {
504
		*out_error_message = "Missing or invalid required value " NMD_DEVICE_PROPS_STATE "!";
505 506
		return NULL;
	}
507 508
	dev_state = g_variant_get_uint32 (variant);
	g_variant_unref (variant);
509

Dan Winship's avatar
Dan Winship committed
510
	if (!g_variant_lookup (device_props, NMD_DEVICE_PROPS_PATH, "o", NULL)) {
511
		*out_error_message = "Missing or invalid required value " NMD_DEVICE_PROPS_PATH "!";
512 513 514
		return NULL;
	}

515 516
	{
		gs_unref_variant GVariant *con_setting = NULL;
Dan Winship's avatar
Dan Winship committed
517

518 519 520 521 522
		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;
		}
Dan Winship's avatar
Dan Winship committed
523

524 525 526 527
		if (!g_variant_lookup (con_setting, NM_SETTING_CONNECTION_UUID, "&s", &uuid)) {
			*out_error_message = "Connection hash did not contain the UUID";
			return NULL;
		}
Dan Winship's avatar
Dan Winship committed
528

529 530 531 532 533 534 535 536 537 538
		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);
	}
539 540

	/* Device it's aren't valid if the device isn't activated */
541 542 543 544 545 546 547
	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);
548 549 550
	}

	if (vpn_ip_iface) {
551 552 553 554
		_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_");
555 556 557 558 559 560 561 562 563 564 565 566 567 568
	}

	/* 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);

569
done:
570 571 572
	/* 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.
	 */
573 574
	if (!NM_IN_STRSET (connectivity_state, NULL, "UNKNOWN"))
		_items_add_key (items, NULL, "CONNECTIVITY_STATE", connectivity_state);
575

576
	_items_add_key0 (items, NULL, "PATH", g_getenv ("PATH"));
577

578
	*out_error_message = NULL;
579 580
	g_ptr_array_add (items, NULL);
	return (char **) g_ptr_array_free (g_steal_pointer (&items), FALSE);
581
}