summary refs log tree commit diff
path: root/libnm-core/nm-vpn-plugin-info.c
diff options
context:
space:
mode:
Diffstat (limited to 'libnm-core/nm-vpn-plugin-info.c')
-rw-r--r--libnm-core/nm-vpn-plugin-info.c396
1 files changed, 366 insertions, 30 deletions
diff --git a/libnm-core/nm-vpn-plugin-info.c b/libnm-core/nm-vpn-plugin-info.c
index 2e6275e3..a4727619 100644
--- a/libnm-core/nm-vpn-plugin-info.c
+++ b/libnm-core/nm-vpn-plugin-info.c
@@ -45,6 +45,7 @@ typedef struct {
 	char *filename;
 	char *name;
 	char *service;
+	char *auth_dialog;
 	char **aliases;
 	GKeyFile *keyfile;
 
@@ -188,6 +189,23 @@ _sort_files (LoadDirInfo *a, LoadDirInfo *b)
 	                  nm_vpn_plugin_info_get_filename (b->plugin_info));
 }
 
+#define DEFINE_DEFAULT_DIR_LIST(dir) \
+	const char *dir[] = { \
+		/* We load plugins from NM_VPN_PLUGIN_DIR *and* DEFAULT_DIR*, with
+		 * preference to the former.
+		 *
+		 * load user directory with highest priority. */ \
+		_nm_vpn_plugin_info_get_default_dir_user (), \
+		\
+		/* lib directory has higher priority then etc. The reason is that
+		 * etc is deprecated and used by old plugins. We expect newer plugins
+		 * to install their file in lib, where they have higher priority.
+		 *
+		 * Optimally, there are no duplicates anyway, so it doesn't really matter. */ \
+		_nm_vpn_plugin_info_get_default_dir_lib (), \
+		_nm_vpn_plugin_info_get_default_dir_etc (), \
+	}
+
 /**
  * _nm_vpn_plugin_info_get_default_dir_etc:
  *
@@ -253,7 +271,10 @@ _nm_vpn_plugin_info_list_load_dir (const char *dirname,
 	GSList *res = NULL;
 	guint i;
 
-	g_return_val_if_fail (dirname && dirname[0], NULL);
+	g_return_val_if_fail (dirname, NULL);
+
+	if (!dirname[0])
+		return NULL;
 
 	dir = g_dir_open (dirname, 0, NULL);
 	if (!dir)
@@ -312,21 +333,7 @@ nm_vpn_plugin_info_list_load ()
 	gint64 uid;
 	GSList *list = NULL;
 	GSList *infos, *info;
-	const char *dir[] = {
-		/* We load plugins from NM_VPN_PLUGIN_DIR *and* DEFAULT_DIR*, with
-		 * preference to the former.
-		 *
-		 * load user directory with highest priority. */
-		_nm_vpn_plugin_info_get_default_dir_user (),
-
-		/* lib directory has higher priority then etc. The reason is that
-		 * etc is deprecated and used by old plugins. We expect newer plugins
-		 * to install their file in lib, where they have higher priority.
-		 *
-		 * Optimally, there are no duplicates anyway, so it doesn't really matter. */
-		_nm_vpn_plugin_info_get_default_dir_lib (),
-		_nm_vpn_plugin_info_get_default_dir_etc (),
-	};
+	DEFINE_DEFAULT_DIR_LIST (dir);
 
 	uid = getuid ();
 
@@ -345,6 +352,66 @@ nm_vpn_plugin_info_list_load ()
 	return list;
 }
 
