test-dispatcher-envp.c 16.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Copyright (C) 2011 Red Hat, Inc.
 *
 */

#include <config.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <glib.h>
#include <glib-object.h>

#include "nm-connection.h"
#include "nm-setting-connection.h"
#include "nm-dispatcher-utils.h"
31
#include "nm-dispatcher-api.h"
32 33 34 35 36 37
#include "nm-utils.h"

/*******************************************/

static gboolean
parse_main (GKeyFile *kf,
Dan Winship's avatar
Dan Winship committed
38 39
            GVariant **out_con_dict,
            GVariant **out_con_props,
40
            char **out_expected_iface,
41
            char **out_action,
42
            char **out_vpn_ip_iface,
43 44 45 46 47
            GError **error)
{
	char *uuid, *id;
	NMConnection *connection;
	NMSettingConnection *s_con;
Dan Winship's avatar
Dan Winship committed
48
	GVariantBuilder props;
49

50 51
	*out_expected_iface = g_key_file_get_string (kf, "main", "expected-iface", error);
	if (*out_expected_iface == NULL)
52 53
		return FALSE;

54 55
	*out_vpn_ip_iface = g_key_file_get_string (kf, "main", "vpn-ip-iface", NULL);

56 57 58 59 60 61 62 63 64 65 66
	*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;

67
	connection = nm_simple_connection_new ();
68 69 70 71 72 73 74 75 76 77 78
	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));

79
	*out_con_dict = nm_connection_to_dbus (connection, NM_CONNECTION_SERIALIZE_ALL);
80 81
	g_object_unref (connection);

Dan Winship's avatar
Dan Winship committed
82 83 84 85 86
	g_variant_builder_init (&props, G_VARIANT_TYPE ("a{sv}"));
	g_variant_builder_add (&props, "{sv}",
	                       "connection-path",
	                       g_variant_new_object_path ("/org/freedesktop/NetworkManager/Connections/5"));
	*out_con_props = g_variant_builder_end (&props);
87 88 89 90 91

	return TRUE;
}

static gboolean
Dan Winship's avatar
Dan Winship committed
92
parse_device (GKeyFile *kf, GVariant **out_device_props, GError **error)
93
{
Dan Winship's avatar
Dan Winship committed
94
	GVariantBuilder props;
95 96 97
	char *tmp;
	gint i;

Dan Winship's avatar
Dan Winship committed
98
	g_variant_builder_init (&props, G_VARIANT_TYPE ("a{sv}"));
99 100 101 102

	i = g_key_file_get_integer (kf, "device", "state", error);
	if (i == 0)
		return FALSE;
Dan Winship's avatar
Dan Winship committed
103 104 105
	g_variant_builder_add (&props, "{sv}",
	                       NMD_DEVICE_PROPS_STATE,
	                       g_variant_new_uint32 (i));
106 107 108 109

	i = g_key_file_get_integer (kf, "device", "type", error);
	if (i == 0)
		return FALSE;
Dan Winship's avatar
Dan Winship committed
110 111 112
	g_variant_builder_add (&props, "{sv}",
	                       NMD_DEVICE_PROPS_TYPE,
	                       g_variant_new_uint32 (i));
113 114 115 116

	tmp = g_key_file_get_string (kf, "device", "interface", error);
	if (tmp == NULL)
		return FALSE;
Dan Winship's avatar
Dan Winship committed
117 118 119
	g_variant_builder_add (&props, "{sv}",
	                       NMD_DEVICE_PROPS_INTERFACE,
	                       g_variant_new_string (tmp));
120 121 122 123 124
	g_free (tmp);

	tmp = g_key_file_get_string (kf, "device", "ip-interface", error);
	if (tmp == NULL)
		return FALSE;
Dan Winship's avatar
Dan Winship committed
125 126 127
	g_variant_builder_add (&props, "{sv}",
	                       NMD_DEVICE_PROPS_IP_INTERFACE,
	                       g_variant_new_string (tmp));
128 129 130 131 132
	g_free (tmp);

	tmp = g_key_file_get_string (kf, "device", "path", error);
	if (tmp == NULL)
		return FALSE;
Dan Winship's avatar
Dan Winship committed
133 134 135
	g_variant_builder_add (&props, "{sv}",
	                       NMD_DEVICE_PROPS_PATH,
	                       g_variant_new_object_path (tmp));
136 137
	g_free (tmp);

Dan Winship's avatar
Dan Winship committed
138
	*out_device_props = g_variant_builder_end (&props);
139 140 141 142 143
	return TRUE;
}

