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/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..a43ba895a3 100644 --- a/libnm-core/Makefile.libnm-core +++ b/libnm-core/Makefile.libnm-core @@ -42,7 +42,8 @@ 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-plugin-info.h libnm_core_private_headers = \ $(core)/crypto.h \ @@ -93,5 +94,6 @@ 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-plugin-info.c diff --git a/libnm-core/nm-core-internal.h b/libnm-core/nm-core-internal.h index 1287b7a2f0..b1fb61b544 100644 --- a/libnm-core/nm-core-internal.h +++ b/libnm-core/nm-core-internal.h @@ -194,6 +194,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; diff --git a/libnm-core/nm-vpn-plugin-info.c b/libnm-core/nm-vpn-plugin-info.c new file mode 100644 index 0000000000..2842ce2937 --- /dev/null +++ b/libnm-core/nm-vpn-plugin-info.c @@ -0,0 +1,895 @@ +/* -*- 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 +#include +#include +#include +#include + +#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; +} 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_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 +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->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..8e937b57b7 --- /dev/null +++ b/libnm-core/nm-vpn-plugin-info.h @@ -0,0 +1,103 @@ +/* -*- 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 +#include + +#include "nm-utils.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); + +G_END_DECLS + +#endif /* __NM_VPN_PLUGIN_INFO_H__ */ diff --git a/libnm/NetworkManager.h b/libnm/NetworkManager.h index 251a042216..9f10cfe9f4 100644 --- a/libnm/NetworkManager.h +++ b/libnm/NetworkManager.h @@ -82,6 +82,7 @@ #include #include #include +#include #include #undef __NETWORKMANAGER_H_INSIDE__ diff --git a/libnm/libnm.ver b/libnm/libnm.ver index c3a23cb574..885e063203 100644 --- a/libnm/libnm.ver +++ b/libnm/libnm.ver @@ -882,4 +882,20 @@ global: nm_utils_bond_mode_string_to_int; nm_utils_enum_from_str; nm_utils_enum_to_str; + nm_vpn_plugin_info_get_filename; + 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_lookup_property; + nm_vpn_plugin_info_new_from_file; + nm_vpn_plugin_info_new_with_data; + 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; } libnm_1_0_0; diff --git a/po/POTFILES.in b/po/POTFILES.in index 6ca4a33ff6..4b117109e7 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -77,6 +77,7 @@ 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-plugin-info.c libnm-glib/nm-device.c libnm-glib/nm-remote-connection.c libnm-util/crypto.c