/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * Copyright (C) 2015 Red Hat, Inc. */ #include "nm-default.h" #include "nm-vpn-plugin-info.h" #include #include "nm-errors.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; char * auth_dialog; char ** aliases; 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; /** * NMVpnPluginInfo: */ struct _NMVpnPluginInfo { GObject parent; NMVpnPluginInfoPrivate _priv; }; struct _NMVpnPluginInfoClass { GObjectClass parent; }; #define NM_VPN_PLUGIN_INFO_GET_PRIVATE(self) \ _NM_GET_PRIVATE(self, NMVpnPluginInfo, NM_IS_VPN_PLUGIN_INFO) 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);) /*****************************************************************************/ static NMVpnPluginInfo *_list_find_by_service(GSList *list, const char *name, const char *service); /*****************************************************************************/ /** * 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: the file to check * @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 nm_str_not_empty(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) (element-type NMVpnPluginInfo): 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, NULL); if (!dirname[0]) return 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: (element-type NMVpnPluginInfo) (transfer 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 *const 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; } /** * nm_vpn_plugin_info_new_search_file: * @name: (allow-none): the name to search for. Either @name or @service * must be present. * @service: (allow-none): the service to search for. Either @name or * @service must be present. * * This has the same effect as doing a full nm_vpn_plugin_info_list_load() * followed by a search for the first matching VPN plugin info that has the * given @name and/or @service. * * Returns: (transfer full): a newly created instance of plugin info * or %NULL if no matching value was found. * * Since: 1.4 */ NMVpnPluginInfo * nm_vpn_plugin_info_new_search_file(const char *name, const char *service) { NMVpnPluginInfo *info; GSList * infos; if (!name && !service) g_return_val_if_reached(NULL); infos = nm_vpn_plugin_info_list_load(); info = nm_g_object_ref(_list_find_by_service(infos, name, service)); g_slist_free_full(infos, g_object_unref); return info; } /*****************************************************************************/ 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: (element-type NMVpnPluginInfo): 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 service type. */ 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: (element-type NMVpnPluginInfo): 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) { 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: (element-type NMVpnPluginInfo): list of plugins * @name: name to search * * Returns: (transfer none): 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: (element-type NMVpnPluginInfo): list of plugins * @filename: filename to search * * Returns: (transfer none): 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; } static NMVpnPluginInfo * _list_find_by_service(GSList *list, const char *name, const char *service) { for (; list; list = list->next) { NMVpnPluginInfoPrivate *priv = NM_VPN_PLUGIN_INFO_GET_PRIVATE(list->data); if (name && !nm_streq(name, priv->name)) continue; if (service && !nm_streq(priv->service, service) && (nm_utils_strv_find_first(priv->aliases, -1, service) < 0)) continue; return list->data; } return NULL; } /** * nm_vpn_plugin_info_list_find_by_service: * @list: (element-type NMVpnPluginInfo): list of plugins * @service: service to search. This can be the main service-type * or one of the provided aliases. * * Returns: (transfer none): the first plugin with a matching @service (or %NULL). * * Since: 1.2 */ NMVpnPluginInfo * nm_vpn_plugin_info_list_find_by_service(GSList *list, const char *service) { if (!service) g_return_val_if_reached(NULL); return _list_find_by_service(list, NULL, service); } /* known_names are well known short names for the service-type. They all implicitly * have a prefix "org.freedesktop.NetworkManager." + known_name. */ static const char *known_names[] = { "openvpn", "vpnc", "pptp", "openconnect", "openswan", "libreswan", "strongswan", "ssh", "l2tp", "iodine", "fortisslvpn", }; /** * nm_vpn_plugin_info_list_find_service_type: * @list: (element-type NMVpnPluginInfo): a possibly empty #GSList of #NMVpnPluginInfo instances * @name: a name to lookup the service-type. * * A VPN plugin provides one or several service-types, like org.freedesktop.NetworkManager.libreswan * Certain plugins provide more then one service type, via aliases (org.freedesktop.NetworkManager.openswan). * This function looks up a service-type (or an alias) based on a name. * * Preferably, the name can be a full service-type/alias of an installed * plugin. Otherwise, it can be the name of a VPN plugin (in which case, the * primary, non-aliased service-type is returned). Otherwise, it can be * one of several well known short-names (which is a hard-coded list of * types in libnm). On success, this returns a full qualified service-type * (or an alias). It doesn't say, that such an plugin is actually available, * but it could be retrieved via nm_vpn_plugin_info_list_find_by_service(). * * Returns: (transfer full): the resolved service-type or %NULL on failure. * * Since: 1.4 */ char * nm_vpn_plugin_info_list_find_service_type(GSList *list, const char *name) { NMVpnPluginInfo *info; char * n; if (!name) g_return_val_if_reached(NULL); if (!*name) return NULL; /* First, try to interpret @name as a full service-type (or alias). */ info = _list_find_by_service(list, NULL, name); if (info) return g_strdup(name); /* try to interpret @name as plugin name, in which case we return * the main service-type (not an alias). */ info = _list_find_by_service(list, name, NULL); if (info) return g_strdup(NM_VPN_PLUGIN_INFO_GET_PRIVATE(info)->service); /* check the hard-coded list of short-names. They all have the same * well-known prefix org.freedesktop.NetworkManager and the name. */ if (nm_utils_strv_find_first((char **) known_names, G_N_ELEMENTS(known_names), name) >= 0) return g_strdup_printf("%s.%s", NM_DBUS_INTERFACE, name); /* try, if there exists a plugin with @name under org.freedesktop.NetworkManager. * Allow this to be a valid abbreviation. */ n = g_strdup_printf("%s.%s", NM_DBUS_INTERFACE, name); if (_list_find_by_service(list, NULL, n)) return n; g_free(n); /* currently, VPN plugins have no way to define a short-name for their * alias name, unless the alias name is prefixed by org.freedesktop.NetworkManager. */ return NULL; } static const char * _service_type_get_default_abbreviation(const char *service_type) { if (!g_str_has_prefix(service_type, NM_DBUS_INTERFACE)) return NULL; service_type += NM_STRLEN(NM_DBUS_INTERFACE); if (service_type[0] != '.') return NULL; service_type++; if (!service_type[0]) return NULL; return service_type; } /** * nm_vpn_plugin_info_list_get_service_types: * @list: (element-type NMVpnPluginInfo): a possibly empty #GSList of #NMVpnPluginInfo * @only_existing: only include results that are actually in @list. * Otherwise, the result is extended with a hard-code list or * well-known plugins * @with_abbreviations: if %FALSE, only full service types are returned. * Otherwise, this also includes abbreviated names that can be used * with nm_vpn_plugin_info_list_find_service_type(). * * Returns: (transfer full): a %NULL terminated strv list of strings. * The list itself and the values must be freed with g_strfreev(). * * Since: 1.4 */ char ** nm_vpn_plugin_info_list_get_service_types(GSList * list, gboolean only_existing, gboolean with_abbreviations) { GSList * iter; GPtrArray * l; guint i, j; const char *n; l = g_ptr_array_sized_new(20); for (iter = list; iter; iter = iter->next) { NMVpnPluginInfoPrivate *priv = NM_VPN_PLUGIN_INFO_GET_PRIVATE(iter->data); g_ptr_array_add(l, g_strdup(priv->service)); if (priv->aliases) { for (i = 0; priv->aliases[i]; i++) g_ptr_array_add(l, g_strdup(priv->aliases[i])); } if (with_abbreviations) { g_ptr_array_add(l, g_strdup(priv->name)); n = _service_type_get_default_abbreviation(priv->service); if (n) g_ptr_array_add(l, g_strdup(n)); for (i = 0; priv->aliases && priv->aliases[i]; i++) { n = _service_type_get_default_abbreviation(priv->aliases[i]); if (n) g_ptr_array_add(l, g_strdup(n)); } } } if (!only_existing) { for (i = 0; i < G_N_ELEMENTS(known_names); i++) { g_ptr_array_add(l, g_strdup_printf("%s.%s", NM_DBUS_INTERFACE, known_names[i])); if (with_abbreviations) g_ptr_array_add(l, g_strdup(known_names[i])); } } if (l->len <= 0) { g_ptr_array_free(l, TRUE); return g_new0(char *, 1); } /* sort the result and remove duplicates. */ g_ptr_array_sort(l, nm_strcmp_p); for (i = 1, j = 1; i < l->len; i++) { if (nm_streq(l->pdata[j - 1], l->pdata[i])) g_free(l->pdata[i]); else l->pdata[j++] = l->pdata[i]; } if (j == l->len) g_ptr_array_add(l, NULL); else l->pdata[j] = NULL; return (char **) g_ptr_array_free(l, FALSE); } /*****************************************************************************/ /** * 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.4 */ const char * nm_vpn_plugin_info_get_service(NMVpnPluginInfo *self) { g_return_val_if_fail(NM_IS_VPN_PLUGIN_INFO(self), NULL); return NM_VPN_PLUGIN_INFO_GET_PRIVATE(self)->service; } /** * nm_vpn_plugin_info_get_auth_dialog: * @self: plugin info instance * * Returns: the absolute path to the auth-dialog helper or %NULL. * * Since: 1.4 **/ const char * nm_vpn_plugin_info_get_auth_dialog(NMVpnPluginInfo *self) { NMVpnPluginInfoPrivate *priv; g_return_val_if_fail(NM_IS_VPN_PLUGIN_INFO(self), NULL); priv = NM_VPN_PLUGIN_INFO_GET_PRIVATE(self); if (G_UNLIKELY(priv->auth_dialog == NULL)) { const char *s; s = g_hash_table_lookup( priv->keys, _nm_utils_strstrdictkey_static(NM_VPN_PLUGIN_INFO_KF_GROUP_GNOME, "auth-dialog")); if (!s || !s[0]) priv->auth_dialog = g_strdup(""); else if (g_path_is_absolute(s)) priv->auth_dialog = g_strdup(s); else { /* for relative paths, we take the basename and assume it's in LIBEXECDIR. */ gs_free char *prog_basename = g_path_get_basename(s); priv->auth_dialog = g_build_filename(LIBEXECDIR, prog_basename, NULL); } } return priv->auth_dialog[0] ? priv->auth_dialog : NULL; } /** * nm_vpn_plugin_info_supports_hints: * @self: plugin info instance * * Returns: %TRUE if the supports hints for secret requests, otherwise %FALSE * * Since: 1.4 */ gboolean nm_vpn_plugin_info_supports_hints(NMVpnPluginInfo *self) { const char *s; g_return_val_if_fail(NM_IS_VPN_PLUGIN_INFO(self), FALSE); s = nm_vpn_plugin_info_lookup_property(self, NM_VPN_PLUGIN_INFO_KF_GROUP_GNOME, "supports-hints"); return _nm_utils_ascii_str_to_bool(s, FALSE); } /** * nm_vpn_plugin_info_get_plugin: * @self: plugin info instance * * 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_supports_multiple: * @self: plugin info instance * * Returns: %TRUE if the service supports multiple instances with different bus names, otherwise %FALSE * * Since: 1.2 */ gboolean nm_vpn_plugin_info_supports_multiple(NMVpnPluginInfo *self) { const char *s; g_return_val_if_fail(NM_IS_VPN_PLUGIN_INFO(self), FALSE); s = nm_vpn_plugin_info_lookup_property(self, NM_VPN_PLUGIN_INFO_KF_GROUP_CONNECTION, "supports-multiple-connections"); return _nm_utils_ascii_str_to_bool(s, FALSE); } /** * nm_vpn_plugin_info_get_aliases: * @self: plugin info instance * * Returns: (array zero-terminated=1) (element-type utf8) (transfer none): * the aliases from the name-file. * * Since: 1.4 */ const char *const * nm_vpn_plugin_info_get_aliases(NMVpnPluginInfo *self) { NMVpnPluginInfoPrivate *priv; g_return_val_if_fail(NM_IS_VPN_PLUGIN_INFO(self), NULL); priv = NM_VPN_PLUGIN_INFO_GET_PRIVATE(self); if (priv->aliases) return (const char *const *) priv->aliases; /* For convenience, we always want to return non-NULL, even for empty * aliases. Hack around that, by making a NULL terminated array using * the NULL of priv->aliases. */ return (const char *const *) &priv->aliases; } /** * nm_vpn_plugin_info_lookup_property: * @self: plugin info instance * @group: group name * @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: (transfer none): 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: (transfer none): 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, nm_vpn_plugin_info_get_service(self), getuid(), NULL, NULL, error); if (priv->editor_plugin) nm_vpn_editor_plugin_set_plugin_info(priv->editor_plugin, self); return priv->editor_plugin; } /*****************************************************************************/ 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 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 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->aliases = g_key_file_get_string_list(priv->keyfile, NM_VPN_PLUGIN_INFO_KF_GROUP_CONNECTION, "aliases", NULL, NULL); if (priv->aliases && !priv->aliases[0]) nm_clear_g_free(&priv->aliases); 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 for all our * values. */ 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); } } nm_clear_pointer(&priv->keyfile, g_key_file_unref); return TRUE; } /** * 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 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->auth_dialog); g_strfreev(priv->aliases); g_free(priv->filename); g_hash_table_unref(priv->keys); nm_clear_pointer(&priv->keyfile, g_key_file_unref); 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); object_class->set_property = set_property; object_class->get_property = get_property; object_class->dispose = dispose; object_class->finalize = finalize; /** * 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: * * Initialize 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; }