nm-vpn-service-plugin.c 42.1 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
/*
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 * Copyright 2007 - 2008 Novell, Inc.
19
 * Copyright 2007 - 2015 Red Hat, Inc.
20 21
 */

22
#include "nm-default.h"
23

24 25
#include "nm-vpn-service-plugin.h"

26 27 28 29 30 31 32 33 34 35 36
#include <errno.h>
#include <signal.h>
#include <stdlib.h>

#include "nm-enum-types.h"
#include "nm-utils.h"
#include "nm-connection.h"
#include "nm-dbus-helpers.h"
#include "nm-core-internal.h"
#include "nm-simple-connection.h"

37
#include "introspection/org.freedesktop.NetworkManager.VPN.Plugin.h"
38

39
#define NM_VPN_SERVICE_PLUGIN_QUIT_TIMER    180
40

41
static void nm_vpn_service_plugin_initable_iface_init (GInitableIface *iface);
42

43 44
G_DEFINE_ABSTRACT_TYPE_WITH_CODE (NMVpnServicePlugin, nm_vpn_service_plugin, G_TYPE_OBJECT,
                                  G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, nm_vpn_service_plugin_initable_iface_init);
45 46 47 48 49 50 51
                                  )

typedef struct {
	NMVpnServiceState state;

	/* DBUS-y stuff */
	GDBusConnection *connection;
52
	NMDBusVpnPlugin *dbus_vpn_service_plugin;
53
	char *dbus_service_name;
54
	gboolean dbus_watch_peer;
55 56 57 58 59

	/* Temporary stuff */
	guint connect_timer;
	guint quit_timer;
	guint fail_stop_id;
60
	guint peer_watch_id;
61 62 63 64 65 66 67 68
	gboolean interactive;

	gboolean got_config;
	gboolean has_ip4, got_ip4;
	gboolean has_ip6, got_ip6;

	/* Config stuff copied from config to ip4config */
	GVariant *banner, *tundev, *gateway, *mtu;
69
} NMVpnServicePluginPrivate;
70

71
#define NM_VPN_SERVICE_PLUGIN_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_VPN_SERVICE_PLUGIN, NMVpnServicePluginPrivate))
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90

enum {
	STATE_CHANGED,
	CONFIG,
	IP4_CONFIG,
	IP6_CONFIG,
	LOGIN_BANNER,
	FAILURE,
	QUIT,
	SECRETS_REQUIRED,

	LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = { 0 };

enum {
	PROP_0,
	PROP_DBUS_SERVICE_NAME,
91
	PROP_DBUS_WATCH_PEER,
92 93 94 95 96 97 98 99
	PROP_STATE,