+/**
+ * nm_vpn_plugin_info_new_search_file:
+ * @name: (allow-none): the name to search for. Either @name or @service
+ *   must be present.
+ * @service: (allow-none): the service to search for. Either @name  or
+ *   @service must be present.
+ *
+ * This has the same effect as doing a full nm_vpn_plugin_info_list_load()
+ * followed by a search for the first matching VPN plugin info that has the
+ * given @name and/or @service.
+ *
+ * Returns: (transfer full): a newly created instance of plugin info
+ *   or %NULL if no matching value was found.
+ *
+ * Since: 1.4
+ */
+NMVpnPluginInfo *
+nm_vpn_plugin_info_new_search_file (const char *name, const char *service)
+{
+	int i;
+	gint64 uid;
+	NMVpnPluginInfo *plugin_info = NULL;
+	GSList *infos, *info;
+	DEFINE_DEFAULT_DIR_LIST (dir);
+
+	if (!name && !service)
+		g_return_val_if_reached (NULL);
+
+	uid = getuid ();
+
+	for (i = 0; !plugin_info && i < G_N_ELEMENTS (dir); i++) {
+		if (   !dir[i]
+		    || _nm_utils_strv_find_first ((char **) dir, i, dir[i]) >= 0)
+			continue;
+
+		/* We still must load the entire directory while searching for the matching
+		 * plugin-info. The reason is that reading the directory has no stable
+		 * order and we can only sort them after reading the entire directory --
+		 * which _nm_vpn_plugin_info_list_load_dir() does. */
+		infos = _nm_vpn_plugin_info_list_load_dir (dir[i], TRUE, uid, NULL, NULL);
+
+		for (info = infos; info; info = info->next) {
+			NMVpnPluginInfo *p = info->data;
+
+			if (name && !nm_streq (nm_vpn_plugin_info_get_name (p), name))
+				continue;
+			if (   service
+			    && !nm_streq (nm_vpn_plugin_info_get_service (p), service)
+			    && (_nm_utils_strv_find_first (NM_VPN_PLUGIN_INFO_GET_PRIVATE (p)->aliases,
+			                                   -1, service) < 0))
+				continue;
+			plugin_info = g_object_ref (p);
+			break;
+		}
+
+		g_slist_free_full (infos, g_object_unref);
+	}
+	return plugin_info;
+}
+
 /*********************************************************************/
 
 static gboolean
@@ -424,7 +491,7 @@ nm_vpn_plugin_info_list_add (GSList **list, NMVpnPluginInfo *plugin_info, GError
 		}
 
 		/* the plugin must have unique values for certain properties. E.g. two different
-		 * plugins cannot share the same service name. */
+		 * plugins cannot share the same service type. */
 		if (!_check_no_conflict (plugin_info, iter->data, error))
 			return FALSE;
 	}
@@ -506,10 +573,24 @@ nm_vpn_plugin_info_list_find_by_filename (GSList *list, const char *filename)
 	return NULL;
 }
 
