mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2026-05-18 19:48:08 +02:00
We use clang-format for automatic formatting of our source files. Since clang-format is actively maintained software, the actual formatting depends on the used version of clang-format. That is unfortunate and painful, but really unavoidable unless clang-format would be strictly bug-compatible. So the version that we must use is from the current Fedora release, which is also tested by our gitlab-ci. Previously, we were using Fedora 34 with clang-tools-extra-12.0.1-1.fc34.x86_64. As Fedora 35 comes along, we need to update our formatting as Fedora 35 comes with version "13.0.0~rc1-1.fc35". An alternative would be to freeze on version 12, but that has different problems (like, it's cumbersome to rebuild clang 12 on Fedora 35 and it would be cumbersome for our developers which are on Fedora 35 to use a clang that they cannot easily install). The (differently painful) solution is to reformat from time to time, as we switch to a new Fedora (and thus clang) version. Usually we would expect that such a reformatting brings minor changes. But this time, the changes are huge. That is mentioned in the release notes [1] as Makes PointerAligment: Right working with AlignConsecutiveDeclarations. (Fixes https://llvm.org/PR27353) [1] https://releases.llvm.org/13.0.0/tools/clang/docs/ReleaseNotes.html#clang-format
518 lines
18 KiB
C
518 lines
18 KiB
C
/* 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;
|
|
}
|