	LAST_PROP
};

static GSList *active_plugins = NULL;

static void
100 101
nm_vpn_service_plugin_set_connection (NMVpnServicePlugin *plugin,
                                      GDBusConnection *connection)
102
{
103
	NMVpnServicePluginPrivate *priv = NM_VPN_SERVICE_PLUGIN_GET_PRIVATE (plugin);
104 105 106 107 108 109 110 111

	g_clear_object (&priv->connection);

	if (connection)
		priv->connection = g_object_ref (connection);
}

/**
112
 * nm_vpn_service_plugin_get_connection:
113 114
 *
 * Returns: (transfer full):
115 116
 *
 * Since: 1.2
117 118
 */
GDBusConnection *
119
nm_vpn_service_plugin_get_connection (NMVpnServicePlugin *plugin)
120 121 122
{
	GDBusConnection *connection;

123
	g_return_val_if_fail (NM_IS_VPN_SERVICE_PLUGIN (plugin), NULL);
124

125
	connection = NM_VPN_SERVICE_PLUGIN_GET_PRIVATE (plugin)->connection;
126 127 128 129 130 131 132

	if (connection)
		g_object_ref (connection);

	return connection;
}

133
static NMVpnServiceState
134
nm_vpn_service_plugin_get_state (NMVpnServicePlugin *plugin)
135
{
136
	g_return_val_if_fail (NM_IS_VPN_SERVICE_PLUGIN (plugin), NM_VPN_SERVICE_STATE_UNKNOWN);
137

138
	return NM_VPN_SERVICE_PLUGIN_GET_PRIVATE (plugin)->state;
139 140
}

141
static void
142 143
nm_vpn_service_plugin_set_state (NMVpnServicePlugin *plugin,
                                 NMVpnServiceState state)
144
{
145
	NMVpnServicePluginPrivate *priv;
146

147
	g_return_if_fail (NM_IS_VPN_SERVICE_PLUGIN (plugin));
148

149
	priv = NM_VPN_SERVICE_PLUGIN_GET_PRIVATE (plugin);
150 151 152
	if (priv->state != state) {
		priv->state = state;
		g_signal_emit (plugin, signals[STATE_CHANGED], 0, state);
153 154
		if (priv->dbus_vpn_service_plugin)
			nmdbus_vpn_plugin_emit_state_changed (priv->dbus_vpn_service_plugin, state);
155 156 157 158
	}
}

void
159 160
nm_vpn_service_plugin_set_login_banner (NMVpnServicePlugin *plugin,
                                        const char *banner)
161
{
162 163
	NMVpnServicePluginPrivate *priv;

164
	g_return_if_fail (NM_IS_VPN_SERVICE_PLUGIN (plugin));
165 166
	g_return_if_fail (banner != NULL);

167
	priv = NM_VPN_SERVICE_PLUGIN_GET_PRIVATE (plugin);
168
	g_signal_emit (plugin, signals[LOGIN_BANNER], 0, banner);
169 170
	if (priv->dbus_vpn_service_plugin)
		nmdbus_vpn_plugin_emit_login_banner (priv->dbus_vpn_service_plugin, banner);
171 172
}

173 174 175 176 177 178 179
static void
_emit_failure (NMVpnServicePlugin *plugin,
               NMVpnPluginFailure reason)
{
	NMVpnServicePluginPrivate *priv = NM_VPN_SERVICE_PLUGIN_GET_PRIVATE (plugin);

	g_signal_emit (plugin, signals[FAILURE], 0, reason);
180 181
	if (priv->dbus_vpn_service_plugin)
		nmdbus_vpn_plugin_emit_failure (priv->dbus_vpn_service_plugin, reason);
182 183
}

184
void
185 186
nm_vpn_service_plugin_failure (NMVpnServicePlugin *plugin,
                               NMVpnPluginFailure reason)
187
{
188
	g_return_if_fail (NM_IS_VPN_SERVICE_PLUGIN (plugin));
189

190 191
	_emit_failure (plugin, reason);
	nm_vpn_service_plugin_disconnect (plugin, NULL);
192 193 194
}

gboolean
195
nm_vpn_service_plugin_disconnect (NMVpnServicePlugin *plugin, GError **err)
196 197 198 199
{
	gboolean ret = FALSE;
	NMVpnServiceState state;

200
	g_return_val_if_fail (NM_IS_VPN_SERVICE_PLUGIN (plugin), FALSE);
201

202
	state = nm_vpn_service_plugin_get_state (plugin);
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
	switch (state) {
	case NM_VPN_SERVICE_STATE_STOPPING:
		g_set_error (err,
		             NM_VPN_PLUGIN_ERROR,
		             NM_VPN_PLUGIN_ERROR_STOPPING_IN_PROGRESS,
		             "%s",
		             "Could not process the request because the VPN connection is already being stopped.");
		break;
	case NM_VPN_SERVICE_STATE_STOPPED:
		g_set_error (err,
		             NM_VPN_PLUGIN_ERROR,
		             NM_VPN_PLUGIN_ERROR_ALREADY_STOPPED,
		             "%s",
		             "Could not process the request because no VPN connection was active.");
		break;
	case NM_VPN_SERVICE_STATE_STARTING:
219
		_emit_failure (plugin, NM_VPN_PLUGIN_FAILURE_CONNECT_FAILED);
220
		/* fall through */
221
	case NM_VPN_SERVICE_STATE_STARTED:
222 223 224
		nm_vpn_service_plugin_set_state (plugin, NM_VPN_SERVICE_STATE_STOPPING);
		ret = NM_VPN_SERVICE_PLUGIN_GET_CLASS (plugin)->disconnect (plugin, err);
		nm_vpn_service_plugin_set_state (plugin, NM_VPN_SERVICE_STATE_STOPPED);
225 226 227
		break;
	case NM_VPN_SERVICE_STATE_INIT:
		ret = TRUE;
228
		nm_vpn_service_plugin_set_state (plugin, NM_VPN_SERVICE_STATE_STOPPED);
229 230 231 232 233 234 235 236 237 238 239 240
		break;

	default:
		g_warning ("Unhandled VPN service state %d", state);
		g_assert_not_reached ();
		break;
	}

	return ret;
}

static void
241
nm_vpn_service_plugin_emit_quit (NMVpnServicePlugin *plugin)
242 243 244 245
{
	g_signal_emit (plugin, signals[QUIT], 0);
}

246 247 248 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 279 280 281 282 283 284 285 286 287 288 289 290
/**
 * nm_vpn_service_plugin_shutdown:
 * @plugin: the #NMVpnServicePlugin instance
 *
 * Shutdown the @plugin and disconnect from D-Bus. After this,
 * the plugin instance is dead and should no longer be used.
 * It ensures to get no more requests from D-Bus. In principle,
 * you don't need to shutdown the plugin, disposing the instance
 * has the same effect. However, this gives a way to deactivate
 * the plugin before giving up the last reference.
 *
 * Since: 1.12
 */
void
nm_vpn_service_plugin_shutdown (NMVpnServicePlugin *plugin)
{
	NMVpnServicePluginPrivate *priv;
	NMVpnServiceState state;
	GError *error = NULL;

	g_return_if_fail (NM_IS_VPN_SERVICE_PLUGIN (plugin));

	priv = NM_VPN_SERVICE_PLUGIN_GET_PRIVATE (plugin);

	nm_clear_g_source (&priv->fail_stop_id);
	nm_clear_g_source (&priv->quit_timer);
	nm_clear_g_source (&priv->connect_timer);

	state = nm_vpn_service_plugin_get_state (plugin);
	if (state == NM_VPN_SERVICE_STATE_STARTED ||
	    state == NM_VPN_SERVICE_STATE_STARTING) {
		nm_vpn_service_plugin_disconnect (plugin, &error);

		if (error) {
			g_warning ("Error disconnecting VPN connection: %s", error->message);
			g_error_free (error);
		}
	}

	if (priv->dbus_vpn_service_plugin) {
		g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (priv->dbus_vpn_service_plugin));
		g_clear_object (&priv->dbus_vpn_service_plugin);
	}
}