+static NMVpnPluginInfo *
+_list_find_by_service (GSList *list, const char *service)
+{
+	for (; list; list = list->next) {
+		NMVpnPluginInfoPrivate *priv = NM_VPN_PLUGIN_INFO_GET_PRIVATE (list->data);
+
+		if (   nm_streq (priv->service, service)
+		    || _nm_utils_strv_find_first (priv->aliases, -1, service) >= 0)
+			return list->data;
+	}
+	return NULL;
+}
+
 /**
  * nm_vpn_plugin_info_list_find_by_service:
  * @list: (element-type NMVpnPluginInfo): list of plugins
- * @service: service to search
+ * @service: service to search. This can be the main service-type
+ *   or one of the provided aliases.
  *
  * Returns: (transfer none): the first plugin with a matching @service (or %NULL).
  *
@@ -518,27 +599,180 @@ nm_vpn_plugin_info_list_find_by_filename (GSList *list, const char *filename)
 NMVpnPluginInfo *
 nm_vpn_plugin_info_list_find_by_service (GSList *list, const char *service)
 {
+	if (!service)
+		g_return_val_if_reached (NULL);
+	return _list_find_by_service (list, service);
+}
+
+/* known_names are well known short names for the service-type. They all implicitly
+ * have a prefix "org.freedesktop.NetworkManager." + known_name. */
+static const char *known_names[] = {
+	"openvpn",
+	"vpnc",
+	"pptp",
+	"openconnect",
+	"openswan",
+	"libreswan",
+	"strongswan",
+	"ssh",
+	"l2tp",
+	"iodine",
+	"fortisslvpn",
+};
+
+/**
+ * nm_vpn_plugin_info_list_find_service_type:
+ * @list: (element-type NMVpnPluginInfo): a possibly empty #GSList of #NMVpnPluginInfo instances
+ * @name: a name to lookup the service-type.
+ *
+ * A VPN plugin provides one or several service-types, like org.freedesktop.NetworkManager.libreswan
+ * Certain plugins provide more then one service type, via aliases (org.freedesktop.NetworkManager.openswan).
+ * This function looks up a service-type (or an alias) based on a name.
+ *
+ * Preferably, the name can be a full service-type/alias of an installed
+ * plugin. Otherwise, it can be the name of a VPN plugin (in which case, the
+ * primary, non-aliased service-type is returned). Otherwise, it can be
+ * one of several well known short-names (which is a hard-coded list of
+ * types in libnm). On success, this returns a full qualified service-type
+ * (or an alias). It doesn't say, that such an plugin is actually available,
+ * but it could be retrieved via nm_vpn_plugin_info_list_find_by_service().
+ *
+ * Returns: (transfer full): the resolved service-type or %NULL on failure.
+ *
+ * Since: 1.4
+ */
+char *
+nm_vpn_plugin_info_list_find_service_type (GSList *list, const char *name)
+{
 	GSList *iter;
+	char *n;
 
-	if (!service)
+	if (!name)
 		g_return_val_if_reached (NULL);
+	if (!*name)
+		return NULL;
+
+	/* First, try to interpret @name as a full service-type (or alias). */
+	if (_list_find_by_service (list, name))
+		return g_strdup (name);
 
-	/* First, consider the primary service name. */
+	/* try to interpret @name as plugin name, in which case we return
+	 * the main service-type (not an alias). */
 	for (iter = list; iter; iter = iter->next) {
-		if (strcmp (NM_VPN_PLUGIN_INFO_GET_PRIVATE (iter->data)->service, service) == 0)
-			return iter->data;
+		NMVpnPluginInfoPrivate *priv = NM_VPN_PLUGIN_INFO_GET_PRIVATE (iter->data);
+
+		if (nm_streq (priv->name, name))
+			return g_strdup (priv->service);
 	}
 
-	/* Then look into the aliases. */
+	/* check the hard-coded list of short-names. They all have have the same
+	 * well-known prefix org.freedesktop.NetworkManager and the name. */
+	if (_nm_utils_strv_find_first ((char **) known_names, G_N_ELEMENTS (known_names), name) >= 0)
+		return g_strdup_printf ("%s.%s", NM_DBUS_INTERFACE, name);
+
+	/* try, if there exists a plugin with @name under org.freedesktop.NetworkManager.
+	 * Allow this to be a valid abbreviation. */
+	n = g_strdup_printf ("%s.%s", NM_DBUS_INTERFACE, name);
+	if (_list_find_by_service (list, n))
+		return n;
+	g_free (n);
+
+	/* currently, VPN plugins have no way to define a short-name for their
+	 * alias name, unless the alias name is prefixed by org.freedesktop.NetworkManager. */
+
+	return NULL;
+}
+
+static const char *
+_service_type_get_default_abbreviation (const char *service_type)
+{
+	if (!g_str_has_prefix (service_type, NM_DBUS_INTERFACE))
+		return NULL;
+	service_type += NM_STRLEN (NM_DBUS_INTERFACE);
+	if (service_type[0] != '.')
+		return NULL;
+	service_type++;
+	if (!service_type[0])
+		return NULL;
+	return service_type;
+}
+
+/**
+ * nm_vpn_plugin_info_list_get_service_types:
+ * @list: (element-type NMVpnPluginInfo): a possibly empty #GSList of #NMVpnPluginInfo
+ * @only_existing: only include results that are actually in @list.
+ *   Otherwise, the result is extended with a hard-code list or
+ *   well-known plugins
+ * @with_abbreviations: if %FALSE, only full service types are returned.
+ *   Otherwise, this also includes abbreviated names that can be used
+ *   with nm_vpn_plugin_info_list_find_service_type().
+ *
+ * Returns: (transfer full): a %NULL terminated strv list of strings.
+ *   The list itself and the values must be freed with g_strfreev().
+ *
+ * Since: 1.4
+ */
+char **
+nm_vpn_plugin_info_list_get_service_types (GSList *list,
+                                           gboolean only_existing,
+                                           gboolean with_abbreviations)
+{
+	GSList *iter;
+	GPtrArray *l;
+	guint i, j;
+	const char *n;
+
+	l = g_ptr_array_sized_new (20);
+
 	for (iter = list; iter; iter = iter->next) {
-		char **aliases = (NM_VPN_PLUGIN_INFO_GET_PRIVATE (iter->data))->aliases;
+		NMVpnPluginInfoPrivate *priv = NM_VPN_PLUGIN_INFO_GET_PRIVATE (iter->data);
 
-		if (!aliases)
-			continue;
-		if (_nm_utils_strv_find_first (aliases, -1, service) >= 0)
-			return iter->data;
+		g_ptr_array_add (l, g_strdup (priv->service));
+		if (priv->aliases) {
+			for (i = 0; priv->aliases[i]; i++)
+				g_ptr_array_add (l, g_strdup (priv->aliases[i]));
+		}
+
+		if (with_abbreviations) {
+			g_ptr_array_add (l, g_strdup (priv->name));
+			n = _service_type_get_default_abbreviation (priv->service);
+			if (n)
+				g_ptr_array_add (l, g_strdup (n));
+			for (i = 0; priv->aliases[i]; i++) {
+				n = _service_type_get_default_abbreviation (priv->aliases[i]);
+				if (n)
+					g_ptr_array_add (l, g_strdup (n));
+			}
+		}
 	}
-	return NULL;
+
+	if (!only_existing) {
+		for (i = 0; i < G_N_ELEMENTS (known_names); i++) {
+			g_ptr_array_add (l, g_strdup_printf ("%s.%s", NM_DBUS_INTERFACE, known_names[i]));
+			if (with_abbreviations)
+				g_ptr_array_add (l, g_strdup (known_names[i]));
+		}
+	}
+
+	if (l->len <= 0) {
+		g_ptr_array_free (l, TRUE);
+		return g_new0 (char *, 1);
+	}
+
+	/* sort the result and remove duplicates. */
+	g_ptr_array_sort (l, nm_strcmp_p);
+	for (i = 1, j = 1; i < l->len; i++) {
+		if (nm_streq (l->pdata[j-1], l->pdata[i]))
+			g_free (l->pdata[i]);
+		else
+			l->pdata[j++] = l->pdata[i];
+	}
+
+	if (j == l->len)
+		g_ptr_array_add (l, NULL);
+	else
+		l->pdata[j] = NULL;
+	return (char **) g_ptr_array_free (l, FALSE);
 }
 
 /*********************************************************************/
