mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2025-12-26 21:40:08 +01:00
Our coding style recommends C style comments (/* */) instead of C++ (//). Also, systemd (which we partly fork) uses C style comments for the SPDX-License-Identifier. Unify the style. $ sed -i '1 s#// SPDX-License-Identifier: \([^ ]\+\)$#/* SPDX-License-Identifier: \1 */#' -- $(git ls-files -- '*.[hc]' '*.[hc]pp')
1342 lines
48 KiB
C
1342 lines
48 KiB
C
/* SPDX-License-Identifier: LGPL-2.1+ */
|
|
/*
|
|
* Copyright (C) 2007 - 2008 Novell, Inc.
|
|
* Copyright (C) 2007 - 2015 Red Hat, Inc.
|
|
*/
|
|
|
|
#include "nm-default.h"
|
|
|
|
#include "nm-vpn-service-plugin.h"
|
|
|
|
#include <signal.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "nm-glib-aux/nm-secret-utils.h"
|
|
#include "nm-glib-aux/nm-dbus-aux.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"
|
|
|
|
#include "introspection/org.freedesktop.NetworkManager.VPN.Plugin.h"
|
|
|
|
#define NM_VPN_SERVICE_PLUGIN_QUIT_TIMER 180
|
|
|
|
static void nm_vpn_service_plugin_initable_iface_init(GInitableIface *iface);
|
|
|
|
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);)
|
|
|
|
typedef struct {
|
|
NMVpnServiceState state;
|
|
|
|
/* DBUS-y stuff */
|
|
GDBusConnection *connection;
|
|
NMDBusVpnPlugin *dbus_vpn_service_plugin;
|
|
char * dbus_service_name;
|
|
gboolean dbus_watch_peer;
|
|
|
|
/* Temporary stuff */
|
|
guint connect_timer;
|
|
guint quit_timer;
|
|
guint fail_stop_id;
|
|
guint peer_watch_id;
|
|
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;
|
|
} NMVpnServicePluginPrivate;
|
|
|
|
#define NM_VPN_SERVICE_PLUGIN_GET_PRIVATE(o) \
|
|
(G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_VPN_SERVICE_PLUGIN, NMVpnServicePluginPrivate))
|
|
|
|
enum {
|
|
STATE_CHANGED,
|
|
CONFIG,
|
|
IP4_CONFIG,
|
|
IP6_CONFIG,
|
|
LOGIN_BANNER,
|
|
FAILURE,
|
|
QUIT,
|
|
SECRETS_REQUIRED,
|
|
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
static guint signals[LAST_SIGNAL] = {0};
|
|
|
|
NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_DBUS_SERVICE_NAME, PROP_DBUS_WATCH_PEER, PROP_STATE, );
|
|
|
|
static GSList *active_plugins = NULL;
|
|
|
|
static void
|
|
nm_vpn_service_plugin_set_connection(NMVpnServicePlugin *plugin, GDBusConnection *connection)
|
|
{
|
|
NMVpnServicePluginPrivate *priv = NM_VPN_SERVICE_PLUGIN_GET_PRIVATE(plugin);
|
|
|
|
g_clear_object(&priv->connection);
|
|
|
|
if (connection)
|
|
priv->connection = g_object_ref(connection);
|
|
}
|
|
|
|
/**
|
|
* nm_vpn_service_plugin_get_connection:
|
|
*
|
|
* Returns: (transfer full):
|
|
*
|
|
* Since: 1.2
|
|
*/
|
|
GDBusConnection *
|
|
nm_vpn_service_plugin_get_connection(NMVpnServicePlugin *plugin)
|
|
{
|
|
GDBusConnection *connection;
|
|
|
|
g_return_val_if_fail(NM_IS_VPN_SERVICE_PLUGIN(plugin), NULL);
|
|
|
|
connection = NM_VPN_SERVICE_PLUGIN_GET_PRIVATE(plugin)->connection;
|
|
|
|
if (connection)
|
|
g_object_ref(connection);
|
|
|
|
return connection;
|
|
}
|
|
|
|
static NMVpnServiceState
|
|
nm_vpn_service_plugin_get_state(NMVpnServicePlugin *plugin)
|
|
{
|
|
g_return_val_if_fail(NM_IS_VPN_SERVICE_PLUGIN(plugin), NM_VPN_SERVICE_STATE_UNKNOWN);
|
|
|
|
return NM_VPN_SERVICE_PLUGIN_GET_PRIVATE(plugin)->state;
|
|
}
|
|
|
|
static void
|
|
nm_vpn_service_plugin_set_state(NMVpnServicePlugin *plugin, NMVpnServiceState state)
|
|
{
|
|
NMVpnServicePluginPrivate *priv;
|
|
|
|
g_return_if_fail(NM_IS_VPN_SERVICE_PLUGIN(plugin));
|
|
|
|
priv = NM_VPN_SERVICE_PLUGIN_GET_PRIVATE(plugin);
|
|
if (priv->state != state) {
|
|
priv->state = state;
|
|
g_signal_emit(plugin, signals[STATE_CHANGED], 0, state);
|
|
if (priv->dbus_vpn_service_plugin)
|
|
nmdbus_vpn_plugin_emit_state_changed(priv->dbus_vpn_service_plugin, state);
|
|
}
|
|
}
|
|
|
|
void
|
|
nm_vpn_service_plugin_set_login_banner(NMVpnServicePlugin *plugin, const char *banner)
|
|
{
|
|
NMVpnServicePluginPrivate *priv;
|
|
|
|
g_return_if_fail(NM_IS_VPN_SERVICE_PLUGIN(plugin));
|
|
g_return_if_fail(banner != NULL);
|
|
|
|
priv = NM_VPN_SERVICE_PLUGIN_GET_PRIVATE(plugin);
|
|
g_signal_emit(plugin, signals[LOGIN_BANNER], 0, banner);
|
|
if (priv->dbus_vpn_service_plugin)
|
|
nmdbus_vpn_plugin_emit_login_banner(priv->dbus_vpn_service_plugin, banner);
|
|
}
|
|
|
|
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);
|
|
if (priv->dbus_vpn_service_plugin)
|
|
nmdbus_vpn_plugin_emit_failure(priv->dbus_vpn_service_plugin, reason);
|
|
}
|
|
|
|
void
|
|
nm_vpn_service_plugin_failure(NMVpnServicePlugin *plugin, NMVpnPluginFailure reason)
|
|
{
|
|
g_return_if_fail(NM_IS_VPN_SERVICE_PLUGIN(plugin));
|
|
|
|
_emit_failure(plugin, reason);
|
|
nm_vpn_service_plugin_disconnect(plugin, NULL);
|
|
}
|
|
|
|
gboolean
|
|
nm_vpn_service_plugin_disconnect(NMVpnServicePlugin *plugin, GError **err)
|
|
{
|
|
gboolean ret = FALSE;
|
|
NMVpnServiceState state;
|
|
|
|
g_return_val_if_fail(NM_IS_VPN_SERVICE_PLUGIN(plugin), FALSE);
|
|
|
|
state = nm_vpn_service_plugin_get_state(plugin);
|
|
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:
|
|
_emit_failure(plugin, NM_VPN_PLUGIN_FAILURE_CONNECT_FAILED);
|
|
/* fall-through */
|
|
case NM_VPN_SERVICE_STATE_STARTED:
|
|
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);
|
|
break;
|
|
case NM_VPN_SERVICE_STATE_INIT:
|
|
ret = TRUE;
|
|
nm_vpn_service_plugin_set_state(plugin, NM_VPN_SERVICE_STATE_STOPPED);
|
|
break;
|
|
|
|
default:
|
|
g_warning("Unhandled VPN service state %d", state);
|
|
g_assert_not_reached();
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
nm_vpn_service_plugin_emit_quit(NMVpnServicePlugin *plugin)
|
|
{
|
|
g_signal_emit(plugin, signals[QUIT], 0);
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
connect_timer_expired(gpointer data)
|
|
{
|
|
NMVpnServicePlugin *plugin = NM_VPN_SERVICE_PLUGIN(data);
|
|
GError * err = NULL;
|
|
|
|
NM_VPN_SERVICE_PLUGIN_GET_PRIVATE(plugin)->connect_timer = 0;
|
|
g_message("Connect timer expired, disconnecting.");
|
|
nm_vpn_service_plugin_disconnect(plugin, &err);
|
|
if (err) {
|
|
g_warning("Disconnect failed: %s", err->message);
|
|
g_error_free(err);
|
|
}
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static gboolean
|
|
quit_timer_expired(gpointer data)
|
|
{
|
|
NMVpnServicePlugin *self = NM_VPN_SERVICE_PLUGIN(data);
|
|
|
|
NM_VPN_SERVICE_PLUGIN_GET_PRIVATE(self)->quit_timer = 0;
|
|
nm_vpn_service_plugin_emit_quit(self);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
schedule_quit_timer(NMVpnServicePlugin *self)
|
|
{
|
|
NMVpnServicePluginPrivate *priv = NM_VPN_SERVICE_PLUGIN_GET_PRIVATE(self);
|
|
|
|
nm_clear_g_source(&priv->quit_timer);
|
|
priv->quit_timer =
|
|
g_timeout_add_seconds(NM_VPN_SERVICE_PLUGIN_QUIT_TIMER, quit_timer_expired, self);
|
|
}
|
|
|
|
static gboolean
|
|
fail_stop(gpointer data)
|
|
{
|
|
NMVpnServicePlugin *self = NM_VPN_SERVICE_PLUGIN(data);
|
|
|
|
NM_VPN_SERVICE_PLUGIN_GET_PRIVATE(self)->fail_stop_id = 0;
|
|
nm_vpn_service_plugin_set_state(self, NM_VPN_SERVICE_STATE_STOPPED);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
schedule_fail_stop(NMVpnServicePlugin *plugin, guint timeout_secs)
|
|
{
|
|
NMVpnServicePluginPrivate *priv = NM_VPN_SERVICE_PLUGIN_GET_PRIVATE(plugin);
|
|
|
|
nm_clear_g_source(&priv->fail_stop_id);
|
|
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);
|
|
}
|
|
|
|
void
|
|
nm_vpn_service_plugin_set_config(NMVpnServicePlugin *plugin, GVariant *config)
|
|
{
|
|
NMVpnServicePluginPrivate *priv = NM_VPN_SERVICE_PLUGIN_GET_PRIVATE(plugin);
|
|
|
|
g_return_if_fail(NM_IS_VPN_SERVICE_PLUGIN(plugin));
|
|
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);
|
|
if (priv->dbus_vpn_service_plugin)
|
|
nmdbus_vpn_plugin_emit_config(priv->dbus_vpn_service_plugin, config);
|
|
|
|
if (priv->has_ip4 == priv->got_ip4 && priv->has_ip6 == priv->got_ip6)
|
|
nm_vpn_service_plugin_set_state(plugin, NM_VPN_SERVICE_STATE_STARTED);
|
|
}
|
|
|
|
void
|
|
nm_vpn_service_plugin_set_ip4_config(NMVpnServicePlugin *plugin, GVariant *ip4_config)
|
|
{
|
|
NMVpnServicePluginPrivate *priv = NM_VPN_SERVICE_PLUGIN_GET_PRIVATE(plugin);
|
|
GVariant * combined_config;
|
|
GVariantBuilder builder;
|
|
GVariantIter iter;
|
|
const char * key;
|
|
GVariant * value;
|
|
|
|
g_return_if_fail(NM_IS_VPN_SERVICE_PLUGIN(plugin));
|
|
g_return_if_fail(ip4_config != NULL);
|
|
|
|
priv->got_ip4 = TRUE;
|
|
|
|
/* Old plugins won't send the "config" signal and thus can't send
|
|
* NM_VPN_SERVICE_PLUGIN_CONFIG_HAS_IP4 either. But since they don't support IPv6,
|
|
* 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)
|
|
g_variant_builder_add(&builder, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_BANNER, priv->banner);
|
|
if (priv->tundev)
|
|
g_variant_builder_add(&builder, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_TUNDEV, priv->tundev);
|
|
if (priv->gateway)
|
|
g_variant_builder_add(&builder,
|
|
"{sv}",
|
|
NM_VPN_PLUGIN_IP4_CONFIG_EXT_GATEWAY,
|
|
priv->gateway);
|
|
if (priv->mtu)
|
|
g_variant_builder_add(&builder, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_MTU, priv->mtu);
|
|
|
|
combined_config = g_variant_builder_end(&builder);
|
|
g_variant_ref_sink(combined_config);
|
|
g_signal_emit(plugin, signals[IP4_CONFIG], 0, combined_config);
|
|
if (priv->dbus_vpn_service_plugin)
|
|
nmdbus_vpn_plugin_emit_ip4_config(priv->dbus_vpn_service_plugin, combined_config);
|
|
g_variant_unref(combined_config);
|
|
|
|
if (priv->has_ip4 == priv->got_ip4 && priv->has_ip6 == priv->got_ip6)
|
|
nm_vpn_service_plugin_set_state(plugin, NM_VPN_SERVICE_STATE_STARTED);
|
|
}
|
|
|
|
void
|
|
nm_vpn_service_plugin_set_ip6_config(NMVpnServicePlugin *plugin, GVariant *ip6_config)
|
|
{
|
|
NMVpnServicePluginPrivate *priv = NM_VPN_SERVICE_PLUGIN_GET_PRIVATE(plugin);
|
|
|
|
g_return_if_fail(NM_IS_VPN_SERVICE_PLUGIN(plugin));
|
|
g_return_if_fail(ip6_config != NULL);
|
|
|
|
g_variant_ref_sink(ip6_config);
|
|
|
|
priv->got_ip6 = TRUE;
|
|
g_signal_emit(plugin, signals[IP6_CONFIG], 0, ip6_config);
|
|
if (priv->dbus_vpn_service_plugin)
|
|
nmdbus_vpn_plugin_emit_ip6_config(priv->dbus_vpn_service_plugin, ip6_config);
|
|
|
|
g_variant_unref(ip6_config);
|
|
|
|
if (priv->has_ip4 == priv->got_ip4 && priv->has_ip6 == priv->got_ip6)
|
|
nm_vpn_service_plugin_set_state(plugin, NM_VPN_SERVICE_STATE_STARTED);
|
|
}
|
|
|
|
static void
|
|
connect_timer_start(NMVpnServicePlugin *plugin)
|
|
{
|
|
NMVpnServicePluginPrivate *priv = NM_VPN_SERVICE_PLUGIN_GET_PRIVATE(plugin);
|
|
|
|
nm_clear_g_source(&priv->connect_timer);
|
|
priv->connect_timer = g_timeout_add_seconds(60, connect_timer_expired, plugin);
|
|
}
|
|
|
|
static void
|
|
peer_vanished(GDBusConnection *connection,
|
|
const char * sender_name,
|
|
const char * object_path,
|
|
const char * interface_name,
|
|
const char * 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 char *peer = g_dbus_message_get_sender(g_dbus_method_invocation_get_message(context));
|
|
|
|
return nm_dbus_connection_signal_subscribe_name_owner_changed(connection,
|
|
peer,
|
|
peer_vanished,
|
|
plugin,
|
|
NULL);
|
|
}
|
|
|
|
static void
|
|
_connect_generic(NMVpnServicePlugin * plugin,
|
|
GDBusMethodInvocation *context,
|
|
GVariant * properties,
|
|
GVariant * details)
|
|
{
|
|
NMVpnServicePluginPrivate *priv = NM_VPN_SERVICE_PLUGIN_GET_PRIVATE(plugin);
|
|
NMVpnServicePluginClass * vpn_class = NM_VPN_SERVICE_PLUGIN_GET_CLASS(plugin);
|
|
NMConnection * connection;
|
|
gboolean success = FALSE;
|
|
GError * error = NULL;
|
|
guint fail_stop_timeout = 0;
|
|
|
|
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;
|
|
}
|
|
|
|
connection =
|
|
_nm_simple_connection_new_from_dbus(properties, NM_SETTING_PARSE_FLAGS_BEST_EFFORT, &error);
|
|
if (!connection) {
|
|
g_dbus_method_invocation_return_error(context,
|
|
NM_VPN_PLUGIN_ERROR,
|
|
NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
|
|
"Invalid connection: %s",
|
|
error->message);
|
|
g_clear_error(&error);
|
|
return;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
nm_clear_g_source(&priv->fail_stop_id);
|
|
|
|
if (priv->dbus_watch_peer)
|
|
priv->peer_watch_id = watch_peer(plugin, context);
|
|
|
|
if (details) {
|
|
priv->interactive = TRUE;
|
|
success = vpn_class->connect_interactive(plugin, connection, details, &error);
|
|
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;
|
|
}
|
|
} else
|
|
success = vpn_class->connect(plugin, connection, &error);
|
|
|
|
if (success) {
|
|
nm_vpn_service_plugin_set_state(plugin, NM_VPN_SERVICE_STATE_STARTING);
|
|
|
|
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.
|
|
*/
|
|
schedule_fail_stop(plugin, fail_stop_timeout);
|
|
}
|
|
|
|
g_object_unref(connection);
|
|
}
|
|
|
|
static void
|
|
impl_vpn_service_plugin_connect(NMVpnServicePlugin * plugin,
|
|
GDBusMethodInvocation *context,
|
|
GVariant * connection,
|
|
gpointer user_data)
|
|
{
|
|
_connect_generic(plugin, context, connection, NULL);
|
|
}
|
|
|
|
static void
|
|
impl_vpn_service_plugin_connect_interactive(NMVpnServicePlugin * plugin,
|
|
GDBusMethodInvocation *context,
|
|
GVariant * connection,
|
|
GVariant * details,
|
|
gpointer user_data)
|
|
{
|
|
_connect_generic(plugin, context, connection, details);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void
|
|
impl_vpn_service_plugin_need_secrets(NMVpnServicePlugin * plugin,
|
|
GDBusMethodInvocation *context,
|
|
GVariant * properties,
|
|
gpointer user_data)
|
|
{
|
|
NMConnection *connection;
|
|
const char * setting_name;
|
|
gboolean needed;
|
|
GError * error = NULL;
|
|
|
|
connection =
|
|
_nm_simple_connection_new_from_dbus(properties, NM_SETTING_PARSE_FLAGS_BEST_EFFORT, &error);
|
|
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;
|
|
}
|
|
|
|
if (!NM_VPN_SERVICE_PLUGIN_GET_CLASS(plugin)->need_secrets) {
|
|
g_dbus_method_invocation_return_value(context, g_variant_new("(s)", ""));
|
|
return;
|
|
}
|
|
|
|
needed = NM_VPN_SERVICE_PLUGIN_GET_CLASS(plugin)->need_secrets(plugin,
|
|
connection,
|
|
&setting_name,
|
|
&error);
|
|
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
|
|
impl_vpn_service_plugin_new_secrets(NMVpnServicePlugin * plugin,
|
|
GDBusMethodInvocation *context,
|
|
GVariant * properties,
|
|
gpointer user_data)
|
|
{
|
|
NMVpnServicePluginPrivate *priv = NM_VPN_SERVICE_PLUGIN_GET_PRIVATE(plugin);
|
|
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;
|
|
}
|
|
|
|
connection =
|
|
_nm_simple_connection_new_from_dbus(properties, NM_SETTING_PARSE_FLAGS_BEST_EFFORT, &error);
|
|
if (!connection) {
|
|
g_dbus_method_invocation_return_error(context,
|
|
NM_VPN_PLUGIN_ERROR,
|
|
NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
|
|
"Invalid connection: %s",
|
|
error->message);
|
|
g_clear_error(&error);
|
|
return;
|
|
}
|
|
|
|
if (!NM_VPN_SERVICE_PLUGIN_GET_CLASS(plugin)->new_secrets) {
|
|
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;
|
|
}
|
|
|
|
success = NM_VPN_SERVICE_PLUGIN_GET_CLASS(plugin)->new_secrets(plugin, connection, &error);
|
|
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.
|
|
*/
|
|
schedule_fail_stop(plugin, 0);
|
|
}
|
|
|
|
g_object_unref(connection);
|
|
}
|
|
|
|
/**
|
|
* nm_vpn_service_plugin_secrets_required:
|
|
* @plugin: the #NMVpnServicePlugin
|
|
* @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.
|
|
*
|
|
* Since: 1.2
|
|
*/
|
|
void
|
|
nm_vpn_service_plugin_secrets_required(NMVpnServicePlugin *plugin,
|
|
const char * message,
|
|
const char ** hints)
|
|
{
|
|
NMVpnServicePluginPrivate *priv = NM_VPN_SERVICE_PLUGIN_GET_PRIVATE(plugin);
|
|
|
|
/* Plugin must be able to accept the new secrets if it calls this method */
|
|
g_return_if_fail(NM_VPN_SERVICE_PLUGIN_GET_CLASS(plugin)->new_secrets);
|
|
|
|
/* 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);
|
|
if (priv->dbus_vpn_service_plugin)
|
|
nmdbus_vpn_plugin_emit_secrets_required(priv->dbus_vpn_service_plugin, message, hints);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
#define DATA_KEY_TAG "DATA_KEY="
|
|
#define DATA_VAL_TAG "DATA_VAL="
|
|
#define SECRET_KEY_TAG "SECRET_KEY="
|
|
#define SECRET_VAL_TAG "SECRET_VAL="
|
|
|
|
/**
|
|
* nm_vpn_service_plugin_read_vpn_details:
|
|
* @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
|
|
*
|
|
* Since: 1.2
|
|
**/
|
|
gboolean
|
|
nm_vpn_service_plugin_read_vpn_details(int fd, GHashTable **out_data, GHashTable **out_secrets)
|
|
{
|
|
gs_unref_hashtable GHashTable *data = NULL;
|
|
gs_unref_hashtable GHashTable *secrets = NULL;
|
|
gboolean success = FALSE;
|
|
GHashTable * hash = NULL;
|
|
GString * key = NULL, *val = NULL;
|
|
nm_auto_free_gstring GString *line = NULL;
|
|
char c;
|
|
|
|
GString *str = NULL;
|
|
|
|
if (out_data)
|
|
g_return_val_if_fail(*out_data == NULL, FALSE);
|
|
if (out_secrets)
|
|
g_return_val_if_fail(*out_secrets == NULL, FALSE);
|
|
|
|
data = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, g_free);
|
|
secrets =
|
|
g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, (GDestroyNotify) nm_free_secret);
|
|
|
|
line = g_string_new(NULL);
|
|
|
|
/* Read stdin for data and secret items until we get a DONE */
|
|
while (1) {
|
|
ssize_t nr;
|
|
|
|
nr = read(fd, &c, 1);
|
|
if (nr < 0) {
|
|
if (errno == EAGAIN) {
|
|
g_usleep(100);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
if (nr > 0 && c != '\n') {
|
|
g_string_append_c(line, c);
|
|
continue;
|
|
}
|
|
|
|
if (str && *line->str == '=') {
|
|
/* continuation */
|
|
g_string_append_c(str, '\n');
|
|
g_string_append(str, line->str + 1);
|
|
} else if (key && val) {
|
|
/* done a line */
|
|
g_return_val_if_fail(hash, FALSE);
|
|
g_hash_table_insert(hash, g_string_free(key, FALSE), g_string_free(val, FALSE));
|
|
key = NULL;
|
|
val = NULL;
|
|
hash = NULL;
|
|
success = TRUE; /* Got at least one value */
|
|
}
|
|
|
|
if (strcmp(line->str, "DONE") == 0) {
|
|
/* finish marker */
|
|
break;
|
|
} else if (strncmp(line->str, DATA_KEY_TAG, strlen(DATA_KEY_TAG)) == 0) {
|
|
if (key != NULL) {
|
|
g_warning("a value expected");
|
|
g_string_free(key, TRUE);
|
|
}
|
|
key = g_string_new(line->str + strlen(DATA_KEY_TAG));
|
|
str = key;
|
|
hash = data;
|
|
} else if (strncmp(line->str, DATA_VAL_TAG, strlen(DATA_VAL_TAG)) == 0) {
|
|
if (val != NULL)
|
|
g_string_free(val, TRUE);
|
|
if (val || !key || hash != data) {
|
|
g_warning("%s not preceded by %s", DATA_VAL_TAG, DATA_KEY_TAG);
|
|
break;
|
|
}
|
|
val = g_string_new(line->str + strlen(DATA_VAL_TAG));
|
|
str = val;
|
|
} else if (strncmp(line->str, SECRET_KEY_TAG, strlen(SECRET_KEY_TAG)) == 0) {
|
|
if (key != NULL) {
|
|
g_warning("a value expected");
|
|
g_string_free(key, TRUE);
|
|
}
|
|
key = g_string_new(line->str + strlen(SECRET_KEY_TAG));
|
|
str = key;
|
|
hash = secrets;
|
|
} else if (strncmp(line->str, SECRET_VAL_TAG, strlen(SECRET_VAL_TAG)) == 0) {
|
|
if (val != NULL)
|
|
g_string_free(val, TRUE);
|
|
if (val || !key || hash != secrets) {
|
|
g_warning("%s not preceded by %s", SECRET_VAL_TAG, SECRET_KEY_TAG);
|
|
break;
|
|
}
|
|
val = g_string_new(line->str + strlen(SECRET_VAL_TAG));
|
|
str = val;
|
|
}
|
|
|
|
g_string_truncate(line, 0);
|
|
|
|
if (nr == 0)
|
|
break;
|
|
}
|
|
|
|
if (success) {
|
|
NM_SET_OUT(out_data, g_steal_pointer(&data));
|
|
NM_SET_OUT(out_secrets, g_steal_pointer(&secrets));
|
|
}
|
|
return success;
|
|
}
|
|
|
|
/**
|
|
* nm_vpn_service_plugin_get_secret_flags:
|
|
* @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
|
|
*
|
|
* Since: 1.2
|
|
**/
|
|
gboolean
|
|
nm_vpn_service_plugin_get_secret_flags(GHashTable * data,
|
|
const char * secret_name,
|
|
NMSettingSecretFlags *out_flags)
|
|
{
|
|
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;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void
|
|
impl_vpn_service_plugin_disconnect(NMVpnServicePlugin * plugin,
|
|
GDBusMethodInvocation *context,
|
|
gpointer user_data)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (nm_vpn_service_plugin_disconnect(plugin, &error))
|
|
g_dbus_method_invocation_return_value(context, NULL);
|
|
else
|
|
g_dbus_method_invocation_take_error(context, error);
|
|
}
|
|
|
|
static void
|
|
impl_vpn_service_plugin_set_config(NMVpnServicePlugin * plugin,
|
|
GDBusMethodInvocation *context,
|
|
GVariant * config,
|
|
gpointer user_data)
|
|
{
|
|
nm_vpn_service_plugin_set_config(plugin, config);
|
|
g_dbus_method_invocation_return_value(context, NULL);
|
|
}
|
|
|
|
static void
|
|
impl_vpn_service_plugin_set_ip4_config(NMVpnServicePlugin * plugin,
|
|
GDBusMethodInvocation *context,
|
|
GVariant * config,
|
|
gpointer user_data)
|
|
{
|
|
nm_vpn_service_plugin_set_ip4_config(plugin, config);
|
|
g_dbus_method_invocation_return_value(context, NULL);
|
|
}
|
|
|
|
static void
|
|
impl_vpn_service_plugin_set_ip6_config(NMVpnServicePlugin * plugin,
|
|
GDBusMethodInvocation *context,
|
|
GVariant * config,
|
|
gpointer user_data)
|
|
{
|
|
nm_vpn_service_plugin_set_ip6_config(plugin, config);
|
|
g_dbus_method_invocation_return_value(context, NULL);
|
|
}
|
|
|
|
static void
|
|
impl_vpn_service_plugin_set_failure(NMVpnServicePlugin * plugin,
|
|
GDBusMethodInvocation *context,
|
|
char * reason,
|
|
gpointer user_data)
|
|
{
|
|
nm_vpn_service_plugin_failure(plugin, NM_VPN_PLUGIN_FAILURE_BAD_IP_CONFIG);
|
|
g_dbus_method_invocation_return_value(context, NULL);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void
|
|
_emit_quit(gpointer data, gpointer user_data)
|
|
{
|
|
NMVpnServicePlugin *plugin = data;
|
|
|
|
nm_vpn_service_plugin_emit_quit(plugin);
|
|
}
|
|
|
|
static void
|
|
sigterm_handler(int signum)
|
|
{
|
|
g_slist_foreach(active_plugins, _emit_quit, NULL);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void
|
|
one_plugin_destroyed(gpointer data, GObject *object)
|
|
{
|
|
active_plugins = g_slist_remove(active_plugins, object);
|
|
}
|
|
|
|
static void
|
|
nm_vpn_service_plugin_init(NMVpnServicePlugin *plugin)
|
|
{
|
|
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)
|
|
{
|
|
NMVpnServicePlugin * plugin = NM_VPN_SERVICE_PLUGIN(initable);
|
|
NMVpnServicePluginPrivate *priv = NM_VPN_SERVICE_PLUGIN_GET_PRIVATE(plugin);
|
|
gs_unref_object GDBusConnection *connection = NULL;
|
|
gs_unref_object GDBusProxy *proxy = NULL;
|
|
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)
|
|
return FALSE;
|
|
|
|
priv->dbus_vpn_service_plugin = nmdbus_vpn_plugin_skeleton_new();
|
|
|
|
_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,
|
|
NULL);
|
|
|
|
if (!g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON(priv->dbus_vpn_service_plugin),
|
|
connection,
|
|
NM_VPN_DBUS_PLUGIN_PATH,
|
|
error))
|
|
return FALSE;
|
|
|
|
nm_vpn_service_plugin_set_connection(plugin, connection);
|
|
nm_vpn_service_plugin_set_state(plugin, NM_VPN_SERVICE_STATE_INIT);
|
|
|
|
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);
|
|
return FALSE;
|
|
}
|
|
g_variant_unref(ret);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
|
|
{
|
|
NMVpnServicePluginPrivate *priv = NM_VPN_SERVICE_PLUGIN_GET_PRIVATE(object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_DBUS_SERVICE_NAME:
|
|
/* construct-only */
|
|
priv->dbus_service_name = g_value_dup_string(value);
|
|
break;
|
|
case PROP_DBUS_WATCH_PEER:
|
|
/* construct-only */
|
|
priv->dbus_watch_peer = g_value_get_boolean(value);
|
|
break;
|
|
case PROP_STATE:
|
|
nm_vpn_service_plugin_set_state(NM_VPN_SERVICE_PLUGIN(object),
|
|
(NMVpnServiceState) g_value_get_enum(value));
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
|
|
{
|
|
NMVpnServicePluginPrivate *priv = NM_VPN_SERVICE_PLUGIN_GET_PRIVATE(object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_DBUS_SERVICE_NAME:
|
|
g_value_set_string(value, priv->dbus_service_name);
|
|
break;
|
|
case PROP_DBUS_WATCH_PEER:
|
|
g_value_set_boolean(value, priv->dbus_watch_peer);
|
|
break;
|
|
case PROP_STATE:
|
|
g_value_set_enum(value, nm_vpn_service_plugin_get_state(NM_VPN_SERVICE_PLUGIN(object)));
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
dispose(GObject *object)
|
|
{
|
|
nm_vpn_service_plugin_shutdown(NM_VPN_SERVICE_PLUGIN(object));
|
|
G_OBJECT_CLASS(nm_vpn_service_plugin_parent_class)->dispose(object);
|
|
}
|
|
|
|
static void
|
|
finalize(GObject *object)
|
|
{
|
|
NMVpnServicePlugin * plugin = NM_VPN_SERVICE_PLUGIN(object);
|
|
NMVpnServicePluginPrivate *priv = NM_VPN_SERVICE_PLUGIN_GET_PRIVATE(plugin);
|
|
|
|
nm_vpn_service_plugin_set_connection(plugin, NULL);
|
|
g_free(priv->dbus_service_name);
|
|
|
|
nm_clear_pointer(&priv->banner, g_variant_unref);
|
|
nm_clear_pointer(&priv->tundev, g_variant_unref);
|
|
nm_clear_pointer(&priv->gateway, g_variant_unref);
|
|
nm_clear_pointer(&priv->mtu, g_variant_unref);
|
|
|
|
G_OBJECT_CLASS(nm_vpn_service_plugin_parent_class)->finalize(object);
|
|
}
|
|
|
|
static void
|
|
state_changed(NMVpnServicePlugin *plugin, NMVpnServiceState state)
|
|
{
|
|
NMVpnServicePluginPrivate *priv = NM_VPN_SERVICE_PLUGIN_GET_PRIVATE(plugin);
|
|
|
|
switch (state) {
|
|
case NM_VPN_SERVICE_STATE_STARTING:
|
|
nm_clear_g_source(&priv->quit_timer);
|
|
nm_clear_g_source(&priv->fail_stop_id);
|
|
break;
|
|
case NM_VPN_SERVICE_STATE_STOPPED:
|
|
if (priv->dbus_watch_peer)
|
|
nm_vpn_service_plugin_emit_quit(plugin);
|
|
else
|
|
schedule_quit_timer(plugin);
|
|
nm_clear_g_dbus_connection_signal(nm_vpn_service_plugin_get_connection(plugin),
|
|
&priv->peer_watch_id);
|
|
break;
|
|
default:
|
|
/* Clean up all timers we might have set up. */
|
|
nm_clear_g_source(&priv->connect_timer);
|
|
nm_clear_g_source(&priv->quit_timer);
|
|
nm_clear_g_source(&priv->fail_stop_id);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
nm_vpn_service_plugin_class_init(NMVpnServicePluginClass *plugin_class)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS(plugin_class);
|
|
|
|
g_type_class_add_private(object_class, sizeof(NMVpnServicePluginPrivate));
|
|
|
|
object_class->get_property = get_property;
|
|
object_class->set_property = set_property;
|
|
object_class->dispose = dispose;
|
|
object_class->finalize = finalize;
|
|
|
|
plugin_class->state_changed = state_changed;
|
|
|
|
/**
|
|
* NMVpnServicePlugin:service-name:
|
|
*
|
|
* The D-Bus service name of this plugin.
|
|
*
|
|
* Since: 1.2
|
|
*/
|
|
obj_properties[PROP_DBUS_SERVICE_NAME] =
|
|
g_param_spec_string(NM_VPN_SERVICE_PLUGIN_DBUS_SERVICE_NAME,
|
|
"",
|
|
"",
|
|
NULL,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
|
|
|
|
/**
|
|
* NMVpnServicePlugin:watch-peer:
|
|
*
|
|
* Whether to watch for D-Bus peer's changes.
|
|
*
|
|
* Since: 1.2
|
|
*/
|
|
obj_properties[PROP_DBUS_WATCH_PEER] =
|
|
g_param_spec_boolean(NM_VPN_SERVICE_PLUGIN_DBUS_WATCH_PEER,
|
|
"",
|
|
"",
|
|
FALSE,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
|
|
|
|
/**
|
|
* NMVpnServicePlugin:state:
|
|
*
|
|
* The state of the plugin.
|
|
*
|
|
* Since: 1.2
|
|
*/
|
|
obj_properties[PROP_STATE] = g_param_spec_enum(NM_VPN_SERVICE_PLUGIN_STATE,
|
|
"",
|
|
"",
|
|
NM_TYPE_VPN_SERVICE_STATE,
|
|
NM_VPN_SERVICE_STATE_INIT,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
|
|
|
g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
|
|
|
|
signals[STATE_CHANGED] = g_signal_new("state-changed",
|
|
G_OBJECT_CLASS_TYPE(object_class),
|
|
G_SIGNAL_RUN_FIRST,
|
|
G_STRUCT_OFFSET(NMVpnServicePluginClass, state_changed),
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
G_TYPE_NONE,
|
|
1,
|
|
G_TYPE_UINT);
|
|
|
|
signals[SECRETS_REQUIRED] = g_signal_new("secrets-required",
|
|
G_OBJECT_CLASS_TYPE(object_class),
|
|
G_SIGNAL_RUN_FIRST,
|
|
0,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
G_TYPE_NONE,
|
|
2,
|
|
G_TYPE_STRING,
|
|
G_TYPE_STRV);
|
|
|
|
signals[CONFIG] = g_signal_new("config",
|
|
G_OBJECT_CLASS_TYPE(object_class),
|
|
G_SIGNAL_RUN_FIRST,
|
|
G_STRUCT_OFFSET(NMVpnServicePluginClass, config),
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
G_TYPE_NONE,
|
|
1,
|
|
G_TYPE_VARIANT);
|
|
|
|
signals[IP4_CONFIG] = g_signal_new("ip4-config",
|
|
G_OBJECT_CLASS_TYPE(object_class),
|
|
G_SIGNAL_RUN_FIRST,
|
|
G_STRUCT_OFFSET(NMVpnServicePluginClass, ip4_config),
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
G_TYPE_NONE,
|
|
1,
|
|
G_TYPE_VARIANT);
|
|
|
|
signals[IP6_CONFIG] = g_signal_new("ip6-config",
|
|
G_OBJECT_CLASS_TYPE(object_class),
|
|
G_SIGNAL_RUN_FIRST,
|
|
G_STRUCT_OFFSET(NMVpnServicePluginClass, ip6_config),
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
G_TYPE_NONE,
|
|
1,
|
|
G_TYPE_VARIANT);
|
|
|
|
signals[LOGIN_BANNER] = g_signal_new("login-banner",
|
|
G_OBJECT_CLASS_TYPE(object_class),
|
|
G_SIGNAL_RUN_FIRST,
|
|
G_STRUCT_OFFSET(NMVpnServicePluginClass, login_banner),
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
G_TYPE_NONE,
|
|
1,
|
|
G_TYPE_STRING);
|
|
|
|
signals[FAILURE] = g_signal_new("failure",
|
|
G_OBJECT_CLASS_TYPE(object_class),
|
|
G_SIGNAL_RUN_FIRST,
|
|
G_STRUCT_OFFSET(NMVpnServicePluginClass, failure),
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
G_TYPE_NONE,
|
|
1,
|
|
G_TYPE_UINT);
|
|
|
|
signals[QUIT] = g_signal_new("quit",
|
|
G_OBJECT_CLASS_TYPE(object_class),
|
|
G_SIGNAL_RUN_FIRST,
|
|
G_STRUCT_OFFSET(NMVpnServicePluginClass, quit),
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
G_TYPE_NONE,
|
|
0,
|
|
G_TYPE_NONE);
|
|
|
|
setup_unix_signal_handler();
|
|
}
|
|
|
|
static void
|
|
nm_vpn_service_plugin_initable_iface_init(GInitableIface *iface)
|
|
{
|
|
iface->init = init_sync;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/* this header is intended to be copied to users of nm_vpn_editor_plugin_call(),
|
|
* to simplify invocation of generic functions. Include it here, to compile
|
|
* the code. */
|
|
#include "nm-utils/nm-vpn-editor-plugin-call.h"
|