291 292 293
static gboolean
connect_timer_expired (gpointer data)
{
294
	NMVpnServicePlugin *plugin = NM_VPN_SERVICE_PLUGIN (data);
295 296
	GError *err = NULL;

297
	NM_VPN_SERVICE_PLUGIN_GET_PRIVATE (plugin)->connect_timer = 0;
298
	g_message ("Connect timer expired, disconnecting.");
299
	nm_vpn_service_plugin_disconnect (plugin, &err);
300 301 302 303 304 305 306 307 308 309 310
	if (err) {
		g_warning ("Disconnect failed: %s", err->message);
		g_error_free (err);
	}

	return G_SOURCE_REMOVE;
}

static gboolean
quit_timer_expired (gpointer data)
{
311
	NMVpnServicePlugin *self = NM_VPN_SERVICE_PLUGIN (data);
312

313 314
	NM_VPN_SERVICE_PLUGIN_GET_PRIVATE (self)->quit_timer = 0;
	nm_vpn_service_plugin_emit_quit (self);
315 316 317 318
	return G_SOURCE_REMOVE;
}

static void
319
schedule_quit_timer (NMVpnServicePlugin *self)
320
{
321
	NMVpnServicePluginPrivate *priv = NM_VPN_SERVICE_PLUGIN_GET_PRIVATE (self);
322 323

	nm_clear_g_source (&priv->quit_timer);
324
	priv->quit_timer = g_timeout_add_seconds (NM_VPN_SERVICE_PLUGIN_QUIT_TIMER,
325 326 327 328 329 330 331
	                                          quit_timer_expired,
	                                          self);
}

static gboolean
fail_stop (gpointer data)
{
332
	NMVpnServicePlugin *self = NM_VPN_SERVICE_PLUGIN (data);
333

334 335
	NM_VPN_SERVICE_PLUGIN_GET_PRIVATE (self)->fail_stop_id = 0;
	nm_vpn_service_plugin_set_state (self, NM_VPN_SERVICE_STATE_STOPPED);
336 337 338 339
	return G_SOURCE_REMOVE;
}

static void
340
schedule_fail_stop (NMVpnServicePlugin *plugin, guint timeout_secs)
341
{
342
	NMVpnServicePluginPrivate *priv = NM_VPN_SERVICE_PLUGIN_GET_PRIVATE (plugin);
343 344

	nm_clear_g_source (&priv->fail_stop_id);
345 346 347 348
	if (timeout_secs)
		priv->fail_stop_id = g_timeout_add_seconds (timeout_secs, fail_stop, plugin);
	else
		priv->fail_stop_id = g_idle_add (fail_stop, plugin);
349 350 351
}

void
352 353
nm_vpn_service_plugin_set_config (NMVpnServicePlugin *plugin,
                                  GVariant *config)
354
{
355
	NMVpnServicePluginPrivate *priv = NM_VPN_SERVICE_PLUGIN_GET_PRIVATE (plugin);
356

357
	g_return_if_fail (NM_IS_VPN_SERVICE_PLUGIN (plugin));
358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385
	g_return_if_fail (config != NULL);

	priv->got_config = TRUE;

	(void) g_variant_lookup (config, NM_VPN_PLUGIN_CONFIG_HAS_IP4, "b", &priv->has_ip4);
	(void) g_variant_lookup (config, NM_VPN_PLUGIN_CONFIG_HAS_IP6, "b", &priv->has_ip6);

	/* Record the items that need to also be inserted into the
	 * ip4config, for compatibility with older daemons.
	 */
	if (priv->banner)
		g_variant_unref (priv->banner);
	priv->banner = g_variant_lookup_value (config, NM_VPN_PLUGIN_CONFIG_BANNER,
	                                       G_VARIANT_TYPE ("s"));
	if (priv->tundev)
		g_variant_unref (priv->tundev);
	priv->tundev = g_variant_lookup_value (config, NM_VPN_PLUGIN_CONFIG_TUNDEV,
	                                       G_VARIANT_TYPE ("s"));
	if (priv->gateway)
		g_variant_unref (priv->gateway);
	priv->gateway = g_variant_lookup_value (config, NM_VPN_PLUGIN_CONFIG_EXT_GATEWAY,
	                                        G_VARIANT_TYPE ("u"));
	if (priv->mtu)
		g_variant_unref (priv->mtu);
	priv->mtu = g_variant_lookup_value (config, NM_VPN_PLUGIN_CONFIG_MTU,
	                                    G_VARIANT_TYPE ("u"));

	g_signal_emit (plugin, signals[CONFIG], 0, config);
386 387
	if (priv->dbus_vpn_service_plugin)
		nmdbus_vpn_plugin_emit_config (priv->dbus_vpn_service_plugin, config);
388 389 390
}

void
391 392
nm_vpn_service_plugin_set_ip4_config (NMVpnServicePlugin *plugin,
                                      GVariant *ip4_config)