@@ -576,6 +810,77 @@ nm_vpn_plugin_info_get_name (NMVpnPluginInfo *self)
 }
 
 /**
+ * nm_vpn_plugin_info_get_service:
+ * @self: plugin info instance
+ *
+ * Returns: (transfer none): the service. Cannot be %NULL.
+ *
+ * Since: 1.4
+ */
+const char *
+nm_vpn_plugin_info_get_service (NMVpnPluginInfo *self)
+{
+	g_return_val_if_fail (NM_IS_VPN_PLUGIN_INFO (self), NULL);
+
+	return NM_VPN_PLUGIN_INFO_GET_PRIVATE (self)->service;
+}
+
+/**
+ * nm_vpn_plugin_info_get_auth_dialog:
+ * @self: plugin info instance
+ *
+ * Returns: the absolute path to the auth-dialog helper or %NULL.
+ *
+ * Since: 1.4
+ **/
+const char *
+nm_vpn_plugin_info_get_auth_dialog (NMVpnPluginInfo *self)
+{
+	NMVpnPluginInfoPrivate *priv;
+
+	g_return_val_if_fail (NM_IS_VPN_PLUGIN_INFO (self), NULL);
+
+	priv = NM_VPN_PLUGIN_INFO_GET_PRIVATE (self);
+
+	if (G_UNLIKELY (priv->auth_dialog == NULL)) {
+		const char *s;
+
+		s = g_hash_table_lookup (priv->keys, _nm_utils_strstrdictkey_static (NM_VPN_PLUGIN_INFO_KF_GROUP_GNOME, "auth-dialog"));
+		if (!s || !s[0])
+			priv->auth_dialog = g_strdup ("");
+		else if (g_path_is_absolute (s))
+			priv->auth_dialog = g_strdup (s);
+		else {
+			/* for relative paths, we take the basename and assume it's in LIBEXECDIR. */
+			gs_free char *prog_basename = g_path_get_basename (s);
+
+			priv->auth_dialog = g_build_filename (LIBEXECDIR, prog_basename, NULL);
+		}
+	}
+
+	return priv->auth_dialog[0] ? priv->auth_dialog : NULL;
+}
+
+/**
+ * nm_vpn_plugin_info_supports_hints:
+ * @self: plugin info instance
+ *
+ * Returns: %TRUE if the supports hints for secret requests, otherwise %FALSE
+ *
+ * Since: 1.4
+ */
+gboolean
+nm_vpn_plugin_info_supports_hints (NMVpnPluginInfo *self)
+{
+	const char *s;
+
+	g_return_val_if_fail (NM_IS_VPN_PLUGIN_INFO (self), FALSE);
+
+	s = nm_vpn_plugin_info_lookup_property (self, NM_VPN_PLUGIN_INFO_KF_GROUP_GNOME, "supports-hints");
+	return _nm_utils_ascii_str_to_bool (s, FALSE);
+}
+
+/**
  * nm_vpn_plugin_info_get_plugin:
  * @self: plugin info instance
  *
@@ -630,6 +935,32 @@ nm_vpn_plugin_info_supports_multiple (NMVpnPluginInfo *self)
 
 
 /**
+ * nm_vpn_plugin_info_get_aliases:
+ * @self: plugin info instance
+ *
+ * Returns: (array zero-terminated=1) (element-type utf8) (transfer none):
+ *   the aliases from the name-file.
+ *
+ * Since: 1.4
+ */
+const char *const*
+nm_vpn_plugin_info_get_aliases (NMVpnPluginInfo *self)
+{
+	NMVpnPluginInfoPrivate *priv;
+
+	g_return_val_if_fail (NM_IS_VPN_PLUGIN_INFO (self), NULL);
+
+	priv = NM_VPN_PLUGIN_INFO_GET_PRIVATE (self);
+	if (priv->aliases)
+		return (const char *const*) priv->aliases;
+
+	/* For convenience, we always want to return non-NULL, even for empty
+	 * aliases. Hack around that, by making a NULL terminated array using
+	 * the NULL of priv->aliases. */
+	return (const char *const*) &priv->aliases;
+}
+
+/**
  * nm_vpn_plugin_info_lookup_property:
  * @self: plugin info instance
  * @group: group name
@@ -753,11 +1084,13 @@ nm_vpn_plugin_info_load_editor_plugin (NMVpnPluginInfo *self, GError **error)
 
 	priv->editor_plugin_loaded = TRUE;
 	priv->editor_plugin = nm_vpn_editor_plugin_load_from_file (plugin_filename,
-	                                                           priv->service,
+	                                                           nm_vpn_plugin_info_get_service (self),
 	                                                           getuid (),
 	                                                           NULL,
 	                                                           NULL,
 	                                                           error);
+	if (priv->editor_plugin)
+		nm_vpn_editor_plugin_set_plugin_info (priv->editor_plugin, self);
 	return priv->editor_plugin;
 }
 
@@ -865,6 +1198,8 @@ init_sync (GInitable *initable, GCancellable *cancellable, GError **error)
 	}
 
 	priv->aliases = g_key_file_get_string_list (priv->keyfile, NM_VPN_PLUGIN_INFO_KF_GROUP_CONNECTION, "aliases", NULL, NULL);
+	if (priv->aliases && !priv->aliases[0])
+		g_clear_pointer (&priv->aliases, g_free);
 
 	priv->keys = g_hash_table_new_full (_nm_utils_strstrdictkey_hash,
 	                                    _nm_utils_strstrdictkey_equal,
@@ -947,6 +1282,7 @@ finalize (GObject *object)
 
 	g_free (priv->name);
 	g_free (priv->service);
+	g_free (priv->auth_dialog);
 	g_strfreev (priv->aliases);
 	g_free (priv->filename);
 	g_hash_table_unref (priv->keys);