summary refs log tree commit diff
path: root/src/libnm-core-impl/nm-vpn-editor-plugin.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libnm-core-impl/nm-vpn-editor-plugin.c')
-rw-r--r--src/libnm-core-impl/nm-vpn-editor-plugin.c518
1 files changed, 518 insertions, 0 deletions
diff --git a/src/libnm-core-impl/nm-vpn-editor-plugin.c b/src/libnm-core-impl/nm-vpn-editor-plugin.c
new file mode 100644
index 00000000..468ddcea
--- /dev/null
+++ b/src/libnm-core-impl/nm-vpn-editor-plugin.c
@@ -0,0 +1,518 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2008 Novell, Inc.
+ * Copyright (C) 2008 - 2010 Red Hat, Inc.
+ * Copyright (C) 2015 Red Hat, Inc.
+ */
+
+#include "libnm-core-impl/nm-default-libnm-core.h"
+
+#include "nm-vpn-editor-plugin.h"
+
+#include <dlfcn.h>
+#include <gmodule.h>
+
+#include "libnm-core-intern/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)
+{
+    /**
+     * 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));
+}
+
+/*****************************************************************************/
+
+typedef struct {
+    NMVpnPluginInfo *plugin_info;
+} NMVpnEditorPluginPrivate;
+
+static void
+_private_destroy(gpointer data)
+{
+    NMVpnEditorPluginPrivate *priv = data;
+
+    if (priv->plugin_info)
+        g_object_remove_weak_pointer((GObject *) priv->plugin_info,
+                                     (gpointer *) &priv->plugin_info);
+
+    g_slice_free(NMVpnEditorPluginPrivate, priv);
+}
+
+static NMVpnEditorPluginPrivate *
+_private_get(NMVpnEditorPlugin *plugin, gboolean create)
+{
+    static GQuark             quark = 0;
+    NMVpnEditorPluginPrivate *priv;
+
+    nm_assert(NM_IS_VPN_EDITOR_PLUGIN(plugin));
+
+    if (G_UNLIKELY(quark == 0))
+        quark = g_quark_from_string("nm-vpn-editor-plugin-private");
+
+    priv = g_object_get_qdata((GObject *) plugin, quark);
+    if (G_LIKELY(priv))
+        return priv;
+    if (!create)
+        return NULL;
+    priv = g_slice_new0(NMVpnEditorPluginPrivate);
+    g_object_set_qdata_full((GObject *) plugin, quark, priv, _private_destroy);
+    return priv;
+}
+
+#define NM_VPN_EDITOR_PLUGIN_GET_PRIVATE(plugin)     _private_get(plugin, TRUE)
+#define NM_VPN_EDITOR_PLUGIN_TRY_GET_PRIVATE(plugin) _private_get(plugin, FALSE)
+
+/*****************************************************************************/
+
+/**
+ * nm_vpn_editor_plugin_get_plugin_info:
+ * @plugin: the #NMVpnEditorPlugin instance
+ *
+ * Returns: (transfer none): if set, return the #NMVpnPluginInfo instance.
+ *
+ * Since: 1.4
+ */
+NMVpnPluginInfo *
+nm_vpn_editor_plugin_get_plugin_info(NMVpnEditorPlugin *plugin)
+{
+    NMVpnEditorPluginPrivate *priv;
+
+    g_return_val_if_fail(NM_IS_VPN_EDITOR_PLUGIN(plugin), NULL);
+
+    priv = NM_VPN_EDITOR_PLUGIN_TRY_GET_PRIVATE(plugin);
+    return priv ? priv->plugin_info : NULL;
+}
+
+/**
+ * nm_vpn_editor_plugin_set_plugin_info:
+ * @plugin: the #NMVpnEditorPlugin instance
+ * @plugin_info: (allow-none): a #NMVpnPluginInfo instance or %NULL
+ *
+ * Set or clear the plugin-info instance.
+ * This takes a weak reference on @plugin_info, to avoid circular
+ * reference as the plugin-info might also reference the editor-plugin.
+ *
+ * Since: 1.4
+ */
+void
+nm_vpn_editor_plugin_set_plugin_info(NMVpnEditorPlugin *plugin, NMVpnPluginInfo *plugin_info)
+{
+    NMVpnEditorPluginInterface *interface;
+    NMVpnEditorPluginPrivate *  priv;
+
+    g_return_if_fail(NM_IS_VPN_EDITOR_PLUGIN(plugin));
+
+    if (!plugin_info) {
+        priv = NM_VPN_EDITOR_PLUGIN_TRY_GET_PRIVATE(plugin);
+        if (!priv)
+            return;
+    } else {
+        g_return_if_fail(NM_IS_VPN_PLUGIN_INFO(plugin_info));
+        priv = NM_VPN_EDITOR_PLUGIN_GET_PRIVATE(plugin);
+    }
+
+    if (priv->plugin_info == plugin_info)
+        return;
+    if (priv->plugin_info)
+        g_object_remove_weak_pointer((GObject *) priv->plugin_info,
+                                     (gpointer *) &priv->plugin_info);
+    priv->plugin_info = plugin_info;
+    if (priv->plugin_info)
+        g_object_add_weak_pointer((GObject *) priv->plugin_info, (gpointer *) &priv->plugin_info);
+
+    if (plugin_info) {
+        interface = NM_VPN_EDITOR_PLUGIN_GET_INTERFACE(plugin);
+        if (interface->notify_plugin_info_set)
+            interface->notify_plugin_info_set(plugin, plugin_info);
+    }
+}
+
+/*****************************************************************************/
+
+/**
+ * nm_vpn_editor_plugin_get_vt:
+ * @plugin: the #NMVpnEditorPlugin
+ * @vt: (out): buffer to be filled with the VT table of the plugin
+ * @vt_size: the size of the buffer. Can be 0 to only query the
+ *   size of plugin's VT.
+ *
+ * Returns an opaque VT function table for the plugin to extend
+ * functionality. The actual meaning of NMVpnEditorPluginVT is not
+ * defined in public API of libnm, instead it must be agreed by
+ * both the plugin and the caller. See the header-only file
+ * 'nm-vpn-editor-plugin-call.h' which defines the meaning.
+ *
+ * Returns: the actual size of the @plugin's virtual function table.
+ *
+ * Since: 1.4
+ **/
+gsize
+nm_vpn_editor_plugin_get_vt(NMVpnEditorPlugin *plugin, NMVpnEditorPluginVT *vt, gsize vt_size)
+{
+    const NMVpnEditorPluginVT * p_vt      = NULL;
+    gsize                       p_vt_size = 0;
+    NMVpnEditorPluginInterface *interface;
+
+    g_return_val_if_fail(NM_IS_VPN_EDITOR_PLUGIN(plugin), 0);
+
+    if (vt_size) {
+        g_return_val_if_fail(vt, 0);
+        memset(vt, 0, vt_size);
+    }
+
+    interface = NM_VPN_EDITOR_PLUGIN_GET_INTERFACE(plugin);
+    if (interface->get_vt) {
+        p_vt = interface->get_vt(plugin, &p_vt_size);
+        if (!p_vt)
+            p_vt_size = 0;
+        g_return_val_if_fail(p_vt_size, 0);
+        memcpy(vt, p_vt, MIN(vt_size, p_vt_size));
+    }
+
+    return p_vt_size;
+}
+
+/*****************************************************************************/
+
+static NMVpnEditorPlugin *
+_nm_vpn_editor_plugin_load(const char *              plugin_name,
+                           gboolean                  do_file_checks,
+                           const char *              check_service,
+                           int                       check_owner,
+                           NMUtilsCheckFilePredicate check_file,
+                           gpointer                  user_data,
+                           GError **                 error)
+{
+    void *                   dl_module = NULL;
+    gboolean                 loaded_before;
+    NMVpnEditorPluginFactory factory                        = NULL;
+    gs_unref_object NMVpnEditorPlugin *editor_plugin        = NULL;
+    gs_free char *                     plugin_filename_free = NULL;
+    const char *                       plugin_filename;
+    gs_free_error GError *factory_error = NULL;
+    gs_free char *        plug_name     = NULL;
+    gs_free char *        plug_service  = NULL;
+
+    g_return_val_if_fail(plugin_name && *plugin_name, NULL);
+
+    /* if @do_file_checks is FALSE, we pass plugin_name directly to
+     * g_module_open().
+     *
+     * Otherwise, we allow for library names without path component.
+     * In which case, we prepend the plugin directory and form an
+     * absolute path. In that case, we perform checks on the file.
+     *
+     * One exception is that we don't allow for the "la" suffix. The
+     * reason is that g_module_open() interprets files with this extension
+     * special and we don't want that. */
+    plugin_filename = plugin_name;
+    if (do_file_checks) {
+        if (!strchr(plugin_name, '/') && !g_str_has_suffix(plugin_name, ".la")) {
+            plugin_filename_free = g_module_build_path(NMVPNDIR, plugin_name);
+            plugin_filename      = plugin_filename_free;
+        }
+    }
+
+    dl_module = dlopen(plugin_filename, RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD);
+    if (!dl_module && do_file_checks) {
+        /* If the module is already loaded, we skip the file checks.
+         *
+         * _nm_utils_check_module_file() fails with ENOENT if the plugin file
+         * does not exist. That is relevant, because nm-applet checks for that. */
+        if (!_nm_utils_check_module_file(plugin_filename,
+                                         check_owner,
+                                         check_file,
+                                         user_data,
+                                         error))
+            return NULL;
+    }
+
+    if (dl_module) {
+        loaded_before = TRUE;
+    } else {
+        loaded_before = FALSE;
+        dl_module     = dlopen(plugin_filename, RTLD_LAZY | RTLD_LOCAL);
+    }
+
+    if (!dl_module) {
+        g_set_error(error,
+                    NM_VPN_PLUGIN_ERROR,
+                    NM_VPN_PLUGIN_ERROR_FAILED,
+                    _("cannot load plugin \"%s\": %s"),
+                    plugin_name,
+                    dlerror() ?: "unknown reason");
+        return NULL;
+    }
+
+    factory = dlsym(dl_module, "nm_vpn_editor_plugin_factory");
+    if (!factory) {
+        g_set_error(error,
+                    NM_VPN_PLUGIN_ERROR,
+                    NM_VPN_PLUGIN_ERROR_FAILED,
+                    _("failed to load nm_vpn_editor_plugin_factory() from %s (%s)"),
+                    plugin_name,
+                    dlerror());
+        dlclose(dl_module);
+        return NULL;
+    }
+
+    editor_plugin = factory(&factory_error);
+
+    if (loaded_before) {
+        /* we want to leak the library, because the factory will register glib
+         * types, which cannot be unregistered.
+         *
+         * However, if the library was already loaded before, we want to return
+         * our part of the reference count. */
+        dlclose(dl_module);
+    }
+
+    if (!editor_plugin) {
+        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_name);
+        }
+        return NULL;
+    }
+
+    g_return_val_if_fail(G_IS_OBJECT(editor_plugin), 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 (!plug_name || !*plug_name) {
+        g_set_error(error,
+                    NM_VPN_PLUGIN_ERROR,
+                    NM_VPN_PLUGIN_ERROR_FAILED,
+                    _("cannot load VPN plugin in '%s': missing plugin name"),
+                    plugin_name);
+        return NULL;
+    }
+    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"),
+                    plugin_name);
+        return NULL;
+    }
+
+    return g_steal_pointer(&editor_plugin);
+}
+
+/**
+ * nm_vpn_editor_plugin_load_from_file:
+ * @plugin_name: The path or name of the shared library to load.
+ *  The path must either be an absolute filename to an existing file.
+ *  Alternatively, it can be the name (without path) of a library in the
+ *  plugin directory of NetworkManager.
+ * @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: (scope call): 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 library @plugin_name and create a new
+ * #NMVpnEditorPlugin instance via the #NMVpnEditorPluginFactory
+ * function.
+ *
+ * If @plugin_name is not an absolute path name, it assumes the file
+ * is in the plugin directory of NetworkManager. In any case, the call
+ * will do certain checks on the file before passing it to dlopen.
+ * A consequence for that is, that you cannot omit the ".so" suffix
+ * as you could for nm_vpn_editor_plugin_load().
+ *
+ * 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_name,
+                                    const char *              check_service,
+                                    int                       check_owner,
+                                    NMUtilsCheckFilePredicate check_file,
+                                    gpointer                  user_data,
+                                    GError **                 error)
+{
+    return _nm_vpn_editor_plugin_load(plugin_name,
+                                      TRUE,
+                                      check_service,
+                                      check_owner,
+                                      check_file,
+                                      user_data,
+                                      error);
+}
+
+/**
+ * nm_vpn_editor_plugin_load:
+ * @plugin_name: The name of the shared library to load.
+ *  This path will be directly passed to dlopen() without
+ *  further checks.
+ * @check_service: if not-null, check that the loaded plugin advertises
+ *  the given service.
+ * @error: on failure the error reason.
+ *
+ * Load the shared library @plugin_name and create a new
+ * #NMVpnEditorPlugin instance via the #NMVpnEditorPluginFactory
+ * function.
+ *
+ * This is similar to nm_vpn_editor_plugin_load_from_file(), but
+ * it does no validation of the plugin name, instead passes it directly
+ * to dlopen(). If you have the full path to a plugin file,
+ * nm_vpn_editor_plugin_load_from_file() is preferred.
+ *
+ * Returns: (transfer full): a new plugin instance or %NULL on error.
+ *
+ * Since: 1.4
+ */
+NMVpnEditorPlugin *
+nm_vpn_editor_plugin_load(const char *plugin_name, const char *check_service, GError **error)
+{
+    return _nm_vpn_editor_plugin_load(plugin_name, FALSE, check_service, -1, NULL, NULL, error);
+}
+
+/*****************************************************************************/
+
+/**
+ * nm_vpn_editor_plugin_get_editor:
+ * @plugin: the #NMVpnEditorPlugin
+ * @connection: the #NMConnection to be edited
+ * @error: on return, an error or %NULL
+ *
+ * Returns: (transfer full): a new #NMVpnEditor or %NULL on error
+ */
+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:
+ * @plugin: the #NMVpnEditorPlugin
+ * @path: full path to the file to attempt to read into a new #NMConnection
+ * @error: on return, an error or %NULL
+ *
+ * Returns: (transfer full): a new #NMConnection imported from @path, or %NULL
+ * on error or if the file at @path was not recognized by this plugin
+ */
+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);
+    }
+
+    g_set_error(error,
+                NM_VPN_PLUGIN_ERROR,
+                NM_VPN_PLUGIN_ERROR_FAILED,
+                _("the plugin does not support import capability"));
+    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);
+    }
+
+    g_set_error(error,
+                NM_VPN_PLUGIN_ERROR,
+                NM_VPN_PLUGIN_ERROR_FAILED,
+                _("the plugin does not support export capability"));
+    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;
+}