393
{
394
	NMVpnServicePluginPrivate *priv = NM_VPN_SERVICE_PLUGIN_GET_PRIVATE (plugin);
395 396 397 398 399 400
	GVariant *combined_config;
	GVariantBuilder builder;
	GVariantIter iter;
	const char *key;
	GVariant *value;

401
	g_return_if_fail (NM_IS_VPN_SERVICE_PLUGIN (plugin));
402 403 404 405 406
	g_return_if_fail (ip4_config != NULL);

	priv->got_ip4 = TRUE;

	/* Old plugins won't send the "config" signal and thus can't send
407
	 * NM_VPN_SERVICE_PLUGIN_CONFIG_HAS_IP4 either.  But since they don't support IPv6,
408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426
	 * we can safely assume that, if we don't receive a "config" signal but do
	 * receive an "ip4-config" signal, the old plugin supports IPv4.
	 */
	if (!priv->got_config)
		priv->has_ip4 = TRUE;

	/* Older NetworkManager daemons expect all config info to be in
	 * the ip4 config, so they won't even notice the "config" signal
	 * being emitted. So just copy all of that data into the ip4
	 * config too.
	 */
	g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
	g_variant_iter_init (&iter, ip4_config);
	while (g_variant_iter_next (&iter, "{&sv}", &key, &value)) {
		g_variant_builder_add (&builder, "{sv}", key, value);
		g_variant_unref (value);
	}

	if (priv->banner)
427
		g_variant_builder_add (&builder, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_BANNER, priv->banner);
428
	if (priv->tundev)
429
		g_variant_builder_add (&builder, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_TUNDEV, priv->tundev);
430
	if (priv->gateway)
431
		g_variant_builder_add (&builder, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_EXT_GATEWAY, priv->gateway);
432
	if (priv->mtu)
433
		g_variant_builder_add (&builder, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_MTU, priv->mtu);
434 435 436 437

	combined_config = g_variant_builder_end (&builder);
	g_variant_ref_sink (combined_config);
	g_signal_emit (plugin, signals[IP4_CONFIG], 0, combined_config);
438 439
	if (priv->dbus_vpn_service_plugin)
		nmdbus_vpn_plugin_emit_ip4_config (priv->dbus_vpn_service_plugin, combined_config);
440 441 442 443
	g_variant_unref (combined_config);

	if (   priv->has_ip4 == priv->got_ip4
	    && priv->has_ip6 == priv->got_ip6)
444
		nm_vpn_service_plugin_set_state (plugin, NM_VPN_SERVICE_STATE_STARTED);
445 446 447
}

void
448 449
nm_vpn_service_plugin_set_ip6_config (NMVpnServicePlugin *plugin,
                                      GVariant *ip6_config)
450
{
451
	NMVpnServicePluginPrivate *priv = NM_VPN_SERVICE_PLUGIN_GET_PRIVATE (plugin);
452

453
	g_return_if_fail (NM_IS_VPN_SERVICE_PLUGIN (plugin));
454 455
	g_return_if_fail (ip6_config != NULL);

456 457
	g_variant_ref_sink (ip6_config);

458 459
	priv->got_ip6 = TRUE;
	g_signal_emit (plugin, signals[IP6_CONFIG], 0, ip6_config);
460 461
	if (priv->dbus_vpn_service_plugin)
		nmdbus_vpn_plugin_emit_ip6_config (priv->dbus_vpn_service_plugin, ip6_config);
462

463 464
	g_variant_unref (ip6_config);

465 466
	if (   priv->has_ip4 == priv->got_ip4
	    && priv->has_ip6 == priv->got_ip6)
467
		nm_vpn_service_plugin_set_state (plugin, NM_VPN_SERVICE_STATE_STARTED);
468 469 470
}

static void
471
connect_timer_start (NMVpnServicePlugin *plugin)
472
{
473
	NMVpnServicePluginPrivate *priv = NM_VPN_SERVICE_PLUGIN_GET_PRIVATE (plugin);
474

475
	nm_clear_g_source (&priv->connect_timer);
476 477 478
	priv->connect_timer = g_timeout_add_seconds (60, connect_timer_expired, plugin);
}

479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509
static void
peer_vanished (GDBusConnection *connection,
               const gchar *sender_name,
               const gchar *object_path,
               const gchar *interface_name,
               const gchar *signal_name,
               GVariant *parameters,
               gpointer user_data)
{
	nm_vpn_service_plugin_disconnect (NM_VPN_SERVICE_PLUGIN (user_data), NULL);
}

static guint
watch_peer (NMVpnServicePlugin *plugin,
            GDBusMethodInvocation *context)
{
	GDBusConnection *connection = g_dbus_method_invocation_get_connection (context);
	const gchar *peer = g_dbus_message_get_sender (g_dbus_method_invocation_get_message (context));

	return g_dbus_connection_signal_subscribe (connection,
	                                           "org.freedesktop.DBus",
	                                           "org.freedesktop.DBus",
	                                           "NameOwnerChanged",
	                                           "/org/freedesktop/DBus",
	                                           peer,
	                                           G_DBUS_SIGNAL_FLAGS_NONE,
	                                           peer_vanished,
	                                           plugin,
	                                           NULL);
}