static gboolean
add_uint_array (GKeyFile *kf,
Dan Winship's avatar
Dan Winship committed
144
                GVariantBuilder *props,
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
                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))) {
165
				guint32 addr;
166 167

				g_assert_cmpint (inet_pton (AF_INET, *iter, &addr), ==, 1);
168
				g_array_append_val (items, addr);
169 170
			}
		}
Dan Winship's avatar
Dan Winship committed
171 172 173 174 175
		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);
176 177 178 179 180 181
	}
	g_strfreev (split);
	return TRUE;
}

static gboolean
Dan Winship's avatar
Dan Winship committed
182
parse_ip4 (GKeyFile *kf, GVariant **out_props, const char *section, GError **error)
183
{
Dan Winship's avatar
Dan Winship committed
184
	GVariantBuilder props;
185 186
	char *tmp;
	char **split, **iter;
Dan Winship's avatar
Dan Winship committed
187
	GPtrArray *addresses, *routes;
188

Dan Winship's avatar
Dan Winship committed
189
	g_variant_builder_init (&props, G_VARIANT_TYPE ("a{sv}"));
190 191

	/* search domains */
192 193 194
	/* Use char** for domains. (DBUS_TYPE_G_ARRAY_OF_STRING of NMIP4Config
	 * becomes G_TYPE_STRV when sending the value over D-Bus)
	 */
195 196 197 198 199 200 201
	tmp = g_key_file_get_string (kf, section, "domains", error);
	if (tmp == NULL)
		return FALSE;
	split = g_strsplit_set (tmp, " ", -1);
	g_free (tmp);

	if (g_strv_length (split) > 0) {
202 203
		for (iter = split; iter && *iter; iter++)
			g_strstrip (*iter);
Dan Winship's avatar
Dan Winship committed
204 205
		g_variant_builder_add (&props, "{sv}", "domains", g_variant_new_strv ((gpointer) split, -1));
		g_strfreev (split);
206 207 208
	}

	/* nameservers */
Dan Winship's avatar
Dan Winship committed
209
	if (!add_uint_array (kf, &props, "ip4", "nameservers", error))
210 211
		return FALSE;
	/* wins-servers */
Dan Winship's avatar
Dan Winship committed
212
	if (!add_uint_array (kf, &props, "ip4", "wins-servers", error))
213 214 215 216 217 218 219 220 221 222
		return FALSE;

	/* Addresses */
	tmp = g_key_file_get_string (kf, section, "addresses", error);
	if (tmp == NULL)
		return FALSE;
	split = g_strsplit_set (tmp, ",", -1);
	g_free (tmp);

	if (g_strv_length (split) > 0) {
Dan Winship's avatar
Dan Winship committed
223
		addresses = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_ip4_address_unref);
224 225
		for (iter = split; iter && *iter; iter++) {
			NMIP4Address *addr;
226
			guint32 a;
227 228 229 230 231 232 233 234 235 236 237 238
			char *p;

			if (strlen (g_strstrip (*iter)) == 0)
				continue;

			addr = nm_ip4_address_new ();

			p = strchr (*iter, '/');
			g_assert (p);
			*p++ = '\0';

			g_assert_cmpint (inet_pton (AF_INET, *iter, &a), ==, 1);
239
			nm_ip4_address_set_address (addr, a);
240 241 242 243 244 245 246
			nm_ip4_address_set_prefix (addr, (guint) atoi (p));

			p = strchr (p, ' ');
			g_assert (p);
			p++;

			g_assert_cmpint (inet_pton (AF_INET, p, &a), ==, 1);
247
			nm_ip4_address_set_gateway (addr, a);
248

Dan Winship's avatar
Dan Winship committed
249
			g_ptr_array_add (addresses, addr);
250 251
		}

Dan Winship's avatar
Dan Winship committed
252 253 254
		g_variant_builder_add (&props, "{sv}", "addresses",
		                       nm_utils_ip4_addresses_to_variant (addresses));
		g_ptr_array_unref (addresses);
255 256 257 258 259 260 261 262 263 264 265
	}
	g_strfreev (split);

	/* Routes */
	tmp = g_key_file_get_string (kf, section, "routes", error);
	g_clear_error (error);
	if (tmp) {
		split = g_strsplit_set (tmp, ",", -1);
		g_free (tmp);

		if (g_strv_length (split) > 0) {
Dan Winship's avatar
Dan Winship committed
266
			routes = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_ip4_route_unref);
267 268
			for (iter = split; iter && *iter; iter++) {
				NMIP4Route *route;
269
				guint32 a;
270 271 272 273 274 275 276 277 278 279 280 281
				char *p;

				if (strlen (g_strstrip (*iter)) == 0)
					continue;

				route = nm_ip4_route_new ();

				p = strchr (*iter, '/');
				g_assert (p);
				*p++ = '\0';

				g_assert_cmpint (inet_pton (AF_INET, *iter, &a), ==, 1);
282
				nm_ip4_route_set_dest (route, a);
283 284 285 286 287 288 289
				nm_ip4_route_set_prefix (route, (guint) atoi (p));

				p = strchr (p, ' ');
				g_assert (p);
				p++;

				g_assert_cmpint (inet_pton (AF_INET, p, &a), ==, 1);
290
				nm_ip4_route_set_next_hop (route, a);
291 292 293 294 295 296

				p = strchr (p, ' ');
				g_assert (p);
				p++;
				nm_ip4_route_set_metric (route, (guint) atoi (p));

Dan Winship's avatar
Dan Winship committed
297
				g_ptr_array_add (routes, route);
298 299
			}

Dan Winship's avatar
Dan Winship committed
300 301 302
			g_variant_builder_add (&props, "{sv}", "routes",
			                       nm_utils_ip4_routes_to_variant (routes));
			g_ptr_array_unref (routes);
303 304 305 306
		}
		g_strfreev (split);
	}

