about summary refs log tree commit diff
path: root/src/libnm-core-impl
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2025-12-13 13:49:03 +0100
committerMichael Biebl <biebl@debian.org>2025-12-13 13:49:03 +0100
commitb5001976dbff19f014171eed4a482e28ae7ec0f5 (patch)
tree64fbd4ff0dd3c92b6e78fa4172c9f57a2b20e1fa /src/libnm-core-impl
parentdef61b130ab011561ed66a1638a41d59c23b4710 (diff)
parent6de29285e533f4fec22a219013f3687edb6b7399 (diff)
Update upstream source from tag 'upstream/1.54.3'
Update to upstream version '1.54.3'
with Debian dir 29de9f9896550eb82236dfd0adfd428a49c51043
Diffstat (limited to 'src/libnm-core-impl')
-rw-r--r--src/libnm-core-impl/nm-setting-8021x.c97
-rw-r--r--src/libnm-core-impl/nm-setting-connection.c41
-rw-r--r--src/libnm-core-impl/nm-setting-private.h17
-rw-r--r--src/libnm-core-impl/nm-setting.c29
-rw-r--r--src/libnm-core-impl/nm-utils.c255
-rw-r--r--src/libnm-core-impl/nm-vpn-plugin-info.c23
6 files changed, 451 insertions, 11 deletions
diff --git a/src/libnm-core-impl/nm-setting-8021x.c b/src/libnm-core-impl/nm-setting-8021x.c
index 4ea60729..f9333803 100644
--- a/src/libnm-core-impl/nm-setting-8021x.c
+++ b/src/libnm-core-impl/nm-setting-8021x.c
@@ -3134,6 +3134,86 @@ need_secrets(NMSetting *setting, gboolean check_rerequest)
 /*****************************************************************************/
 
 static void