510
static void
511
_connect_generic (NMVpnServicePlugin *plugin,
512 513 514 515
                  GDBusMethodInvocation *context,
                  GVariant *properties,
                  GVariant *details)
{
516 517
	NMVpnServicePluginPrivate *priv = NM_VPN_SERVICE_PLUGIN_GET_PRIVATE (plugin);
	NMVpnServicePluginClass *vpn_class = NM_VPN_SERVICE_PLUGIN_GET_CLASS (plugin);
518 519 520
	NMConnection *connection;
	gboolean success = FALSE;
	GError *error = NULL;
521
	guint fail_stop_timeout = 0;
522 523 524 525 526 527 528 529 530 531 532

	if (priv->state != NM_VPN_SERVICE_STATE_STOPPED &&
	    priv->state != NM_VPN_SERVICE_STATE_INIT) {
		g_dbus_method_invocation_return_error (context,
		                                       NM_VPN_PLUGIN_ERROR,
		                                       NM_VPN_PLUGIN_ERROR_WRONG_STATE,
		                                       "Could not start connection: wrong plugin state %d",
		                                       priv->state);
		return;
	}

533
	connection = _nm_simple_connection_new_from_dbus (properties, NM_SETTING_PARSE_FLAGS_BEST_EFFORT, &error);
534 535 536 537
	if (!connection) {
		g_dbus_method_invocation_return_error (context,
		                                       NM_VPN_PLUGIN_ERROR,
		                                       NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
538 539
		                                       "Invalid connection: %s",
		                                       error->message);
540
		g_clear_error (&error);
541
		return;
542 543 544 545 546 547 548 549 550 551 552
	}

	priv->interactive = FALSE;
	if (details && !vpn_class->connect_interactive) {
		g_dbus_method_invocation_return_error (context,
		                                       NM_VPN_PLUGIN_ERROR,
		                                       NM_VPN_PLUGIN_ERROR_INTERACTIVE_NOT_SUPPORTED,
		                                       "Plugin does not implement ConnectInteractive()");
		return;
	}

553
	nm_clear_g_source (&priv->fail_stop_id);
554

555 556 557
	if (priv->dbus_watch_peer)
		priv->peer_watch_id = watch_peer (plugin, context);

558 559 560
	if (details) {
		priv->interactive = TRUE;
		success = vpn_class->connect_interactive (plugin, connection, details, &error);
561 562 563 564
		if (g_error_matches (error, NM_VPN_PLUGIN_ERROR, NM_VPN_PLUGIN_ERROR_INTERACTIVE_NOT_SUPPORTED)) {
			/* Give NetworkManager a bit of time to fall back to Connect() */
			fail_stop_timeout = 5;
		}
565 566 567 568
	} else
		success = vpn_class->connect (plugin, connection, &error);

	if (success) {
569 570
		nm_vpn_service_plugin_set_state (plugin, NM_VPN_SERVICE_STATE_STARTING);

571 572 573 574 575 576 577 578 579 580
		g_dbus_method_invocation_return_value (context, NULL);

		/* Add a timer to make sure we do not wait indefinitely for the successful connect. */
		connect_timer_start (plugin);
	} else {
		g_dbus_method_invocation_take_error (context, error);

		/* Stop the plugin from an idle handler so that the Connect
		 * method return gets sent before the STOP StateChanged signal.
		 */
581
		schedule_fail_stop (plugin, fail_stop_timeout);
582 583 584 585 586 587
	}

	g_object_unref (connection);
}

static void
588 589 590 591
impl_vpn_service_plugin_connect (NMVpnServicePlugin *plugin,
                                 GDBusMethodInvocation *context,
                                 GVariant *connection,
                                 gpointer user_data)
592 593 594 595 596
{
	_connect_generic (plugin, context, connection, NULL);
}

static void
597 598 599 600 601
impl_vpn_service_plugin_connect_interactive (NMVpnServicePlugin *plugin,
                                             GDBusMethodInvocation *context,
                                             GVariant *connection,
                                             GVariant *details,
                                             gpointer user_data)
602 603 604 605
{
	_connect_generic (plugin, context, connection, details);
}

606
/*****************************************************************************/
607 608

static void
609 610 611 612
impl_vpn_service_plugin_need_secrets (NMVpnServicePlugin *plugin,
                                      GDBusMethodInvocation *context,
                                      GVariant *properties,
                                      gpointer user_data)
613 614 615 616 617 618
{
	NMConnection *connection;
	const char *setting_name;
	gboolean needed;
	GError *error = NULL;

619
	connection = _nm_simple_connection_new_from_dbus (properties, NM_SETTING_PARSE_FLAGS_BEST_EFFORT, &error);
620 621 622 623 624 625 626 627 628 629
	if (!connection) {
		g_dbus_method_invocation_return_error (context,
		                                       NM_VPN_PLUGIN_ERROR,
		                                       NM_VPN_PLUGIN_ERROR_INVALID_CONNECTION,
		                                       "The connection was invalid: %s",
		                                       error->message);
		g_error_free (error);
		return;
	}

630
	if (!NM_VPN_SERVICE_PLUGIN_GET_CLASS (plugin)->need_secrets) {
631 632 633 634 635
		g_dbus_method_invocation_return_value (context,
		                                       g_variant_new ("(s)", ""));
		return;
	}

636
	needed = NM_VPN_SERVICE_PLUGIN_GET_CLASS (plugin)->need_secrets (plugin, connection, &setting_name, &error);
637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658
	if (error) {
		g_dbus_method_invocation_take_error (context, error);
		return;
	}

	if (needed) {
		/* Push back the quit timer so the VPN plugin doesn't quit in the
		 * middle of asking the user for secrets.
		 */
		schedule_quit_timer (plugin);

		g_assert (setting_name);
		g_dbus_method_invocation_return_value (context,
		                                       g_variant_new ("(s)", setting_name));
	} else {
		/* No secrets required */
		g_dbus_method_invocation_return_value (context,
		                                       g_variant_new ("(s)", ""));
	}
}

static void
659 660 661 662
impl_vpn_service_plugin_new_secrets (NMVpnServicePlugin *plugin,
                                     GDBusMethodInvocation *context,
                                     GVariant *properties,
                                     gpointer user_data)