Dan Winship's avatar
Dan Winship committed
307
	*out_props = g_variant_builder_end (&props);
308 309 310 311 312 313
	return TRUE;
}

static gboolean
parse_dhcp (GKeyFile *kf,
            const char *group_name,
Dan Winship's avatar
Dan Winship committed
314
            GVariant **out_props,
315 316 317
            GError **error)
{
	char **keys, **iter, *val;
Dan Winship's avatar
Dan Winship committed
318
	GVariantBuilder props;
319 320 321 322 323

	keys = g_key_file_get_keys (kf, group_name, NULL, error);
	if (!keys)
		return FALSE;

Dan Winship's avatar
Dan Winship committed
324
	g_variant_builder_init (&props, G_VARIANT_TYPE ("a{sv}"));
325 326 327 328
	for (iter = keys; iter && *iter; iter++) {
		val = g_key_file_get_string (kf, group_name, *iter, error);
		if (!val)
			return FALSE;
Dan Winship's avatar
Dan Winship committed
329
		g_variant_builder_add (&props, "{sv}", *iter, g_variant_new_string (val));
330 331 332
		g_free (val);
	}

Dan Winship's avatar
Dan Winship committed
333
	*out_props = g_variant_builder_end (&props);
334 335 336 337 338
	return TRUE;
}