+get_private_files(NMSetting *setting, GPtrArray *files)
+{
+    const struct {
+        const char *property;
+        NMSetting8021xCKScheme (*get_scheme_func)(NMSetting8021x *);
+        const char *(*get_path_func)(NMSetting8021x *);
+    } cert_props[] = {
+        {NM_SETTING_802_1X_CA_CERT,
+         nm_setting_802_1x_get_ca_cert_scheme,
+         nm_setting_802_1x_get_ca_cert_path},
+        {NM_SETTING_802_1X_CLIENT_CERT,
+         nm_setting_802_1x_get_client_cert_scheme,
+         nm_setting_802_1x_get_client_cert_path},
+        {NM_SETTING_802_1X_PRIVATE_KEY,
+         nm_setting_802_1x_get_private_key_scheme,
+         nm_setting_802_1x_get_private_key_path},
+        {NM_SETTING_802_1X_PHASE2_CA_CERT,
+         nm_setting_802_1x_get_phase2_ca_cert_scheme,
+         nm_setting_802_1x_get_phase2_ca_cert_path},
+        {NM_SETTING_802_1X_PHASE2_CLIENT_CERT,
+         nm_setting_802_1x_get_phase2_client_cert_scheme,
+         nm_setting_802_1x_get_phase2_client_cert_path},
+        {NM_SETTING_802_1X_PHASE2_PRIVATE_KEY,
+         nm_setting_802_1x_get_phase2_private_key_scheme,
+         nm_setting_802_1x_get_phase2_private_key_path},
+    };
+    NMSetting8021x *s_8021x = NM_SETTING_802_1X(setting);
+    const char     *path;
+    guint           i;
+
+    if (NM_MORE_ASSERT_ONCE(5)) {
+        GObjectClass        *klass;
+        gs_free GParamSpec **properties = NULL;
+        guint                n_properties;
+        gboolean             found;
+        guint                j;
+
+        /* Check that all the properties in the setting with flag CERT_KEY_FILE
+         * are listed in the table, and vice versa. */
+
+        klass = G_OBJECT_GET_CLASS(setting);
+
+        properties = g_object_class_list_properties(klass, &n_properties);
+        for (i = 0; i < n_properties; i++) {
+            if (!(properties[i]->flags & NM_SETTING_PARAM_CERT_KEY_FILE))
+                continue;
+
+            found = FALSE;
+            for (j = 0; j < G_N_ELEMENTS(cert_props); j++) {
+                if (nm_streq0(properties[i]->name, cert_props[j].property)) {
+                    found = TRUE;
+                    break;
+                }
+            }
+
+            nm_assert(found);
+        }
+
+        for (i = 0; i < G_N_ELEMENTS(cert_props); i++) {
+            GParamSpec *prop;
+
+            prop = g_object_class_find_property(klass, cert_props[i].property);
+            nm_assert(prop);
+            nm_assert(prop->flags & NM_SETTING_PARAM_CERT_KEY_FILE);
+        }
+    }
+
+    for (i = 0; i < G_N_ELEMENTS(cert_props); i++) {
+        if (cert_props[i].get_scheme_func(s_8021x) == NM_SETTING_802_1X_CK_SCHEME_PATH) {
+            path = cert_props[i].get_path_func(s_8021x);
+            if (path) {
+                g_ptr_array_add(files, (gpointer) path);
+            }
+        }
+    }
+}
+
+/*****************************************************************************/
+
+static void
 get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
 {
     NMSetting8021x        *setting = NM_SETTING_802_1X(object);
@@ -3223,8 +3303,9 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
     object_class->set_property = set_property;
     object_class->finalize     = finalize;
 
-    setting_class->verify       = verify;
-    setting_class->need_secrets = need_secrets;
+    setting_class->verify            = verify;
+    setting_class->need_secrets      = need_secrets;
+    setting_class->get_private_files = get_private_files;
 
     /**
      * NMSetting8021x:eap:
@@ -3359,7 +3440,7 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
                                              obj_properties,
                                              NM_SETTING_802_1X_CA_CERT,
                                              PROP_CA_CERT,
-                                             NM_SETTING_PARAM_NONE,
+                                             NM_SETTING_PARAM_CERT_KEY_FILE,
                                              NMSetting8021xPrivate,
                                              ca_cert);
 
@@ -3556,7 +3637,7 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
                                              obj_properties,
                                              NM_SETTING_802_1X_CLIENT_CERT,
                                              PROP_CLIENT_CERT,
-                                             NM_SETTING_PARAM_NONE,
+                                             NM_SETTING_PARAM_CERT_KEY_FILE,
                                              NMSetting8021xPrivate,
                                              client_cert);
 
@@ -3803,7 +3884,7 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
                                              obj_properties,
                                              NM_SETTING_802_1X_PHASE2_CA_CERT,
                                              PROP_PHASE2_CA_CERT,
-                                             NM_SETTING_PARAM_NONE,
+                                             NM_SETTING_PARAM_CERT_KEY_FILE,
                                              NMSetting8021xPrivate,
                                              phase2_ca_cert);
 
@@ -4006,7 +4087,7 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
                                              obj_properties,
                                              NM_SETTING_802_1X_PHASE2_CLIENT_CERT,
                                              PROP_PHASE2_CLIENT_CERT,
-                                             NM_SETTING_PARAM_NONE,
+                                             NM_SETTING_PARAM_CERT_KEY_FILE,
                                              NMSetting8021xPrivate,
                                              phase2_client_cert);
 
@@ -4175,7 +4256,7 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
                                              obj_properties,
                                              NM_SETTING_802_1X_PRIVATE_KEY,
                                              PROP_PRIVATE_KEY,
-                                             NM_SETTING_PARAM_NONE,
+                                             NM_SETTING_PARAM_CERT_KEY_FILE,
                                              NMSetting8021xPrivate,
                                              private_key);
 
@@ -4276,7 +4357,7 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
                                              obj_properties,
                                              NM_SETTING_802_1X_PHASE2_PRIVATE_KEY,
                                              PROP_PHASE2_PRIVATE_KEY,
-                                             NM_SETTING_PARAM_NONE,
+                                             NM_SETTING_PARAM_CERT_KEY_FILE,
                                              NMSetting8021xPrivate,
                                              phase2_private_key);
 
diff --git a/src/libnm-core-impl/nm-setting-connection.c b/src/libnm-core-impl/nm-setting-connection.c
index 4829dcdc..ec3a1fed 100644
--- a/src/libnm-core-impl/nm-setting-connection.c
+++ b/src/libnm-core-impl/nm-setting-connection.c
@@ -431,6 +431,47 @@ nm_setting_connection_permissions_user_allowed_by_uid(NMSettingConnection *setti
     return _permissions_user_allowed(setting, NULL, uid);
 }
 
+guint
+_nm_setting_connection_get_num_permissions_users(NMSettingConnection *setting)
+{
+    NMSettingConnectionPrivate *priv;
+    guint                       i;
+    guint                       count = 0;
+
+    nm_assert(NM_IS_SETTING_CONNECTION(setting));
+    priv = NM_SETTING_CONNECTION_GET_PRIVATE(setting);
+
+    for (i = 0; priv->permissions && i < priv->permissions->len; i++) {
+        const Permission *permission = &nm_g_array_index(priv->permissions, Permission, i);
+
+        if (permission->ptype == PERM_TYPE_USER) {
+            count++;
+        }
+    }
+
+    return count;
+}
+
+const char *
+_nm_setting_connection_get_first_permissions_user(NMSettingConnection *setting)
+{
+    NMSettingConnectionPrivate *priv;
+    guint                       i;
+
+    nm_assert(NM_IS_SETTING_CONNECTION(setting));
+    priv = NM_SETTING_CONNECTION_GET_PRIVATE(setting);
+
+    for (i = 0; priv->permissions && i < priv->permissions->len; i++) {
+        const Permission *permission = &nm_g_array_index(priv->permissions, Permission, i);
+
+        if (permission->ptype == PERM_TYPE_USER) {
+            return permission->item;
+        }
+    }
+
+    return NULL;
+}
+
 /**
  * nm_setting_connection_add_permission:
  * @setting: the #NMSettingConnection
diff --git a/src/libnm-core-impl/nm-setting-private.h b/src/libnm-core-impl/nm-setting-private.h
index 8ee770f4..61f96789 100644
--- a/src/libnm-core-impl/nm-setting-private.h
+++ b/src/libnm-core-impl/nm-setting-private.h
@@ -154,6 +154,11 @@ struct _NMSettingClass {
                                guint /* NMSettingParseFlags */ parse_flags,
                                GError                        **error);
 