663
{
664
	NMVpnServicePluginPrivate *priv = NM_VPN_SERVICE_PLUGIN_GET_PRIVATE (plugin);
665 666 667 668 669 670 671 672 673 674 675 676 677
	NMConnection *connection;
	GError *error = NULL;
	gboolean success;

	if (priv->state != NM_VPN_SERVICE_STATE_STARTING) {
		g_dbus_method_invocation_return_error (context,
		                                       NM_VPN_PLUGIN_ERROR,
		                                       NM_VPN_PLUGIN_ERROR_WRONG_STATE,
		                                       "Could not accept new secrets: wrong plugin state %d",
		                                       priv->state);
		return;
	}

678
	connection = _nm_simple_connection_new_from_dbus (properties, NM_SETTING_PARSE_FLAGS_BEST_EFFORT, &error);
679 680 681 682
	if (!connection) {
		g_dbus_method_invocation_return_error (context,
		                                       NM_VPN_PLUGIN_ERROR,
		                                       NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
683 684
		                                       "Invalid connection: %s",
		                                       error->message);
685 686 687 688
		g_clear_error (&error);
		return;
	}

689
	if (!NM_VPN_SERVICE_PLUGIN_GET_CLASS (plugin)->new_secrets) {
690 691 692 693 694 695 696 697
		g_dbus_method_invocation_return_error (context,
		                                       NM_VPN_PLUGIN_ERROR,
		                                       NM_VPN_PLUGIN_ERROR_INTERACTIVE_NOT_SUPPORTED,
		                                       "Could not accept new secrets: plugin cannot process interactive secrets");
		g_object_unref (connection);
		return;
	}

698
	success = NM_VPN_SERVICE_PLUGIN_GET_CLASS (plugin)->new_secrets (plugin, connection, &error);
699 700 701 702 703 704 705 706 707 708 709
	if (success) {
		g_dbus_method_invocation_return_value (context, NULL);

		/* Add a timer to make sure we do not wait indefinitely for the successful connect. */
		connect_timer_start (plugin);
	} else {
		g_dbus_method_invocation_take_error (context, error);

		/* Stop the plugin from and idle handler so that the NewSecrets
		 * method return gets sent before the STOP StateChanged signal.
		 */
710
		schedule_fail_stop (plugin, 0);
711 712 713 714 715 716
	}

	g_object_unref (connection);
}

/**
717 718
 * nm_vpn_service_plugin_secrets_required:
 * @plugin: the #NMVpnServicePlugin
719 720 721 722 723 724 725 726
 * @message: an information message about why secrets are required, if any
 * @hints: VPN specific secret names for required new secrets
 *
 * Called by VPN plugin implementations to signal to NetworkManager that secrets
 * are required during the connection process.  This signal may be used to
 * request new secrets when the secrets originally provided by NetworkManager
 * are insufficient, or the VPN process indicates that it needs additional
 * information to complete the request.
727 728
 *
 * Since: 1.2
729 730
 */
void
731 732 733
nm_vpn_service_plugin_secrets_required (NMVpnServicePlugin *plugin,
                                        const char *message,
                                        const char **hints)
734
{
735
	NMVpnServicePluginPrivate *priv = NM_VPN_SERVICE_PLUGIN_GET_PRIVATE (plugin);
736 737

	/* Plugin must be able to accept the new secrets if it calls this method */
738
	g_return_if_fail (NM_VPN_SERVICE_PLUGIN_GET_CLASS (plugin)->new_secrets);
739 740 741 742 743 744 745 746 747 748 749 750

	/* Plugin cannot call this method if NetworkManager didn't originally call
	 * ConnectInteractive().
	 */
	g_return_if_fail (priv->interactive == TRUE);

	/* Cancel the connect timer since secrets might take a while.  It'll
	 * get restarted when the secrets come back via NewSecrets().
	 */
	nm_clear_g_source (&priv->connect_timer);

	g_signal_emit (plugin, signals[SECRETS_REQUIRED], 0, message, hints);
751 752
	if (priv->dbus_vpn_service_plugin)
		nmdbus_vpn_plugin_emit_secrets_required (priv->dbus_vpn_service_plugin, message, hints);
753 754
}

755
/*****************************************************************************/
756 757 758 759 760 761 762

#define DATA_KEY_TAG "DATA_KEY="
#define DATA_VAL_TAG "DATA_VAL="
#define SECRET_KEY_TAG "SECRET_KEY="
#define SECRET_VAL_TAG "SECRET_VAL="

/**
763
 * nm_vpn_service_plugin_read_vpn_details:
764 765 766 767 768 769 770 771 772 773
 * @fd: file descriptor to read from, usually stdin (0)
 * @out_data: (out) (transfer full): on successful return, a hash table
 * (mapping char*:char*) containing the key/value pairs of VPN data items
 * @out_secrets: (out) (transfer full): on successful return, a hash table
 * (mapping char*:char*) containing the key/value pairsof VPN secrets
 *
 * Parses key/value pairs from a file descriptor (normally stdin) passed by
 * an applet when the applet calls the authentication dialog of the VPN plugin.
 *
 * Returns: %TRUE if reading values was successful, %FALSE if not
774 775
 *
 * Since: 1.2
776 777
 **/
gboolean
778 779 780
nm_vpn_service_plugin_read_vpn_details (int fd,
                                        GHashTable **out_data,
                                        GHashTable **out_secrets)
781
{
782 783
	gs_unref_hashtable GHashTable *data = NULL;
	gs_unref_hashtable GHashTable *secrets = NULL;
784 785
	gboolean success = FALSE;
	char *key = NULL, *val = NULL;
786
	nm_auto_free_gstring GString *line = NULL;
787 788 789 790 791 792 793
	gchar c;

	if (out_data)
		g_return_val_if_fail (*out_data == NULL, FALSE);
	if (out_secrets)
		g_return_val_if_fail (*out_secrets == NULL, FALSE);

794
	data = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_free);
Thomas Haller's avatar
Thomas Haller committed
795
	secrets = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, (GDestroyNotify) nm_free_secret);