static gboolean
get_dispatcher_file (const char *file,
Dan Winship's avatar
Dan Winship committed
339 340 341 342 343 344 345
                     GVariant **out_con_dict,
                     GVariant **out_con_props,
                     GVariant **out_device_props,
                     GVariant **out_device_ip4_props,
                     GVariant **out_device_ip6_props,
                     GVariant **out_device_dhcp4_props,
                     GVariant **out_device_dhcp6_props,
346
                     char **out_vpn_ip_iface,
Dan Winship's avatar
Dan Winship committed
347 348
                     GVariant **out_vpn_ip4_props,
                     GVariant **out_vpn_ip6_props,
349 350 351 352 353 354 355 356 357 358 359 360 361
                     char **out_expected_iface,
                     char **out_action,
                     GHashTable **out_env,
                     GError **error)
{
	GKeyFile *kf;
	gboolean success = FALSE;
	char **keys, **iter, *val;

	kf = g_key_file_new ();
	if (!g_key_file_load_from_file (kf, file, G_KEY_FILE_NONE, error))
		return FALSE;

362
	if (!parse_main (kf,
Dan Winship's avatar
Dan Winship committed
363
	                 out_con_dict,
364 365 366 367 368
	                 out_con_props,
	                 out_expected_iface,
	                 out_action,
	                 out_vpn_ip_iface,
	                 error))
369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412
		goto out;

	if (!parse_device (kf, out_device_props, error))
		goto out;

	if (g_key_file_has_group (kf, "ip4")) {
		if (!parse_ip4 (kf, out_device_ip4_props, "ip4", error))
			goto out;
	}

	if (g_key_file_has_group (kf, "dhcp4")) {
		if (!parse_dhcp (kf, "dhcp4", out_device_dhcp4_props, error))
			goto out;
	}

	if (g_key_file_has_group (kf, "dhcp6")) {
		if (!parse_dhcp (kf, "dhcp6", out_device_dhcp4_props, error))
			goto out;
	}

	g_assert (g_key_file_has_group (kf, "env"));
	keys = g_key_file_get_keys (kf, "env", NULL, error);
	*out_env = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
	for (iter = keys; iter && *iter; iter++) {
		val = g_key_file_get_string (kf, "env", *iter, error);
		if (!val)
			goto out;
		g_hash_table_insert (*out_env,
		                     g_strdup_printf ("%s=%s", *iter, val),
		                     GUINT_TO_POINTER (1));
		g_free (val);
	}
	g_strfreev (keys);

	success = TRUE;

out:
	g_key_file_free (kf);
	return success;
}

/*******************************************/

static void
413
test_generic (const char *file, const char *override_vpn_ip_iface)
414
{
Dan Winship's avatar
Dan Winship committed
415 416 417 418 419 420 421
	GVariant *con_dict = NULL;
	GVariant *con_props = NULL;
	GVariant *device_props = NULL;
	GVariant *device_ip4_props = NULL;
	GVariant *device_ip6_props = NULL;
	GVariant *device_dhcp4_props = NULL;
	GVariant *device_dhcp6_props = NULL;
422
	char *vpn_ip_iface = NULL;
Dan Winship's avatar
Dan Winship committed
423 424
	GVariant *vpn_ip4_props = NULL;
	GVariant *vpn_ip6_props = NULL;
425 426 427 428 429 430 431 432 433 434
	char *expected_iface = NULL;
	char *action = NULL;
	char *out_iface = NULL;
	GHashTable *expected_env = NULL;
	GError *error = NULL;
	gboolean success;
	char *p;
	char **denv, **iter;

	/* Read in the test file */
435
	p = g_build_filename (SRCDIR, file, NULL);
436
	success = get_dispatcher_file (p,
Dan Winship's avatar
Dan Winship committed
437
	                               &con_dict,
438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456
	                               &con_props,
	                               &device_props,
	                               &device_ip4_props,
	                               &device_ip6_props,
	                               &device_dhcp4_props,
	                               &device_dhcp6_props,
	                               &vpn_ip_iface,
	                               &vpn_ip4_props,
	                               &vpn_ip6_props,
	                               &expected_iface,
	                               &action,
	                               &expected_env,
	                               &error);
	g_free (p);
	g_assert_no_error (error);
	g_assert (success);

	/* Get the environment from the dispatcher code */
	denv = nm_dispatcher_utils_construct_envp (action,
Dan Winship's avatar
Dan Winship committed
457
	                                           con_dict,
458 459 460 461 462 463
	                                           con_props,
	                                           device_props,
	                                           device_ip4_props,
	                                           device_ip6_props,
	                                           device_dhcp4_props,
	                                           device_dhcp6_props,
464
	                                           override_vpn_ip_iface ? override_vpn_ip_iface : vpn_ip_iface,
465 466 467 468 469
	                                           vpn_ip4_props,
	                                           vpn_ip6_props,
	                                           &out_iface);

	/* Print out environment for now */
470 471
#ifdef DEBUG
	g_message ("\n******* Generated environment:");
472 473
	for (iter = denv; iter && *iter; iter++)
		g_message ("   %s", *iter);
474
#endif
475

476
#ifdef DEBUG
477 478 479
	{
		GHashTableIter k;
		const char *key;
480 481

		g_message ("\n******* Expected environment:");
482 483 484 485
		g_hash_table_iter_init (&k, expected_env);
		while (g_hash_table_iter_next (&k, (gpointer) &key, NULL))
			g_message ("   %s", key);
	}
486 487 488
#endif

	g_assert_cmpint (g_strv_length (denv), ==, g_hash_table_size (expected_env));
489 490 491 492

	/* Compare dispatcher generated env and expected env */
	for (iter = denv; iter && *iter; iter++) {
		gpointer foo;
493
		const char *i_value = *iter;
494

495 496 497 498 499 500 501 502
		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);
503
		if (!foo)
504
			g_warning ("Failed to find %s in environment", i_value);
505 506 507 508
		g_assert (foo);
	}

	g_assert_cmpstr (expected_iface, ==, out_iface);