+    /* returns a list of certificate/key files referenced in the connection.
+     * When the connection is private, we need to verify that the owner of
+     * the connection has access to them. */
+    void (*get_private_files)(NMSetting *setting, GPtrArray *files);
+
     const struct _NMMetaSettingInfo *setting_info;
 };
 
@@ -334,6 +339,11 @@ struct _NMRange {
  */
 #define NM_SETTING_PARAM_TO_DBUS_IGNORE_FLAGS (1 << (7 + G_PARAM_USER_SHIFT))
 
+/* The property can refer to a certificate or key stored on disk. As such,
+ * special care is needed when accessing the file for private connections.
+ */
+#define NM_SETTING_PARAM_CERT_KEY_FILE (1 << (8 + G_PARAM_USER_SHIFT))
+
 extern const NMSettInfoPropertType nm_sett_info_propert_type_setting_name;
 extern const NMSettInfoPropertType nm_sett_info_propert_type_deprecated_interface_name;
 extern const NMSettInfoPropertType nm_sett_info_propert_type_deprecated_ignore_i;
@@ -859,9 +869,10 @@ _nm_properties_override(GArray *properties_override, const NMSettInfoProperty *p
     {                                                                                              \
         GParamSpec *_param_spec;                                                                   \
                                                                                                    \
-        G_STATIC_ASSERT(!NM_FLAGS_ANY((param_flags),                                               \
-                                      ~(NM_SETTING_PARAM_SECRET | NM_SETTING_PARAM_INFERRABLE      \
-                                        | NM_SETTING_PARAM_FUZZY_IGNORE)));                        \
+        G_STATIC_ASSERT(                                                                           \
+            !NM_FLAGS_ANY((param_flags),                                                           \
+                          ~(NM_SETTING_PARAM_SECRET | NM_SETTING_PARAM_INFERRABLE                  \
+                            | NM_SETTING_PARAM_FUZZY_IGNORE | NM_SETTING_PARAM_CERT_KEY_FILE)));   \
                                                                                                    \
         _param_spec = g_param_spec_boxed("" prop_name "",                                          \
                                          "",                                                       \
diff --git a/src/libnm-core-impl/nm-setting.c b/src/libnm-core-impl/nm-setting.c
index 98424c76..295eabe7 100644
--- a/src/libnm-core-impl/nm-setting.c
+++ b/src/libnm-core-impl/nm-setting.c
@@ -2262,6 +2262,34 @@ init_from_dbus(NMSetting                      *setting,
     return TRUE;
 }
 
+static void
+get_private_files(NMSetting *setting, GPtrArray *files)
+{
+    if (NM_MORE_ASSERTS) {
+        GParamSpec **properties;
+        guint        n_properties;
+        int          i;
+
+        properties = g_object_class_list_properties(G_OBJECT_GET_CLASS(setting), &n_properties);
+        for (i = 0; i < n_properties; i++) {
+            if (properties[i]->flags & NM_SETTING_PARAM_CERT_KEY_FILE) {
+                /* Certificates and keys needs special handling, see setting 802.1X */
+                nm_assert_not_reached();
+            }
+        }
+        g_free(properties);
+    }
+}
+
+void
+_nm_setting_get_private_files(NMSetting *setting, GPtrArray *files)
+{
+    g_return_if_fail(NM_IS_SETTING(setting));
+    g_return_if_fail(files);
+
+    NM_SETTING_GET_CLASS(setting)->get_private_files(setting, files);
+}
+
 /**
  * nm_setting_get_dbus_property_type:
  * @setting: an #NMSetting
@@ -4672,6 +4700,7 @@ nm_setting_class_init(NMSettingClass *setting_class)
     setting_class->enumerate_values          = enumerate_values;
     setting_class->aggregate                 = aggregate;
     setting_class->init_from_dbus            = init_from_dbus;
+    setting_class->get_private_files         = get_private_files;
 
     /**
      * NMSetting:name:
diff --git a/src/libnm-core-impl/nm-utils.c b/src/libnm-core-impl/nm-utils.c
index 6d5df98c..9a78e947 100644
--- a/src/libnm-core-impl/nm-utils.c
+++ b/src/libnm-core-impl/nm-utils.c
@@ -17,6 +17,7 @@
 #include <linux/pkt_sched.h>
 #include <linux/if_infiniband.h>
 
+#include "libnm-glib-aux/nm-io-utils.h"
 #include "libnm-glib-aux/nm-uuid.h"
 #include "libnm-glib-aux/nm-json-aux.h"
 #include "libnm-glib-aux/nm-str-buf.h"
@@ -6195,3 +6196,257 @@ nm_utils_ensure_gtypes(void)
     for (meta_type = 0; meta_type < _NM_META_SETTING_TYPE_NUM; meta_type++)
         nm_meta_setting_infos[meta_type].get_setting_gtype();
 }
+
+/*****************************************************************************/
+
+typedef struct {
+    GPid       pid;
+    GSource   *child_watch_source;
+    GMainLoop *loop;
+    GError    *error;
+
+    int child_stdout;
+    int child_stderr;
+
+    GSource *output_source;
+    GSource *error_source;
+
+    NMStrBuf output_buffer;
+    NMStrBuf error_buffer;
+} HelperInfo;
+
+static void
+helper_complete(HelperInfo *info, GError *error_take)
+{
+    if (error_take) {
+        if (!info->error)
+            info->error = error_take;
+        else
+            g_error_free(error_take);
+    }
+
+    if (info->output_source || info->error_source || info->pid != -1) {
+        /* Wait that the pipe is closed and process has terminated */
+        return;
+    }
+
+    if (info->error && info->error_buffer.len > 0) {
+        /* Prefer the message from stderr as it's more informative */
+        g_error_free(info->error);
+        info->error = g_error_new(NM_CONNECTION_ERROR,
+                                  NM_CONNECTION_ERROR_FAILED,
+                                  "%s",
+                                  nm_str_buf_get_str(&info->error_buffer));
+    }
+
+    g_main_loop_quit(info->loop);
+}
+
+static gboolean
+helper_have_err_data(int fd, GIOCondition condition, gpointer user_data)
+{
+    HelperInfo *info = user_data;
+    gssize      n_read;
+    GError     *error = NULL;
+
+    n_read = nm_utils_fd_read(fd, &info->error_buffer);
+
+    if (n_read > 0)
+        return G_SOURCE_CONTINUE;
+
+    nm_clear_g_source_inst(&info->error_source);
+    nm_clear_fd(&info->child_stderr);
+
+    if (n_read < 0) {
+        error = g_error_new(NM_UTILS_ERROR,
+                            NM_UTILS_ERROR_UNKNOWN,
+                            "read from process returned %d (%s)",
+                            (int) -n_read,
+                            nm_strerror_native((int) -n_read));
+    }
+
+    helper_complete(info, error);
+    return G_SOURCE_CONTINUE;
+}
+
+static gboolean
+helper_have_data(int fd, GIOCondition condition, gpointer user_data)
+{
+    HelperInfo *info = user_data;
+    gssize      n_read;
+    GError     *error = NULL;
+
+    n_read = nm_utils_fd_read(fd, &info->output_buffer);
+
+    if (n_read > 0)
+        return G_SOURCE_CONTINUE;
+
+    nm_clear_g_source_inst(&info->output_source);
+    nm_clear_fd(&info->child_stdout);
+
+    if (n_read < 0) {
+        error = g_error_new(NM_UTILS_ERROR,
+                            NM_UTILS_ERROR_UNKNOWN,
+                            "read from process returned %d (%s)",
+                            (int) -n_read,
+                            nm_strerror_native((int) -n_read));
+    }
+
+    helper_complete(info, error);
+    return G_SOURCE_CONTINUE;
+}
+
+static void
+helper_child_terminated(GPid pid, int status, gpointer user_data)
+{
+    HelperInfo   *info        = user_data;
+    gs_free char *status_desc = NULL;
+    GError       *error       = NULL;
+
+    info->pid = -1;
+    nm_clear_g_source_inst(&info->child_watch_source);
+
+    if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
+        if (!status_desc)
+            status_desc = nm_utils_get_process_exit_status_desc(status);
+        error =
+            g_error_new(NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, "helper process %s", status_desc);
+    }
+
+    helper_complete(info, error);
+}
+
+#define RUN_CERT_DIR NMRUNDIR "/cert"
+
+/**
+ * nm_utils_copy_cert_as_user:
+ * @filename: the file name of the certificate or key to copy
+ * @user: the user to impersonate when reading the file
+ * @error: (nullable): return location for a #GError, or %NULL
+ *
+ * Reads @filename on behalf of user @user and writes the
+ * content to a new file in /run/NetworkManager/cert/.
+ * The new file has permission 600 and is owned by root.
+ *
+ * This function is useful for VPN plugins that run as root and need
+ * to verify that the user owning the connection (the one listed in the
+ * connection.permissions property) can access the file.
+ *
+ * Returns: (transfer full): the name of the new temporary file. Or %NULL
+ *   if an error occurred, including when the given user can't access the
+ *   file.
+ *
+ * Since: 1.56, 1.54.3
+ */
+char *
+nm_utils_copy_cert_as_user(const char *filename, const char *user, GError **error)
+{
+    gs_unref_bytes GBytes *bytes      = NULL;
+    char                   dst_path[] = RUN_CERT_DIR "/XXXXXX";
+    HelperInfo             info       = {
+                          .child_stdout = -1,
+                          .child_stderr = -1,
+    };
+    GMainContext *context;
+    int           fd = -1;
+
+    g_return_val_if_fail(filename, NULL);
+    g_return_val_if_fail(user, NULL);
+    g_return_val_if_fail(!error || !*error, NULL);
+
+    if (geteuid() != 0) {
+        g_set_error_literal(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                            _("This function needs to be called by root"));
+        return NULL;
+    }
+
+    if (!g_spawn_async_with_pipes(
+            "/",
+            (char **)
+                NM_MAKE_STRV(LIBEXECDIR "/nm-libnm-helper", "read-file-as-user", filename, user),
+            (char **) NM_MAKE_STRV(),
+            G_SPAWN_CLOEXEC_PIPES | G_SPAWN_DO_NOT_REAP_CHILD,
+            NULL,
+            NULL,
+            &info.pid,
+            NULL,
+            &info.child_stdout,
+            &info.child_stderr,
+            error)) {
+        return NULL;
+    }
+
+    context   = g_main_context_new();
+    info.loop = g_main_loop_new(context, FALSE);
+
+    /* Watch process */
+    info.child_watch_source = nm_g_child_watch_source_new(info.pid,
+                                                          G_PRIORITY_DEFAULT,
+                                                          helper_child_terminated,
+                                                          &info,
+                                                          NULL);
+    g_source_attach(info.child_watch_source, context);
+
+    /* Watch stdout */
+    info.output_buffer = NM_STR_BUF_INIT(0, FALSE);
+    info.output_source = nm_g_unix_fd_source_new(info.child_stdout,
+                                                 G_IO_IN | G_IO_ERR | G_IO_HUP,
+                                                 G_PRIORITY_DEFAULT,
+                                                 helper_have_data,
+                                                 &info,
+                                                 NULL);
+    g_source_attach(info.output_source, context);
+
+    /* Watch stderr */
+    info.error_buffer = NM_STR_BUF_INIT(0, FALSE);
+    info.error_source = nm_g_unix_fd_source_new(info.child_stderr,
+                                                G_IO_IN | G_IO_ERR | G_IO_HUP,
+                                                G_PRIORITY_DEFAULT,
+                                                helper_have_err_data,
+                                                &info,
+                                                NULL);
+    g_source_attach(info.error_source, context);
+
+    /* Wait termination */
+    g_main_loop_run(info.loop);
+    g_clear_pointer(&info.loop, g_main_loop_unref);
+    g_clear_pointer(&context, g_main_context_unref);
+
+    if (info.error) {
+        nm_str_buf_destroy(&info.output_buffer);
+        nm_str_buf_destroy(&info.error_buffer);
+        g_propagate_error(error, g_steal_pointer(&info.error));
+        return NULL;
+    }
+
+    /* Write the data to a new file */
+
+    bytes = g_bytes_new(nm_str_buf_get_str_unsafe(&info.output_buffer), info.output_buffer.len);
+    nm_str_buf_destroy(&info.output_buffer);
+    nm_str_buf_destroy(&info.error_buffer);
+
+    mkdir(RUN_CERT_DIR, 0600);
+    fd = mkstemp(dst_path);
+    if (fd < 0) {
+        g_set_error_literal(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                            _("Failure creating the temporary file"));
+        return NULL;
+    }
+    nm_close(fd);
+
+    if (!nm_utils_file_set_contents(dst_path,
+                                    g_bytes_get_data(bytes, NULL),
+                                    g_bytes_get_size(bytes),
+                                    0600,
+                                    NULL,
+                                    NULL,
+                                    error)) {
+        return NULL;
+    }
+
+    return g_strdup(dst_path);
+}
diff --git a/src/libnm-core-impl/nm-vpn-plugin-info.c b/src/libnm-core-impl/nm-vpn-plugin-info.c
index 223d8ab3..04b0fb16 100644
--- a/src/libnm-core-impl/nm-vpn-plugin-info.c
+++ b/src/libnm-core-impl/nm-vpn-plugin-info.c
@@ -914,6 +914,29 @@ nm_vpn_plugin_info_supports_multiple(NMVpnPluginInfo *self)
 }
 
 /**
+ * nm_vpn_plugin_info_supports_safe_private_file_access:
+ * @self: plugin info instance
+ *
+ * Returns: %TRUE if the service supports reading files (certificates, keys) of
+ *     private connections in a safe way (i.e. checking user permissions), or
+       if the service doesn't need to read any file from disk.
+ *
+ * Since: 1.56, 1.54.3
+ */
+gboolean
+nm_vpn_plugin_info_supports_safe_private_file_access(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-safe-private-file-access");
+    return _nm_utils_ascii_str_to_bool(s, FALSE);
+}
+
+/**
  * nm_vpn_plugin_info_get_aliases:
  * @self: plugin info instance
  *