796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847

	line = g_string_new (NULL);

	/* Read stdin for data and secret items until we get a DONE */
	while (1) {
		ssize_t nr;
		GHashTable *hash = NULL;

		errno = 0;
		nr = read (fd, &c, 1);
		if (nr == -1) {
			if (errno == EAGAIN) {
				g_usleep (100);
				continue;
			}
			break;
		}

		if (c != '\n') {
			g_string_append_c (line, c);
			continue;
		}

		/* Check for the finish marker */
		if (strcmp (line->str, "DONE") == 0)
			break;

		/* Otherwise it's a data/secret item */
		if (strncmp (line->str, DATA_KEY_TAG, strlen (DATA_KEY_TAG)) == 0) {
			hash = data;
			key = g_strdup (line->str + strlen (DATA_KEY_TAG));
		} else if (strncmp (line->str, DATA_VAL_TAG, strlen (DATA_VAL_TAG)) == 0) {
			hash = data;
			val = g_strdup (line->str + strlen (DATA_VAL_TAG));
		} else if (strncmp (line->str, SECRET_KEY_TAG, strlen (SECRET_KEY_TAG)) == 0) {
			hash = secrets;
			key = g_strdup (line->str + strlen (SECRET_KEY_TAG));
		} else if (strncmp (line->str, SECRET_VAL_TAG, strlen (SECRET_VAL_TAG)) == 0) {
			hash = secrets;
			val = g_strdup (line->str + strlen (SECRET_VAL_TAG));
		}
		g_string_truncate (line, 0);

		if (key && val && hash) {
			g_hash_table_insert (hash, key, val);
			key = NULL;
			val = NULL;
			success = TRUE;  /* Got at least one value */
		}
	}

	if (success) {
848 849
		NM_SET_OUT (out_data, g_steal_pointer (&data));
		NM_SET_OUT (out_secrets, g_steal_pointer (&secrets));
850 851 852 853 854
	}
	return success;
}

/**
855
 * nm_vpn_service_plugin_get_secret_flags:
856 857 858 859 860 861 862 863 864 865
 * @data: hash table containing VPN key/value pair data items
 * @secret_name: VPN secret key name for which to retrieve flags for
 * @out_flags: (out): on success, the flags associated with @secret_name
 *
 * Given a VPN secret key name, attempts to find the corresponding flags data
 * item in @data.  If found, converts the flags data item to
 * #NMSettingSecretFlags and returns it.
 *
 * Returns: %TRUE if the flag data item was found and successfully converted
 * to flags, %FALSE if not
866 867
 *
 * Since: 1.2
868 869
 **/
gboolean
870 871 872
nm_vpn_service_plugin_get_secret_flags (GHashTable *data,
                                        const char *secret_name,
                                        NMSettingSecretFlags *out_flags)
873
{
874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895
	gs_free char *flag_name_free = NULL;
	const char *s;
	gint64 t1;
	NMSettingSecretFlags t0;

	g_return_val_if_fail (data, FALSE);
	g_return_val_if_fail (out_flags && *out_flags == NM_SETTING_SECRET_FLAG_NONE, FALSE);
	if (!secret_name || !*secret_name)
		g_return_val_if_reached (FALSE);

	s = g_hash_table_lookup (data,
	                         nm_construct_name_a ("%s-flags", secret_name, &flag_name_free));
	if (!s)
		return FALSE;
	t1 = _nm_utils_ascii_str_to_int64 (s, 10, 0, G_MAXINT64, -1);
	if (t1 == -1)
		return FALSE;
	t0 = (NMSettingSecretFlags) t1;
	if ((gint64) t0 != t1)
		return FALSE;
	NM_SET_OUT (out_flags, t0);
	return TRUE;
896 897
}

898
/*****************************************************************************/
899 900

static void
901 902 903
impl_vpn_service_plugin_disconnect (NMVpnServicePlugin *plugin,
                                    GDBusMethodInvocation *context,
                                    gpointer user_data)
904 905 906
{
	GError *error = NULL;

907
	if (nm_vpn_service_plugin_disconnect (plugin, &error))
908 909 910 911 912 913
		g_dbus_method_invocation_return_value (context, NULL);
	else
		g_dbus_method_invocation_take_error (context, error);
}

static void
914 915 916 917
impl_vpn_service_plugin_set_config (NMVpnServicePlugin *plugin,
                                    GDBusMethodInvocation *context,
                                    GVariant *config,
                                    gpointer user_data)
918
{
919
	nm_vpn_service_plugin_set_config (plugin, config);
920 921 922 923
	g_dbus_method_invocation_return_value (context, NULL);
}

static void
924 925 926 927
impl_vpn_service_plugin_set_ip4_config (NMVpnServicePlugin *plugin,
                                        GDBusMethodInvocation *context,
                                        GVariant *config,
                                        gpointer user_data)
928
{
929
	nm_vpn_service_plugin_set_ip4_config (plugin, config);
930 931 932 933
	g_dbus_method_invocation_return_value (context, NULL);
}

static void
934 935 936 937
impl_vpn_service_plugin_set_ip6_config (NMVpnServicePlugin *plugin,
                                        GDBusMethodInvocation *context,
                                        GVariant *config,
                                        gpointer user_data)
938
{
939
	nm_vpn_service_plugin_set_ip6_config (plugin, config);
940 941 942 943
	g_dbus_method_invocation_return_value (context, NULL);
}

static void
944 945 946 947
impl_vpn_service_plugin_set_failure (NMVpnServicePlugin *plugin,
                                     GDBusMethodInvocation *context,
                                     char *reason,
                                     gpointer user_data)