509 510 511 512 513

	g_free (out_iface);
	g_free (vpn_ip_iface);
	g_free (expected_iface);
	g_free (action);
Dan Winship's avatar
Dan Winship committed
514 515 516
	g_variant_unref (con_dict);
	g_variant_unref (con_props);
	g_variant_unref (device_props);
517
	if (device_ip4_props)
Dan Winship's avatar
Dan Winship committed
518
		g_variant_unref (device_ip4_props);
519
	if (device_ip6_props)
Dan Winship's avatar
Dan Winship committed
520
		g_variant_unref (device_ip6_props);
521
	if (device_dhcp4_props)
Dan Winship's avatar
Dan Winship committed
522
		g_variant_unref (device_dhcp4_props);
523
	if (device_dhcp6_props)
Dan Winship's avatar
Dan Winship committed
524
		g_variant_unref (device_dhcp6_props);
525
	if (vpn_ip4_props)
Dan Winship's avatar
Dan Winship committed
526
		g_variant_unref (vpn_ip4_props);
527
	if (vpn_ip6_props)
Dan Winship's avatar
Dan Winship committed
528
		g_variant_unref (vpn_ip6_props);
529
	g_hash_table_destroy (expected_env);
530 531 532 533 534
}

/*******************************************/

static void
535
test_old_up (void)
536
{
537
	test_generic ("dispatcher-old-up", NULL);
538 539 540
}

static void
541
test_old_down (void)
542
{
543
	test_generic ("dispatcher-old-down", NULL);
544 545 546
}

static void
547
test_old_vpn_up (void)
548
{
549
	test_generic ("dispatcher-old-vpn-up", NULL);
550 551 552
}

static void
553
test_old_vpn_down (void)
554
{
555
	test_generic ("dispatcher-old-vpn-down", NULL);
556 557 558
}

static void
559
test_up_empty_vpn_iface (void)
560 561 562 563
{
	/* Test that an empty VPN iface variable, like is passed through D-Bus
	 * from NM, is ignored by the dispatcher environment construction code.
	 */
564
	test_generic ("dispatcher-old-up", "");
565 566 567 568
}

/*******************************************/

Dan Winship's avatar
Dan Winship committed
569 570
int
main (int argc, char **argv)
571 572
{
	g_test_init (&argc, &argv, NULL);
573 574

#if !GLIB_CHECK_VERSION (2, 35, 0)
575
	g_type_init ();
576
#endif
577

578 579 580 581
	g_test_add_func ("/dispatcher/old_up", test_old_up);
	g_test_add_func ("/dispatcher/old_down", test_old_down);
	g_test_add_func ("/dispatcher/old_vpn_up", test_old_vpn_up);
	g_test_add_func ("/dispatcher/old_vpn_down", test_old_vpn_down);
582

583
	g_test_add_func ("/dispatcher/up_empty_vpn_iface", test_up_empty_vpn_iface);
584

585 586 587
	return g_test_run ();
}