diff options
author | Thomas Haller <thaller@redhat.com> | 2015-07-29 23:26:52 +0200 |
---|---|---|
committer | Thomas Haller <thaller@redhat.com> | 2015-07-29 23:33:31 +0200 |
commit | 3e39e5b4f04aa1ad16b226ae687413d02aa5b8eb (patch) | |
tree | 345be85fe931b3f65489e36e47da1e38b170ba25 | |
parent | 5f7f38a5867338d910fd10a49957d2796dce9435 (diff) | |
parent | 6be8a1f5495a6fb73f591cb47dd53a00cfda778b (diff) |
libnm: merge branch 'th/libnm-vpn-plugin-bgo749877'
- add new NMVpnPluginInfo class with new API to load
VPN name files.
- move NMVpnEditorPlugin to libnm-core, including code
to load the client plugin from the shared library.
- deprecate NMVpnPluginOld and add NMVpnServicePlugin.
The latter is identical to NMVpnPluginOld but renamed
and introduced as new API for 1.2.
https://bugzilla.gnome.org/show_bug.cgi?id=749877
28 files changed, 3614 insertions, 583 deletions
diff --git a/clients/tui/vpn-helpers.c b/clients/tui/vpn-helpers.c index be8f538463..9cf8ce84c0 100644 --- a/clients/tui/vpn-helpers.c +++ b/clients/tui/vpn-helpers.c @@ -34,142 +34,41 @@ #include <gmodule.h> #include <glib/gi18n.h> -#include <nm-connection.h> -#include <nm-setting-connection.h> -#include <nm-setting-vpn.h> - #include "nm-glib.h" #include "vpn-helpers.h" -#define NM_VPN_API_SUBJECT_TO_CHANGE -#include "nm-vpn-plugin-ui-interface.h" - -#define VPN_NAME_FILES_DIR SYSCONFDIR"/NetworkManager/VPN" - -static GHashTable *plugins = NULL; +#include "nm-macros-internal.h" -G_DEFINE_QUARK (NMA_ERROR, nma_error) -#define NMA_ERROR nma_error_quark () -#define NMA_ERROR_GENERIC 0 +static gboolean plugins_loaded; +static GSList *plugins = NULL; -NMVpnPluginUiInterface * +NMVpnEditorPlugin * vpn_get_plugin_by_service (const char *service) { + NMVpnEditorPlugin *plugin = NULL; + NMVpnPluginInfo *plugin_info; + g_return_val_if_fail (service != NULL, NULL); - return g_hash_table_lookup (plugins, service); + if (G_UNLIKELY (!plugins_loaded)) + vpn_get_plugins (); + + plugin_info = nm_vpn_plugin_info_list_find_by_service (plugins, service); + if (plugin_info) { + plugin = nm_vpn_plugin_info_get_editor_plugin (plugin_info); + if (!plugin) + plugin = nm_vpn_plugin_info_load_editor_plugin (plugin_info, NULL); + } + return plugin; } -GHashTable * -vpn_get_plugins (GError **error) +GSList * +vpn_get_plugins () { - GDir *dir; - const char *f; - - if (error) - g_return_val_if_fail (*error == NULL, NULL); - - if (plugins) + if (G_LIKELY (plugins_loaded)) return plugins; - - dir = g_dir_open (VPN_NAME_FILES_DIR, 0, NULL); - if (!dir) { - g_set_error (error, NMA_ERROR, NMA_ERROR_GENERIC, "Couldn't read VPN .name files directory " VPN_NAME_FILES_DIR "."); - return NULL; - } - - plugins = g_hash_table_new_full (g_str_hash, g_str_equal, - (GDestroyNotify) g_free, (GDestroyNotify) g_object_unref); - - while ((f = g_dir_read_name (dir))) { - char *path = NULL, *service = NULL; - char *so_path = NULL, *so_name = NULL; - GKeyFile *keyfile = NULL; - GModule *module; - NMVpnPluginUiFactory factory = NULL; - - if (!g_str_has_suffix (f, ".name")) - continue; - - path = g_strdup_printf ("%s/%s", VPN_NAME_FILES_DIR, f); - - keyfile = g_key_file_new (); - if (!g_key_file_load_from_file (keyfile, path, 0, NULL)) - goto next; - - service = g_key_file_get_string (keyfile, "VPN Connection", "service", NULL); - if (!service) - goto next; - - so_path = g_key_file_get_string (keyfile, "GNOME", "properties", NULL); - if (!so_path) - goto next; - - /* Remove any path and extension components, then reconstruct path - * to the SO in LIBDIR - */ - so_name = g_path_get_basename (so_path); - g_free (so_path); - so_path = g_strdup_printf ("%s/NetworkManager/%s", LIBDIR, so_name); - g_free (so_name); - - module = g_module_open (so_path, G_MODULE_BIND_LAZY | G_MODULE_BIND_LOCAL); - if (!module) { - g_set_error (error, NMA_ERROR, NMA_ERROR_GENERIC, "Cannot load the VPN plugin which provides the " - "service '%s'.", service); - goto next; - } - - if (g_module_symbol (module, "nm_vpn_plugin_ui_factory", (gpointer) &factory)) { - NMVpnPluginUiInterface *plugin; - GError *factory_error = NULL; - gboolean success = FALSE; - - plugin = factory (&factory_error); - if (plugin) { - char *plug_name = NULL, *plug_service = NULL; - - /* Validate plugin properties */ - g_object_get (G_OBJECT (plugin), - NM_VPN_PLUGIN_UI_INTERFACE_NAME, &plug_name, - NM_VPN_PLUGIN_UI_INTERFACE_SERVICE, &plug_service, - NULL); - if (!plug_name || !strlen (plug_name)) { - g_set_error (error, NMA_ERROR, NMA_ERROR_GENERIC, "cannot load VPN plugin in '%s': missing plugin name", - g_module_name (module)); - } else if (!plug_service || strcmp (plug_service, service)) { - g_set_error (error, NMA_ERROR, NMA_ERROR_GENERIC, "cannot load VPN plugin in '%s': invalid service name", - g_module_name (module)); - } else { - /* Success! */ - g_object_set_data_full (G_OBJECT (plugin), "gmodule", module, - (GDestroyNotify) g_module_close); - g_hash_table_insert (plugins, g_strdup (service), plugin); - success = TRUE; - } - g_free (plug_name); - g_free (plug_service); - } else { - g_set_error (error, NMA_ERROR, NMA_ERROR_GENERIC, "cannot load VPN plugin in '%s': %s", - g_module_name (module), g_module_error ()); - } - - if (!success) - g_module_close (module); - } else { - g_set_error (error, NMA_ERROR, NMA_ERROR_GENERIC, "cannot locate nm_vpn_plugin_ui_factory() in '%s': %s", - g_module_name (module), g_module_error ()); - g_module_close (module); - } - - next: - g_free (so_path); - g_free (service); - g_key_file_free (keyfile); - g_free (path); - } - g_dir_close (dir); - + plugins_loaded = TRUE; + plugins = nm_vpn_plugin_info_list_load (); return plugins; } @@ -409,7 +308,7 @@ vpn_supports_ipv6 (NMConnection *connection) { NMSettingVpn *s_vpn; const char *service_type; - NMVpnPluginUiInterface *plugin; + NMVpnEditorPlugin *plugin; guint32 capabilities; s_vpn = nm_connection_get_setting_vpn (connection); @@ -421,6 +320,6 @@ vpn_supports_ipv6 (NMConnection *connection) plugin = vpn_get_plugin_by_service (service_type); g_return_val_if_fail (plugin != NULL, FALSE); - capabilities = nm_vpn_plugin_ui_interface_get_capabilities (plugin); - return (capabilities & NM_VPN_PLUGIN_UI_CAPABILITY_IPV6) != 0; + capabilities = nm_vpn_editor_plugin_get_capabilities (plugin); + return NM_FLAGS_HAS (capabilities, NM_VPN_EDITOR_PLUGIN_CAPABILITY_IPV6); } diff --git a/clients/tui/vpn-helpers.h b/clients/tui/vpn-helpers.h index 3bb26c6d76..4503b8178f 100644 --- a/clients/tui/vpn-helpers.h +++ b/clients/tui/vpn-helpers.h @@ -19,16 +19,13 @@ #ifndef _VPN_HELPERS_H_ #define _VPN_HELPERS_H_ -#include <nm-connection.h> - -#define NM_VPN_API_SUBJECT_TO_CHANGE -#include <nm-vpn-plugin-ui-interface.h> +#include <NetworkManager.h> #include "nm-glib.h" -GHashTable *vpn_get_plugins (GError **error); +GSList *vpn_get_plugins (void); -NMVpnPluginUiInterface *vpn_get_plugin_by_service (const char *service); +NMVpnEditorPlugin *vpn_get_plugin_by_service (const char *service); typedef void (*VpnImportSuccessCallback) (NMConnection *connection, gpointer user_data); void vpn_import (VpnImportSuccessCallback callback, gpointer user_data); diff --git a/contrib/fedora/rpm/NetworkManager.spec b/contrib/fedora/rpm/NetworkManager.spec index c3769a2249..048cedae1f 100644 --- a/contrib/fedora/rpm/NetworkManager.spec +++ b/contrib/fedora/rpm/NetworkManager.spec @@ -440,6 +440,7 @@ make install DESTDIR=$RPM_BUILD_ROOT mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/%{name}/conf.d mkdir -p $RPM_BUILD_ROOT%{nmlibdir}/conf.d +mkdir -p $RPM_BUILD_ROOT%{nmlibdir}/VPN %{__cp} %{SOURCE2} $RPM_BUILD_ROOT%{nmlibdir}/conf.d/ %{__cp} %{SOURCE3} $RPM_BUILD_ROOT%{nmlibdir}/conf.d/ @@ -541,6 +542,7 @@ fi %dir %{_sysconfdir}/%{name}/conf.d %dir %{nmlibdir} %dir %{nmlibdir}/conf.d +%dir %{nmlibdir}/VPN %{_mandir}/man1/* %{_mandir}/man5/* %{_mandir}/man8/* diff --git a/docs/libnm/Makefile.am b/docs/libnm/Makefile.am index 2525856d5f..23631ddc24 100644 --- a/docs/libnm/Makefile.am +++ b/docs/libnm/Makefile.am @@ -54,6 +54,7 @@ IGNORE_HFILES= \ nm-types.h \ nm-utils-private.h \ nm-vpn-plugin-old.h \ + nm-vpn-service-plugin.h \ nm-core-tests-enum-types.h # Images to copy into HTML directory. diff --git a/libnm-core/Makefile.am b/libnm-core/Makefile.am index 73c5eaccf2..08f2afc86a 100644 --- a/libnm-core/Makefile.am +++ b/libnm-core/Makefile.am @@ -6,6 +6,8 @@ AM_CPPFLAGS = \ -I${top_srcdir}/include \ -DG_LOG_DOMAIN=\""libnm"\" \ -DLOCALEDIR=\"$(datadir)/locale\" \ + -DNMCONFDIR=\"$(nmconfdir)\" \ + -DNMLIBDIR=\"$(nmlibdir)\" \ -DNETWORKMANAGER_COMPILATION \ -DNM_VERSION_MAX_ALLOWED=NM_VERSION_NEXT_STABLE \ $(GLIB_CFLAGS) diff --git a/libnm-core/Makefile.libnm-core b/libnm-core/Makefile.libnm-core index bbd504503a..a3763f4e56 100644 --- a/libnm-core/Makefile.libnm-core +++ b/libnm-core/Makefile.libnm-core @@ -42,7 +42,9 @@ libnm_core_headers = \ $(core)/nm-setting.h \ $(core)/nm-simple-connection.h \ $(core)/nm-utils.h \ - $(core)/nm-vpn-dbus-interface.h + $(core)/nm-vpn-dbus-interface.h \ + $(core)/nm-vpn-editor-plugin.h \ + $(core)/nm-vpn-plugin-info.h libnm_core_private_headers = \ $(core)/crypto.h \ @@ -93,5 +95,7 @@ libnm_core_sources = \ $(core)/nm-setting-wireless.c \ $(core)/nm-setting.c \ $(core)/nm-simple-connection.c \ - $(core)/nm-utils.c + $(core)/nm-utils.c \ + $(core)/nm-vpn-editor-plugin.c \ + $(core)/nm-vpn-plugin-info.c diff --git a/libnm-core/nm-core-internal.h b/libnm-core/nm-core-internal.h index 2ff9e5348e..ea095a6510 100644 --- a/libnm-core/nm-core-internal.h +++ b/libnm-core/nm-core-internal.h @@ -136,6 +136,19 @@ char ** _nm_utils_slist_to_strv (GSList *slist, gboolean deep_copy); GPtrArray * _nm_utils_strv_to_ptrarray (char **strv); char ** _nm_utils_ptrarray_to_strv (GPtrArray *ptrarray); +gboolean _nm_utils_check_file (const char *filename, + gint64 check_owner, + NMUtilsCheckFilePredicate check_file, + gpointer user_data, + struct stat *out_st, + GError **error); + +char *_nm_utils_check_module_file (const char *name, + int check_owner, + NMUtilsCheckFilePredicate check_file, + gpointer user_data, + GError **error); + #define NM_UTILS_UUID_TYPE_LEGACY 0 #define NM_UTILS_UUID_TYPE_VARIANT3 1 @@ -187,6 +200,26 @@ gboolean _nm_dbus_error_has_name (GError *error, /***********************************************************/ +gboolean _nm_vpn_plugin_info_check_file (const char *filename, + gboolean check_absolute, + gboolean do_validate_filename, + gint64 check_owner, + NMUtilsCheckFilePredicate check_file, + gpointer user_data, + GError **error); + +const char *_nm_vpn_plugin_info_get_default_dir_etc (void); +const char *_nm_vpn_plugin_info_get_default_dir_lib (void); +const char *_nm_vpn_plugin_info_get_default_dir_user (void); + +GSList *_nm_vpn_plugin_info_list_load_dir (const char *dirname, + gboolean do_validate_filename, + gint64 check_owner, + NMUtilsCheckFilePredicate check_file, + gpointer user_data); + +/***********************************************************/ + typedef struct { const char *name; gboolean numeric; @@ -202,4 +235,14 @@ int _nm_utils_dns_option_find_idx (GPtrArray *array, const char *option) /***********************************************************/ +typedef struct _NMUtilsStrStrDictKey NMUtilsStrStrDictKey; +guint _nm_utils_strstrdictkey_hash (gconstpointer a); +gboolean _nm_utils_strstrdictkey_equal (gconstpointer a, gconstpointer b); +NMUtilsStrStrDictKey *_nm_utils_strstrdictkey_create (const char *v1, const char *v2); + +#define _nm_utils_strstrdictkey_static(v1, v2) \ + ( (NMUtilsStrStrDictKey *) ("\03" v1 "\0" v2 "") ) + +/***********************************************************/ + #endif diff --git a/libnm-core/nm-utils.c b/libnm-core/nm-utils.c index f6a9e5a05a..2cc6ed3955 100644 --- a/libnm-core/nm-utils.c +++ b/libnm-core/nm-utils.c @@ -30,6 +30,7 @@ #include <libintl.h> #include <gmodule.h> #include <glib/gi18n-lib.h> +#include <sys/stat.h> #include "nm-glib.h" #include "nm-utils.h" @@ -2409,6 +2410,160 @@ nm_utils_file_is_pkcs12 (const char *filename) /**********************************************************************************************/ +gboolean +_nm_utils_check_file (const char *filename, + gint64 check_owner, + NMUtilsCheckFilePredicate check_file, + gpointer user_data, + struct stat *out_st, + GError **error) +{ + struct stat st_backup; + + if (!out_st) + out_st = &st_backup; + + if (stat (filename, out_st) != 0) { + int errsv = errno; + + g_set_error (error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, + _("failed stat file %s: %s"), filename, strerror (errsv)); + return FALSE; + } + + /* ignore non-files. */ + if (!S_ISREG (out_st->st_mode)) { + g_set_error (error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, + _("not a file (%s)"), filename); + return FALSE; + } + + /* with check_owner enabled, check that the file belongs to the + * owner or root. */ + if ( check_owner >= 0 + && (out_st->st_uid != 0 && (gint64) out_st->st_uid != check_owner)) { + g_set_error (error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, + _("invalid file owner %d for %s"), out_st->st_uid, filename); + return FALSE; + } + + /* with check_owner enabled, check that the file cannot be modified + * by other users (except root). */ + if ( check_owner >= 0 + && NM_FLAGS_ANY (out_st->st_mode, S_IWGRP | S_IWOTH | S_ISUID)) { + g_set_error (error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, + _("file permissions for %s"), filename); + return FALSE; + } + + if ( check_file + && !check_file (filename, out_st, user_data, error)) { + if (error && !*error) { + g_set_error (error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, + _("reject %s"), filename); + } + return FALSE; + } + + return TRUE; +} + + +static char * +_resolve_module_file_name (const char *file_name) +{ + char *name = NULL; + + /* g_module_open() is searching for the exact file to load, + * but it doesn't give us a hook to check file permissions + * and ownership. Reimplement the file name resolution. + * + * Copied from g_module_open(). */ + + /* check whether we have a readable file right away */ + if (g_file_test (file_name, G_FILE_TEST_IS_REGULAR)) + name = g_strdup (file_name); + + /* try completing file name with standard library suffix */ + if ( !name + && !g_str_has_suffix (file_name, "." G_MODULE_SUFFIX)) { + name = g_strconcat (file_name, "." G_MODULE_SUFFIX, NULL); + if (!g_file_test (name, G_FILE_TEST_IS_REGULAR)) { + g_free (name); + name = NULL; + } + } + + /* g_module_open() would also try appending ".la". We don't do that + * because we require the user to specify a shared library (directly). */ + + return name; +} + +char * +_nm_utils_check_module_file (const char *name, + int check_owner, + NMUtilsCheckFilePredicate check_file, + gpointer user_data, + GError **error) +{ + gs_free char *name_resolved = NULL; + char *s; + + if (!g_path_is_absolute (name)) { + g_set_error (error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, + _("path is not absolute (%s)"), name); + return NULL; + } + + name_resolved = _resolve_module_file_name (name); + + if (!name_resolved) { + g_set_error (error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, + _("could not resolve plugin path (%s)"), name); + return NULL; + } + + if (g_str_has_suffix (name_resolved, ".la")) { + /* g_module_open() treats files that end with .la special. + * We don't want to parse the libtool archive. Just error out. */ + g_set_error (error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, + _("libtool archives are not supported (%s)"), name_resolved); + return NULL; + } + + if (!_nm_utils_check_file (name_resolved, + check_owner, + check_file, + user_data, + NULL, + error)) { + return NULL; + } + + s = name_resolved; + name_resolved = NULL; + return s; +} + +/**********************************************************************************************/ + /** * nm_utils_file_search_in_paths: * @progname: the helper program name, like "iptables" @@ -3488,6 +3643,105 @@ nm_utils_bond_mode_string_to_int (const char *mode) /**********************************************************************************************/ +#define STRSTRDICTKEY_V1_SET 0x01 +#define STRSTRDICTKEY_V2_SET 0x02 +#define STRSTRDICTKEY_ALL_SET 0x03 + +struct _NMUtilsStrStrDictKey { + char type; + char data[1]; +}; + +guint +_nm_utils_strstrdictkey_hash (gconstpointer a) +{ + const NMUtilsStrStrDictKey *k = a; + const signed char *p; + guint32 h = 5381; + + if (k) { + if (((int) k->type) & ~STRSTRDICTKEY_ALL_SET) + g_return_val_if_reached (0); + + h = (h << 5) + h + k->type; + if (k->type & STRSTRDICTKEY_ALL_SET) { + p = (void *) k->data; + for (; *p != '\0'; p++) + h = (h << 5) + h + *p; + if (k->type == STRSTRDICTKEY_ALL_SET) { + /* the key contains two strings. Continue... */ + h = (h << 5) + h + '\0'; + for (p++; *p != '\0'; p++) + h = (h << 5) + h + *p; + } + } + } + + return h; +} + +gboolean +_nm_utils_strstrdictkey_equal (gconstpointer a, gconstpointer b) +{ + const NMUtilsStrStrDictKey *k1 = a; + const NMUtilsStrStrDictKey *k2 = b; + + if (k1 == k2) + return TRUE; + if (!k1 || !k2) + return FALSE; + + if (k1->type != k2->type) + return FALSE; + + if (k1->type & STRSTRDICTKEY_ALL_SET) { + if (strcmp (k1->data, k2->data) != 0) + return FALSE; + + if (k1->type == STRSTRDICTKEY_ALL_SET) { + gsize l = strlen (k1->data) + 1; + + return strcmp (&k1->data[l], &k2->data[l]) == 0; + } + } + + return TRUE; +} + +NMUtilsStrStrDictKey * +_nm_utils_strstrdictkey_create (const char *v1, const char *v2) +{ + char type = 0; + gsize l1 = 0, l2 = 0; + NMUtilsStrStrDictKey *k; + + if (!v1 && !v2) + return g_malloc0 (1); + + /* we need to distinguish between ("",NULL) and (NULL,""). + * Thus, in @type we encode which strings we have present + * as not-NULL. */ + if (v1) { + type |= STRSTRDICTKEY_V1_SET; + l1 = strlen (v1) + 1; + } + if (v2) { + type |= STRSTRDICTKEY_V2_SET; + l2 = strlen (v2) + 1; + } + + k = g_malloc (G_STRUCT_OFFSET (NMUtilsStrStrDictKey, data) + l1 + l2); + k->type = type; + if (v1) + memcpy (&k->data[0], v1, l1); + if (v2) + memcpy (&k->data[l1], v2, l2); + + return k; +} + +/**********************************************************************************************/ + /* _nm_utils_ascii_str_to_int64: * * A wrapper for g_ascii_strtoll, that checks whether the whole string diff --git a/libnm-core/nm-utils.h b/libnm-core/nm-utils.h index c9a01d6e6e..64e57e875a 100644 --- a/libnm-core/nm-utils.h +++ b/libnm-core/nm-utils.h @@ -128,6 +128,10 @@ gboolean nm_utils_file_is_pkcs12 (const char *filename); typedef gboolean (*NMUtilsFileSearchInPathsPredicate) (const char *filename, gpointer user_data); +struct stat; + +typedef gboolean (*NMUtilsCheckFilePredicate) (const char *filename, const struct stat *stat, gpointer user_data, GError **error); + const char *nm_utils_file_search_in_paths (const char *progname, const char *try_first, const char *const *paths, diff --git a/libnm-core/nm-vpn-editor-plugin.c b/libnm-core/nm-vpn-editor-plugin.c new file mode 100644 index 0000000000..d5fa6d819b --- /dev/null +++ b/libnm-core/nm-vpn-editor-plugin.c @@ -0,0 +1,284 @@ +/* -*- 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 2008 Novell, Inc. + * Copyright 2008 - 2010 Red Hat, Inc. + * Copyright 2015 Red Hat, Inc. + */ + +#include "config.h" + +#include "nm-vpn-editor-plugin.h" + +#include <gio/gio.h> +#include <glib/gi18n-lib.h> + +#include "nm-macros-internal.h" +#include "gsystem-local-alloc.h" +#include "nm-core-internal.h" + +static void nm_vpn_editor_plugin_default_init (NMVpnEditorPluginInterface *iface); + +G_DEFINE_INTERFACE (NMVpnEditorPlugin, nm_vpn_editor_plugin, G_TYPE_OBJECT) + +static void +nm_vpn_editor_plugin_default_init (NMVpnEditorPluginInterface *iface) +{ + /* Properties */ + + /** + * NMVpnEditorPlugin:name: + * + * Short display name of the VPN plugin. + */ + g_object_interface_install_property (iface, + g_param_spec_string (NM_VPN_EDITOR_PLUGIN_NAME, "", "", + NULL, + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS)); + + /** + * NMVpnEditorPlugin:description: + * + * Longer description of the VPN plugin. + */ + g_object_interface_install_property (iface, + g_param_spec_string (NM_VPN_EDITOR_PLUGIN_DESCRIPTION, "", "", + NULL, + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS)); + + /** + * NMVpnEditorPlugin:service: + * + * D-Bus service name of the plugin's VPN service. + */ + g_object_interface_install_property (iface, + g_param_spec_string (NM_VPN_EDITOR_PLUGIN_SERVICE, "", "", + NULL, + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS)); +} + +/*********************************************************************/ + +/** + * nm_vpn_editor_plugin_load_from_file: + * @plugin_filename: The path to the share library to load. + * Apply some common heuristics to find the library, such as + * appending "so" file ending. + * If the path is not an absolute path or no matching module + * can be found, lookup inside a directory defined at compile time. + * Due to this, @check_file might be called for two different paths. + * @check_name: if not-null, check that the loaded plugin has + * the given name. + * @check_service: if not-null, check that the loaded plugin advertises + * the given service. + * @check_owner: if non-negative, check whether the file is owned + * by UID @check_owner or by root. In this case also check that + * the file is not writable by anybody else. + * @check_file: optional callback to validate the file prior to + * loading the shared library. + * @user_data: user data for @check_file + * @error: on failure the error reason. + * + * Load the shared libary @plugin_filename and create a new + * #NMVpnEditorPlugin instace via the #NMVpnEditorPluginFactory + * function. + * + * Returns: (transfer-full): a new plugin instance or %NULL on error. + * + * Since: 1.2 + */ +NMVpnEditorPlugin * +nm_vpn_editor_plugin_load_from_file (const char *plugin_filename, + const char *check_name, + const char *check_service, + int check_owner, + NMUtilsCheckFilePredicate check_file, + gpointer user_data, + GError **error) +{ + GModule *module = NULL; + gs_free_error GError *local = NULL; + NMVpnEditorPluginFactory factory = NULL; + NMVpnEditorPlugin *editor_plugin = NULL; + + g_return_val_if_fail (plugin_filename && *plugin_filename, NULL); + + if (g_path_is_absolute (plugin_filename)) { + gs_free char *module_filename = NULL; + + module_filename = _nm_utils_check_module_file (plugin_filename, + check_owner, + check_file, + user_data, + &local); + if (module_filename) + module = g_module_open (module_filename, G_MODULE_BIND_LAZY | G_MODULE_BIND_LOCAL); + } + + if (!module) { + if (local) { + g_propagate_error (error, local); + local = NULL; + } else { + g_set_error (error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, + _("cannot load plugin %s"), plugin_filename); + } + return NULL; + } + g_clear_error (&local); + + if (g_module_symbol (module, "nm_vpn_editor_plugin_factory", (gpointer) &factory)) { + gs_free_error GError *factory_error = NULL; + gboolean success = FALSE; + + editor_plugin = factory (&factory_error); + + g_assert (!editor_plugin || G_IS_OBJECT (editor_plugin)); + + if (editor_plugin) { + gs_free char *plug_name = NULL, *plug_service = NULL; + + /* Validate plugin properties */ + + g_object_get (G_OBJECT (editor_plugin), + NM_VPN_EDITOR_PLUGIN_NAME, &plug_name, + NM_VPN_EDITOR_PLUGIN_SERVICE, &plug_service, + NULL); + + if (check_name && g_strcmp0 (plug_name, check_name) != 0) { + g_set_error (error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, + _("cannot load VPN plugin in '%s': invalid plugin name"), + g_module_name (module)); + } else if ( check_service + && g_strcmp0 (plug_service, check_service) != 0) { + g_set_error (error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, + _("cannot load VPN plugin in '%s': invalid service name"), + g_module_name (module)); + } else { + /* Success! */ + g_object_set_data_full (G_OBJECT (editor_plugin), "gmodule", module, + (GDestroyNotify) g_module_close); + success = TRUE; + } + } else { + if (factory_error) { + g_propagate_error (error, factory_error); + factory_error = NULL; + } else { + g_set_error (error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, + _("unknown error initializing plugin %s"), plugin_filename); + } + } + + if (!success) { + g_module_close (module); + editor_plugin = NULL; + } + } else { + g_set_error (error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, + _("failed to load nm_vpn_editor_plugin_factory() from %s (%s)"), + g_module_name (module), g_module_error ()); + g_module_close (module); + editor_plugin = NULL; + } + + return editor_plugin; +} + +/*********************************************************************/ + +/** + * nm_vpn_editor_plugin_get_editor: + * + * Returns: (transfer full): + */ +NMVpnEditor * +nm_vpn_editor_plugin_get_editor (NMVpnEditorPlugin *plugin, + NMConnection *connection, + GError **error) +{ + g_return_val_if_fail (NM_IS_VPN_EDITOR_PLUGIN (plugin), NULL); + + return NM_VPN_EDITOR_PLUGIN_GET_INTERFACE (plugin)->get_editor (plugin, connection, error); +} + +NMVpnEditorPluginCapability +nm_vpn_editor_plugin_get_capabilities (NMVpnEditorPlugin *plugin) +{ + g_return_val_if_fail (NM_IS_VPN_EDITOR_PLUGIN (plugin), 0); + + return NM_VPN_EDITOR_PLUGIN_GET_INTERFACE (plugin)->get_capabilities (plugin); +} + +/** + * nm_vpn_editor_plugin_import: + * + * Returns: (transfer full): + */ +NMConnection * +nm_vpn_editor_plugin_import (NMVpnEditorPlugin *plugin, + const char *path, + GError **error) +{ + g_return_val_if_fail (NM_IS_VPN_EDITOR_PLUGIN (plugin), NULL); + + if (nm_vpn_editor_plugin_get_capabilities (plugin) & NM_VPN_EDITOR_PLUGIN_CAPABILITY_IMPORT) { + g_return_val_if_fail (NM_VPN_EDITOR_PLUGIN_GET_INTERFACE (plugin)->import_from_file != NULL, NULL); + return NM_VPN_EDITOR_PLUGIN_GET_INTERFACE (plugin)->import_from_file (plugin, path, error); + } + return NULL; +} + +gboolean +nm_vpn_editor_plugin_export (NMVpnEditorPlugin *plugin, + const char *path, + NMConnection *connection, + GError **error) +{ + g_return_val_if_fail (NM_IS_VPN_EDITOR_PLUGIN (plugin), FALSE); + + if (nm_vpn_editor_plugin_get_capabilities (plugin) & NM_VPN_EDITOR_PLUGIN_CAPABILITY_EXPORT) { + g_return_val_if_fail (NM_VPN_EDITOR_PLUGIN_GET_INTERFACE (plugin)->export_to_file != NULL, FALSE); + return NM_VPN_EDITOR_PLUGIN_GET_INTERFACE (plugin)->export_to_file (plugin, path, connection, error); + } + return FALSE; +} + +char * +nm_vpn_editor_plugin_get_suggested_filename (NMVpnEditorPlugin *plugin, + NMConnection *connection) +{ + g_return_val_if_fail (NM_IS_VPN_EDITOR_PLUGIN (plugin), NULL); + + if (NM_VPN_EDITOR_PLUGIN_GET_INTERFACE (plugin)->get_suggested_filename) + return NM_VPN_EDITOR_PLUGIN_GET_INTERFACE (plugin)->get_suggested_filename (plugin, connection); + return NULL; +} + diff --git a/libnm/nm-vpn-editor-plugin.h b/libnm-core/nm-vpn-editor-plugin.h index 5910aa3653..83d765d097 100644 --- a/libnm/nm-vpn-editor-plugin.h +++ b/libnm-core/nm-vpn-editor-plugin.h @@ -15,8 +15,8 @@ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * - * Copyright 2008 - 2014 Red Hat, Inc. * Copyright 2008 Novell, Inc. + * Copyright 2008 - 2015 Red Hat, Inc. */ #ifndef __NM_VPN_EDITOR_PLUGIN_H__ @@ -28,7 +28,9 @@ #include <glib.h> #include <glib-object.h> -#include <nm-types.h> + +#include "nm-connection.h" +#include "nm-utils.h" G_BEGIN_DECLS @@ -137,53 +139,15 @@ gboolean nm_vpn_editor_plugin_export (NMVpnEditorPlugin *pl char *nm_vpn_editor_plugin_get_suggested_filename (NMVpnEditorPlugin *plugin, NMConnection *connection); -/**************************************************/ -/* Editor interface */ -/**************************************************/ - -#define NM_TYPE_VPN_EDITOR (nm_vpn_editor_get_type ()) -#define NM_VPN_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_VPN_EDITOR, NMVpnEditor)) -#define NM_IS_VPN_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_VPN_EDITOR)) -#define NM_VPN_EDITOR_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), NM_TYPE_VPN_EDITOR, NMVpnEditorInterface)) - -/** - * NMVpnEditorInterface: - * @g_iface: the parent interface - * @get_widget: return the #GtkWidget for the VPN editor's UI - * @placeholder: not currently used - * @update_connection: called to save the user-entered options to the connection - * object. Should return %FALSE and set @error if the current options are - * invalid. @error should contain enough information for the plugin to - * determine which UI widget is invalid at a later point in time. For - * example, creating unique error codes for what error occurred and populating - * the message field of @error with the name of the invalid property. - * @changed: emitted when the value of a UI widget changes. May trigger a - * validity check via @update_connection to write values to the connection. - * - * Interface for editing a specific #NMConnection - */ -typedef struct { - GTypeInterface g_iface; - - GObject * (*get_widget) (NMVpnEditor *editor); - - void (*placeholder) (void); - - gboolean (*update_connection) (NMVpnEditor *editor, - NMConnection *connection, - GError **error); - - void (*changed) (NMVpnEditor *editor); -} NMVpnEditorInterface; - -GType nm_vpn_editor_get_type (void); - -GObject * nm_vpn_editor_get_widget (NMVpnEditor *editor); - -gboolean nm_vpn_editor_update_connection (NMVpnEditor *editor, - NMConnection *connection, - GError **error); +NM_AVAILABLE_IN_1_2 +NMVpnEditorPlugin *nm_vpn_editor_plugin_load_from_file (const char *plugin_filename, + const char *check_name, + const char *check_service, + int check_owner, + NMUtilsCheckFilePredicate check_file, + gpointer user_data, + GError **error); G_END_DECLS -#endif /* NM_VPN_EDITOR_PLUGIN_H */ +#endif /* __NM_VPN_EDITOR_PLUGIN_H__ */ diff --git a/libnm-core/nm-vpn-plugin-info.c b/libnm-core/nm-vpn-plugin-info.c new file mode 100644 index 0000000000..762064a5b7 --- /dev/null +++ b/libnm-core/nm-vpn-plugin-info.c @@ -0,0 +1,1016 @@ +/* -*- 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 2015 Red Hat, Inc. + */ + +#include "config.h" + +#include "nm-vpn-plugin-info.h" + +#include <gio/gio.h> +#include <glib/gi18n-lib.h> +#include <string.h> +#include <errno.h> +#include <sys/stat.h> + +#include "gsystem-local-alloc.h" +#include "nm-errors.h" +#include "nm-macros-internal.h" +#include "nm-core-internal.h" + +#define DEFAULT_DIR_ETC NMCONFDIR"/VPN" +#define DEFAULT_DIR_LIB NMLIBDIR"/VPN" + +enum { + PROP_0, + PROP_NAME, + PROP_FILENAME, + PROP_KEYFILE, + + LAST_PROP, +}; + +typedef struct { + char *filename; + char *name; + char *service; + GKeyFile *keyfile; + + /* It is convenient for nm_vpn_plugin_info_lookup_property() to return a const char *, + * contrary to what g_key_file_get_string() does. Hence we must cache the returned + * value somewhere... let's put it in an internal hash table. + * This contains a clone of all the strings in keyfile. */ + GHashTable *keys; + + gboolean editor_plugin_loaded; + NMVpnEditorPlugin *editor_plugin; +} NMVpnPluginInfoPrivate; + +static void nm_vpn_plugin_info_initable_iface_init (GInitableIface *iface); + +G_DEFINE_TYPE_WITH_CODE (NMVpnPluginInfo, nm_vpn_plugin_info, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, nm_vpn_plugin_info_initable_iface_init); + ) + +#define NM_VPN_PLUGIN_INFO_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_VPN_PLUGIN_INFO, NMVpnPluginInfoPrivate)) + +/*********************************************************************/ + +/** + * nm_vpn_plugin_info_validate_filename: + * @filename: the filename to check + * + * Regular name files have a certain pattern. That basically means + * they have the file extension "name". Check if @filename + * is valid according to that pattern. + * + * Since: 1.2 + */ +gboolean +nm_vpn_plugin_info_validate_filename (const char *filename) +{ + if (!filename || !g_str_has_suffix (filename, ".name")) + return FALSE; + + /* originally, we didn't do further checks... but here we go. */ + if (filename[0] == '.') { + /* this also rejects name ".name" alone. */ + return FALSE; + } + return TRUE; +} + +static gboolean +nm_vpn_plugin_info_check_file_full (const char *filename, + gboolean check_absolute, + gboolean do_validate_filename, + gint64 check_owner, + NMUtilsCheckFilePredicate check_file, + gpointer user_data, + struct stat *out_st, + GError **error) +{ + if (!filename || !*filename) { + g_set_error (error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, + _("missing filename")); + return FALSE; + } + + if (check_absolute && !g_path_is_absolute (filename)) { + g_set_error (error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, + _("filename must be an absolute path (%s)"), filename); + return FALSE; + } + + if ( do_validate_filename + && !nm_vpn_plugin_info_validate_filename (filename)) { + g_set_error (error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, + _("filename has invalid format (%s)"), filename); + return FALSE; + } + + return _nm_utils_check_file (filename, + check_owner, + check_file, + user_data, + out_st, + error); +} + +/** + * _nm_vpn_plugin_info_check_file: + * @filename: + * @check_absolute: if %TRUE, only allow absolute path names. + * @do_validate_filename: if %TRUE, only accept the filename if + * nm_vpn_plugin_info_validate_filename() succeeds. + * @check_owner: if non-negative, only accept the file if the + * owner UID is equal to @check_owner or if the owner is 0. + * In this case, also check that the file is not writable by + * other users. + * @check_file: pass a callback to do your own validation. + * @user_data: user data for @check_file. + * @error: (allow-none): (out): the error reason if the check fails. + * + * Check whether the file exists and is a valid name file (in keyfile format). + * Additionally, also check for file permissions. + * + * Returns: %TRUE if a file @filename exists and has valid permissions. + * + * Since: 1.2 + */ +gboolean +_nm_vpn_plugin_info_check_file (const char *filename, + gboolean check_absolute, + gboolean do_validate_filename, + gint64 check_owner, + NMUtilsCheckFilePredicate check_file, + gpointer user_data, + GError **error) +{ + return nm_vpn_plugin_info_check_file_full (filename, check_absolute, do_validate_filename, check_owner, check_file, user_data, NULL, error); +} + +typedef struct { + NMVpnPluginInfo *plugin_info; + struct stat stat; +} LoadDirInfo; + +static int +_sort_files (LoadDirInfo *a, LoadDirInfo *b) +{ + time_t ta, tb; + + ta = MAX (a->stat.st_mtime, a->stat.st_ctime); + tb = MAX (b->stat.st_mtime, b->stat.st_ctime); + if (ta < tb) + return 1; + if (ta > tb) + return -1; + return g_strcmp0 (nm_vpn_plugin_info_get_filename (a->plugin_info), + nm_vpn_plugin_info_get_filename (b->plugin_info)); +} + +/** + * _nm_vpn_plugin_info_get_default_dir_etc: + * + * Returns: (transfer-none): compile time constant of the default + * VPN plugin directory. + */ +const char * +_nm_vpn_plugin_info_get_default_dir_etc () +{ + return DEFAULT_DIR_ETC; +} + +/** + * _nm_vpn_plugin_info_get_default_dir_lib: + * + * Returns: (transfer-none): compile time constant of the default + * VPN plugin directory. + */ +const char * +_nm_vpn_plugin_info_get_default_dir_lib () +{ + return DEFAULT_DIR_LIB; +} + +/** + * _nm_vpn_plugin_info_get_default_dir_user: + * + * Returns: The user can specify a different directory for VPN plugins + * by setting NM_VPN_PLUGIN_DIR environment variable. Return + * that directory. + */ +const char * +_nm_vpn_plugin_info_get_default_dir_user () +{ + return g_getenv ("NM_VPN_PLUGIN_DIR"); +} + +/** + * _nm_vpn_plugin_info_list_load_dir: + * @dirname: the name of the directory to load. + * @do_validate_filename: only consider filenames that have a certain + * pattern (i.e. end with ".name"). + * @check_owner: if set to a non-negative number, check that the file + * owner is either the same uid or 0. In that case, also check + * that the file is not writable by group or other. + * @check_file: (allow-none): callback to check whether the file is valid. + * @user_data: data for @check_file + * + * Iterate over the content of @dirname and load name files. + * + * Returns: (transfer-full): list of loaded plugin infos. + */ +GSList * +_nm_vpn_plugin_info_list_load_dir (const char *dirname, + gboolean do_validate_filename, + gint64 check_owner, + NMUtilsCheckFilePredicate check_file, + gpointer user_data) +{ + GDir *dir; + const char *fn; + GArray *array; + GSList *res = NULL; + guint i; + + g_return_val_if_fail (dirname && dirname[0], NULL); + + dir = g_dir_open (dirname, 0, NULL); + if (!dir) + return NULL; + + array = g_array_new (FALSE, FALSE, sizeof (LoadDirInfo)); + + while ((fn = g_dir_read_name (dir))) { + gs_free char *filename = NULL; + LoadDirInfo info = { 0 }; + + filename = g_build_filename (dirname, fn, NULL); + if (nm_vpn_plugin_info_check_file_full (filename, + FALSE, + do_validate_filename, + check_owner, + check_file, + user_data, + &info.stat, + NULL)) { + info.plugin_info = nm_vpn_plugin_info_new_from_file (filename, NULL); + if (info.plugin_info) { + g_array_append_val (array, info); + continue; + } + } + } + g_dir_close (dir); + + /* sort the files so that we have a stable behavior. The directory might contain + * duplicate VPNs, so while nm_vpn_plugin_info_list_load() would load them all, the + * caller probably wants to reject duplicates. Having a stable order means we always + * reject the same files in face of duplicates. */ + g_array_sort (array, (GCompareFunc) _sort_files); + + for (i = 0; i < array->len; i++) + res = g_slist_prepend (res, g_array_index (array, LoadDirInfo, i).plugin_info); + + g_array_unref (array); + + return g_slist_reverse (res); +} + +/** + * nm_vpn_plugin_info_list_load: + * + * Returns: (tranfer-full): list of plugins loaded from the default + * directories rejecting duplicates. + * + * Since: 1.2 + */ +GSList * +nm_vpn_plugin_info_list_load () +{ + int i; + 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 (), + }; + + uid = getuid (); + + for (i = 0; i < G_N_ELEMENTS (dir); i++) { + if ( !dir[i] + || _nm_utils_strv_find_first ((char **) dir, i, dir[i]) >= 0) + continue; + + infos = _nm_vpn_plugin_info_list_load_dir (dir[i], TRUE, uid, NULL, NULL); + + for (info = infos; info; info = info->next) + nm_vpn_plugin_info_list_add (&list, info->data, NULL); + + g_slist_free_full (infos, g_object_unref); + } + return list; +} + +/*********************************************************************/ + +static gboolean +_check_no_conflict (NMVpnPluginInfo *i1, NMVpnPluginInfo *i2, GError **error) +{ + NMVpnPluginInfoPrivate *priv1, *priv2; + uint i; + struct { + const char *group; + const char *key; + } check_list[] = { + { NM_VPN_PLUGIN_INFO_KF_GROUP_CONNECTION, "service" }, + { NM_VPN_PLUGIN_INFO_KF_GROUP_LIBNM, "plugin" }, + { NM_VPN_PLUGIN_INFO_KF_GROUP_GNOME, "properties" }, + }; + + priv1 = NM_VPN_PLUGIN_INFO_GET_PRIVATE (i1); + priv2 = NM_VPN_PLUGIN_INFO_GET_PRIVATE (i2); + + for (i = 0; i < G_N_ELEMENTS (check_list); i++) { + gs_free NMUtilsStrStrDictKey *k = NULL; + const char *s1, *s2; + + k = _nm_utils_strstrdictkey_create (check_list[i].group, check_list[i].key); + s1 = g_hash_table_lookup (priv1->keys, k); + if (!s1) + continue; + s2 = g_hash_table_lookup (priv2->keys, k); + if (!s2) + continue; + + if (strcmp (s1, s2) == 0) { + g_set_error (error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, + _("there exists a conflicting plugin (%s) that has the same %s.%s value"), + priv2->name, + check_list[i].group, check_list[i].key); + return FALSE; + } + } + return TRUE; +} + +/** + * nm_vpn_plugin_info_list_add: + * @list: list of plugins + * @plugin_info: instance to add + * @error: failure reason + * + * Returns: %TRUE if the plugin was added to @list. This will fail + * to add duplicate plugins. + * + * Since: 1.2 + */ +gboolean +nm_vpn_plugin_info_list_add (GSList **list, NMVpnPluginInfo *plugin_info, GError **error) +{ + GSList *iter; + const char *name; + + g_return_val_if_fail (list, FALSE); + g_return_val_if_fail (NM_IS_VPN_PLUGIN_INFO (plugin_info), FALSE); + + name = nm_vpn_plugin_info_get_name (plugin_info); + for (iter = *list; iter; iter = iter->next) { + if (iter->data == plugin_info) + return TRUE; + + if (strcmp (nm_vpn_plugin_info_get_name (iter->data), name) == 0) { + g_set_error (error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, + _("there exists a conflicting plugin with the same name (%s)"), + name); + return FALSE; + } + + /* the plugin must have unique values for certain properties. E.g. two different + * plugins cannot share the same D-Bus service name. */ + if (!_check_no_conflict (plugin_info, iter->data, error)) + return FALSE; + } + + *list = g_slist_append (*list, g_object_ref (plugin_info)); + return TRUE; +} + +/** + * nm_vpn_plugin_info_list_remove: + * @list: list of plugins + * @plugin_info: instance + * + * Remove @plugin_info from @list. + * + * Returns: %TRUE if @plugin_info was in @list and successfully removed. + * + * Since: 1.2 + */ +gboolean +nm_vpn_plugin_info_list_remove (GSList **list, NMVpnPluginInfo *plugin_info) +{ + if (!plugin_info) + return FALSE; + + g_return_val_if_fail (list, FALSE); + g_return_val_if_fail (NM_IS_VPN_PLUGIN_INFO (plugin_info), FALSE); + + if (!g_slist_find (*list, plugin_info)) + return FALSE; + + *list = g_slist_remove (*list, plugin_info); + g_object_unref (plugin_info); + return TRUE; +} + +/** + * nm_vpn_plugin_info_list_find_by_name: + * @list: list of plugins + * @name: name to search + * + * Returns: the first plugin with a matching @name (or %NULL). + * + * Since: 1.2 + */ +NMVpnPluginInfo * +nm_vpn_plugin_info_list_find_by_name (GSList *list, const char *name) +{ + GSList *iter; + + if (!name) + g_return_val_if_reached (NULL); + + for (iter = list; iter; iter = iter->next) { + if (strcmp (nm_vpn_plugin_info_get_name (iter->data), name) == 0) + return iter->data; + } + return NULL; +} + +/** + * nm_vpn_plugin_info_list_find_by_filename: + * @list: list of plugins + * @filename: filename to search + * + * Returns: the first plugin with a matching @filename (or %NULL). + * + * Since: 1.2 + */ +NMVpnPluginInfo * +nm_vpn_plugin_info_list_find_by_filename (GSList *list, const char *filename) +{ + GSList *iter; + + if (!filename) + g_return_val_if_reached (NULL); + + for (iter = list; iter; iter = iter->next) { + if (g_strcmp0 (nm_vpn_plugin_info_get_filename (iter->data), filename) == 0) + return iter->data; + } + return NULL; +} + +/** + * nm_vpn_plugin_info_list_find_by_service: + * @list: list of plugins + * @service: service to search + * + * Returns: the first plugin with a matching @service (or %NULL). + * + * Since: 1.2 + */ +NMVpnPluginInfo * +nm_vpn_plugin_info_list_find_by_service (GSList *list, const char *service) +{ + GSList *iter; + + if (!service) + g_return_val_if_reached (NULL); + + for (iter = list; iter; iter = iter->next) { + if (strcmp (nm_vpn_plugin_info_get_service (iter->data), service) == 0) + return iter->data; + } + return NULL; +} + +/*********************************************************************/ + +/** + * nm_vpn_plugin_info_get_filename: + * @self: plugin info instance + * + * Returns: (transfer-none): the filename. Can be %NULL. + * + * Since: 1.2 + */ +const char * +nm_vpn_plugin_info_get_filename (NMVpnPluginInfo *self) +{ + g_return_val_if_fail (NM_IS_VPN_PLUGIN_INFO (self), NULL); + + return NM_VPN_PLUGIN_INFO_GET_PRIVATE (self)->filename; +} + +/** + * nm_vpn_plugin_info_get_name: + * @self: plugin info instance + * + * Returns: (transfer-none): the name. Cannot be %NULL. + * + * Since: 1.2 + */ +const char * +nm_vpn_plugin_info_get_name (NMVpnPluginInfo *self) +{ + g_return_val_if_fail (NM_IS_VPN_PLUGIN_INFO (self), NULL); + + return NM_VPN_PLUGIN_INFO_GET_PRIVATE (self)->name; +} + +/** + * nm_vpn_plugin_info_get_service: + * @self: plugin info instance + * + * Returns: (transfer-none): the service. Cannot be %NULL. + * + * Since: 1.2 + */ +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_plugin: + * @self: plugin info instance + * + * Returns: (transfer-none): the plugin. Can be %NULL. + * + * Since: 1.2 + */ +const char * +nm_vpn_plugin_info_get_plugin (NMVpnPluginInfo *self) +{ + g_return_val_if_fail (NM_IS_VPN_PLUGIN_INFO (self), NULL); + + return g_hash_table_lookup (NM_VPN_PLUGIN_INFO_GET_PRIVATE (self)->keys, + _nm_utils_strstrdictkey_static (NM_VPN_PLUGIN_INFO_KF_GROUP_LIBNM, "plugin")); +} + +/** + * nm_vpn_plugin_info_get_program: + * @self: plugin info instance + * + * Returns: (transfer-none): the program. Can be %NULL. + * + * Since: 1.2 + */ +const char * +nm_vpn_plugin_info_get_program (NMVpnPluginInfo *self) +{ + g_return_val_if_fail (NM_IS_VPN_PLUGIN_INFO (self), NULL); + + return g_hash_table_lookup (NM_VPN_PLUGIN_INFO_GET_PRIVATE (self)->keys, + _nm_utils_strstrdictkey_static (NM_VPN_PLUGIN_INFO_KF_GROUP_CONNECTION, "program")); +} + +/** + * nm_vpn_plugin_info_lookup_property: + * @self: plugin info instance + * @group: group name + * @key: name of the property + * + * Returns: (transfer-none): #NMVpnPluginInfo is internally a #GKeyFile. Returns the matching + * property. + * + * Since: 1.2 + */ +const char * +nm_vpn_plugin_info_lookup_property (NMVpnPluginInfo *self, const char *group, const char *key) +{ + NMVpnPluginInfoPrivate *priv; + gs_free NMUtilsStrStrDictKey *k = NULL; + + g_return_val_if_fail (NM_IS_VPN_PLUGIN_INFO (self), NULL); + g_return_val_if_fail (group, NULL); + g_return_val_if_fail (key, NULL); + + priv = NM_VPN_PLUGIN_INFO_GET_PRIVATE (self); + + k = _nm_utils_strstrdictkey_create (group, key); + return g_hash_table_lookup (priv->keys, k); +} + +/*********************************************************************/ + +/** + * nm_vpn_plugin_info_get_editor_plugin: + * @self: plugin info instance + * + * Returns: the cached #NMVpnEditorPlugin instance. + * + * Since: 1.2 + */ +NMVpnEditorPlugin * +nm_vpn_plugin_info_get_editor_plugin (NMVpnPluginInfo *self) +{ + g_return_val_if_fail (NM_IS_VPN_PLUGIN_INFO (self), NULL); + + return NM_VPN_PLUGIN_INFO_GET_PRIVATE (self)->editor_plugin; +} + +/** + * nm_vpn_plugin_info_set_editor_plugin: + * @self: plugin info instance + * @plugin: (allow-none): plugin instance + * + * Set the internal plugin instance. If %NULL, only clear the previous instance. + * + * Since: 1.2 + */ +void +nm_vpn_plugin_info_set_editor_plugin (NMVpnPluginInfo *self, NMVpnEditorPlugin *plugin) +{ + NMVpnPluginInfoPrivate *priv; + NMVpnEditorPlugin *old; + + g_return_if_fail (NM_IS_VPN_PLUGIN_INFO (self)); + g_return_if_fail (!plugin || G_IS_OBJECT (plugin)); + + priv = NM_VPN_PLUGIN_INFO_GET_PRIVATE (self); + + if (!plugin) { + priv->editor_plugin_loaded = FALSE; + g_clear_object (&priv->editor_plugin); + } else { + old = priv->editor_plugin; + priv->editor_plugin = g_object_ref (plugin); + priv->editor_plugin_loaded = TRUE; + if (old) + g_object_unref (old); + } +} + +/** + * nm_vpn_plugin_info_load_editor_plugin: + * @self: plugin info instance + * @error: error reason on failure + * + * Returns: loads the plugin and returns the newly created instance. + * The plugin is owned by @self and can be later retrieved again + * via nm_vpn_plugin_info_get_editor_plugin(). You can load the + * plugin only once, unless you reset the state via + * nm_vpn_plugin_info_set_editor_plugin(). + * + * Since: 1.2 + */ +NMVpnEditorPlugin * +nm_vpn_plugin_info_load_editor_plugin (NMVpnPluginInfo *self, GError **error) +{ + NMVpnPluginInfoPrivate *priv; + const char *plugin_filename; + + g_return_val_if_fail (NM_IS_VPN_PLUGIN_INFO (self), NULL); + + priv = NM_VPN_PLUGIN_INFO_GET_PRIVATE (self); + + if (priv->editor_plugin) + return priv->editor_plugin; + + plugin_filename = nm_vpn_plugin_info_get_plugin (self); + if (!plugin_filename || !*plugin_filename) { + g_set_error (error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, + _("missing \"plugin\" setting")); + return NULL; + } + + /* We only try once to load the plugin. If we previously tried and it was + * unsuccessful, error out immediately. */ + if (priv->editor_plugin_loaded) { + g_set_error (error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, + _("%s: don't retry loading plugin which already failed previously"), priv->name); + return NULL; + } + + priv->editor_plugin_loaded = TRUE; + priv->editor_plugin = nm_vpn_editor_plugin_load_from_file (plugin_filename, + priv->name, + nm_vpn_plugin_info_get_service (self), + getuid (), + NULL, + NULL, + error); + return priv->editor_plugin; +} + +/*********************************************************************/ + +/** + * nm_vpn_plugin_info_new_from_file: + * @filename: filename to read. + * @error: on failure, the error reason. + * + * Read the plugin info from file @filename. Does not do + * any further verification on the file. You might want to check + * file permissions and ownership of the file. + * + * Returns: %NULL if there is any error or a newly created + * #NMVpnPluginInfo instance. + * + * Since: 1.2 + */ +NMVpnPluginInfo * +nm_vpn_plugin_info_new_from_file (const char *filename, + GError **error) +{ + g_return_val_if_fail (filename, NULL); + + return NM_VPN_PLUGIN_INFO (g_initable_new (NM_TYPE_VPN_PLUGIN_INFO, + NULL, + error, + NM_VPN_PLUGIN_INFO_FILENAME, filename, + NULL)); +} + +/** + * nm_vpn_plugin_info_new_with_data: + * @filename: optional filename. + * @keyfile: inject data for the plugin info instance. + * @error: construction may fail if the keyfile lacks mandatory fields. + * In this case, return the error reason. + * + * This constructor does not read any data from file but + * takes instead a @keyfile argument. + * + * Returns: new plugin info instance. + * + * Since: 1.2 + */ +NMVpnPluginInfo * +nm_vpn_plugin_info_new_with_data (const char *filename, + GKeyFile *keyfile, + GError **error) +{ + g_return_val_if_fail (keyfile, NULL); + + return NM_VPN_PLUGIN_INFO (g_initable_new (NM_TYPE_VPN_PLUGIN_INFO, + NULL, + error, + NM_VPN_PLUGIN_INFO_FILENAME, filename, + NM_VPN_PLUGIN_INFO_KEYFILE, keyfile, + NULL)); +} + +/*********************************************************************/ + +static void +nm_vpn_plugin_info_init (NMVpnPluginInfo *plugin) +{ +} + +static gboolean +init_sync (GInitable *initable, GCancellable *cancellable, GError **error) +{ + NMVpnPluginInfo *self = NM_VPN_PLUGIN_INFO (initable); + NMVpnPluginInfoPrivate *priv = NM_VPN_PLUGIN_INFO_GET_PRIVATE (self); + gs_strfreev char **groups = NULL; + guint i, j; + + if (!priv->keyfile) { + if (!priv->filename) { + g_set_error_literal (error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS, + _("missing filename to load VPN plugin info")); + return FALSE; + } + priv->keyfile = g_key_file_new (); + if (!g_key_file_load_from_file (priv->keyfile, priv->filename, G_KEY_FILE_NONE, error)) + return FALSE; + } + + /* we reqire at least a "name" */ + priv->name = g_key_file_get_string (priv->keyfile, NM_VPN_PLUGIN_INFO_KF_GROUP_CONNECTION, "name", NULL); + if (!priv->name || !priv->name[0]) { + g_set_error_literal (error, NM_VPN_PLUGIN_ERROR, NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS, + _("missing name for VPN plugin info")); + return FALSE; + } + + /* we also require "service", because that how we associate NMSettingVpn:service-type with the + * NMVpnPluginInfo. */ + priv->service = g_key_file_get_string (priv->keyfile, NM_VPN_PLUGIN_INFO_KF_GROUP_CONNECTION, "service", NULL); + if (!priv->service || !*priv->service) { + g_set_error_literal (error, NM_VPN_PLUGIN_ERROR, NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS, + _("missing service for VPN plugin info")); + return FALSE; + } + + priv->keys = g_hash_table_new_full (_nm_utils_strstrdictkey_hash, + _nm_utils_strstrdictkey_equal, + g_free, g_free); + groups = g_key_file_get_groups (priv->keyfile, NULL); + for (i = 0; groups && groups[i]; i++) { + gs_strfreev char **keys = NULL; + + keys = g_key_file_get_keys (priv->keyfile, groups[i], NULL, NULL); + for (j = 0; keys && keys[j]; j++) { + char *s; + + /* Lookup the value via get_string(). We want that behavior. + * You could still lookup the original values via g_key_file_get_value() + * based on priv->keyfile. */ + s = g_key_file_get_string (priv->keyfile, groups[i], keys[j], NULL); + if (s) + g_hash_table_insert (priv->keys, _nm_utils_strstrdictkey_create (groups[i], keys[j]), s); + } + } + + return TRUE; +} + +static void +set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + NMVpnPluginInfoPrivate *priv = NM_VPN_PLUGIN_INFO_GET_PRIVATE (object); + + switch (prop_id) { + case PROP_FILENAME: + priv->filename = g_value_dup_string (value); + break; + case PROP_KEYFILE: + priv->keyfile = g_value_dup_boxed (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) +{ + NMVpnPluginInfoPrivate *priv = NM_VPN_PLUGIN_INFO_GET_PRIVATE (object); + + switch (prop_id) { + case PROP_NAME: + g_value_set_string (value, priv->name); + break; + case PROP_FILENAME: + g_value_set_string (value, priv->filename); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +dispose (GObject *object) +{ + NMVpnPluginInfo *self = NM_VPN_PLUGIN_INFO (object); + NMVpnPluginInfoPrivate *priv = NM_VPN_PLUGIN_INFO_GET_PRIVATE (self); + + g_clear_object (&priv->editor_plugin); + + G_OBJECT_CLASS (nm_vpn_plugin_info_parent_class)->dispose (object); +} + +static void +finalize (GObject *object) +{ + NMVpnPluginInfo *self = NM_VPN_PLUGIN_INFO (object); + NMVpnPluginInfoPrivate *priv = NM_VPN_PLUGIN_INFO_GET_PRIVATE (self); + + g_free (priv->name); + g_free (priv->service); + g_free (priv->filename); + g_key_file_unref (priv->keyfile); + g_hash_table_unref (priv->keys); + + G_OBJECT_CLASS (nm_vpn_plugin_info_parent_class)->finalize (object); +} + +static void +nm_vpn_plugin_info_class_init (NMVpnPluginInfoClass *plugin_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (plugin_class); + + g_type_class_add_private (object_class, sizeof (NMVpnPluginInfoPrivate)); + + /* virtual methods */ + object_class->set_property = set_property; + object_class->get_property = get_property; + object_class->dispose = dispose; + object_class->finalize = finalize; + + /* properties */ + + /** + * NMVpnPluginInfo:name: + * + * The name of the VPN plugin. + * + * Since: 1.2 + */ + g_object_class_install_property + (object_class, PROP_NAME, + g_param_spec_string (NM_VPN_PLUGIN_INFO_NAME, "", "", + NULL, + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS)); + + /** + * NMVpnPluginInfo:filename: + * + * The filename from which the info was loaded. + * Can be %NULL if the instance was not loaded from + * a file (i.e. the keyfile instance was passed to the + * constructor). + * + * Since: 1.2 + */ + g_object_class_install_property + (object_class, PROP_FILENAME, + g_param_spec_string (NM_VPN_PLUGIN_INFO_FILENAME, "", "", + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + /** + * NMVpnPluginInfo:keyfile: + * + * Initalize the instance with a different keyfile instance. + * When passing a keyfile instance, the constructor will not + * try to read from filename. + * + * Since: 1.2 + */ + g_object_class_install_property + (object_class, PROP_KEYFILE, + g_param_spec_boxed (NM_VPN_PLUGIN_INFO_KEYFILE, "", "", + G_TYPE_KEY_FILE, + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); +} + +static void +nm_vpn_plugin_info_initable_iface_init (GInitableIface *iface) +{ + iface->init = init_sync; +} + diff --git a/libnm-core/nm-vpn-plugin-info.h b/libnm-core/nm-vpn-plugin-info.h new file mode 100644 index 0000000000..9a7246f840 --- /dev/null +++ b/libnm-core/nm-vpn-plugin-info.h @@ -0,0 +1,114 @@ +/* -*- 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 2015 Red Hat, Inc. + */ + +#ifndef __NM_VPN_PLUGIN_INFO_H__ +#define __NM_VPN_PLUGIN_INFO_H__ + +#include <glib.h> +#include <glib-object.h> + +#include "nm-utils.h" +#include "nm-vpn-editor-plugin.h" + +G_BEGIN_DECLS + +#define NM_TYPE_VPN_PLUGIN_INFO (nm_vpn_plugin_info_get_type ()) +#define NM_VPN_PLUGIN_INFO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_VPN_PLUGIN_INFO, NMVpnPluginInfo)) +#define NM_VPN_PLUGIN_INFO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_VPN_PLUGIN_INFO, NMVpnPluginInfoClass)) +#define NM_IS_VPN_PLUGIN_INFO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_VPN_PLUGIN_INFO)) +#define NM_IS_VPN_PLUGIN_INFO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_VPN_PLUGIN_INFO)) +#define NM_VPN_PLUGIN_INFO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_VPN_PLUGIN_INFO, NMVpnPluginInfoClass)) + +#define NM_VPN_PLUGIN_INFO_NAME "name" +#define NM_VPN_PLUGIN_INFO_FILENAME "filename" +#define NM_VPN_PLUGIN_INFO_KEYFILE "keyfile" + +#define NM_VPN_PLUGIN_INFO_KF_GROUP_CONNECTION "VPN Connection" +#define NM_VPN_PLUGIN_INFO_KF_GROUP_LIBNM "libnm" +#define NM_VPN_PLUGIN_INFO_KF_GROUP_GNOME "GNOME" + +typedef struct { + NM_AVAILABLE_IN_1_2 + GObject parent; +} NMVpnPluginInfo NM_AVAILABLE_IN_1_2; + +typedef struct { + NM_AVAILABLE_IN_1_2 + GObjectClass parent; + + /*< private >*/ + NM_AVAILABLE_IN_1_2 + gpointer padding[8]; +} NMVpnPluginInfoClass NM_AVAILABLE_IN_1_2; + +NM_AVAILABLE_IN_1_2 +GType nm_vpn_plugin_info_get_type (void); + +NM_AVAILABLE_IN_1_2 +NMVpnPluginInfo *nm_vpn_plugin_info_new_from_file (const char *filename, + GError **error); + +NM_AVAILABLE_IN_1_2 +NMVpnPluginInfo *nm_vpn_plugin_info_new_with_data (const char *filename, + GKeyFile *keyfile, + GError **error); + +NM_AVAILABLE_IN_1_2 +const char *nm_vpn_plugin_info_get_name (NMVpnPluginInfo *self); +NM_AVAILABLE_IN_1_2 +const char *nm_vpn_plugin_info_get_filename (NMVpnPluginInfo *self); +NM_AVAILABLE_IN_1_2 +const char *nm_vpn_plugin_info_get_service (NMVpnPluginInfo *self); +NM_AVAILABLE_IN_1_2 +const char *nm_vpn_plugin_info_get_plugin (NMVpnPluginInfo *self); +NM_AVAILABLE_IN_1_2 +const char *nm_vpn_plugin_info_get_program (NMVpnPluginInfo *self); +NM_AVAILABLE_IN_1_2 +const char *nm_vpn_plugin_info_lookup_property (NMVpnPluginInfo *self, const char *group, const char *key); + +NM_AVAILABLE_IN_1_2 +gboolean nm_vpn_plugin_info_validate_filename (const char *filename); + +NM_AVAILABLE_IN_1_2 +GSList *nm_vpn_plugin_info_list_load (void); +NM_AVAILABLE_IN_1_2 +gboolean nm_vpn_plugin_info_list_add (GSList **list, NMVpnPluginInfo *plugin_info, GError **error); +NM_AVAILABLE_IN_1_2 +gboolean nm_vpn_plugin_info_list_remove (GSList **list, NMVpnPluginInfo *plugin_info); +NM_AVAILABLE_IN_1_2 +NMVpnPluginInfo *nm_vpn_plugin_info_list_find_by_name (GSList *list, const char *name); +NM_AVAILABLE_IN_1_2 +NMVpnPluginInfo *nm_vpn_plugin_info_list_find_by_filename (GSList *list, const char *filename); +NM_AVAILABLE_IN_1_2 +NMVpnPluginInfo *nm_vpn_plugin_info_list_find_by_service (GSList *list, const char *service); + + +NM_AVAILABLE_IN_1_2 +NMVpnEditorPlugin *nm_vpn_plugin_info_get_editor_plugin (NMVpnPluginInfo *plugin_info); +NM_AVAILABLE_IN_1_2 +void nm_vpn_plugin_info_set_editor_plugin (NMVpnPluginInfo *self, + NMVpnEditorPlugin *plugin); +NM_AVAILABLE_IN_1_2 +NMVpnEditorPlugin *nm_vpn_plugin_info_load_editor_plugin (NMVpnPluginInfo *plugin_info, + GError **error); + +G_END_DECLS + +#endif /* __NM_VPN_PLUGIN_INFO_H__ */ diff --git a/libnm-core/tests/test-general.c b/libnm-core/tests/test-general.c index e8e96bb756..dfe591298e 100644 --- a/libnm-core/tests/test-general.c +++ b/libnm-core/tests/test-general.c @@ -4282,6 +4282,54 @@ test_nm_utils_ascii_str_to_int64 (void) /******************************************************************************/ static void +test_nm_utils_strstrdictkey () +{ +#define _VALUES_STATIC(_v1, _v2) { .v1 = _v1, .v2 = _v2, .v_static = _nm_utils_strstrdictkey_static (_v1, _v2), } + const struct { + const char *v1; + const char *v2; + NMUtilsStrStrDictKey *v_static; + } *val1, *val2, values[] = { + { NULL, NULL }, + { "", NULL }, + { NULL, "" }, + { "a", NULL }, + { NULL, "a" }, + _VALUES_STATIC ("", ""), + _VALUES_STATIC ("a", ""), + _VALUES_STATIC ("", "a"), + _VALUES_STATIC ("a", "b"), + }; + guint i, j; + + for (i = 0; i < G_N_ELEMENTS (values); i++) { + gs_free NMUtilsStrStrDictKey *key1 = NULL; + + val1 = &values[i]; + + key1 = _nm_utils_strstrdictkey_create (val1->v1, val1->v2); + if (val1->v_static) { + g_assert (_nm_utils_strstrdictkey_equal (key1, val1->v_static)); + g_assert (_nm_utils_strstrdictkey_equal (val1->v_static, key1)); + g_assert_cmpint (_nm_utils_strstrdictkey_hash (key1), ==, _nm_utils_strstrdictkey_hash (val1->v_static)); + } + + for (j = 0; j < G_N_ELEMENTS (values); j++) { + gs_free NMUtilsStrStrDictKey *key2 = NULL; + + val2 = &values[j]; + key2 = _nm_utils_strstrdictkey_create (val2->v1, val2->v2); + if (i != j) { + g_assert (!_nm_utils_strstrdictkey_equal (key1, key2)); + g_assert (!_nm_utils_strstrdictkey_equal (key2, key1)); + } + } + } +} + +/******************************************************************************/ + +static void test_nm_utils_dns_option_validate_do (char *option, gboolean ipv6, const NMUtilsDNSOptionDesc *descs, gboolean exp_result, char *exp_name, gboolean exp_value) { @@ -4764,6 +4812,7 @@ int main (int argc, char **argv) g_test_add_func ("/core/general/nm_utils_is_power_of_two", test_nm_utils_is_power_of_two); g_test_add_func ("/core/general/_glib_compat_g_ptr_array_insert", test_g_ptr_array_insert); g_test_add_func ("/core/general/_nm_utils_ptrarray_find_binary_search", test_nm_utils_ptrarray_find_binary_search); + g_test_add_func ("/core/general/_nm_utils_strstrdictkey", test_nm_utils_strstrdictkey); g_test_add_func ("/core/general/_nm_utils_dns_option_validate", test_nm_utils_dns_option_validate); g_test_add_func ("/core/general/_nm_utils_dns_option_find_idx", test_nm_utils_dns_option_find_idx); diff --git a/libnm/Makefile.am b/libnm/Makefile.am index 2c9d94ef54..0a41ab5752 100644 --- a/libnm/Makefile.am +++ b/libnm/Makefile.am @@ -50,12 +50,13 @@ libnminclude_hfiles = \ nm-remote-connection.h \ nm-types.h \ nm-vpn-connection.h \ - nm-vpn-editor-plugin.h \ + nm-vpn-editor.h \ nm-wimax-nsp.h libnminclude_nointrospect_hfiles = \ nm-secret-agent-old.h \ - nm-vpn-plugin-old.h + nm-vpn-plugin-old.h \ + nm-vpn-service-plugin.h libnminclude_HEADERS = \ $(libnminclude_hfiles) \ @@ -108,7 +109,8 @@ libnm_la_csources = \ nm-secret-agent-old.c \ nm-vpn-connection.c \ nm-vpn-plugin-old.c \ - nm-vpn-editor-plugin.c \ + nm-vpn-editor.c \ + nm-vpn-service-plugin.c \ nm-wimax-nsp.c libnm_la_SOURCES = \ diff --git a/libnm/NetworkManager.h b/libnm/NetworkManager.h index 251a042216..085437e6f5 100644 --- a/libnm/NetworkManager.h +++ b/libnm/NetworkManager.h @@ -81,7 +81,9 @@ #include <nm-version.h> #include <nm-vpn-connection.h> #include <nm-vpn-dbus-interface.h> +#include <nm-vpn-editor.h> #include <nm-vpn-editor-plugin.h> +#include <nm-vpn-plugin-info.h> #include <nm-wimax-nsp.h> #undef __NETWORKMANAGER_H_INSIDE__ diff --git a/libnm/libnm.ver b/libnm/libnm.ver index c3a23cb574..6e1eda21b9 100644 --- a/libnm/libnm.ver +++ b/libnm/libnm.ver @@ -821,17 +821,6 @@ global: nm_vpn_plugin_error_get_type; nm_vpn_plugin_error_quark; nm_vpn_plugin_failure_get_type; - nm_vpn_plugin_old_disconnect; - nm_vpn_plugin_old_failure; - nm_vpn_plugin_old_get_connection; - nm_vpn_plugin_old_get_secret_flags; - nm_vpn_plugin_old_get_state; - nm_vpn_plugin_old_get_type; - nm_vpn_plugin_old_read_vpn_details; - nm_vpn_plugin_old_secrets_required; - nm_vpn_plugin_old_set_ip4_config; - nm_vpn_plugin_old_set_login_banner; - nm_vpn_plugin_old_set_state; nm_vpn_service_state_get_type; nm_wep_key_type_get_type; nm_wimax_nsp_connection_valid; @@ -882,4 +871,35 @@ global: nm_utils_bond_mode_string_to_int; nm_utils_enum_from_str; nm_utils_enum_to_str; + nm_vpn_editor_plugin_load_from_file; + nm_vpn_plugin_info_get_filename; + nm_vpn_plugin_info_get_editor_plugin; + nm_vpn_plugin_info_get_name; + nm_vpn_plugin_info_get_plugin; + nm_vpn_plugin_info_get_program; + nm_vpn_plugin_info_get_service; + nm_vpn_plugin_info_get_type; + nm_vpn_plugin_info_load_editor_plugin; + nm_vpn_plugin_info_lookup_property; + nm_vpn_plugin_info_new_from_file; + nm_vpn_plugin_info_new_with_data; + nm_vpn_plugin_info_set_editor_plugin; + nm_vpn_plugin_info_validate_filename; + nm_vpn_plugin_info_list_add; + nm_vpn_plugin_info_list_find_by_filename; + nm_vpn_plugin_info_list_find_by_name; + nm_vpn_plugin_info_list_find_by_service; + nm_vpn_plugin_info_list_load; + nm_vpn_plugin_info_list_remove; + nm_vpn_service_plugin_disconnect; + nm_vpn_service_plugin_failure; + nm_vpn_service_plugin_get_connection; + nm_vpn_service_plugin_get_secret_flags; + nm_vpn_service_plugin_get_state; + nm_vpn_service_plugin_get_type; + nm_vpn_service_plugin_read_vpn_details; + nm_vpn_service_plugin_secrets_required; + nm_vpn_service_plugin_set_ip4_config; + nm_vpn_service_plugin_set_login_banner; + nm_vpn_service_plugin_set_state; } libnm_1_0_0; diff --git a/libnm/nm-vpn-editor-plugin.c b/libnm/nm-vpn-editor-plugin.c deleted file mode 100644 index 904a6be483..0000000000 --- a/libnm/nm-vpn-editor-plugin.c +++ /dev/null @@ -1,181 +0,0 @@ -/* -*- 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 2008 - 2010 Red Hat, Inc. - * Copyright 2008 Novell, Inc. - */ - -#include "config.h" - -#include "nm-vpn-editor-plugin.h" - -static void nm_vpn_editor_plugin_default_init (NMVpnEditorPluginInterface *iface); - -G_DEFINE_INTERFACE (NMVpnEditorPlugin, nm_vpn_editor_plugin, G_TYPE_OBJECT) - -static void -nm_vpn_editor_plugin_default_init (NMVpnEditorPluginInterface *iface) -{ - /* Properties */ - - /** - * NMVpnEditorPlugin:name: - * - * Short display name of the VPN plugin. - */ - g_object_interface_install_property (iface, - g_param_spec_string (NM_VPN_EDITOR_PLUGIN_NAME, "", "", - NULL, - G_PARAM_READABLE | - G_PARAM_STATIC_STRINGS)); - - /** - * NMVpnEditorPlugin:description: - * - * Longer description of the VPN plugin. - */ - g_object_interface_install_property (iface, - g_param_spec_string (NM_VPN_EDITOR_PLUGIN_DESCRIPTION, "", "", - NULL, - G_PARAM_READABLE | - G_PARAM_STATIC_STRINGS)); - - /** - * NMVpnEditorPlugin:service: - * - * D-Bus service name of the plugin's VPN service. - */ - g_object_interface_install_property (iface, - g_param_spec_string (NM_VPN_EDITOR_PLUGIN_SERVICE, "", "", - NULL, - G_PARAM_READABLE | - G_PARAM_STATIC_STRINGS)); -} - -/** - * nm_vpn_editor_plugin_get_editor: - * - * Returns: (transfer full): - */ -NMVpnEditor * -nm_vpn_editor_plugin_get_editor (NMVpnEditorPlugin *plugin, - NMConnection *connection, - GError **error) -{ - g_return_val_if_fail (NM_IS_VPN_EDITOR_PLUGIN (plugin), NULL); - - return NM_VPN_EDITOR_PLUGIN_GET_INTERFACE (plugin)->get_editor (plugin, connection, error); -} - -NMVpnEditorPluginCapability -nm_vpn_editor_plugin_get_capabilities (NMVpnEditorPlugin *plugin) -{ - g_return_val_if_fail (NM_IS_VPN_EDITOR_PLUGIN (plugin), 0); - - return NM_VPN_EDITOR_PLUGIN_GET_INTERFACE (plugin)->get_capabilities (plugin); -} - -/** - * nm_vpn_editor_plugin_import: - * - * Returns: (transfer full): - */ -NMConnection * -nm_vpn_editor_plugin_import (NMVpnEditorPlugin *plugin, - const char *path, - GError **error) -{ - g_return_val_if_fail (NM_IS_VPN_EDITOR_PLUGIN (plugin), NULL); - - if (nm_vpn_editor_plugin_get_capabilities (plugin) & NM_VPN_EDITOR_PLUGIN_CAPABILITY_IMPORT) { - g_return_val_if_fail (NM_VPN_EDITOR_PLUGIN_GET_INTERFACE (plugin)->import_from_file != NULL, NULL); - return NM_VPN_EDITOR_PLUGIN_GET_INTERFACE (plugin)->import_from_file (plugin, path, error); - } - return NULL; -} - -gboolean -nm_vpn_editor_plugin_export (NMVpnEditorPlugin *plugin, - const char *path, - NMConnection *connection, - GError **error) -{ - g_return_val_if_fail (NM_IS_VPN_EDITOR_PLUGIN (plugin), FALSE); - - if (nm_vpn_editor_plugin_get_capabilities (plugin) & NM_VPN_EDITOR_PLUGIN_CAPABILITY_EXPORT) { - g_return_val_if_fail (NM_VPN_EDITOR_PLUGIN_GET_INTERFACE (plugin)->export_to_file != NULL, FALSE); - return NM_VPN_EDITOR_PLUGIN_GET_INTERFACE (plugin)->export_to_file (plugin, path, connection, error); - } - return FALSE; -} - -char * -nm_vpn_editor_plugin_get_suggested_filename (NMVpnEditorPlugin *plugin, - NMConnection *connection) -{ - g_return_val_if_fail (NM_IS_VPN_EDITOR_PLUGIN (plugin), NULL); - - if (NM_VPN_EDITOR_PLUGIN_GET_INTERFACE (plugin)->get_suggested_filename) - return NM_VPN_EDITOR_PLUGIN_GET_INTERFACE (plugin)->get_suggested_filename (plugin, connection); - return NULL; -} - - -static void nm_vpn_editor_default_init (NMVpnEditorInterface *iface); - -G_DEFINE_INTERFACE (NMVpnEditor, nm_vpn_editor, G_TYPE_OBJECT) - -static void -nm_vpn_editor_default_init (NMVpnEditorInterface *iface) -{ - GType iface_type = G_TYPE_FROM_INTERFACE (iface); - - /* Signals */ - g_signal_new ("changed", - iface_type, - G_SIGNAL_RUN_FIRST, - G_STRUCT_OFFSET (NMVpnEditorInterface, changed), - NULL, NULL, - g_cclosure_marshal_VOID__VOID, - G_TYPE_NONE, 0); -} - -/** - * nm_vpn_editor_get_widget: - * - * Returns: (transfer none): - */ -GObject * -nm_vpn_editor_get_widget (NMVpnEditor *editor) -{ - g_return_val_if_fail (NM_IS_VPN_EDITOR (editor), NULL); - - return NM_VPN_EDITOR_GET_INTERFACE (editor)->get_widget (editor); -} - -gboolean -nm_vpn_editor_update_connection (NMVpnEditor *editor, - NMConnection *connection, - GError **error) -{ - g_return_val_if_fail (NM_IS_VPN_EDITOR (editor), FALSE); - - if (error) - g_return_val_if_fail (*error == NULL, FALSE); - - return NM_VPN_EDITOR_GET_INTERFACE (editor)->update_connection (editor, connection, error); -} diff --git a/libnm/nm-vpn-editor.c b/libnm/nm-vpn-editor.c new file mode 100644 index 0000000000..ba6bd5e03c --- /dev/null +++ b/libnm/nm-vpn-editor.c @@ -0,0 +1,69 @@ +/* -*- 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 2008 - 2010 Red Hat, Inc. + * Copyright 2008 Novell, Inc. + */ + +#include "config.h" + +#include "nm-vpn-editor.h" + +static void nm_vpn_editor_default_init (NMVpnEditorInterface *iface); + +G_DEFINE_INTERFACE (NMVpnEditor, nm_vpn_editor, G_TYPE_OBJECT) + +static void +nm_vpn_editor_default_init (NMVpnEditorInterface *iface) +{ + GType iface_type = G_TYPE_FROM_INTERFACE (iface); + + /* Signals */ + g_signal_new ("changed", + iface_type, + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (NMVpnEditorInterface, changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +/** + * nm_vpn_editor_get_widget: + * + * Returns: (transfer none): + */ +GObject * +nm_vpn_editor_get_widget (NMVpnEditor *editor) +{ + g_return_val_if_fail (NM_IS_VPN_EDITOR (editor), NULL); + + return NM_VPN_EDITOR_GET_INTERFACE (editor)->get_widget (editor); +} + +gboolean +nm_vpn_editor_update_connection (NMVpnEditor *editor, + NMConnection *connection, + GError **error) +{ + g_return_val_if_fail (NM_IS_VPN_EDITOR (editor), FALSE); + + if (error) + g_return_val_if_fail (*error == NULL, FALSE); + + return NM_VPN_EDITOR_GET_INTERFACE (editor)->update_connection (editor, connection, error); +} diff --git a/libnm/nm-vpn-editor.h b/libnm/nm-vpn-editor.h new file mode 100644 index 0000000000..a11e9fd16d --- /dev/null +++ b/libnm/nm-vpn-editor.h @@ -0,0 +1,86 @@ +/* -*- 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 2008 Novell, Inc. + * Copyright 2008 - 2015 Red Hat, Inc. + */ + +#ifndef __NM_VPN_EDITOR_H__ +#define __NM_VPN_EDITOR_H__ + +#if !defined (__NETWORKMANAGER_H_INSIDE__) && !defined (NETWORKMANAGER_COMPILATION) +#error "Only <NetworkManager.h> can be included directly." +#endif + +#include <glib.h> +#include <glib-object.h> +#include <nm-types.h> + +#include "nm-vpn-editor-plugin.h" + +G_BEGIN_DECLS + +/**************************************************/ +/* Editor interface */ +/**************************************************/ + +#define NM_TYPE_VPN_EDITOR (nm_vpn_editor_get_type ()) +#define NM_VPN_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_VPN_EDITOR, NMVpnEditor)) +#define NM_IS_VPN_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_VPN_EDITOR)) +#define NM_VPN_EDITOR_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), NM_TYPE_VPN_EDITOR, NMVpnEditorInterface)) + +/** + * NMVpnEditorInterface: + * @g_iface: the parent interface + * @get_widget: return the #GtkWidget for the VPN editor's UI + * @placeholder: not currently used + * @update_connection: called to save the user-entered options to the connection + * object. Should return %FALSE and set @error if the current options are + * invalid. @error should contain enough information for the plugin to + * determine which UI widget is invalid at a later point in time. For + * example, creating unique error codes for what error occurred and populating + * the message field of @error with the name of the invalid property. + * @changed: emitted when the value of a UI widget changes. May trigger a + * validity check via @update_connection to write values to the connection. + * + * Interface for editing a specific #NMConnection + */ +typedef struct { + GTypeInterface g_iface; + + GObject * (*get_widget) (NMVpnEditor *editor); + + void (*placeholder) (void); + + gboolean (*update_connection) (NMVpnEditor *editor, + NMConnection *connection, + GError **error); + + void (*changed) (NMVpnEditor *editor); +} NMVpnEditorInterface; + +GType nm_vpn_editor_get_type (void); + +GObject * nm_vpn_editor_get_widget (NMVpnEditor *editor); + +gboolean nm_vpn_editor_update_connection (NMVpnEditor *editor, + NMConnection *connection, + GError **error); + +G_END_DECLS + +#endif /* __NM_VPN_EDITOR_H__ */ diff --git a/libnm/nm-vpn-plugin-old.c b/libnm/nm-vpn-plugin-old.c index 5748060ff0..ff7ce834a8 100644 --- a/libnm/nm-vpn-plugin-old.c +++ b/libnm/nm-vpn-plugin-old.c @@ -19,10 +19,6 @@ * Copyright 2007 - 2008 Red Hat, Inc. */ -/* This interface is expected to be deprecated in NM 1.2, at which point there - * will be a new "NMVpnPlugin" class to replace it. - */ - #include "config.h" #include <errno.h> @@ -70,7 +66,7 @@ typedef struct { gboolean has_ip6, got_ip6; /* Config stuff copied from config to ip4config */ - char *banner, *tundev, *gateway, *mtu; + GVariant *banner, *tundev, *gateway, *mtu; } NMVpnPluginOldPrivate; #define NM_VPN_PLUGIN_OLD_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_VPN_PLUGIN_OLD, NMVpnPluginOldPrivate)) @@ -117,6 +113,8 @@ nm_vpn_plugin_old_set_connection (NMVpnPluginOld *plugin, * nm_vpn_plugin_old_get_connection: * * Returns: (transfer full): + * + * Deprecated: 1.2: replaced by NMVpnServicePlugin */ GDBusConnection * nm_vpn_plugin_old_get_connection (NMVpnPluginOld *plugin) @@ -300,14 +298,22 @@ nm_vpn_plugin_old_set_config (NMVpnPluginOld *plugin, /* Record the items that need to also be inserted into the * ip4config, for compatibility with older daemons. */ - g_clear_pointer (&priv->banner, g_free); - (void) g_variant_lookup (config, NM_VPN_PLUGIN_CONFIG_BANNER, "&s", &priv->banner); - g_clear_pointer (&priv->tundev, g_free); - (void) g_variant_lookup (config, NM_VPN_PLUGIN_CONFIG_TUNDEV, "&s", &priv->tundev); - g_clear_pointer (&priv->gateway, g_free); - (void) g_variant_lookup (config, NM_VPN_PLUGIN_CONFIG_EXT_GATEWAY, "&s", &priv->gateway); - g_clear_pointer (&priv->mtu, g_free); - (void) g_variant_lookup (config, NM_VPN_PLUGIN_CONFIG_MTU, "&s", &priv->mtu); + 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); } @@ -320,7 +326,8 @@ nm_vpn_plugin_old_set_ip4_config (NMVpnPluginOld *plugin, GVariant *combined_config; GVariantBuilder builder; GVariantIter iter; - const char *key, *value; + const char *key; + GVariant *value; g_return_if_fail (NM_IS_VPN_PLUGIN_OLD (plugin)); g_return_if_fail (ip4_config != NULL); @@ -340,19 +347,21 @@ nm_vpn_plugin_old_set_ip4_config (NMVpnPluginOld *plugin, * being emitted. So just copy all of that data into the ip4 * config too. */ - g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{ss}")); + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}")); g_variant_iter_init (&iter, ip4_config); - while (g_variant_iter_next (&iter, "{&s&s}", &key, &value)) - g_variant_builder_add (&builder, "{ss}", key, value); + 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, "{ss}", NM_VPN_PLUGIN_IP4_CONFIG_BANNER, &priv->banner); + g_variant_builder_add (&builder, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_BANNER, &priv->banner); if (priv->tundev) - g_variant_builder_add (&builder, "{ss}", NM_VPN_PLUGIN_IP4_CONFIG_TUNDEV, &priv->tundev); + g_variant_builder_add (&builder, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_TUNDEV, &priv->tundev); if (priv->gateway) - g_variant_builder_add (&builder, "{ss}", NM_VPN_PLUGIN_IP4_CONFIG_EXT_GATEWAY, &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, "{ss}", NM_VPN_PLUGIN_IP4_CONFIG_MTU, &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); @@ -595,6 +604,8 @@ impl_vpn_plugin_old_new_secrets (NMVpnPluginOld *plugin, * 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. + * + * Deprecated: 1.2: replaced by NMVpnServicePlugin */ void nm_vpn_plugin_old_secrets_required (NMVpnPluginOld *plugin, @@ -647,6 +658,8 @@ free_secret (gpointer data) * an applet when the applet calls the authentication dialog of the VPN plugin. * * Returns: %TRUE if reading values was successful, %FALSE if not + * + * Deprecated: 1.2: replaced by NMVpnServicePlugin **/ gboolean nm_vpn_plugin_old_read_vpn_details (int fd, @@ -748,6 +761,8 @@ nm_vpn_plugin_old_read_vpn_details (int fd, * * Returns: %TRUE if the flag data item was found and successfully converted * to flags, %FALSE if not + * + * Deprecated: 1.2: replaced by NMVpnServicePlugin **/ gboolean nm_vpn_plugin_old_get_secret_flags (GHashTable *data, @@ -1026,10 +1041,10 @@ finalize (GObject *object) nm_vpn_plugin_old_set_connection (plugin, NULL); g_free (priv->dbus_service_name); - g_clear_pointer (&priv->banner, g_free); - g_clear_pointer (&priv->tundev, g_free); - g_clear_pointer (&priv->gateway, g_free); - g_clear_pointer (&priv->mtu, g_free); + g_clear_pointer (&priv->banner, g_variant_unref); + g_clear_pointer (&priv->tundev, g_variant_unref); + g_clear_pointer (&priv->gateway, g_variant_unref); + g_clear_pointer (&priv->mtu, g_variant_unref); G_OBJECT_CLASS (nm_vpn_plugin_old_parent_class)->finalize (object); } @@ -1077,6 +1092,8 @@ nm_vpn_plugin_old_class_init (NMVpnPluginOldClass *plugin_class) * NMVpnPluginOld:service-name: * * The D-Bus service name of this plugin. + * + * Deprecated: 1.2: replaced by NMVpnServicePlugin */ g_object_class_install_property (object_class, PROP_DBUS_SERVICE_NAME, @@ -1090,6 +1107,8 @@ nm_vpn_plugin_old_class_init (NMVpnPluginOldClass *plugin_class) * NMVpnPluginOld:state: * * The state of the plugin. + * + * Deprecated: 1.2: replaced by NMVpnServicePlugin */ g_object_class_install_property (object_class, PROP_STATE, diff --git a/libnm/nm-vpn-plugin-old.h b/libnm/nm-vpn-plugin-old.h index af8f4ff61b..59cf4600c5 100644 --- a/libnm/nm-vpn-plugin-old.h +++ b/libnm/nm-vpn-plugin-old.h @@ -16,7 +16,7 @@ * Boston, MA 02110-1301 USA. * * Copyright 2007 - 2008 Novell, Inc. - * Copyright 2007 - 2013 Red Hat, Inc. + * Copyright 2007 - 2015 Red Hat, Inc. */ #ifndef __NM_VPN_PLUGIN_OLD_H__ @@ -39,94 +39,122 @@ G_BEGIN_DECLS #define NM_VPN_PLUGIN_OLD_STATE "state" typedef struct { + NM_DEPRECATED_IN_1_2 GObject parent; -} NMVpnPluginOld; +} NMVpnPluginOld NM_DEPRECATED_IN_1_2; typedef struct { + NM_DEPRECATED_IN_1_2 GObjectClass parent; /* Signals */ + NM_DEPRECATED_IN_1_2 void (*state_changed) (NMVpnPluginOld *plugin, NMVpnServiceState state); + NM_DEPRECATED_IN_1_2 void (*ip4_config) (NMVpnPluginOld *plugin, GVariant *ip4_config); + NM_DEPRECATED_IN_1_2 void (*login_banner) (NMVpnPluginOld *plugin, const char *banner); + NM_DEPRECATED_IN_1_2 void (*failure) (NMVpnPluginOld *plugin, NMVpnPluginFailure reason); + NM_DEPRECATED_IN_1_2 void (*quit) (NMVpnPluginOld *plugin); + NM_DEPRECATED_IN_1_2 void (*config) (NMVpnPluginOld *plugin, GVariant *config); + NM_DEPRECATED_IN_1_2 void (*ip6_config) (NMVpnPluginOld *plugin, GVariant *config); /* virtual methods */ + NM_DEPRECATED_IN_1_2 gboolean (*connect) (NMVpnPluginOld *plugin, NMConnection *connection, GError **err); + NM_DEPRECATED_IN_1_2 gboolean (*need_secrets) (NMVpnPluginOld *plugin, NMConnection *connection, const char **setting_name, GError **error); + NM_DEPRECATED_IN_1_2 gboolean (*disconnect) (NMVpnPluginOld *plugin, GError **err); + NM_DEPRECATED_IN_1_2 gboolean (*new_secrets) (NMVpnPluginOld *plugin, NMConnection *connection, GError **error); + NM_DEPRECATED_IN_1_2 gboolean (*connect_interactive) (NMVpnPluginOld *plugin, NMConnection *connection, GVariant *details, GError **error); /*< private >*/ + NM_DEPRECATED_IN_1_2 gpointer padding[8]; -} NMVpnPluginOldClass; +} NMVpnPluginOldClass NM_DEPRECATED_IN_1_2; +NM_DEPRECATED_IN_1_2 GType nm_vpn_plugin_old_get_type (void); +NM_DEPRECATED_IN_1_2 GDBusConnection *nm_vpn_plugin_old_get_connection (NMVpnPluginOld *plugin); +NM_DEPRECATED_IN_1_2 NMVpnServiceState nm_vpn_plugin_old_get_state (NMVpnPluginOld *plugin); +NM_DEPRECATED_IN_1_2 void nm_vpn_plugin_old_set_state (NMVpnPluginOld *plugin, NMVpnServiceState state); +NM_DEPRECATED_IN_1_2 void nm_vpn_plugin_old_secrets_required (NMVpnPluginOld *plugin, const char *message, const char **hints); +NM_DEPRECATED_IN_1_2 void nm_vpn_plugin_old_set_login_banner (NMVpnPluginOld *plugin, const char *banner); +NM_DEPRECATED_IN_1_2 void nm_vpn_plugin_old_failure (NMVpnPluginOld *plugin, NMVpnPluginFailure reason); +NM_DEPRECATED_IN_1_2 void nm_vpn_plugin_old_set_config (NMVpnPluginOld *plugin, GVariant *config); +NM_DEPRECATED_IN_1_2 void nm_vpn_plugin_old_set_ip4_config (NMVpnPluginOld *plugin, GVariant *ip4_config); +NM_DEPRECATED_IN_1_2 void nm_vpn_plugin_old_set_ip6_config (NMVpnPluginOld *plugin, GVariant *ip6_config); +NM_DEPRECATED_IN_1_2 gboolean nm_vpn_plugin_old_disconnect (NMVpnPluginOld *plugin, GError **err); /* Utility functions */ +NM_DEPRECATED_IN_1_2 gboolean nm_vpn_plugin_old_read_vpn_details (int fd, GHashTable **out_data, GHashTable **out_secrets); +NM_DEPRECATED_IN_1_2 gboolean nm_vpn_plugin_old_get_secret_flags (GHashTable *data, const char *secret_name, NMSettingSecretFlags *out_flags); diff --git a/libnm/nm-vpn-service-plugin.c b/libnm/nm-vpn-service-plugin.c new file mode 100644 index 0000000000..1f25be6e82 --- /dev/null +++ b/libnm/nm-vpn-service-plugin.c @@ -0,0 +1,1208 @@ +/* -*- 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. + * Copyright 2007 - 2015 Red Hat, Inc. + */ + +#include "config.h" + +#include "nm-vpn-service-plugin.h" + +#include <errno.h> +#include <signal.h> +#include <stdlib.h> + +#include <glib/gi18n-lib.h> + +#include "nm-glib.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 "nm-macros-internal.h" + +#include "nmdbus-vpn-plugin.h" + +#define NM_VPN_SERVICE_PLUGIN_QUIT_TIMER 20 + +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; + + /* Temporary stuff */ + guint connect_timer; + guint quit_timer; + guint fail_stop_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 }; + +enum { + PROP_0, + PROP_DBUS_SERVICE_NAME, + PROP_STATE, + + LAST_PROP +}; + +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; +} + +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; +} + +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); + } +} + +void +nm_vpn_service_plugin_set_login_banner (NMVpnServicePlugin *plugin, + const char *banner) +{ + g_return_if_fail (NM_IS_VPN_SERVICE_PLUGIN (plugin)); + g_return_if_fail (banner != NULL); + + g_signal_emit (plugin, signals[LOGIN_BANNER], 0, banner); +} + +void +nm_vpn_service_plugin_failure (NMVpnServicePlugin *plugin, + NMVpnPluginFailure reason) +{ + g_return_if_fail (NM_IS_VPN_SERVICE_PLUGIN (plugin)); + + g_signal_emit (plugin, signals[FAILURE], 0, reason); +} + +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: + 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; + 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); +} + +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) +{ + NMVpnServicePluginPrivate *priv = NM_VPN_SERVICE_PLUGIN_GET_PRIVATE (plugin); + + nm_clear_g_source (&priv->fail_stop_id); + 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); + + g_warn_if_fail (priv->has_ip4 || 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); +} + +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); + 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); + + priv->got_ip6 = TRUE; + g_signal_emit (plugin, signals[IP6_CONFIG], 0, 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); + + priv->connect_timer = g_timeout_add_seconds (60, connect_timer_expired, plugin); +} + +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; + + 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, &error); + if (!connection) { + g_dbus_method_invocation_return_error (context, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS, + "Invalid connection: (%d) %s", + error->code, error->message); + g_clear_error (&error); + } + + 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_vpn_service_plugin_set_state (plugin, NM_VPN_SERVICE_STATE_STARTING); + + if (details) { + priv->interactive = TRUE; + success = vpn_class->connect_interactive (plugin, connection, details, &error); + } else + success = vpn_class->connect (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 an idle handler so that the Connect + * method return gets sent before the STOP StateChanged signal. + */ + schedule_fail_stop (plugin); + } + + 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, &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, &error); + if (!connection) { + g_dbus_method_invocation_return_error (context, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS, + "Invalid connection: (%d) %s", + error->code, 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); + } + + 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); +} + +/***************************************************************/ + +#define DATA_KEY_TAG "DATA_KEY=" +#define DATA_VAL_TAG "DATA_VAL=" +#define SECRET_KEY_TAG "SECRET_KEY=" +#define SECRET_VAL_TAG "SECRET_VAL=" + +static void +free_secret (gpointer data) +{ + char *secret = data; + + memset (secret, 0, strlen (secret)); + g_free (secret); +} + +/** + * 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) +{ + GHashTable *data, *secrets; + gboolean success = FALSE; + char *key = NULL, *val = NULL; + GString *line; + 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); + + data = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + secrets = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, free_secret); + + 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) { + if (out_data) + *out_data = data; + else + g_hash_table_destroy (data); + + if (out_secrets) + *out_secrets = secrets; + else + g_hash_table_destroy (secrets); + } else { + g_hash_table_destroy (data); + g_hash_table_destroy (secrets); + } + + g_string_free (line, TRUE); + 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) +{ + char *flag_name; + const char *val; + unsigned long tmp; + gboolean success = FALSE; + + g_return_val_if_fail (data != NULL, FALSE); + g_return_val_if_fail (secret_name != NULL, FALSE); + g_return_val_if_fail (out_flags != NULL, FALSE); + g_return_val_if_fail (*out_flags == NM_SETTING_SECRET_FLAG_NONE, FALSE); + + flag_name = g_strdup_printf ("%s-flags", secret_name); + + /* Try new flags value first */ + val = g_hash_table_lookup (data, flag_name); + if (val) { + errno = 0; + tmp = strtoul (val, NULL, 10); + if (errno == 0 && tmp <= NM_SETTING_SECRET_FLAGS_ALL) { + *out_flags = (NMSettingSecretFlags) tmp; + success = TRUE; + } + } + + g_free (flag_name); + return success; +} + +/***************************************************************/ + +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 +sigterm_handler (int signum) +{ + g_slist_foreach (active_plugins, (GFunc) nm_vpn_service_plugin_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); + GDBusConnection *connection = NULL; + GDBusProxy *proxy; + GVariant *ret; + gboolean success = FALSE; + + 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) + goto out; + + 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); + g_object_unref (proxy); + if (!ret) { + if (error && *error) + g_dbus_error_strip_remote_error (*error); + goto out; + } + g_variant_unref (ret); + + priv->dbus_vpn_service_plugin = nmdbus_vpn_plugin_skeleton_new (); + if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (priv->dbus_vpn_service_plugin), + connection, + NM_VPN_DBUS_PLUGIN_PATH, + error)) + goto out; + + _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); + + nm_vpn_service_plugin_set_connection (plugin, connection); + nm_vpn_service_plugin_set_state (plugin, NM_VPN_SERVICE_STATE_INIT); + + success = TRUE; + + out: + g_clear_object (&connection); + + return success; +} + +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_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_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) +{ + NMVpnServicePlugin *plugin = NM_VPN_SERVICE_PLUGIN (object); + NMVpnServicePluginPrivate *priv = NM_VPN_SERVICE_PLUGIN_GET_PRIVATE (plugin); + NMVpnServiceState state; + GError *err = NULL; + + 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, &err); + + if (err) { + g_warning ("Error disconnecting VPN connection: %s", err->message); + g_error_free (err); + } + + 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); + + g_clear_pointer (&priv->banner, g_variant_unref); + g_clear_pointer (&priv->tundev, g_variant_unref); + g_clear_pointer (&priv->gateway, g_variant_unref); + g_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: + schedule_quit_timer (plugin); + 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)); + + /* virtual methods */ + object_class->set_property = set_property; + object_class->get_property = get_property; + object_class->dispose = dispose; + object_class->finalize = finalize; + + plugin_class->state_changed = state_changed; + + /* properties */ + + /** + * NMVpnServicePlugin:service-name: + * + * The D-Bus service name of this plugin. + * + * Since: 1.2 + */ + g_object_class_install_property + (object_class, 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:state: + * + * The state of the plugin. + * + * Since: 1.2 + */ + g_object_class_install_property + (object_class, 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)); + + /* signals */ + 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; +} diff --git a/libnm/nm-vpn-service-plugin.h b/libnm/nm-vpn-service-plugin.h new file mode 100644 index 0000000000..81ba40457f --- /dev/null +++ b/libnm/nm-vpn-service-plugin.h @@ -0,0 +1,164 @@ +/* -*- 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. + * Copyright 2007 - 2015 Red Hat, Inc. + */ + +#ifndef __NM_VPN_SERVICE_PLUGIN_H__ +#define __NM_VPN_SERVICE_PLUGIN_H__ + +#include <gio/gio.h> +#include <nm-vpn-dbus-interface.h> +#include <nm-connection.h> + +G_BEGIN_DECLS + +#define NM_TYPE_VPN_SERVICE_PLUGIN (nm_vpn_service_plugin_get_type ()) +#define NM_VPN_SERVICE_PLUGIN(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_VPN_SERVICE_PLUGIN, NMVpnServicePlugin)) +#define NM_VPN_SERVICE_PLUGIN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_VPN_SERVICE_PLUGIN, NMVpnServicePluginClass)) +#define NM_IS_VPN_SERVICE_PLUGIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_VPN_SERVICE_PLUGIN)) +#define NM_IS_VPN_SERVICE_PLUGIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_VPN_SERVICE_PLUGIN)) +#define NM_VPN_SERVICE_PLUGIN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_VPN_SERVICE_PLUGIN, NMVpnServicePluginClass)) + +#define NM_VPN_SERVICE_PLUGIN_DBUS_SERVICE_NAME "service-name" +#define NM_VPN_SERVICE_PLUGIN_STATE "state" + +typedef struct { + NM_AVAILABLE_IN_1_2 + GObject parent; +} NMVpnServicePlugin NM_AVAILABLE_IN_1_2; + +typedef struct { + NM_AVAILABLE_IN_1_2 + GObjectClass parent; + + /* Signals */ + NM_AVAILABLE_IN_1_2 + void (*state_changed) (NMVpnServicePlugin *plugin, + NMVpnServiceState state); + + NM_AVAILABLE_IN_1_2 + void (*ip4_config) (NMVpnServicePlugin *plugin, + GVariant *ip4_config); + + NM_AVAILABLE_IN_1_2 + void (*login_banner) (NMVpnServicePlugin *plugin, + const char *banner); + + NM_AVAILABLE_IN_1_2 + void (*failure) (NMVpnServicePlugin *plugin, + NMVpnPluginFailure reason); + + NM_AVAILABLE_IN_1_2 + void (*quit) (NMVpnServicePlugin *plugin); + + NM_AVAILABLE_IN_1_2 + void (*config) (NMVpnServicePlugin *plugin, + GVariant *config); + + NM_AVAILABLE_IN_1_2 + void (*ip6_config) (NMVpnServicePlugin *plugin, + GVariant *config); + + /* virtual methods */ + NM_AVAILABLE_IN_1_2 + gboolean (*connect) (NMVpnServicePlugin *plugin, + NMConnection *connection, + GError **err); + + NM_AVAILABLE_IN_1_2 + gboolean (*need_secrets) (NMVpnServicePlugin *plugin, + NMConnection *connection, + const char **setting_name, + GError **error); + + NM_AVAILABLE_IN_1_2 + gboolean (*disconnect) (NMVpnServicePlugin *plugin, + GError **err); + + NM_AVAILABLE_IN_1_2 + gboolean (*new_secrets) (NMVpnServicePlugin *plugin, + NMConnection *connection, + GError **error); + + NM_AVAILABLE_IN_1_2 + gboolean (*connect_interactive) (NMVpnServicePlugin *plugin, + NMConnection *connection, + GVariant *details, + GError **error); + + /*< private >*/ + NM_AVAILABLE_IN_1_2 + gpointer padding[8]; +} NMVpnServicePluginClass NM_AVAILABLE_IN_1_2; + +NM_AVAILABLE_IN_1_2 +GType nm_vpn_service_plugin_get_type (void); + +NM_AVAILABLE_IN_1_2 +GDBusConnection *nm_vpn_service_plugin_get_connection (NMVpnServicePlugin *plugin); +NM_AVAILABLE_IN_1_2 +NMVpnServiceState nm_vpn_service_plugin_get_state (NMVpnServicePlugin *plugin); +NM_AVAILABLE_IN_1_2 +void nm_vpn_service_plugin_set_state (NMVpnServicePlugin *plugin, + NMVpnServiceState state); + +NM_AVAILABLE_IN_1_2 +void nm_vpn_service_plugin_secrets_required (NMVpnServicePlugin *plugin, + const char *message, + const char **hints); + +NM_AVAILABLE_IN_1_2 +void nm_vpn_service_plugin_set_login_banner (NMVpnServicePlugin *plugin, + const char *banner); + +NM_AVAILABLE_IN_1_2 +void nm_vpn_service_plugin_failure (NMVpnServicePlugin *plugin, + NMVpnPluginFailure reason); + +NM_AVAILABLE_IN_1_2 +void nm_vpn_service_plugin_set_config (NMVpnServicePlugin *plugin, + GVariant *config); + +NM_AVAILABLE_IN_1_2 +void nm_vpn_service_plugin_set_ip4_config (NMVpnServicePlugin *plugin, + GVariant *ip4_config); + +NM_AVAILABLE_IN_1_2 +void nm_vpn_service_plugin_set_ip6_config (NMVpnServicePlugin *plugin, + GVariant *ip6_config); + +NM_AVAILABLE_IN_1_2 +gboolean nm_vpn_service_plugin_disconnect (NMVpnServicePlugin *plugin, + GError **err); + +/* Utility functions */ + +NM_AVAILABLE_IN_1_2 +gboolean nm_vpn_service_plugin_read_vpn_details (int fd, + GHashTable **out_data, + GHashTable **out_secrets); + +NM_AVAILABLE_IN_1_2 +gboolean nm_vpn_service_plugin_get_secret_flags (GHashTable *data, + const char *secret_name, + NMSettingSecretFlags *out_flags); + +G_END_DECLS + +#endif /* __NM_VPN_SERVICE_PLUGIN_H__ */ diff --git a/po/POTFILES.in b/po/POTFILES.in index 6ca4a33ff6..8bffd707a9 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -77,6 +77,8 @@ libnm-core/nm-setting-wireless-security.c libnm-core/nm-setting-wireless.c libnm-core/nm-setting.c libnm-core/nm-utils.c +libnm-core/nm-vpn-editor-plugin.c +libnm-core/nm-vpn-plugin-info.c libnm-glib/nm-device.c libnm-glib/nm-remote-connection.c libnm-util/crypto.c @@ -127,6 +129,7 @@ libnm/nm-object.c libnm/nm-remote-connection.c libnm/nm-remote-settings.c libnm/nm-vpn-plugin-old.c +libnm/nm-vpn-service-plugin.c policy/org.freedesktop.NetworkManager.policy.in.in src/NetworkManagerUtils.c src/main.c diff --git a/src/vpn-manager/nm-vpn-manager.c b/src/vpn-manager/nm-vpn-manager.c index 2a6f48b79c..8dc46c41bf 100644 --- a/src/vpn-manager/nm-vpn-manager.c +++ b/src/vpn-manager/nm-vpn-manager.c @@ -29,44 +29,38 @@ #include "nm-vpn-connection.h" #include "nm-setting-vpn.h" #include "nm-vpn-dbus-interface.h" +#include "nm-core-internal.h" #include "nm-enum-types.h" #include "nm-logging.h" - -#define VPN_NAME_FILES_DIR NMCONFDIR "/VPN" +#include "gsystem-local-alloc.h" G_DEFINE_TYPE (NMVpnManager, nm_vpn_manager, G_TYPE_OBJECT) #define NM_VPN_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_VPN_MANAGER, NMVpnManagerPrivate)) typedef struct { - GHashTable *services; - GFileMonitor *monitor; - guint monitor_id; + GSList *services; + GFileMonitor *monitor_etc; + GFileMonitor *monitor_lib; + guint monitor_id_etc; + guint monitor_id_lib; } NMVpnManagerPrivate; - static NMVpnService * -get_service_by_namefile (NMVpnManager *self, const char *namefile) +_plugin_info_get_service (NMVpnPluginInfo *plugin_info) { - NMVpnManagerPrivate *priv = NM_VPN_MANAGER_GET_PRIVATE (self); - GHashTableIter iter; - gpointer data; - - g_return_val_if_fail (namefile, NULL); - g_return_val_if_fail (g_path_is_absolute (namefile), NULL); - - g_hash_table_iter_init (&iter, priv->services); - while (g_hash_table_iter_next (&iter, NULL, &data)) { - NMVpnService *candidate = NM_VPN_SERVICE (data); - const char *service_namefile; - - service_namefile = nm_vpn_service_get_name_file (candidate); - if (!strcmp (namefile, service_namefile)) - return candidate; - } + if (plugin_info) + return NM_VPN_SERVICE (g_object_get_data (G_OBJECT (plugin_info), "service-instance")); return NULL; } +static void +_plugin_info_set_service (NMVpnPluginInfo *plugin_info, NMVpnService *service) +{ + g_object_set_data_full (G_OBJECT (plugin_info), "service-instance", service, + (GDestroyNotify) g_object_unref); +} + gboolean nm_vpn_manager_activate_connection (NMVpnManager *manager, NMVpnConnection *vpn, @@ -75,6 +69,7 @@ nm_vpn_manager_activate_connection (NMVpnManager *manager, NMConnection *connection; NMSettingVpn *s_vpn; NMVpnService *service; + NMVpnPluginInfo *plugin_info; const char *service_name; NMDevice *device; @@ -98,8 +93,8 @@ nm_vpn_manager_activate_connection (NMVpnManager *manager, g_assert (s_vpn); service_name = nm_setting_vpn_get_service_type (s_vpn); - g_assert (service_name); - service = g_hash_table_lookup (NM_VPN_MANAGER_GET_PRIVATE (manager)->services, service_name); + plugin_info = nm_vpn_plugin_info_list_find_by_service (NM_VPN_MANAGER_GET_PRIVATE (manager)->services, service_name); + service = _plugin_info_get_service (plugin_info); if (!service) { g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_CONNECTION_NOT_AVAILABLE, "The VPN service '%s' was not installed.", @@ -119,33 +114,31 @@ nm_vpn_manager_deactivate_connection (NMVpnManager *self, } static void -try_add_service (NMVpnManager *self, const char *namefile) +try_add_service (NMVpnManager *self, NMVpnPluginInfo *plugin_info) { NMVpnManagerPrivate *priv = NM_VPN_MANAGER_GET_PRIVATE (self); NMVpnService *service = NULL; - GHashTableIter iter; GError *error = NULL; - const char *service_name; - - g_return_if_fail (g_path_is_absolute (namefile)); - /* Make sure we don't add dupes */ - g_hash_table_iter_init (&iter, priv->services); - while (g_hash_table_iter_next (&iter, NULL, (gpointer) &service)) { - if (g_strcmp0 (namefile, nm_vpn_service_get_name_file (service)) == 0) - return; - } + /* Make sure we don't add dupes. + * We don't really allow reload of the same file. What we do allow is however to + * delete a file and re-add it. */ + if (nm_vpn_plugin_info_list_find_by_filename (priv->services, + nm_vpn_plugin_info_get_filename (plugin_info))) + return; + if (!nm_vpn_plugin_info_list_add (&priv->services, plugin_info, NULL)) + return; /* New service */ - service = nm_vpn_service_new (namefile, &error); + service = nm_vpn_service_new (plugin_info, &error); if (service) { - service_name = nm_vpn_service_get_dbus_service (service); - g_hash_table_insert (priv->services, (char *) service_name, service); - nm_log_info (LOGD_VPN, "VPN: loaded %s", service_name); + _plugin_info_set_service (plugin_info, service); + nm_log_info (LOGD_VPN, "VPN: loaded %s - %s", + nm_vpn_plugin_info_get_name (plugin_info), + nm_vpn_service_get_dbus_service (service)); } else { - nm_log_warn (LOGD_VPN, "failed to load VPN service file %s: (%d) %s", - namefile, - error ? error->code : -1, + nm_log_warn (LOGD_VPN, "failed to load VPN service file %s: %s", + nm_vpn_plugin_info_get_filename (plugin_info), error && error->message ? error->message : "(unknown)"); g_clear_error (&error); } @@ -160,41 +153,65 @@ vpn_dir_changed (GFileMonitor *monitor, { NMVpnManager *self = NM_VPN_MANAGER (user_data); NMVpnManagerPrivate *priv = NM_VPN_MANAGER_GET_PRIVATE (self); + NMVpnPluginInfo *plugin_info; NMVpnService *service; - char *path; + gs_free char *path = NULL; + GError *error = NULL; path = g_file_get_path (file); - if (!g_str_has_suffix (path, ".name")) { - g_free (path); + if (!nm_vpn_plugin_info_validate_filename (path)) return; - } switch (event_type) { case G_FILE_MONITOR_EVENT_DELETED: - nm_log_dbg (LOGD_VPN, "service file %s deleted", path); + plugin_info = nm_vpn_plugin_info_list_find_by_filename (priv->services, path); + if (!plugin_info) + break; - service = get_service_by_namefile (self, path); + nm_log_dbg (LOGD_VPN, "vpn: service file %s deleted", path); + service = _plugin_info_get_service (plugin_info); if (service) { - const char *service_name = nm_vpn_service_get_dbus_service (service); - /* Stop active VPN connections and destroy the service */ nm_vpn_service_stop_connections (service, FALSE, NM_VPN_CONNECTION_STATE_REASON_SERVICE_STOPPED); - nm_log_info (LOGD_VPN, "VPN: unloaded %s", service_name); - g_hash_table_remove (priv->services, service_name); + nm_log_info (LOGD_VPN, "VPN: unloaded %s", nm_vpn_service_get_dbus_service (service)); + + _plugin_info_set_service (plugin_info, NULL); } + nm_vpn_plugin_info_list_remove (&priv->services, plugin_info); break; case G_FILE_MONITOR_EVENT_CREATED: case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT: - nm_log_dbg (LOGD_VPN, "service file %s created or modified", path); - try_add_service (self, path); + plugin_info = nm_vpn_plugin_info_list_find_by_filename (priv->services, path); + if (plugin_info) { + /* we don't support reloading an existing plugin. You can only remove the file + * and re-add it. By reloading we want to support the use case of installing + * a VPN plugin after NM started. No need to burden ourself with a complete + * reload. */ + break; + } + + if (!_nm_vpn_plugin_info_check_file (path, TRUE, TRUE, 0, + NULL, NULL, &error)) { + nm_log_dbg (LOGD_VPN, "vpn: ignore changed service file %s (%s)", path, error->message); + g_clear_error (&error); + break; + } + plugin_info = nm_vpn_plugin_info_new_from_file (path, &error); + if (!plugin_info) { + nm_log_dbg (LOGD_VPN, "vpn: ignore changed service file %s due to invalid content (%s)", path, error->message); + g_clear_error (&error); + break; + } + + nm_log_dbg (LOGD_VPN, "vpn: service file %s created or modified", path); + try_add_service (self, plugin_info); + g_object_unref (plugin_info); break; default: - nm_log_dbg (LOGD_VPN, "service file %s change event %d", path, event_type); + nm_log_dbg (LOGD_VPN, "vpn: service file %s change event %d", path, event_type); break; } - - g_free (path); } /******************************************************************************/ @@ -206,50 +223,40 @@ nm_vpn_manager_init (NMVpnManager *self) { NMVpnManagerPrivate *priv = NM_VPN_MANAGER_GET_PRIVATE (self); GFile *file; - GDir *dir; - const char *fn; - char *path; - - priv->services = g_hash_table_new_full (g_str_hash, g_str_equal, - NULL, g_object_unref); + GSList *infos, *info; + const char *conf_dir_etc = _nm_vpn_plugin_info_get_default_dir_etc (); + const char *conf_dir_lib = _nm_vpn_plugin_info_get_default_dir_lib (); /* Watch the VPN directory for changes */ - file = g_file_new_for_path (VPN_NAME_FILES_DIR "/"); - priv->monitor = g_file_monitor_directory (file, G_FILE_MONITOR_NONE, NULL, NULL); + file = g_file_new_for_path (conf_dir_lib); + priv->monitor_lib = g_file_monitor_directory (file, G_FILE_MONITOR_NONE, NULL, NULL); g_object_unref (file); - if (priv->monitor) { - priv->monitor_id = g_signal_connect (priv->monitor, "changed", - G_CALLBACK (vpn_dir_changed), self); + if (priv->monitor_lib) { + priv->monitor_id_lib = g_signal_connect (priv->monitor_lib, "changed", + G_CALLBACK (vpn_dir_changed), self); } - /* Load VPN service files */ - dir = g_dir_open (VPN_NAME_FILES_DIR, 0, NULL); - if (dir) { - while ((fn = g_dir_read_name (dir))) { - /* only parse filenames that end with .name */ - if (g_str_has_suffix (fn, ".name")) { - path = g_build_filename (VPN_NAME_FILES_DIR, fn, NULL); - try_add_service (self, path); - g_free (path); - } - } - g_dir_close (dir); + file = g_file_new_for_path (conf_dir_etc); + priv->monitor_etc = g_file_monitor_directory (file, G_FILE_MONITOR_NONE, NULL, NULL); + g_object_unref (file); + if (priv->monitor_etc) { + priv->monitor_id_etc = g_signal_connect (priv->monitor_etc, "changed", + G_CALLBACK (vpn_dir_changed), self); } -} - -static void -stop_all_services (NMVpnManager *self) -{ - NMVpnManagerPrivate *priv = NM_VPN_MANAGER_GET_PRIVATE (self); - GHashTableIter iter; - NMVpnService *service; - g_hash_table_iter_init (&iter, priv->services); - while (g_hash_table_iter_next (&iter, NULL, (gpointer) &service)) { - nm_vpn_service_stop_connections (service, - TRUE, - NM_VPN_CONNECTION_STATE_REASON_SERVICE_STOPPED); - } + /* first read conf_dir_lib. The name files are not really user configuration, but + * plugin configuration. Hence we expect ~newer~ plugins to install their files + * in /usr/lib/NetworkManager. We want to prefer those files. + * In case of no-conflict, the order doesn't matter. */ + infos = _nm_vpn_plugin_info_list_load_dir (conf_dir_lib, TRUE, 0, NULL, NULL); + for (info = infos; info; info = info->next) + try_add_service (self, info->data); + g_slist_free_full (infos, g_object_unref); + + infos = _nm_vpn_plugin_info_list_load_dir (conf_dir_etc, TRUE, 0, NULL, NULL); + for (info = infos; info; info = info->next) + try_add_service (self, info->data); + g_slist_free_full (infos, g_object_unref); } static void @@ -257,17 +264,29 @@ dispose (GObject *object) { NMVpnManagerPrivate *priv = NM_VPN_MANAGER_GET_PRIVATE (object); - if (priv->monitor) { - if (priv->monitor_id) - g_signal_handler_disconnect (priv->monitor, priv->monitor_id); - g_file_monitor_cancel (priv->monitor); - g_clear_object (&priv->monitor); + if (priv->monitor_etc) { + if (priv->monitor_id_etc) + g_signal_handler_disconnect (priv->monitor_etc, priv->monitor_id_etc); + g_file_monitor_cancel (priv->monitor_etc); + g_clear_object (&priv->monitor_etc); } - if (priv->services) { - stop_all_services (NM_VPN_MANAGER (object)); - g_hash_table_destroy (priv->services); - priv->services = NULL; + if (priv->monitor_lib) { + if (priv->monitor_id_lib) + g_signal_handler_disconnect (priv->monitor_lib, priv->monitor_id_lib); + g_file_monitor_cancel (priv->monitor_lib); + g_clear_object (&priv->monitor_lib); + } + + while (priv->services) { + NMVpnPluginInfo *plugin_info = priv->services->data; + NMVpnService *service = _plugin_info_get_service (plugin_info); + + if (service) { + nm_vpn_service_stop_connections (service, TRUE, NM_VPN_CONNECTION_STATE_REASON_SERVICE_STOPPED); + _plugin_info_set_service (plugin_info, NULL); + } + nm_vpn_plugin_info_list_remove (&priv->services, plugin_info); } G_OBJECT_CLASS (nm_vpn_manager_parent_class)->dispose (object); diff --git a/src/vpn-manager/nm-vpn-service.c b/src/vpn-manager/nm-vpn-service.c index 56cb6abe74..4661ab4223 100644 --- a/src/vpn-manager/nm-vpn-service.c +++ b/src/vpn-manager/nm-vpn-service.c @@ -35,10 +35,7 @@ G_DEFINE_TYPE (NMVpnService, nm_vpn_service, G_TYPE_OBJECT) typedef struct { - char *name; - char *dbus_service; - char *program; - char *namefile; + NMVpnPluginInfo *plugin_info; NMVpnConnection *active; GSList *pending; @@ -50,67 +47,50 @@ typedef struct { #define NM_VPN_SERVICE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_VPN_SERVICE, NMVpnServicePrivate)) -#define VPN_CONNECTION_GROUP "VPN Connection" - static gboolean start_pending_vpn (NMVpnService *self, GError **error); static void _name_owner_changed (GObject *object, GParamSpec *pspec, gpointer user_data); NMVpnService * -nm_vpn_service_new (const char *namefile, GError **error) +nm_vpn_service_new (NMVpnPluginInfo *plugin_info, GError **error) { NMVpnService *self; NMVpnServicePrivate *priv; - GKeyFile *kf; - g_return_val_if_fail (namefile != NULL, NULL); - g_return_val_if_fail (g_path_is_absolute (namefile), NULL); + g_return_val_if_fail (NM_IS_VPN_PLUGIN_INFO (plugin_info), NULL); + g_return_val_if_fail (nm_vpn_plugin_info_get_filename (plugin_info), NULL); - kf = g_key_file_new (); - if (!g_key_file_load_from_file (kf, namefile, G_KEY_FILE_NONE, error)) { - g_key_file_free (kf); + if (!nm_vpn_plugin_info_get_program (plugin_info)) { + g_set_error (error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, + "missing \"program\" entry"); return NULL; } self = (NMVpnService *) g_object_new (NM_TYPE_VPN_SERVICE, NULL); priv = NM_VPN_SERVICE_GET_PRIVATE (self); - priv->namefile = g_strdup (namefile); - - priv->dbus_service = g_key_file_get_string (kf, VPN_CONNECTION_GROUP, "service", error); - if (!priv->dbus_service) - goto error; - - priv->program = g_key_file_get_string (kf, VPN_CONNECTION_GROUP, "program", error); - if (!priv->program) - goto error; - - priv->name = g_key_file_get_string (kf, VPN_CONNECTION_GROUP, "name", error); - if (!priv->name) - goto error; + priv->plugin_info = g_object_ref (plugin_info); priv->proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS | G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, NULL, - priv->dbus_service, + nm_vpn_plugin_info_get_service (plugin_info), NM_VPN_DBUS_PLUGIN_PATH, NM_VPN_DBUS_PLUGIN_INTERFACE, NULL, error); - if (!priv->proxy) - goto error; + if (!priv->proxy) { + g_object_unref (self); + return NULL; + } g_signal_connect (priv->proxy, "notify::g-name-owner", G_CALLBACK (_name_owner_changed), self); _name_owner_changed (G_OBJECT (priv->proxy), NULL, self); - g_key_file_free (kf); return self; - -error: - g_object_unref (self); - g_key_file_free (kf); - return NULL; } const char * @@ -118,15 +98,7 @@ nm_vpn_service_get_dbus_service (NMVpnService *service) { g_return_val_if_fail (NM_IS_VPN_SERVICE (service), NULL); - return NM_VPN_SERVICE_GET_PRIVATE (service)->dbus_service; -} - -const char * -nm_vpn_service_get_name_file (NMVpnService *service) -{ - g_return_val_if_fail (NM_IS_VPN_SERVICE (service), NULL); - - return NM_VPN_SERVICE_GET_PRIVATE (service)->namefile; + return nm_vpn_plugin_info_get_service (NM_VPN_SERVICE_GET_PRIVATE (service)->plugin_info); } static void @@ -187,7 +159,7 @@ _daemon_exec_timeout (gpointer data) NMVpnService *self = NM_VPN_SERVICE (data); NMVpnServicePrivate *priv = NM_VPN_SERVICE_GET_PRIVATE (self); - nm_log_warn (LOGD_VPN, "VPN service '%s' start timed out", priv->name); + nm_log_warn (LOGD_VPN, "VPN service '%s' start timed out", nm_vpn_plugin_info_get_name (priv->plugin_info)); priv->start_timeout = 0; nm_vpn_service_stop_connections (self, FALSE, NM_VPN_CONNECTION_STATE_REASON_SERVICE_START_TIMEOUT); return G_SOURCE_REMOVE; @@ -204,17 +176,21 @@ nm_vpn_service_daemon_exec (NMVpnService *service, GError **error) g_return_val_if_fail (NM_IS_VPN_SERVICE (service), FALSE); - vpn_argv[0] = priv->program; + vpn_argv[0] = (char *) nm_vpn_plugin_info_get_program (priv->plugin_info); vpn_argv[1] = NULL; + g_assert (vpn_argv[0]); + success = g_spawn_async (NULL, vpn_argv, NULL, 0, nm_utils_setpgid, NULL, &pid, &spawn_error); if (success) { nm_log_info (LOGD_VPN, "VPN service '%s' started (%s), PID %ld", - priv->name, priv->dbus_service, (long int) pid); + nm_vpn_plugin_info_get_name (priv->plugin_info), + nm_vpn_service_get_dbus_service (service), + (long int) pid); priv->start_timeout = g_timeout_add_seconds (5, _daemon_exec_timeout, service); } else { nm_log_warn (LOGD_VPN, "VPN service '%s': could not launch the VPN service. error: (%d) %s.", - priv->name, + nm_vpn_plugin_info_get_name (priv->plugin_info), spawn_error ? spawn_error->code : -1, spawn_error && spawn_error->message ? spawn_error->message : "(unknown)"); @@ -244,7 +220,7 @@ start_active_vpn (NMVpnService *self, GError **error) return TRUE; } else if (priv->start_timeout == 0) { /* VPN service not running, start it */ - nm_log_info (LOGD_VPN, "Starting VPN service '%s'...", priv->name); + nm_log_info (LOGD_VPN, "Starting VPN service '%s'...", nm_vpn_plugin_info_get_name (priv->plugin_info)); return nm_vpn_service_daemon_exec (self, error); } @@ -324,14 +300,14 @@ _name_owner_changed (GObject *object, if (owner && !priv->service_running) { /* service appeared */ priv->service_running = TRUE; - nm_log_info (LOGD_VPN, "VPN service '%s' appeared; activating connections", priv->name); + nm_log_info (LOGD_VPN, "VPN service '%s' appeared; activating connections", nm_vpn_plugin_info_get_name (priv->plugin_info)); /* Expect success because the VPN service has already appeared */ success = start_active_vpn (service, NULL); g_warn_if_fail (success); } else if (!owner && priv->service_running) { /* service went away */ priv->service_running = FALSE; - nm_log_info (LOGD_VPN, "VPN service '%s' disappeared", priv->name); + nm_log_info (LOGD_VPN, "VPN service '%s' disappeared", nm_vpn_plugin_info_get_name (priv->plugin_info)); nm_vpn_service_stop_connections (service, FALSE, NM_VPN_CONNECTION_STATE_REASON_SERVICE_STOPPED); } @@ -351,10 +327,9 @@ dispose (GObject *object) NMVpnService *self = NM_VPN_SERVICE (object); NMVpnServicePrivate *priv = NM_VPN_SERVICE_GET_PRIVATE (self); - if (priv->start_timeout) { - g_source_remove (priv->start_timeout); - priv->start_timeout = 0; - } + nm_clear_g_source (&priv->start_timeout); + + g_clear_object (&priv->plugin_info); /* VPNService owner is required to stop connections before releasing */ g_assert (priv->active == NULL); @@ -371,19 +346,6 @@ dispose (GObject *object) } static void -finalize (GObject *object) -{ - NMVpnServicePrivate *priv = NM_VPN_SERVICE_GET_PRIVATE (object); - - g_free (priv->name); - g_free (priv->dbus_service); - g_free (priv->program); - g_free (priv->namefile); - - G_OBJECT_CLASS (nm_vpn_service_parent_class)->finalize (object); -} - -static void nm_vpn_service_class_init (NMVpnServiceClass *service_class) { GObjectClass *object_class = G_OBJECT_CLASS (service_class); @@ -392,5 +354,4 @@ nm_vpn_service_class_init (NMVpnServiceClass *service_class) /* virtual methods */ object_class->dispose = dispose; - object_class->finalize = finalize; } diff --git a/src/vpn-manager/nm-vpn-service.h b/src/vpn-manager/nm-vpn-service.h index 6e935950e2..a748d32f39 100644 --- a/src/vpn-manager/nm-vpn-service.h +++ b/src/vpn-manager/nm-vpn-service.h @@ -25,6 +25,7 @@ #include "nm-glib.h" #include "nm-device.h" #include "nm-vpn-connection.h" +#include "nm-vpn-plugin-info.h" #define NM_TYPE_VPN_SERVICE (nm_vpn_service_get_type ()) #define NM_VPN_SERVICE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_VPN_SERVICE, NMVpnService)) @@ -43,14 +44,11 @@ typedef struct { GType nm_vpn_service_get_type (void); -NMVpnService * nm_vpn_service_new (const char *namefile, GError **error); +NMVpnService * nm_vpn_service_new (NMVpnPluginInfo *plugin_info, GError **error); /* Returns the VPN service's D-Bus service name */ const char *nm_vpn_service_get_dbus_service (NMVpnService *service); -/* Returns the path of the VPN service's .name file */ -const char *nm_vpn_service_get_name_file (NMVpnService *service); - gboolean nm_vpn_service_activate (NMVpnService *service, NMVpnConnection *vpn, GError **error); |