948
{
949
	nm_vpn_service_plugin_failure (plugin, NM_VPN_PLUGIN_FAILURE_BAD_IP_CONFIG);
950 951 952
	g_dbus_method_invocation_return_value (context, NULL);
}

953
/*****************************************************************************/
954

955 956 957 958 959 960 961 962
static void
_emit_quit (gpointer data, gpointer user_data)
{
	NMVpnServicePlugin *plugin = data;

	nm_vpn_service_plugin_emit_quit (plugin);
}

963 964 965
static void
sigterm_handler (int signum)
{
966
	g_slist_foreach (active_plugins, _emit_quit, NULL);
967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982
}

static void
setup_unix_signal_handler (void)
{
	struct sigaction action;
	sigset_t block_mask;

	action.sa_handler = sigterm_handler;
	sigemptyset (&block_mask);
	action.sa_mask = block_mask;
	action.sa_flags = 0;
	sigaction (SIGINT, &action, NULL);
	sigaction (SIGTERM, &action, NULL);
}

983
/*****************************************************************************/
984 985 986 987 988 989 990 991 992

static void
one_plugin_destroyed (gpointer data,
                      GObject *object)
{
	active_plugins = g_slist_remove (active_plugins, object);
}

static void
993
nm_vpn_service_plugin_init (NMVpnServicePlugin *plugin)
994 995 996 997 998 999 1000 1001 1002 1003
{
	active_plugins = g_slist_append (active_plugins, plugin);
	g_object_weak_ref (G_OBJECT (plugin),
	                   one_plugin_destroyed,
	                   NULL);
}

static gboolean
init_sync (GInitable *initable, GCancellable *cancellable, GError **error)
{
1004 1005
	NMVpnServicePlugin *plugin = NM_VPN_SERVICE_PLUGIN (initable);
	NMVpnServicePluginPrivate *priv = NM_VPN_SERVICE_PLUGIN_GET_PRIVATE (plugin);
1006 1007
	gs_unref_object GDBusConnection *connection = NULL;
	gs_unref_object GDBusProxy *proxy = NULL;
1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028
	GVariant *ret;

	if (!priv->dbus_service_name) {
		g_set_error_literal (error, NM_VPN_PLUGIN_ERROR, NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
		                     _("No service name specified"));
		return FALSE;
	}

	connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, error);
	if (!connection)
		return FALSE;

	proxy = g_dbus_proxy_new_sync (connection,
	                               G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
	                               G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
	                               NULL,
	                               DBUS_SERVICE_DBUS,
	                               DBUS_PATH_DBUS,
	                               DBUS_INTERFACE_DBUS,
	                               cancellable, error);
	if (!proxy)
1029
		return FALSE;
1030

1031
	priv->dbus_vpn_service_plugin = nmdbus_vpn_plugin_skeleton_new ();
1032

1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043
	_nm_dbus_bind_properties (plugin, priv->dbus_vpn_service_plugin);
	_nm_dbus_bind_methods (plugin, priv->dbus_vpn_service_plugin,
	                       "Connect", impl_vpn_service_plugin_connect,
	                       "ConnectInteractive", impl_vpn_service_plugin_connect_interactive,
	                       "NeedSecrets", impl_vpn_service_plugin_need_secrets,
	                       "NewSecrets", impl_vpn_service_plugin_new_secrets,
	                       "Disconnect", impl_vpn_service_plugin_disconnect,
	                       "SetConfig", impl_vpn_service_plugin_set_config,
	                       "SetIp4Config", impl_vpn_service_plugin_set_ip4_config,
	                       "SetIp6Config", impl_vpn_service_plugin_set_ip6_config,
	                       "SetFailure", impl_vpn_service_plugin_set_failure,
1044 1045
	                       NULL);

1046 1047 1048 1049
	if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (priv->dbus_vpn_service_plugin),
	                                       connection,
	                                       NM_VPN_DBUS_PLUGIN_PATH,
	                                       error))
1050
		return FALSE;
1051

1052 1053
	nm_vpn_service_plugin_set_connection (plugin, connection);
	nm_vpn_service_plugin_set_state (plugin, NM_VPN_SERVICE_STATE_INIT);
1054

1055 1056 1057 1058 1059 1060 1061 1062
	ret = g_dbus_proxy_call_sync (proxy,
	                              "RequestName",
	                              g_variant_new ("(su)", priv->dbus_service_name, 0),
	                              G_DBUS_CALL_FLAGS_NONE, -1,
	                              cancellable, error);
	if (!ret) {
		if (error && *error)
			g_dbus_error_strip_remote_error (*error);
1063
		return FALSE;
1064 1065 1066
	}
	g_variant_unref (ret);

1067
	return TRUE;
1068 1069 1070 1071 1072 1073
}

static void
set_property (GObject *object, guint prop_id,
              const GValue *value, GParamSpec *pspec)
{
1074
	NMVpnServicePluginPrivate *priv = NM_VPN_SERVICE_PLUGIN_GET_PRIVATE (object);
1075 1076 1077

	switch (prop_id) {
	case PROP_DBUS_SERVICE_NAME:
1078
		/* construct-only */
1079 1080
		priv->dbus_service_name = g_value_dup_string (value);
		break;
1081
	case PROP_DBUS_WATCH_PEER:
1082
		/* construct-only */
1083 1084
		priv->dbus_watch_peer = g_value_get_boolean (value);
		break;
1085
	case PROP_STATE:
1086
		nm_vpn_service_plugin_set_state (NM_VPN_SERVICE_PLUGIN (object),
1087
		                                 (NMVpnServiceState) g_value_get_enum (value));
1088 1089 1090 1091 1092 1093