NetworkManager/src/settings/nm-settings.c
Thomas Haller aade7e5317 settings: refactor handling of storages with meta-data/tombstones
Currently, meta-data has a very narrow use: as tombstones.

Later, we will need to store additional per UUID meta-data. For example,
when a profile becomes unsaved, we may need to remember the original
filename.

Refactor the code for that. This is for the most part just renaming
and slightly different handling of the fields.
2019-07-25 22:02:00 +02:00

3690 lines
129 KiB
C

/* NetworkManager system settings service
*
* Søren Sandmann <sandmann@daimi.au.dk>
* Dan Williams <dcbw@redhat.com>
* Tambet Ingo <tambet@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* (C) Copyright 2007 - 2011 Red Hat, Inc.
* (C) Copyright 2008 Novell, Inc.
*/
#include "nm-default.h"
#include "nm-settings.h"
#include <unistd.h>
#include <sys/stat.h>
#include <gmodule.h>
#include <pwd.h>
#if HAVE_SELINUX
#include <selinux/selinux.h>
#endif
#include "nm-libnm-core-intern/nm-common-macros.h"
#include "nm-glib-aux/nm-keyfile-aux.h"
#include "nm-keyfile-internal.h"
#include "nm-dbus-interface.h"
#include "nm-connection.h"
#include "nm-setting-8021x.h"
#include "nm-setting-bluetooth.h"
#include "nm-setting-cdma.h"
#include "nm-setting-connection.h"
#include "nm-setting-gsm.h"
#include "nm-setting-ip4-config.h"
#include "nm-setting-ip6-config.h"
#include "nm-setting-olpc-mesh.h"
#include "nm-setting-ppp.h"
#include "nm-setting-pppoe.h"
#include "nm-setting-serial.h"
#include "nm-setting-vpn.h"
#include "nm-setting-wired.h"
#include "nm-setting-adsl.h"
#include "nm-setting-wireless.h"
#include "nm-setting-wireless-security.h"
#include "nm-setting-proxy.h"
#include "nm-setting-bond.h"
#include "nm-utils.h"
#include "nm-core-internal.h"
#include "nm-std-aux/c-list-util.h"
#include "nm-glib-aux/nm-c-list.h"
#include "nm-dbus-object.h"
#include "devices/nm-device-ethernet.h"
#include "nm-settings-connection.h"
#include "nm-settings-plugin.h"
#include "nm-dbus-manager.h"
#include "nm-auth-utils.h"
#include "nm-auth-subject.h"
#include "nm-session-monitor.h"
#include "plugins/keyfile/nms-keyfile-plugin.h"
#include "plugins/keyfile/nms-keyfile-storage.h"
#include "nm-agent-manager.h"
#include "nm-config.h"
#include "nm-audit-manager.h"
#include "NetworkManagerUtils.h"
#include "nm-dispatcher.h"
#include "nm-hostname-manager.h"
/*****************************************************************************/
static NM_CACHED_QUARK_FCN ("default-wired-connection", _default_wired_connection_quark)
/*****************************************************************************/
typedef struct _StorageData {
CList sd_lst;
NMSettingsStorage *storage;
NMConnection *connection;
bool prioritize:1;
} StorageData;
static StorageData *
_storage_data_new_stale (NMSettingsStorage *storage,
NMConnection *connection)
{
StorageData *sd;
sd = g_slice_new (StorageData);
sd->storage = g_object_ref (storage);
sd->connection = nm_g_object_ref (connection);
sd->prioritize = FALSE;
return sd;
}
static void
_storage_data_destroy (StorageData *sd)
{
c_list_unlink_stale (&sd->sd_lst);
g_object_unref (sd->storage);
nm_g_object_unref (sd->connection);
g_slice_free (StorageData, sd);
}
static StorageData *
_storage_data_find_in_lst (CList *head,
NMSettingsStorage *storage)
{
StorageData *sd;
nm_assert (head);
nm_assert (NM_IS_SETTINGS_STORAGE (storage));
c_list_for_each_entry (sd, head, sd_lst) {
if (sd->storage == storage)
return sd;
}
return NULL;
}
static void
nm_assert_storage_data_lst (CList *head)
{
#if NM_MORE_ASSERTS > 5
const char *uuid = NULL;
StorageData *sd;
CList *iter;
nm_assert (head);
if (c_list_is_empty (head))
return;
c_list_for_each_entry (sd, head, sd_lst) {
const char *u;
nm_assert (NM_IS_SETTINGS_STORAGE (sd->storage));
nm_assert (!sd->connection || NM_IS_CONNECTION (sd->connection));
u = nm_settings_storage_get_uuid (sd->storage);
if (!uuid) {
uuid = u;
nm_assert (nm_utils_is_uuid (uuid));
} else
nm_assert (nm_streq0 (uuid, u));
}
/* assert that all storages are unique. */
c_list_for_each_entry (sd, head, sd_lst) {
for (iter = sd->sd_lst.next; iter != head; iter = iter->next)
nm_assert (c_list_entry (iter, StorageData, sd_lst)->storage != sd->storage);
}
#endif
}
static gboolean
_storage_data_is_alive (StorageData *sd)
{
/* If the storage tracks a connection, it is considered alive.
*
* Meta-data storages are special: they never track a connection.
* We need to check them specially to know when to drop them. */
return sd->connection
|| nm_settings_storage_is_meta_data_alive (sd->storage);
}
/*****************************************************************************/
typedef struct {
const char *uuid;
NMSettingsConnection *sett_conn;
NMSettingsStorage *storage;
CList sd_lst_head;
CList dirty_sd_lst_head;
CList sce_dirty_lst;
char _uuid_data[];
} SettConnEntry;
static SettConnEntry *
_sett_conn_entry_new (const char *uuid)
{
SettConnEntry *sett_conn_entry;
gsize l_p_1;
nm_assert (nm_utils_is_uuid (uuid));
l_p_1 = strlen (uuid) + 1;
sett_conn_entry = g_malloc (sizeof (SettConnEntry) + l_p_1);
sett_conn_entry->uuid = sett_conn_entry->_uuid_data;
sett_conn_entry->sett_conn = NULL;
sett_conn_entry->storage = NULL;
c_list_init (&sett_conn_entry->sd_lst_head);
c_list_init (&sett_conn_entry->dirty_sd_lst_head);
c_list_init (&sett_conn_entry->sce_dirty_lst);
memcpy (sett_conn_entry->_uuid_data, uuid, l_p_1);
return sett_conn_entry;
}
static void
_sett_conn_entry_free (SettConnEntry *sett_conn_entry)
{
c_list_unlink_stale (&sett_conn_entry->sce_dirty_lst);
nm_c_list_free_all (&sett_conn_entry->sd_lst_head, StorageData, sd_lst, _storage_data_destroy);
nm_c_list_free_all (&sett_conn_entry->dirty_sd_lst_head, StorageData, sd_lst, _storage_data_destroy);
nm_g_object_unref (sett_conn_entry->sett_conn);
nm_g_object_unref (sett_conn_entry->storage);
g_free (sett_conn_entry);
}
static NMSettingsConnection *
_sett_conn_entry_get_conn (SettConnEntry *sett_conn_entry)
{
return sett_conn_entry ? sett_conn_entry->sett_conn : NULL;
}
/*****************************************************************************/
NM_GOBJECT_PROPERTIES_DEFINE (NMSettings,
PROP_UNMANAGED_SPECS,
PROP_HOSTNAME,
PROP_CAN_MODIFY,
PROP_CONNECTIONS,
PROP_STARTUP_COMPLETE,
);
enum {
CONNECTION_ADDED,
CONNECTION_UPDATED,
CONNECTION_REMOVED,
CONNECTION_FLAGS_CHANGED,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
typedef struct {
NMAgentManager *agent_mgr;
NMConfig *config;
NMPlatform *platform;
NMHostnameManager *hostname_manager;
NMSessionMonitor *session_monitor;
CList auth_lst_head;
NMSKeyfilePlugin *keyfile_plugin;
GSList *plugins;
NMKeyFileDB *kf_db_timestamps;
NMKeyFileDB *kf_db_seen_bssids;
GHashTable *sce_idx;
CList sce_dirty_lst_head;
CList connections_lst_head;
NMSettingsConnection **connections_cached_list;
GSList *unmanaged_specs;
GSList *unrecognized_specs;
GHashTable *startup_complete_idx;
NMSettingsConnection *startup_complete_blocked_by;
gulong startup_complete_platform_change_id;
guint startup_complete_timeout_id;
guint connections_len;
guint connections_generation;
guint kf_db_flush_idle_id_timestamps;
guint kf_db_flush_idle_id_seen_bssids;
bool started:1;
} NMSettingsPrivate;
struct _NMSettings {
NMDBusObject parent;
NMSettingsPrivate _priv;
};
struct _NMSettingsClass {
NMDBusObjectClass parent;
};
G_DEFINE_TYPE (NMSettings, nm_settings, NM_TYPE_DBUS_OBJECT);
#define NM_SETTINGS_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMSettings, NM_IS_SETTINGS)
/*****************************************************************************/
/* FIXME: a lot of logging lines are directly connected to a profile. Set the @con_uuid
* argument for structured logging. */
#define _NMLOG_DOMAIN LOGD_SETTINGS
#define _NMLOG(level, ...) __NMLOG_DEFAULT (level, _NMLOG_DOMAIN, "settings", __VA_ARGS__)
/*****************************************************************************/
static const NMDBusInterfaceInfoExtended interface_info_settings;
static const GDBusSignalInfo signal_info_new_connection;
static const GDBusSignalInfo signal_info_connection_removed;
static void default_wired_clear_tag (NMSettings *self,
NMDevice *device,
NMSettingsConnection *sett_conn,
gboolean add_to_no_auto_default);
static void _clear_connections_cached_list (NMSettingsPrivate *priv);
static void _startup_complete_check (NMSettings *self,
gint64 now_us);
/*****************************************************************************/
static void
_emit_connection_added (NMSettings *self,
NMSettingsConnection *sett_conn)
{
g_signal_emit (self, signals[CONNECTION_ADDED], 0, sett_conn);
}
static void
_emit_connection_updated (NMSettings *self,
NMSettingsConnection *sett_conn,
NMSettingsConnectionUpdateReason update_reason)
{
_nm_settings_connection_emit_signal_updated_internal (sett_conn, update_reason);
g_signal_emit (self, signals[CONNECTION_UPDATED], 0, sett_conn, (guint) update_reason);
}
static void
_emit_connection_removed (NMSettings *self,
NMSettingsConnection *sett_conn)
{
g_signal_emit (self, signals[CONNECTION_REMOVED], 0, sett_conn);
}
static void
_emit_connection_flags_changed (NMSettings *self,
NMSettingsConnection *sett_conn)
{
g_signal_emit (self, signals[CONNECTION_FLAGS_CHANGED], 0, sett_conn);
}
/*****************************************************************************/
typedef struct {
NMSettingsConnection *sett_conn;
gint64 start_at;
gint64 timeout;
} StartupCompleteData;
static void
_startup_complete_data_destroy (StartupCompleteData *scd)
{
g_object_unref (scd->sett_conn);
g_slice_free (StartupCompleteData, scd);
}
static gboolean
_startup_complete_check_is_ready (NMPlatform *platform,
NMSettingsConnection *sett_conn)
{
const NMPlatformLink *plink;
const char *ifname;
/* FIXME: instead of just looking for the interface name, it would be better
* to wait for a device that is compatible with the profile. */
ifname = nm_connection_get_interface_name (nm_settings_connection_get_connection (sett_conn));
if (!ifname)
return TRUE;
plink = nm_platform_link_get_by_ifname (platform, ifname);
return plink && plink->initialized;
}
static gboolean
_startup_complete_timeout_cb (gpointer user_data)
{
NMSettings *self = user_data;
NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
priv->startup_complete_timeout_id = 0;
_startup_complete_check (self, 0);
return G_SOURCE_REMOVE;
}
static void
_startup_complete_platform_change_cb (NMPlatform *platform,
int obj_type_i,
int ifindex,
const NMPlatformLink *link,
int change_type_i,
NMSettings *self)
{
const NMPlatformSignalChangeType change_type = change_type_i;
NMSettingsPrivate *priv;
const char *ifname;
if (change_type == NM_PLATFORM_SIGNAL_REMOVED)
return;
if (!link->initialized)
return;
priv = NM_SETTINGS_GET_PRIVATE (self);
ifname = nm_connection_get_interface_name (nm_settings_connection_get_connection (priv->startup_complete_blocked_by));
if ( ifname
&& !nm_streq (ifname, link->name))
return;
nm_assert (priv->startup_complete_timeout_id > 0);
nm_clear_g_source (&priv->startup_complete_timeout_id);
priv->startup_complete_timeout_id = g_idle_add (_startup_complete_timeout_cb, self);
}
static void
_startup_complete_check (NMSettings *self,
gint64 now_us)
{
NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
gint64 next_expiry;
StartupCompleteData *scd;
NMSettingsConnection *next_sett_conn = NULL;
GHashTableIter iter;
if (!priv->started) {
/* before we are started, we don't setup the timers... */
return;
}
if (!priv->startup_complete_idx)
goto ready;
if (!now_us)
now_us = nm_utils_get_monotonic_timestamp_us ();
next_expiry = 0;
g_hash_table_iter_init (&iter, priv->startup_complete_idx);
while (g_hash_table_iter_next (&iter, (gpointer *) &scd, NULL)) {
gint64 expiry;
if (scd->start_at == 0) {
/* once ready, the decision is remembered and there is nothing
* left to check. */
continue;
}
expiry = scd->start_at + scd->timeout;
if (expiry <= now_us) {
scd->start_at = 0;
continue;
}
if (_startup_complete_check_is_ready (priv->platform, scd->sett_conn)) {
scd->start_at = 0;
continue;
}
next_expiry = expiry;
next_sett_conn = scd->sett_conn;
/* we found one timeout for which to wait. that's good enough. */
break;
}
nm_clear_g_source (&priv->startup_complete_timeout_id);
nm_g_object_ref_set (&priv->startup_complete_blocked_by, next_sett_conn);
if (next_expiry > 0) {
nm_assert (priv->startup_complete_blocked_by);
if (priv->startup_complete_platform_change_id == 0) {
priv->startup_complete_platform_change_id = g_signal_connect (priv->platform,
NM_PLATFORM_SIGNAL_LINK_CHANGED,
G_CALLBACK (_startup_complete_platform_change_cb),
self);
}
priv->startup_complete_timeout_id = g_timeout_add (NM_MIN (3600u*1000u, (next_expiry - now_us) / 1000u),
_startup_complete_timeout_cb,
self);
_LOGT ("startup-complete: wait for device \"%s\" due to connection %s (%s)",
nm_connection_get_interface_name (nm_settings_connection_get_connection (priv->startup_complete_blocked_by)),
nm_settings_connection_get_uuid (priv->startup_complete_blocked_by),
nm_settings_connection_get_id (priv->startup_complete_blocked_by));
return;
}
nm_clear_pointer (&priv->startup_complete_idx, g_hash_table_destroy);
nm_clear_g_signal_handler (priv->platform, &priv->startup_complete_platform_change_id);
ready:
_LOGT ("startup-complete: ready, no profiles to wait for");
nm_assert (priv->started);
nm_assert (!priv->startup_complete_blocked_by);
nm_assert (!priv->startup_complete_idx);
nm_assert (priv->startup_complete_timeout_id == 0);
nm_assert (priv->startup_complete_platform_change_id == 0);
_notify (self, PROP_STARTUP_COMPLETE);
}
static void
_startup_complete_notify_connection (NMSettings *self,
NMSettingsConnection *sett_conn,
gboolean forget)
{
NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
gint64 timeout;
gint64 now_us = 0;
nm_assert ( !priv->started
|| priv->startup_complete_idx);
timeout = 0;
if (!forget) {
NMSettingConnection *s_con;
gint32 v;
s_con = nm_connection_get_setting_connection (nm_settings_connection_get_connection (sett_conn));
v = nm_setting_connection_get_wait_device_timeout (s_con);
if (v > 0) {
nm_assert (nm_setting_connection_get_interface_name (s_con));
timeout = ((gint64) v) * 1000;
}
}
if (timeout == 0) {
if ( !priv->startup_complete_idx
|| !g_hash_table_remove (priv->startup_complete_idx, &sett_conn))
return;
} else {
StartupCompleteData *scd;
if (!priv->startup_complete_idx) {
nm_assert (!priv->started);
priv->startup_complete_idx = g_hash_table_new_full (nm_pdirect_hash,
nm_pdirect_equal,
NULL,
(GDestroyNotify) _startup_complete_data_destroy);
scd = NULL;
} else
scd = g_hash_table_lookup (priv->startup_complete_idx, &sett_conn);
if (!scd) {
now_us = nm_utils_get_monotonic_timestamp_us ();
scd = g_slice_new (StartupCompleteData);
*scd = (StartupCompleteData) {
.sett_conn = g_object_ref (sett_conn),
.start_at = now_us,
.timeout = timeout,
};
g_hash_table_add (priv->startup_complete_idx, scd);
} else {
if (scd->start_at == 0) {
/* the entry already is ready and no longer relevant. Ignore it. */
return;
}
scd->timeout = timeout;
}
}
_startup_complete_check (self, now_us);
}
const char *
nm_settings_get_startup_complete_blocked_reason (NMSettings *self)
{
NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
const char *uuid = NULL;
if (priv->started) {
if (!priv->startup_complete_idx)
return NULL;
if (priv->startup_complete_blocked_by)
uuid = nm_settings_connection_get_uuid (priv->startup_complete_blocked_by);
}
return uuid ?: "unknown";
}
/*****************************************************************************/
const GSList *
nm_settings_get_unmanaged_specs (NMSettings *self)
{
NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
return priv->unmanaged_specs;
}
static gboolean
update_specs (NMSettings *self, GSList **specs_ptr,
GSList * (*get_specs_func) (NMSettingsPlugin *))
{
NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
GSList *new = NULL;
GSList *iter;
for (iter = priv->plugins; iter; iter = g_slist_next (iter)) {
GSList *specs;
specs = get_specs_func (iter->data);
while (specs) {
GSList *s = specs;
specs = g_slist_remove_link (specs, s);
if (nm_utils_g_slist_find_str (new, s->data)) {
g_free (s->data);
g_slist_free_1 (s);
continue;
}
s->next = new;
new = s;
}
}
if (nm_utils_g_slist_strlist_cmp (new, *specs_ptr) == 0) {
g_slist_free_full (new, g_free);
return FALSE;
}
g_slist_free_full (*specs_ptr, g_free);
*specs_ptr = new;
return TRUE;
}
static void
_plugin_unmanaged_specs_changed (NMSettingsPlugin *config,
gpointer user_data)
{
NMSettings *self = NM_SETTINGS (user_data);
NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
if (update_specs (self, &priv->unmanaged_specs,
nm_settings_plugin_get_unmanaged_specs))
_notify (self, PROP_UNMANAGED_SPECS);
}
static void
_plugin_unrecognized_specs_changed (NMSettingsPlugin *config,
gpointer user_data)
{
NMSettings *self = NM_SETTINGS (user_data);
NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
update_specs (self, &priv->unrecognized_specs,
nm_settings_plugin_get_unrecognized_specs);
}
/*****************************************************************************/
static void
connection_flags_changed (NMSettingsConnection *sett_conn,
gpointer user_data)
{
_emit_connection_flags_changed (NM_SETTINGS (user_data), sett_conn);
}
/*****************************************************************************/
static SettConnEntry *
_sett_conn_entries_get (NMSettings *self,
const char *uuid)
{
nm_assert (uuid);
return g_hash_table_lookup (NM_SETTINGS_GET_PRIVATE (self)->sce_idx, &uuid);
}
static SettConnEntry *
_sett_conn_entries_create_and_add (NMSettings *self,
const char *uuid)
{
NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
SettConnEntry *sett_conn_entry;
sett_conn_entry = _sett_conn_entry_new (uuid);
if (!g_hash_table_add (priv->sce_idx, sett_conn_entry))
nm_assert_not_reached ();
else if (g_hash_table_size (priv->sce_idx) == 1)
g_object_ref (self);
return sett_conn_entry;
}
static void
_sett_conn_entries_remove_and_destroy (NMSettings *self,
SettConnEntry *sett_conn_entry)
{
NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
if (!g_hash_table_remove (priv->sce_idx, sett_conn_entry))
nm_assert_not_reached ();
else if (g_hash_table_size (priv->sce_idx) == 0)
g_object_unref (self);
}
/*****************************************************************************/
static int
_sett_conn_entry_sds_update_cmp (const CList *ls_a,
const CList *ls_b,
gconstpointer user_data)
{
const GSList *plugins = user_data;
StorageData *sd_a = c_list_entry (ls_a, StorageData, sd_lst);
StorageData *sd_b = c_list_entry (ls_b, StorageData, sd_lst);
/* prioritized entries are sorted first (higher priority). */
NM_CMP_FIELD_UNSAFE (sd_b, sd_a, prioritize);
/* nm_settings_storage_cmp() compares in ascending order. Meaning,
* if the storage has higher priority, it gives a positive number (as one
* would expect).
*
* We want to sort the list in reverse though, with highest priority first. */
return nm_settings_storage_cmp (sd_b->storage, sd_a->storage, plugins);
}
static void
_sett_conn_entry_sds_update (NMSettings *self,
SettConnEntry *sett_conn_entry)
{
StorageData *sd;
StorageData *sd_safe;
StorageData *sd_dirty;
gboolean reprioritize;
nm_assert_storage_data_lst (&sett_conn_entry->sd_lst_head);
nm_assert_storage_data_lst (&sett_conn_entry->dirty_sd_lst_head);
/* we merge the dirty list with the previous list.
*
* The idea is:
*
* - _connection_changed_track() appends events for the same UUID. Meaning:
* if the storage is new, it get appended (having lower priority).
* If it already exist and is an update for an event that we already
* track it, it keeps the list position in @dirty_sd_lst_head unchanged.
*
* - during merge, we want to preserve the previous order (with higher
* priority first in the list).
*/
/* first go through all storages that we track and check whether they
* got an update...*/
reprioritize = FALSE;
c_list_for_each_entry (sd, &sett_conn_entry->dirty_sd_lst_head, sd_lst) {
if (sd->prioritize) {
reprioritize = TRUE;
break;
}
}
nm_assert_storage_data_lst (&sett_conn_entry->sd_lst_head);
c_list_for_each_entry_safe (sd, sd_safe, &sett_conn_entry->sd_lst_head, sd_lst) {
sd_dirty = _storage_data_find_in_lst (&sett_conn_entry->dirty_sd_lst_head, sd->storage);
if (!sd_dirty) {
/* there is no update for this storage (except maybe reprioritize). */
if (reprioritize)
sd->prioritize = FALSE;
continue;
}
nm_g_object_ref_set (&sd->connection, sd_dirty->connection);
sd->prioritize = sd_dirty->prioritize;
_storage_data_destroy (sd_dirty);
}
nm_assert_storage_data_lst (&sett_conn_entry->sd_lst_head);
/* all remaining (so far unseen) dirty entries are appended to the merged list.
* (append means lower priority). */
c_list_splice (&sett_conn_entry->sd_lst_head, &sett_conn_entry->dirty_sd_lst_head);
nm_assert_storage_data_lst (&sett_conn_entry->sd_lst_head);
/* we drop the entries that are no longer "alive" (meaning, they no longer
* indicate a connection and are not a tombstone). */
c_list_for_each_entry_safe (sd, sd_safe, &sett_conn_entry->sd_lst_head, sd_lst) {
if (!_storage_data_is_alive (sd))
_storage_data_destroy (sd);
}
nm_assert_storage_data_lst (&sett_conn_entry->sd_lst_head);
nm_assert (c_list_is_empty (&sett_conn_entry->dirty_sd_lst_head));
/* as last, we sort the entries. Note that this is a stable-sort... */
c_list_sort (&sett_conn_entry->sd_lst_head,
_sett_conn_entry_sds_update_cmp,
NM_SETTINGS_GET_PRIVATE (self)->plugins);
nm_assert_storage_data_lst (&sett_conn_entry->sd_lst_head);
nm_assert (c_list_is_empty (&sett_conn_entry->dirty_sd_lst_head));
}
/*****************************************************************************/
static NMConnection *
_connection_changed_normalize_connection (NMSettingsStorage *storage,
NMConnection *connection,
GVariant *secrets_to_merge,
NMConnection **out_connection_cloned)
{
gs_unref_object NMConnection *connection_cloned = NULL;
gs_free_error GError *error = NULL;
const char *uuid;
nm_assert (NM_IS_SETTINGS_STORAGE (storage));
nm_assert (out_connection_cloned && !*out_connection_cloned);
if (!connection)
return NULL;
nm_assert (NM_IS_CONNECTION (connection));
uuid = nm_settings_storage_get_uuid (storage);
if (secrets_to_merge) {
connection_cloned = nm_simple_connection_new_clone (connection);
connection = connection_cloned;
nm_connection_update_secrets (connection,
NULL,
secrets_to_merge,
NULL);
}
if (!_nm_connection_ensure_normalized (connection,
!!connection_cloned,
uuid,
FALSE,
connection_cloned ? NULL : &connection_cloned,
&error)) {
/* this is most likely a bug in the plugin. It provided a connection that no longer verifies.
* Well, I guess it could also happen when we merge @secrets_to_merge above. In any case
* somewhere is a bug. */
_LOGT ("storage[%s,"NM_SETTINGS_STORAGE_PRINT_FMT"]: plugin provided an invalid connection: %s",
uuid,
NM_SETTINGS_STORAGE_PRINT_ARG (storage),
error->message);
return NULL;
}
if (connection_cloned)
connection = connection_cloned;
*out_connection_cloned = g_steal_pointer (&connection_cloned);
return connection;
}
/*****************************************************************************/
static void
_connection_changed_update (NMSettings *self,
SettConnEntry *sett_conn_entry,
NMConnection *connection,
NMSettingsConnectionIntFlags sett_flags,
NMSettingsConnectionIntFlags sett_mask,
NMSettingsConnectionUpdateReason update_reason)
{
NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
gs_unref_object NMConnection *connection_old = NULL;
NMSettingsStorage *storage = sett_conn_entry->storage;
gs_unref_object NMSettingsConnection *sett_conn = g_object_ref (sett_conn_entry->sett_conn);
const char *path;
gboolean is_new;
nm_assert (!NM_FLAGS_ANY (sett_mask, ~_NM_SETTINGS_CONNECTION_INT_FLAGS_PERSISTENT_MASK));
nm_assert (!NM_FLAGS_ANY (sett_flags, ~sett_mask));
is_new = c_list_is_empty (&sett_conn->_connections_lst);
_LOGT ("update[%s]: %s connection \"%s\" ("NM_SETTINGS_STORAGE_PRINT_FMT")",
nm_settings_storage_get_uuid (storage),
is_new ? "adding" : "updating",
nm_connection_get_id (connection),
NM_SETTINGS_STORAGE_PRINT_ARG (storage));
_nm_settings_connection_set_storage (sett_conn, storage);
_nm_settings_connection_set_connection (sett_conn, connection, &connection_old, update_reason);
if (is_new) {
_nm_settings_connection_register_kf_dbs (sett_conn,
priv->kf_db_timestamps,
priv->kf_db_seen_bssids);
_clear_connections_cached_list (priv);
c_list_link_tail (&priv->connections_lst_head, &sett_conn->_connections_lst);
priv->connections_len++;
priv->connections_generation++;
g_signal_connect (sett_conn, NM_SETTINGS_CONNECTION_FLAGS_CHANGED, G_CALLBACK (connection_flags_changed), self);
}
if (NM_FLAGS_HAS (update_reason, NM_SETTINGS_CONNECTION_UPDATE_REASON_BLOCK_AUTOCONNECT)) {
nm_settings_connection_autoconnect_blocked_reason_set (sett_conn,
NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST,
TRUE);
}
sett_mask |= NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE;
if (nm_settings_connection_check_visibility (sett_conn, priv->session_monitor))
sett_flags |= NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE;
else
nm_assert (!NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE));
sett_mask |= NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED;
if (nm_settings_storage_is_keyfile_run (storage))
sett_flags |= NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED;
else {
nm_assert (!NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED));
/* Profiles that don't reside in /run, are never nm-generated
* and never volatile. */
sett_mask |= ( NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
| NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE);
sett_flags &= ~( NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
| NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE);
}
nm_settings_connection_set_flags_full (sett_conn,
sett_mask,
sett_flags);
if (is_new) {
/* FIXME(shutdown): The NMSettings instance can't be disposed
* while there is any exported connection. Ideally we should
* unexport all connections on NMSettings' disposal, but for now
* leak @self on termination when there are connections alive. */
path = nm_dbus_object_export (NM_DBUS_OBJECT (sett_conn));
} else
path = nm_dbus_object_get_path (NM_DBUS_OBJECT (sett_conn));
if ( is_new
|| connection_old) {
nm_utils_log_connection_diff (nm_settings_connection_get_connection (sett_conn),
connection_old,
LOGL_DEBUG,
LOGD_CORE,
is_new ? "new connection" : "update connection",
"++ ",
path);
}
if (is_new) {
nm_dbus_object_emit_signal (NM_DBUS_OBJECT (self),
&interface_info_settings,
&signal_info_new_connection,
"(o)",
path);
_notify (self, PROP_CONNECTIONS);
_emit_connection_added (self, sett_conn);
} else {
_nm_settings_connection_emit_dbus_signal_updated (sett_conn);
_emit_connection_updated (self, sett_conn, update_reason);
}
if ( !priv->started
|| priv->startup_complete_idx) {
if (nm_settings_has_connection (self, sett_conn))
_startup_complete_notify_connection (self, sett_conn, FALSE);
}
}
static void
_connection_changed_delete (NMSettings *self,
NMSettingsStorage *storage,
NMSettingsConnection *sett_conn,
gboolean allow_add_to_no_auto_default)
{
NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
gs_unref_object NMConnection *connection_for_agents = NULL;
NMDevice *device;
const char *uuid;
nm_assert (NM_IS_SETTINGS_CONNECTION (sett_conn));
nm_assert (c_list_contains (&priv->connections_lst_head, &sett_conn->_connections_lst));
nm_assert (nm_dbus_object_is_exported (NM_DBUS_OBJECT (sett_conn)));
uuid = nm_settings_storage_get_uuid (storage);
_LOGT ("update[%s]: delete connection \"%s\" ("NM_SETTINGS_STORAGE_PRINT_FMT")",
uuid,
nm_settings_connection_get_id (sett_conn),
NM_SETTINGS_STORAGE_PRINT_ARG (storage));
/* When the default wired sett_conn is removed (either deleted or saved to
* a new persistent sett_conn by a plugin), write the MAC address of the
* wired device to the config file and don't create a new default wired
* sett_conn for that device again.
*/
device = nm_settings_connection_default_wired_get_device (sett_conn);
if (device)
default_wired_clear_tag (self, device, sett_conn, allow_add_to_no_auto_default);
g_signal_handlers_disconnect_by_func (sett_conn, G_CALLBACK (connection_flags_changed), self);
_clear_connections_cached_list (priv);
c_list_unlink (&sett_conn->_connections_lst);
priv->connections_len--;
priv->connections_generation++;
/* Tell agents to remove secrets for this connection */
connection_for_agents = nm_simple_connection_new_clone (nm_settings_connection_get_connection (sett_conn));
nm_connection_clear_secrets (connection_for_agents);
nm_agent_manager_delete_secrets (priv->agent_mgr,
nm_dbus_object_get_path (NM_DBUS_OBJECT (self)),
connection_for_agents);
_notify (self, PROP_CONNECTIONS);
_nm_settings_connection_emit_dbus_signal_removed (sett_conn);
nm_dbus_object_emit_signal (NM_DBUS_OBJECT (self),
&interface_info_settings,
&signal_info_connection_removed,
"(o)",
nm_dbus_object_get_path (NM_DBUS_OBJECT (sett_conn)));
nm_dbus_object_unexport (NM_DBUS_OBJECT (sett_conn));
nm_settings_connection_set_flags (sett_conn,
NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE
| NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE,
FALSE);
_emit_connection_removed (self, sett_conn);
_nm_settings_connection_cleanup_after_remove (sett_conn);
nm_key_file_db_remove_key (priv->kf_db_timestamps, uuid);
nm_key_file_db_remove_key (priv->kf_db_seen_bssids, uuid);
if ( !priv->started
|| priv->startup_complete_idx)
_startup_complete_notify_connection (self, sett_conn, TRUE);
}
static void
_connection_changed_process_one (NMSettings *self,
SettConnEntry *sett_conn_entry,
gboolean allow_add_to_no_auto_default,
NMSettingsConnectionIntFlags sett_flags,
NMSettingsConnectionIntFlags sett_mask,
gboolean override_sett_flags,
NMSettingsConnectionUpdateReason update_reason)
{
StorageData *sd_best;
c_list_unlink (&sett_conn_entry->sce_dirty_lst);
_sett_conn_entry_sds_update (self, sett_conn_entry);
sd_best = c_list_first_entry (&sett_conn_entry->sd_lst_head, StorageData, sd_lst);;
if ( !sd_best
|| !sd_best->connection) {
gs_unref_object NMSettingsConnection *sett_conn = NULL;
gs_unref_object NMSettingsStorage *storage = NULL;
if (!sett_conn_entry->sett_conn) {
if (!sd_best) {
_sett_conn_entries_remove_and_destroy (self, sett_conn_entry);
return;
}
if (sett_conn_entry->storage != sd_best->storage) {
_LOGT ("update[%s]: shadow UUID ("NM_SETTINGS_STORAGE_PRINT_FMT")",
sett_conn_entry->uuid,
NM_SETTINGS_STORAGE_PRINT_ARG (sd_best->storage));
}
nm_g_object_ref_set (&sett_conn_entry->storage, sd_best->storage);
return;
}
sett_conn = g_steal_pointer (&sett_conn_entry->sett_conn);
if (sd_best) {
storage = g_object_ref (sd_best->storage);
nm_g_object_ref_set (&sett_conn_entry->storage, storage);
nm_assert_valid_settings_storage (NULL, storage);
} else {
storage = g_object_ref (sett_conn_entry->storage);
_sett_conn_entries_remove_and_destroy (self, sett_conn_entry);
}
_connection_changed_delete (self, storage, sett_conn, allow_add_to_no_auto_default);
return;
}
if (override_sett_flags) {
NMSettingsConnectionIntFlags s_f, s_m;
nm_settings_storage_load_sett_flags (sd_best->storage, &s_f, &s_m);
nm_assert (!NM_FLAGS_ANY (s_f, ~s_m));
sett_mask |= s_m;
sett_flags = (sett_flags & ~s_m) | (s_f & s_m);
}
nm_g_object_ref_set (&sett_conn_entry->storage, sd_best->storage);
if (!sett_conn_entry->sett_conn)
sett_conn_entry->sett_conn = nm_settings_connection_new ();
_connection_changed_update (self,
sett_conn_entry,
sd_best->connection,
sett_flags,
sett_mask,
update_reason);
}
static void
_connection_changed_process_all_dirty (NMSettings *self,
gboolean allow_add_to_no_auto_default,
NMSettingsConnectionIntFlags sett_flags,
NMSettingsConnectionIntFlags sett_mask,
gboolean override_sett_flags,
NMSettingsConnectionUpdateReason update_reason)
{
NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
SettConnEntry *sett_conn_entry;
while ((sett_conn_entry = c_list_first_entry (&priv->sce_dirty_lst_head, SettConnEntry, sce_dirty_lst))) {
_connection_changed_process_one (self,
sett_conn_entry,
allow_add_to_no_auto_default,
sett_flags,
sett_mask,
override_sett_flags,
update_reason);
}
}
static SettConnEntry *
_connection_changed_track (NMSettings *self,
NMSettingsStorage *storage,
NMConnection *connection,
gboolean prioritize)
{
NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
SettConnEntry *sett_conn_entry;
StorageData *sd;
const char *uuid;
nm_assert_valid_settings_storage (NULL, storage);
uuid = nm_settings_storage_get_uuid (storage);
nm_assert (!connection || NM_IS_CONNECTION (connection));
nm_assert (!connection || (_nm_connection_verify (connection, NULL) == NM_SETTING_VERIFY_SUCCESS));
nm_assert (!connection || nm_streq0 (uuid, nm_connection_get_uuid (connection)));
nmtst_connection_assert_unchanging (connection);
sett_conn_entry = _sett_conn_entries_get (self, uuid)
?: _sett_conn_entries_create_and_add (self, uuid);
if (_LOGT_ENABLED ()) {
const char *filename;
const NMSettingsMetaData *meta_data;
filename = nm_settings_storage_get_filename (storage);
if (connection) {
_LOGT ("storage[%s,"NM_SETTINGS_STORAGE_PRINT_FMT"]: change event with connection \"%s\"%s%s%s",
sett_conn_entry->uuid,
NM_SETTINGS_STORAGE_PRINT_ARG (storage),
nm_connection_get_id (connection),
NM_PRINT_FMT_QUOTED (filename, " (file \"", filename, "\")", ""));
} else if ((meta_data = nm_settings_storage_is_meta_data (storage))) {
if (meta_data->is_tombstone) {
_LOGT ("storage[%s,"NM_SETTINGS_STORAGE_PRINT_FMT"]: change event for hiding profile%s%s%s",
sett_conn_entry->uuid,
NM_SETTINGS_STORAGE_PRINT_ARG (storage),
NM_PRINT_FMT_QUOTED (filename, " (file \"", filename, "\")", ""));
} else {
_LOGT ("storage[%s,"NM_SETTINGS_STORAGE_PRINT_FMT"]: change event with meta data for profile%s%s%s",
sett_conn_entry->uuid,
NM_SETTINGS_STORAGE_PRINT_ARG (storage),
NM_PRINT_FMT_QUOTED (filename, " (file \"", filename, "\")", ""));
}
} else {
_LOGT ("storage[%s,"NM_SETTINGS_STORAGE_PRINT_FMT"]: change event for dropping profile%s%s%s",
sett_conn_entry->uuid,
NM_SETTINGS_STORAGE_PRINT_ARG (storage),
NM_PRINT_FMT_QUOTED (filename, " (file \"", filename, "\")", ""));
}
}
/* see _sett_conn_entry_sds_update() for why we append the new events
* and leave existing ones at their position. */
sd = _storage_data_find_in_lst (&sett_conn_entry->dirty_sd_lst_head, storage);
if (sd)
nm_g_object_ref_set (&sd->connection, connection);
else {
sd = _storage_data_new_stale (storage, connection);
c_list_link_tail (&sett_conn_entry->dirty_sd_lst_head, &sd->sd_lst);
}
if (prioritize) {
StorageData *sd2;
/* only one entry can be prioritized. */
c_list_for_each_entry (sd2, &sett_conn_entry->dirty_sd_lst_head, sd_lst)
sd2->prioritize = FALSE;
sd->prioritize = TRUE;
}
nm_c_list_move_tail (&priv->sce_dirty_lst_head, &sett_conn_entry->sce_dirty_lst);
return sett_conn_entry;
}
/*****************************************************************************/
static void
_plugin_connections_reload_cb (NMSettingsPlugin *plugin,
NMSettingsStorage *storage,
NMConnection *connection,
gpointer user_data)
{
_connection_changed_track (user_data, storage, connection, FALSE);
}
static void
_plugin_connections_reload (NMSettings *self)
{
NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
GSList *iter;
for (iter = priv->plugins; iter; iter = iter->next) {
nm_settings_plugin_reload_connections (iter->data,
_plugin_connections_reload_cb,
self);
}
_connection_changed_process_all_dirty (self,
FALSE,
NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
TRUE,
NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_SYSTEM_SECRETS
| NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_AGENT_SECRETS);
for (iter = priv->plugins; iter; iter = iter->next)
nm_settings_plugin_load_connections_done (iter->data);
}
/*****************************************************************************/
static gboolean
_add_connection_to_first_plugin (NMSettings *self,
NMConnection *new_connection,
gboolean in_memory,
gboolean is_nm_generated,
gboolean is_volatile,
NMSettingsStorage **out_new_storage,
NMConnection **out_new_connection,
GError **error)
{
NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
GError *first_error = NULL;
GSList *iter;
const char *uuid;
uuid = nm_connection_get_uuid (new_connection);
nm_assert (nm_utils_is_uuid (uuid));
for (iter = priv->plugins; iter; iter = iter->next) {
NMSettingsPlugin *plugin = NM_SETTINGS_PLUGIN (iter->data);
gs_unref_object NMSettingsStorage *storage = NULL;
gs_unref_object NMConnection *connection_to_add = NULL;
gs_unref_object NMConnection *connection_to_add_cloned = NULL;
NMConnection *connection_to_add_real = NULL;
gs_unref_variant GVariant *agent_owned_secrets = NULL;
gs_free_error GError *add_error = NULL;
gboolean success;
const char *filename;
if (plugin == (NMSettingsPlugin *) priv->keyfile_plugin) {
success = nms_keyfile_plugin_add_connection (priv->keyfile_plugin,
new_connection,
is_nm_generated,
is_volatile,
in_memory,
&storage,
&connection_to_add,
&add_error);
} else {
if (in_memory)
continue;
nm_assert (!is_nm_generated);
nm_assert (!is_volatile);
success = nm_settings_plugin_add_connection (plugin,
new_connection,
&storage,
&connection_to_add,
&add_error);
}
if (!success) {
_LOGT ("add-connection: failed to add %s/'%s': %s",
nm_connection_get_uuid (new_connection),
nm_connection_get_id (new_connection),
add_error->message);
if (!first_error)
first_error = g_steal_pointer (&add_error);
continue;
}
if (!nm_streq0 (nm_settings_storage_get_uuid (storage), uuid)) {
nm_assert_not_reached ();
continue;
}
agent_owned_secrets = nm_connection_to_dbus (new_connection,
NM_CONNECTION_SERIALIZE_ONLY_SECRETS
| NM_CONNECTION_SERIALIZE_WITH_SECRETS_AGENT_OWNED);
connection_to_add_real = _connection_changed_normalize_connection (storage,
connection_to_add,
agent_owned_secrets,
&connection_to_add_cloned);
if (!connection_to_add_real) {
nm_assert_not_reached ();
continue;
}
filename = nm_settings_storage_get_filename (storage);
_LOGT ("add-connection: successfully added connection %s,'%s' ("NM_SETTINGS_STORAGE_PRINT_FMT"%s%s%s",
nm_settings_storage_get_uuid (storage),
nm_connection_get_id (new_connection),
NM_SETTINGS_STORAGE_PRINT_ARG (storage),
NM_PRINT_FMT_QUOTED (filename, ", \"", filename, "\")", ")"));
*out_new_storage = g_steal_pointer (&storage);
*out_new_connection = g_steal_pointer (&connection_to_add_cloned)
?: g_steal_pointer (&connection_to_add);
nm_assert (NM_IS_CONNECTION (*out_new_connection));
return TRUE;
}
nm_assert (first_error);
g_propagate_error (error, first_error);
return FALSE;
}
/**
* nm_settings_add_connection:
* @self: the #NMSettings object
* @connection: the source connection to create a new #NMSettingsConnection from
* @persist_mode: the persist-mode for this profile.
* @add_reason: the add-reason flags.
* @sett_flags: the settings flags to set.
* @out_sett_conn: (allow-none) (transfer none): the added settings connection on success.
* @error: on return, a location to store any errors that may occur
*
* Creates a new #NMSettingsConnection for the given source @connection.
* The returned object is owned by @self and the caller must reference
* the object to continue using it.
*
* Returns: TRUE on success.
*/
gboolean
nm_settings_add_connection (NMSettings *self,
NMConnection *connection,
NMSettingsConnectionPersistMode persist_mode,
NMSettingsConnectionAddReason add_reason,
NMSettingsConnectionIntFlags sett_flags,
NMSettingsConnection **out_sett_conn,
GError **error)
{
gs_unref_object NMConnection *connection_cloned_1 = NULL;
gs_unref_object NMConnection *new_connection = NULL;
gs_unref_object NMSettingsStorage *new_storage = NULL;
gs_free_error GError *local = NULL;
SettConnEntry *sett_conn_entry;
const char *uuid;
StorageData *sd;
nm_assert (NM_IN_SET (persist_mode, NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK,
NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY));
nm_assert (!NM_FLAGS_ANY (sett_flags, ~_NM_SETTINGS_CONNECTION_INT_FLAGS_PERSISTENT_MASK));
nm_assert ( !NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED)
|| persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY);
nm_assert ( !NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE)
|| persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY);
nm_assert (!NM_FLAGS_ANY (add_reason, ~NM_SETTINGS_CONNECTION_ADD_REASON_BLOCK_AUTOCONNECT));
NM_SET_OUT (out_sett_conn, NULL);
uuid = nm_connection_get_uuid (connection);
/* Make sure a connection with this UUID doesn't already exist */
if (_sett_conn_entry_get_conn (_sett_conn_entries_get (self, uuid))) {
g_set_error_literal (error,
NM_SETTINGS_ERROR,
NM_SETTINGS_ERROR_UUID_EXISTS,
"a connection with this UUID already exists");
return FALSE;
}
if (!_nm_connection_ensure_normalized (connection,
FALSE,
NULL,
FALSE,
&connection_cloned_1,
&local)) {
g_set_error (error,
NM_SETTINGS_ERROR,
NM_SETTINGS_ERROR_INVALID_CONNECTION,
"connection is invalid: %s",
local->message);
return FALSE;
}
if (connection_cloned_1)
connection = connection_cloned_1;
if (!_add_connection_to_first_plugin (self,
connection,
( persist_mode != NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK
|| NM_FLAGS_ANY (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
| NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED)),
NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED),
NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE),
&new_storage,
&new_connection,
&local)) {
g_set_error (error,
NM_SETTINGS_ERROR,
NM_SETTINGS_ERROR_FAILED,
"failure adding connection: %s",
local->message);
return FALSE;
}
sett_conn_entry = _connection_changed_track (self, new_storage, new_connection, TRUE);
c_list_for_each_entry (sd, &sett_conn_entry->sd_lst_head, sd_lst) {
const NMSettingsMetaData *meta_data;
meta_data = nm_settings_storage_is_meta_data_alive (sd->storage);
if ( !meta_data
|| !meta_data->is_tombstone)
continue;
if (nm_settings_storage_is_keyfile_run (sd->storage)) {
/* We remove this file from /run. */
} else {
if (nm_settings_storage_is_keyfile_run (new_storage)) {
/* Don't remove the file from /etc if we just wrote an in-memory connection */
continue;
}
}
nm_settings_plugin_delete_connection (nm_settings_storage_get_plugin (sd->storage),
sd->storage,
NULL);
nm_assert (!_storage_data_is_alive (sd));
_connection_changed_track (self, sd->storage, NULL, FALSE);
}
_connection_changed_process_all_dirty (self,
FALSE,
sett_flags,
_NM_SETTINGS_CONNECTION_INT_FLAGS_PERSISTENT_MASK,
FALSE,
NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_SYSTEM_SECRETS
| NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_AGENT_SECRETS
| ( NM_FLAGS_HAS (add_reason, NM_SETTINGS_CONNECTION_ADD_REASON_BLOCK_AUTOCONNECT)
? NM_SETTINGS_CONNECTION_UPDATE_REASON_BLOCK_AUTOCONNECT
: NM_SETTINGS_CONNECTION_UPDATE_REASON_NONE));
nm_assert (sett_conn_entry == _sett_conn_entries_get (self, sett_conn_entry->uuid));
nm_assert (NM_IS_SETTINGS_CONNECTION (sett_conn_entry->sett_conn));
NM_SET_OUT (out_sett_conn, _sett_conn_entry_get_conn (sett_conn_entry));
return TRUE;
}
/*****************************************************************************/
gboolean
nm_settings_update_connection (NMSettings *self,
NMSettingsConnection *sett_conn,
NMConnection *connection,
NMSettingsConnectionPersistMode persist_mode,
NMSettingsConnectionIntFlags sett_flags,
NMSettingsConnectionIntFlags sett_mask,
NMSettingsConnectionUpdateReason update_reason,
const char *log_context_name,
GError **error)
{
NMSettingsPrivate *priv;
gs_unref_object NMConnection *connection_cloned_1 = NULL;
gs_unref_object NMConnection *new_connection_cloned = NULL;
gs_unref_object NMConnection *new_connection = NULL;
NMConnection *new_connection_real;
gs_unref_object NMSettingsStorage *cur_storage = NULL;
gs_unref_object NMSettingsStorage *new_storage = NULL;
gboolean cur_in_memory;
gboolean new_in_memory;
const char *uuid;
g_return_val_if_fail (NM_IS_SETTINGS (self), FALSE);
g_return_val_if_fail (NM_IS_SETTINGS_CONNECTION (sett_conn), FALSE);
g_return_val_if_fail (!connection || NM_IS_CONNECTION (connection), FALSE);
nm_assert (!NM_FLAGS_ANY (sett_mask, ~_NM_SETTINGS_CONNECTION_INT_FLAGS_PERSISTENT_MASK));
nm_assert (!NM_FLAGS_ANY (sett_flags, ~sett_mask));
nm_assert (NM_IN_SET (persist_mode, NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP,
NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST,
NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK,
NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED,
NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY));
priv = NM_SETTINGS_GET_PRIVATE (self);
cur_storage = g_object_ref (nm_settings_connection_get_storage (sett_conn));
uuid = nm_settings_storage_get_uuid (cur_storage);
nm_assert (NM_IS_SETTINGS_STORAGE (cur_storage));
nm_assert (_sett_conn_entry_get_conn (_sett_conn_entries_get (self, uuid)) == sett_conn);
if (connection) {
gs_free_error GError *local = NULL;
if (!_nm_connection_ensure_normalized (connection,
FALSE,
uuid,
TRUE,
&connection_cloned_1,
&local)) {
_LOGT ("update[%s]: %s: failed because profile is invalid: %s",
nm_settings_storage_get_uuid (cur_storage),
log_context_name,
local->message);
g_set_error (error,
NM_SETTINGS_ERROR,
NM_SETTINGS_ERROR_INVALID_CONNECTION,
"connection is invalid: %s",
local->message);
return FALSE;
}
if (connection_cloned_1)
connection = connection_cloned_1;
} else
connection = nm_settings_connection_get_connection (sett_conn);
cur_in_memory = nm_settings_storage_is_keyfile_run (cur_storage);
if (persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP) {
persist_mode = cur_in_memory
? NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED
: NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK;
}
if ( NM_FLAGS_HAS (sett_mask, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED)
&& !NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED)) {
NMDevice *device;
/* The connection has been changed by the user, it should no longer be
* considered a default wired connection, and should no longer affect
* the no-auto-default configuration option.
*/
device = nm_settings_connection_default_wired_get_device (sett_conn);
if (device) {
nm_assert (cur_in_memory);
nm_assert (!NM_FLAGS_ANY (nm_settings_connection_get_flags (sett_conn),
NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
| NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE));
default_wired_clear_tag (self, device, sett_conn, FALSE);
if (NM_IN_SET (persist_mode, NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP,
NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST)) {
/* making a default-wired-connection a regulard connection implies persisting
* it to disk (unless specified differently). */
persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK;
}
}
}
if ( persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST
&& NM_FLAGS_ANY (sett_mask, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
| NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE)
&& NM_FLAGS_ANY ((sett_flags ^ nm_settings_connection_get_flags (sett_conn)) & sett_mask,
NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
| NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE)) {
/* we update the nm-generated/volatile setting of a profile (which is inherrently
* in-memory. The caller did not request to persist this to disk, however we need
* to store the flags in run. */
nm_assert (cur_in_memory);
persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED;
}
if (persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK)
new_in_memory = FALSE;
else if (NM_IN_SET (persist_mode, NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED,
NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY))
new_in_memory = TRUE;
else {
nm_assert (persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST);
new_in_memory = cur_in_memory;
}
if (!new_in_memory) {
/* Persistent connections cannot be volatile nor nm-generated.
*
* That is obviously true for volatile, as it is enforced by Update2() API.
*
* For nm-generated profiles also, because the nm-generated flag is only stored
* for in-memory profiles. If we would persist the profile to /etc it would loose
* the nm-generated flag after restart/reload, and that cannot be right. If a profile
* ends up on disk, the information who created it gets lost. */
nm_assert (!NM_FLAGS_ANY (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
| NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE));
sett_mask |= NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
| NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE;
sett_flags &= ~( NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
| NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE);
}
if (persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST) {
new_storage = g_object_ref (cur_storage);
new_connection = g_object_ref (connection);
_LOGT ("update[%s]: %s: update profile \"%s\" (not persisted)",
nm_settings_storage_get_uuid (cur_storage),
log_context_name,
nm_connection_get_id (connection));
} else {
gboolean success;
gboolean migrate_storage;
gs_free_error GError *local = NULL;
if (new_in_memory != cur_in_memory)
migrate_storage = TRUE;
else if ( !new_in_memory
&& nm_settings_storage_is_keyfile_lib (cur_storage)) {
/* the profile is a keyfile in /usr/lib. It cannot be overwritten, we must migrate it
* from /usr/lib to /etc. */
migrate_storage = TRUE;
} else
migrate_storage = FALSE;
if (migrate_storage) {
success = _add_connection_to_first_plugin (self,
connection,
new_in_memory,
NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED),
NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE),
&new_storage,
&new_connection,
&local);
} else {
NMSettingsPlugin *plugin;
plugin = nm_settings_storage_get_plugin (cur_storage);
if (plugin == (NMSettingsPlugin *) priv->keyfile_plugin) {
success = nms_keyfile_plugin_update_connection (priv->keyfile_plugin,
cur_storage,
connection,
NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED),
NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE),
NM_FLAGS_HAS (update_reason, NM_SETTINGS_CONNECTION_UPDATE_REASON_FORCE_RENAME),
&new_storage,
&new_connection,
&local);
} else {
success = nm_settings_plugin_update_connection (nm_settings_storage_get_plugin (cur_storage),
cur_storage,
connection,
&new_storage,
&new_connection,
&local);
}
}
if (!success) {
gboolean ignore_failure;
ignore_failure = NM_FLAGS_ANY (update_reason, NM_SETTINGS_CONNECTION_UPDATE_REASON_IGNORE_PERSIST_FAILURE);
_LOGT ("update[%s]: %s: %sfailure to %s connection \"%s\" on storage: %s",
nm_settings_storage_get_uuid (cur_storage),
log_context_name,
ignore_failure ? "ignore " : "",
migrate_storage ? "write" : "update",
nm_connection_get_id (connection),
local->message);
if (!ignore_failure) {
g_set_error (error,
NM_SETTINGS_ERROR,
NM_SETTINGS_ERROR_INVALID_CONNECTION,
"failed to %s connection: %s",
migrate_storage ? "write" : "update",
local->message);
return FALSE;
}
new_storage = g_object_ref (cur_storage);
new_connection = g_object_ref (connection);
} else {
_LOGT ("update[%s]: %s: %s profile \"%s\"",
nm_settings_storage_get_uuid (cur_storage),
log_context_name,
migrate_storage ? "write" : "update",
nm_connection_get_id (connection));
}
}
nm_assert_valid_settings_storage (NULL, new_storage);
nm_assert (NM_IS_CONNECTION (new_connection));
nm_assert (nm_streq (uuid, nm_settings_storage_get_uuid (new_storage)));
if (persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST)
new_connection_real = new_connection;
else {
gs_unref_variant GVariant *agent_owned_secrets = NULL;
agent_owned_secrets = nm_connection_to_dbus (connection,
NM_CONNECTION_SERIALIZE_ONLY_SECRETS
| NM_CONNECTION_SERIALIZE_WITH_SECRETS_AGENT_OWNED);
new_connection_real = _connection_changed_normalize_connection (new_storage,
new_connection,
agent_owned_secrets,
&new_connection_cloned);
if (!new_connection_real) {
nm_assert_not_reached ();
new_connection_real = new_connection;
}
}
nm_assert (NM_IS_CONNECTION (new_connection_real));
_connection_changed_track (self, new_storage, new_connection_real, TRUE);
if (new_storage != cur_storage) {
gs_free_error GError *local = NULL;
gboolean remove_from_disk;
if (persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED)
remove_from_disk = FALSE;
else if (nm_settings_storage_is_keyfile_lib (cur_storage))
remove_from_disk = FALSE;
else
remove_from_disk = TRUE;
if (remove_from_disk) {
if (!nm_settings_plugin_delete_connection (nm_settings_storage_get_plugin (cur_storage),
cur_storage,
&local)) {
const char *filename;
filename = nm_settings_storage_get_filename (cur_storage);
_LOGW ("update[%s]: failed to delete moved storage "NM_SETTINGS_STORAGE_PRINT_FMT"%s%s%s: %s",
nm_settings_storage_get_uuid (cur_storage),
NM_SETTINGS_STORAGE_PRINT_ARG (cur_storage),
local->message,
NM_PRINT_FMT_QUOTED (filename, " (file \"", filename, "\")", ""));
}
_connection_changed_track (self, cur_storage, NULL, FALSE);
}
}
_connection_changed_process_all_dirty (self,
FALSE,
sett_flags,
sett_mask,
FALSE,
update_reason);
return TRUE;
}
void
nm_settings_delete_connection (NMSettings *self,
NMSettingsConnection *sett_conn,
gboolean allow_add_to_no_auto_default)
{
NMSettingsPrivate *priv;
NMSettingsStorage *cur_storage;
gs_free_error GError *local = NULL;
SettConnEntry *sett_conn_entry;
const char *uuid;
gboolean delete;
gboolean tombstone_in_memory = FALSE;
gboolean tombstone_on_disk = FALSE;
gs_unref_object NMSettingsStorage *tombstone_1_storage = NULL;
gs_unref_object NMSettingsStorage *tombstone_2_storage = NULL;
g_return_if_fail (NM_IS_SETTINGS (self));
g_return_if_fail (NM_IS_SETTINGS_CONNECTION (sett_conn));
g_return_if_fail (nm_settings_has_connection (self, sett_conn));
priv = NM_SETTINGS_GET_PRIVATE (self);
cur_storage = nm_settings_connection_get_storage (sett_conn);
nm_assert (NM_IS_SETTINGS_STORAGE (cur_storage));
uuid = nm_settings_storage_get_uuid (cur_storage);
nm_assert (nm_utils_is_uuid (uuid));
sett_conn_entry = _sett_conn_entries_get (self, uuid);
g_return_if_fail (sett_conn_entry);
nm_assert (sett_conn_entry->sett_conn == sett_conn);
g_return_if_fail (sett_conn_entry->storage == cur_storage);
if (NMS_IS_KEYFILE_STORAGE (cur_storage)) {
NMSKeyfileStorage *s = NMS_KEYFILE_STORAGE (cur_storage);
if (NM_IN_SET (s->storage_type, NMS_KEYFILE_STORAGE_TYPE_RUN,
NMS_KEYFILE_STORAGE_TYPE_ETC))
delete = TRUE;
else {
tombstone_on_disk = TRUE;
delete = FALSE;
}
} else
delete = TRUE;
if (delete) {
StorageData *sd;
if (!nm_settings_plugin_delete_connection (nm_settings_storage_get_plugin (cur_storage),
cur_storage,
&local)) {
_LOGW ("delete-connection: failed to delete storage "NM_SETTINGS_STORAGE_PRINT_FMT": %s",
NM_SETTINGS_STORAGE_PRINT_ARG (cur_storage),
local->message);
g_clear_error (&local);
/* there is no aborting back form this. We must get rid of the connection and
* cannot do better than warn. Proceed... */
tombstone_in_memory = TRUE;
}
sett_conn_entry = _connection_changed_track (self, cur_storage, NULL, FALSE);
c_list_for_each_entry (sd, &sett_conn_entry->sd_lst_head, sd_lst) {
if (sd->storage == cur_storage)
continue;
if (!_storage_data_is_alive (sd))
continue;
if (nm_settings_storage_is_meta_data (sd->storage))
continue;
/* we have still conflicting storages. We need to hide them with tombstones. */
if (nm_settings_storage_is_keyfile_run (sd->storage)) {
tombstone_in_memory = TRUE;
continue;
}
tombstone_on_disk = TRUE;
}
}
if (tombstone_on_disk) {
if (!nms_keyfile_plugin_set_nmmeta_tombstone (priv->keyfile_plugin,
FALSE,
uuid,
FALSE,
TRUE,
&tombstone_1_storage,
NULL))
tombstone_in_memory = TRUE;
if (tombstone_1_storage)
_connection_changed_track (self, tombstone_1_storage, NULL, FALSE);
}
if (tombstone_in_memory) {
if (!nms_keyfile_plugin_set_nmmeta_tombstone (priv->keyfile_plugin,
FALSE,
uuid,
TRUE,
TRUE,
&tombstone_2_storage,
NULL)) {
nms_keyfile_plugin_set_nmmeta_tombstone (priv->keyfile_plugin,
TRUE,
uuid,
TRUE,
TRUE,
&tombstone_2_storage,
NULL);
}
_connection_changed_track (self, tombstone_2_storage, NULL, FALSE);
}
_connection_changed_process_all_dirty (self,
allow_add_to_no_auto_default,
NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
FALSE,
NM_SETTINGS_CONNECTION_UPDATE_REASON_NONE);
}
/*****************************************************************************/
static void
send_agent_owned_secrets (NMSettings *self,
NMSettingsConnection *sett_conn,
NMAuthSubject *subject)
{
NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
gs_unref_object NMConnection *for_agent = NULL;
/* Dupe the connection so we can clear out non-agent-owned secrets,
* as agent-owned secrets are the only ones we send back to be saved.
* Only send secrets to agents of the same UID that called update too.
*/
for_agent = nm_simple_connection_new_clone (nm_settings_connection_get_connection (sett_conn));
_nm_connection_clear_secrets_by_secret_flags (for_agent,
NM_SETTING_SECRET_FLAG_AGENT_OWNED);
nm_agent_manager_save_secrets (priv->agent_mgr,
nm_dbus_object_get_path (NM_DBUS_OBJECT (sett_conn)),
for_agent,
subject);
}
static void
pk_add_cb (NMAuthChain *chain,
GDBusMethodInvocation *context,
gpointer user_data)
{
NMSettings *self = NM_SETTINGS (user_data);
NMAuthCallResult result;
gs_free_error GError *error = NULL;
NMConnection *connection = NULL;
gs_unref_object NMSettingsConnection *added = NULL;
NMSettingsAddCallback callback;
gpointer callback_data;
NMAuthSubject *subject;
const char *perm;
nm_assert (G_IS_DBUS_METHOD_INVOCATION (context));
c_list_unlink (nm_auth_chain_parent_lst_list (chain));
perm = nm_auth_chain_get_data (chain, "perm");
nm_assert (perm);
result = nm_auth_chain_get_result (chain, perm);
if (result != NM_AUTH_CALL_RESULT_YES) {
error = g_error_new_literal (NM_SETTINGS_ERROR,
NM_SETTINGS_ERROR_PERMISSION_DENIED,
"Insufficient privileges.");
} else {
/* Authorized */
connection = nm_auth_chain_get_data (chain, "connection");
nm_assert (NM_IS_CONNECTION (connection));
nm_settings_add_connection (self,
connection,
GPOINTER_TO_UINT (nm_auth_chain_get_data (chain, "persist-mode")),
GPOINTER_TO_UINT (nm_auth_chain_get_data (chain, "add-reason")),
GPOINTER_TO_UINT (nm_auth_chain_get_data (chain, "sett-flags")),
&added,
&error);
/* The callback may remove the connection from the settings manager (e.g.
* because it's found to be incompatible with the device on AddAndActivate).
* But we need to keep it alive for a bit longer, precisely to check wehther
* it's still known to the setting manager. */
nm_g_object_ref (added);
}
callback = nm_auth_chain_get_data (chain, "callback");
callback_data = nm_auth_chain_get_data (chain, "callback-data");
subject = nm_auth_chain_get_data (chain, "subject");
callback (self, added, error, context, subject, callback_data);
/* Send agent-owned secrets to the agents */
if ( added
&& nm_settings_has_connection (self, added))
send_agent_owned_secrets (self, added, subject);
}
void
nm_settings_add_connection_dbus (NMSettings *self,
NMConnection *connection,
NMSettingsConnectionPersistMode persist_mode,
NMSettingsConnectionAddReason add_reason,
NMSettingsConnectionIntFlags sett_flags,
NMAuthSubject *subject,
GDBusMethodInvocation *context,
NMSettingsAddCallback callback,
gpointer user_data)
{
NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
NMSettingConnection *s_con;
NMAuthChain *chain;
GError *error = NULL, *tmp_error = NULL;
const char *perm;
g_return_if_fail (NM_IS_CONNECTION (connection));
g_return_if_fail (NM_IS_AUTH_SUBJECT (subject));
g_return_if_fail (G_IS_DBUS_METHOD_INVOCATION (context));
nm_assert (!NM_FLAGS_ANY (sett_flags, ~_NM_SETTINGS_CONNECTION_INT_FLAGS_PERSISTENT_MASK));
/* Connection must be valid, of course */
if (_nm_connection_verify (connection, &tmp_error) != NM_SETTING_VERIFY_SUCCESS) {
error = g_error_new (NM_SETTINGS_ERROR,
NM_SETTINGS_ERROR_INVALID_CONNECTION,
"The connection was invalid: %s",
tmp_error->message);
g_error_free (tmp_error);
goto done;
}
/* FIXME: The kernel doesn't support Ad-Hoc WPA connections well at this time,
* and turns them into open networks. It's been this way since at least
* 2.6.30 or so; until that's fixed, disable WPA-protected Ad-Hoc networks.
*/
if (nm_utils_connection_is_adhoc_wpa (connection)) {
error = g_error_new_literal (NM_SETTINGS_ERROR,
NM_SETTINGS_ERROR_INVALID_CONNECTION,
"WPA Ad-Hoc disabled due to kernel bugs");
goto done;
}
if (!nm_auth_is_subject_in_acl_set_error (connection,
subject,
NM_SETTINGS_ERROR,
NM_SETTINGS_ERROR_PERMISSION_DENIED,
&error))
goto done;
/* If the caller is the only user in the connection's permissions, then
* we use the 'modify.own' permission instead of 'modify.system'. If the
* request affects more than just the caller, require 'modify.system'.
*/
s_con = nm_connection_get_setting_connection (connection);
nm_assert (s_con);
if (nm_setting_connection_get_num_permissions (s_con) == 1)
perm = NM_AUTH_PERMISSION_SETTINGS_MODIFY_OWN;
else
perm = NM_AUTH_PERMISSION_SETTINGS_MODIFY_SYSTEM;
/* Validate the user request */
chain = nm_auth_chain_new_subject (subject, context, pk_add_cb, self);
if (!chain) {
error = g_error_new_literal (NM_SETTINGS_ERROR,
NM_SETTINGS_ERROR_PERMISSION_DENIED,
"Unable to authenticate the request.");
goto done;
}
c_list_link_tail (&priv->auth_lst_head, nm_auth_chain_parent_lst_list (chain));
nm_auth_chain_set_data (chain, "perm", (gpointer) perm, NULL);
nm_auth_chain_set_data (chain, "connection", g_object_ref (connection), g_object_unref);
nm_auth_chain_set_data (chain, "callback", callback, NULL);
nm_auth_chain_set_data (chain, "callback-data", user_data, NULL);
nm_auth_chain_set_data (chain, "subject", g_object_ref (subject), g_object_unref);
nm_auth_chain_set_data (chain, "persist-mode", GUINT_TO_POINTER (persist_mode), NULL);
nm_auth_chain_set_data (chain, "add-reason", GUINT_TO_POINTER (add_reason), NULL);
nm_auth_chain_set_data (chain, "sett-flags", GUINT_TO_POINTER (sett_flags), NULL);
nm_auth_chain_add_call_unsafe (chain, perm, TRUE);
return;
done:
nm_assert (error);
callback (self, NULL, error, context, subject, user_data);
g_error_free (error);
}
static void
settings_add_connection_add_cb (NMSettings *self,
NMSettingsConnection *connection,
GError *error,
GDBusMethodInvocation *context,
NMAuthSubject *subject,
gpointer user_data)
{
gboolean is_add_connection_2 = GPOINTER_TO_INT (user_data);
if (error) {
g_dbus_method_invocation_return_gerror (context, error);
nm_audit_log_connection_op (NM_AUDIT_OP_CONN_ADD, NULL, FALSE, NULL, subject, error->message);
return;
}
if (is_add_connection_2) {
GVariantBuilder builder;
g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
g_dbus_method_invocation_return_value (context,
g_variant_new ("(oa{sv})",
nm_dbus_object_get_path (NM_DBUS_OBJECT (connection)),
&builder));
} else {
g_dbus_method_invocation_return_value (context,
g_variant_new ("(o)",
nm_dbus_object_get_path (NM_DBUS_OBJECT (connection))));
}
nm_audit_log_connection_op (NM_AUDIT_OP_CONN_ADD, connection, TRUE, NULL,
subject, NULL);
}
static void
settings_add_connection_helper (NMSettings *self,
GDBusMethodInvocation *context,
gboolean is_add_connection_2,
GVariant *settings,
NMSettingsAddConnection2Flags flags)
{
gs_unref_object NMConnection *connection = NULL;
GError *error = NULL;
gs_unref_object NMAuthSubject *subject = NULL;
NMSettingsConnectionPersistMode persist_mode;
connection = _nm_simple_connection_new_from_dbus (settings,
NM_SETTING_PARSE_FLAGS_STRICT
| NM_SETTING_PARSE_FLAGS_NORMALIZE,
&error);
if ( !connection
|| !nm_connection_verify_secrets (connection, &error)) {
g_dbus_method_invocation_take_error (context, error);
return;
}
subject = nm_auth_subject_new_unix_process_from_context (context);
if (!subject) {
g_dbus_method_invocation_return_error_literal (context,
NM_SETTINGS_ERROR,
NM_SETTINGS_ERROR_PERMISSION_DENIED,
"Unable to determine UID of request.");
return;
}
if (NM_FLAGS_HAS (flags, NM_SETTINGS_ADD_CONNECTION2_FLAG_TO_DISK))
persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK;
else {
nm_assert (NM_FLAGS_HAS (flags, NM_SETTINGS_ADD_CONNECTION2_FLAG_IN_MEMORY));
persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY;
}
nm_settings_add_connection_dbus (self,
connection,
persist_mode,
NM_FLAGS_HAS (flags, NM_SETTINGS_ADD_CONNECTION2_FLAG_BLOCK_AUTOCONNECT)
? NM_SETTINGS_CONNECTION_ADD_REASON_BLOCK_AUTOCONNECT
: NM_SETTINGS_CONNECTION_ADD_REASON_NONE,
NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
subject,
context,
settings_add_connection_add_cb,
GINT_TO_POINTER (!!is_add_connection_2));
}
static void
impl_settings_add_connection (NMDBusObject *obj,
const NMDBusInterfaceInfoExtended *interface_info,
const NMDBusMethodInfoExtended *method_info,
GDBusConnection *connection,
const char *sender,
GDBusMethodInvocation *invocation,
GVariant *parameters)
{
NMSettings *self = NM_SETTINGS (obj);
gs_unref_variant GVariant *settings = NULL;
g_variant_get (parameters, "(@a{sa{sv}})", &settings);
settings_add_connection_helper (self, invocation, FALSE, settings, NM_SETTINGS_ADD_CONNECTION2_FLAG_TO_DISK);
}
static void
impl_settings_add_connection_unsaved (NMDBusObject *obj,
const NMDBusInterfaceInfoExtended *interface_info,
const NMDBusMethodInfoExtended *method_info,
GDBusConnection *connection,
const char *sender,
GDBusMethodInvocation *invocation,
GVariant *parameters)
{
NMSettings *self = NM_SETTINGS (obj);
gs_unref_variant GVariant *settings = NULL;
g_variant_get (parameters, "(@a{sa{sv}})", &settings);
settings_add_connection_helper (self, invocation, FALSE, settings, NM_SETTINGS_ADD_CONNECTION2_FLAG_IN_MEMORY);
}
static void
impl_settings_add_connection2 (NMDBusObject *obj,
const NMDBusInterfaceInfoExtended *interface_info,
const NMDBusMethodInfoExtended *method_info,
GDBusConnection *connection,
const char *sender,
GDBusMethodInvocation *invocation,
GVariant *parameters)
{
NMSettings *self = NM_SETTINGS (obj);
gs_unref_variant GVariant *settings = NULL;
gs_unref_variant GVariant *args = NULL;
NMSettingsAddConnection2Flags flags;
const char *args_name;
GVariantIter iter;
guint32 flags_u;
g_variant_get (parameters, "(@a{sa{sv}}u@a{sv})", &settings, &flags_u, &args);
if (NM_FLAGS_ANY (flags_u, ~((guint32) ( NM_SETTINGS_ADD_CONNECTION2_FLAG_TO_DISK
| NM_SETTINGS_ADD_CONNECTION2_FLAG_IN_MEMORY
| NM_SETTINGS_ADD_CONNECTION2_FLAG_BLOCK_AUTOCONNECT)))) {
g_dbus_method_invocation_take_error (invocation,
g_error_new_literal (NM_SETTINGS_ERROR,
NM_SETTINGS_ERROR_INVALID_ARGUMENTS,
"Unknown flags"));
return;
}
flags = flags_u;
if (!NM_FLAGS_ANY (flags, NM_SETTINGS_ADD_CONNECTION2_FLAG_TO_DISK
| NM_SETTINGS_ADD_CONNECTION2_FLAG_IN_MEMORY)) {
g_dbus_method_invocation_take_error (invocation,
g_error_new_literal (NM_SETTINGS_ERROR,
NM_SETTINGS_ERROR_INVALID_ARGUMENTS,
"Requires either to-disk (0x1) or in-memory (0x2) flags"));
return;
}
if (NM_FLAGS_ALL (flags, NM_SETTINGS_ADD_CONNECTION2_FLAG_TO_DISK
| NM_SETTINGS_ADD_CONNECTION2_FLAG_IN_MEMORY)) {
g_dbus_method_invocation_take_error (invocation,
g_error_new_literal (NM_SETTINGS_ERROR,
NM_SETTINGS_ERROR_INVALID_ARGUMENTS,
"Cannot set to-disk (0x1) and in-memory (0x2) flags together"));
return;
}
nm_assert (g_variant_is_of_type (args, G_VARIANT_TYPE ("a{sv}")));
g_variant_iter_init (&iter, args);
while (g_variant_iter_next (&iter, "{&sv}", &args_name, NULL)) {
g_dbus_method_invocation_take_error (invocation,
g_error_new (NM_SETTINGS_ERROR,
NM_SETTINGS_ERROR_INVALID_ARGUMENTS,
"Unsupported argument '%s'", args_name));
return;
}
settings_add_connection_helper (self, invocation, TRUE, settings, flags);
}
/*****************************************************************************/
static void
impl_settings_load_connections (NMDBusObject *obj,
const NMDBusInterfaceInfoExtended *interface_info,
const NMDBusMethodInfoExtended *method_info,
GDBusConnection *dbus_connection,
const char *sender,
GDBusMethodInvocation *invocation,
GVariant *parameters)
{
NMSettings *self = NM_SETTINGS (obj);
NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
gs_unref_ptrarray GPtrArray *failures = NULL;
gs_free const char **filenames = NULL;
gs_free char *op_result_str = NULL;
g_variant_get (parameters, "(^a&s)", &filenames);
/* The permission is already enforced by the D-Bus daemon, but we ensure
* that the caller is still alive so that clients are forced to wait and
* we'll be able to switch to polkit without breaking behavior.
*/
if (!nm_dbus_manager_ensure_uid (nm_dbus_object_get_manager (obj),
invocation,
G_MAXULONG,
NM_SETTINGS_ERROR,
NM_SETTINGS_ERROR_PERMISSION_DENIED))
return;
if ( filenames
&& filenames[0]) {
NMSettingsPluginConnectionLoadEntry *entries;
gsize n_entries;
gsize i;
GSList *iter;
entries = nm_settings_plugin_create_connection_load_entries (filenames, &n_entries);
for (iter = priv->plugins; iter; iter = iter->next) {
NMSettingsPlugin *plugin = iter->data;
nm_settings_plugin_load_connections (plugin,
entries,
n_entries,
_plugin_connections_reload_cb,
self);
}
for (i = 0; i < n_entries; i++) {
NMSettingsPluginConnectionLoadEntry *entry = &entries[i];
if (!entry->handled)
_LOGW ("load: no settings plugin could load \"%s\"", entry->filename);
else if (entry->error) {
_LOGW ("load: failure to load \"%s\": %s", entry->filename, entry->error->message);
g_clear_error (&entry->error);
} else
continue;
if (!failures)
failures = g_ptr_array_new ();
g_ptr_array_add (failures, (char *) entry->filename);
}
nm_clear_g_free (&entries);
_connection_changed_process_all_dirty (self,
TRUE,
NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
TRUE,
NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_SYSTEM_SECRETS
| NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_AGENT_SECRETS);
for (iter = priv->plugins; iter; iter = iter->next)
nm_settings_plugin_load_connections_done (iter->data);
}
if (failures)
g_ptr_array_add (failures, NULL);
nm_audit_log_connection_op (NM_AUDIT_OP_CONNS_LOAD,
NULL,
!failures,
(op_result_str = g_strjoinv (",", (char **) filenames)),
invocation,
NULL);
g_dbus_method_invocation_return_value (invocation,
g_variant_new ("(b^as)",
(gboolean) (!failures),
failures
? (const char **) failures->pdata
: NM_PTRARRAY_EMPTY (const char *)));
}
static void
impl_settings_reload_connections (NMDBusObject *obj,
const NMDBusInterfaceInfoExtended *interface_info,
const NMDBusMethodInfoExtended *method_info,
GDBusConnection *connection,
const char *sender,
GDBusMethodInvocation *invocation,
GVariant *parameters)
{
NMSettings *self = NM_SETTINGS (obj);
/* The permission is already enforced by the D-Bus daemon, but we ensure
* that the caller is still alive so that clients are forced to wait and
* we'll be able to switch to polkit without breaking behavior.
*/
if (!nm_dbus_manager_ensure_uid (nm_dbus_object_get_manager (obj),
invocation,
G_MAXULONG,
NM_SETTINGS_ERROR,
NM_SETTINGS_ERROR_PERMISSION_DENIED))
return;
_plugin_connections_reload (self);
nm_audit_log_connection_op (NM_AUDIT_OP_CONNS_RELOAD, NULL, TRUE, NULL, invocation, NULL);
g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", TRUE));
}
/*****************************************************************************/
static void
_clear_connections_cached_list (NMSettingsPrivate *priv)
{
if (!priv->connections_cached_list)
return;
nm_assert (priv->connections_len == NM_PTRARRAY_LEN (priv->connections_cached_list));
#if NM_MORE_ASSERTS
/* set the pointer to a bogus value. This makes it more apparent
* if somebody has a reference to the cached list and still uses
* it. That is a bug, this code just tries to make it blow up
* more eagerly. */
memset (priv->connections_cached_list,
0xdeaddead,
sizeof (NMSettingsConnection *) * (priv->connections_len + 1));
#endif
nm_clear_g_free (&priv->connections_cached_list);
}
static void
impl_settings_list_connections (NMDBusObject *obj,
const NMDBusInterfaceInfoExtended *interface_info,
const NMDBusMethodInfoExtended *method_info,
GDBusConnection *dbus_connection,
const char *sender,
GDBusMethodInvocation *invocation,
GVariant *parameters)
{
NMSettings *self = NM_SETTINGS (obj);
NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
gs_free const char **strv = NULL;
strv = nm_dbus_utils_get_paths_for_clist (&priv->connections_lst_head,
priv->connections_len,
G_STRUCT_OFFSET (NMSettingsConnection, _connections_lst),
TRUE);
g_dbus_method_invocation_return_value (invocation,
g_variant_new ("(^ao)", strv));
}
NMSettingsConnection *
nm_settings_get_connection_by_uuid (NMSettings *self, const char *uuid)
{
g_return_val_if_fail (NM_IS_SETTINGS (self), NULL);
g_return_val_if_fail (uuid != NULL, NULL);
return _sett_conn_entry_get_conn (_sett_conn_entries_get (self, uuid));
}
const char *
nm_settings_get_dbus_path_for_uuid (NMSettings *self,
const char *uuid)
{
NMSettingsConnection *sett_conn;
sett_conn = nm_settings_get_connection_by_uuid (self, uuid);
if (!sett_conn)
return NULL;
return nm_dbus_object_get_path (NM_DBUS_OBJECT (sett_conn));
}
static void
impl_settings_get_connection_by_uuid (NMDBusObject *obj,
const NMDBusInterfaceInfoExtended *interface_info,
const NMDBusMethodInfoExtended *method_info,
GDBusConnection *dbus_connection,
const char *sender,
GDBusMethodInvocation *invocation,
GVariant *parameters)
{
NMSettings *self = NM_SETTINGS (obj);
NMSettingsConnection *sett_conn;
gs_unref_object NMAuthSubject *subject = NULL;
GError *error = NULL;
const char *uuid;
g_variant_get (parameters, "(&s)", &uuid);
sett_conn = nm_settings_get_connection_by_uuid (self, uuid);
if (!sett_conn) {
error = g_error_new_literal (NM_SETTINGS_ERROR,
NM_SETTINGS_ERROR_INVALID_CONNECTION,
"No connection with the UUID was found.");
goto error;
}
subject = nm_auth_subject_new_unix_process_from_context (invocation);
if (!subject) {
error = g_error_new_literal (NM_SETTINGS_ERROR,
NM_SETTINGS_ERROR_PERMISSION_DENIED,
"Unable to determine UID of request.");
goto error;
}
if (!nm_auth_is_subject_in_acl_set_error (nm_settings_connection_get_connection (sett_conn),
subject,
NM_SETTINGS_ERROR,
NM_SETTINGS_ERROR_PERMISSION_DENIED,
&error))
goto error;
g_dbus_method_invocation_return_value (invocation,
g_variant_new ("(o)",
nm_dbus_object_get_path (NM_DBUS_OBJECT (sett_conn))));
return;
error:
g_dbus_method_invocation_take_error (invocation, error);
}
/**
* nm_settings_get_connections:
* @self: the #NMSettings
* @out_len: (out) (allow-none): returns the number of returned
* connections.
*
* Returns: (transfer none): a list of NMSettingsConnections. The list is
* unsorted and NULL terminated. The result is never %NULL, in case of no
* connections, it returns an empty list.
* The returned list is cached internally, only valid until the next
* NMSettings operation.
*/
NMSettingsConnection *const*
nm_settings_get_connections (NMSettings *self, guint *out_len)
{
NMSettingsPrivate *priv;
NMSettingsConnection **v;
NMSettingsConnection *con;
guint i;
g_return_val_if_fail (NM_IS_SETTINGS (self), NULL);
priv = NM_SETTINGS_GET_PRIVATE (self);
nm_assert (priv->connections_len == c_list_length (&priv->connections_lst_head));
if (G_UNLIKELY (!priv->connections_cached_list)) {
v = g_new (NMSettingsConnection *, priv->connections_len + 1);
i = 0;
c_list_for_each_entry (con, &priv->connections_lst_head, _connections_lst) {
nm_assert (i < priv->connections_len);
v[i++] = con;
}
nm_assert (i == priv->connections_len);
v[i] = NULL;
priv->connections_cached_list = v;
}
NM_SET_OUT (out_len, priv->connections_len);
return priv->connections_cached_list;
}
/**
* nm_settings_get_connections_clone:
* @self: the #NMSetting
* @out_len: (allow-none): optional output argument
* @func: caller-supplied function for filtering connections
* @func_data: caller-supplied data passed to @func
* @sort_compare_func: (allow-none): optional function pointer for
* sorting the returned list.
* @sort_data: user data for @sort_compare_func.
*
* Returns: (transfer container) (element-type NMSettingsConnection):
* an NULL terminated array of #NMSettingsConnection objects that were
* filtered by @func (or all connections if no filter was specified).
* The order is arbitrary.
* Caller is responsible for freeing the returned array with free(),
* the contained values do not need to be unrefed.
*/
NMSettingsConnection **
nm_settings_get_connections_clone (NMSettings *self,
guint *out_len,
NMSettingsConnectionFilterFunc func,
gpointer func_data,
GCompareDataFunc sort_compare_func,
gpointer sort_data)
{
NMSettingsConnection *const*list_cached;
NMSettingsConnection **list;
guint len, i, j;
g_return_val_if_fail (NM_IS_SETTINGS (self), NULL);
list_cached = nm_settings_get_connections (self, &len);
#if NM_MORE_ASSERTS
nm_assert (list_cached);
for (i = 0; i < len; i++)
nm_assert (NM_IS_SETTINGS_CONNECTION (list_cached[i]));
nm_assert (!list_cached[i]);
#endif
list = g_new (NMSettingsConnection *, ((gsize) len + 1));
if (func) {
for (i = 0, j = 0; i < len; i++) {
if (func (self, list_cached[i], func_data))
list[j++] = list_cached[i];
}
list[j] = NULL;
len = j;
} else
memcpy (list, list_cached, sizeof (list[0]) * ((gsize) len + 1));
if ( len > 1
&& sort_compare_func) {
g_qsort_with_data (list, len, sizeof (NMSettingsConnection *),
sort_compare_func, sort_data);
}
NM_SET_OUT (out_len, len);
return list;
}
NMSettingsConnection *
nm_settings_get_connection_by_path (NMSettings *self, const char *path)
{
NMSettingsPrivate *priv;
NMSettingsConnection *connection;
g_return_val_if_fail (NM_IS_SETTINGS (self), NULL);
g_return_val_if_fail (path, NULL);
priv = NM_SETTINGS_GET_PRIVATE (self);
connection = nm_dbus_manager_lookup_object (nm_dbus_object_get_manager (NM_DBUS_OBJECT (self)),
path);
if ( !connection
|| !NM_IS_SETTINGS_CONNECTION (connection))
return NULL;
nm_assert (c_list_contains (&priv->connections_lst_head, &connection->_connections_lst));
return connection;
}
gboolean
nm_settings_has_connection (NMSettings *self, NMSettingsConnection *connection)
{
gboolean has;
g_return_val_if_fail (NM_IS_SETTINGS (self), FALSE);
g_return_val_if_fail (NM_IS_SETTINGS_CONNECTION (connection), FALSE);
has = !c_list_is_empty (&connection->_connections_lst);
nm_assert (has == nm_c_list_contains_entry (&NM_SETTINGS_GET_PRIVATE (self)->connections_lst_head,
connection,
_connections_lst));
nm_assert (({
NMSettingsConnection *candidate = NULL;
const char *path;
path = nm_dbus_object_get_path (NM_DBUS_OBJECT (connection));
if (path)
candidate = nm_settings_get_connection_by_path (self, path);
(has == (connection == candidate));
}));
return has;
}
/*****************************************************************************/
static void
add_plugin (NMSettings *self,
NMSettingsPlugin *plugin,
const char *pname,
const char *path)
{
NMSettingsPrivate *priv;
nm_assert (NM_IS_SETTINGS (self));
nm_assert (NM_IS_SETTINGS_PLUGIN (plugin));
nm_assert (pname);
nm_assert (nm_streq0 (pname, nm_settings_plugin_get_plugin_name (plugin)));
priv = NM_SETTINGS_GET_PRIVATE (self);
nm_assert (!g_slist_find (priv->plugins, plugin));
priv->plugins = g_slist_append (priv->plugins, g_object_ref (plugin));
nm_shutdown_wait_obj_register_full (G_OBJECT (plugin),
g_strdup_printf ("%s-settings-plugin", pname),
TRUE);
_LOGI ("Loaded settings plugin: %s (%s%s%s)",
pname,
NM_PRINT_FMT_QUOTED (path, "\"", path, "\"", "internal"));
}
static gboolean
add_plugin_load_file (NMSettings *self, const char *pname, GError **error)
{
gs_free char *full_name = NULL;
gs_free char *path = NULL;
gs_unref_object NMSettingsPlugin *plugin = NULL;
GModule *module;
NMSettingsPluginFactoryFunc factory_func;
struct stat st;
int errsv;
full_name = g_strdup_printf ("nm-settings-plugin-%s", pname);
path = g_module_build_path (NMPLUGINDIR, full_name);
if (stat (path, &st) != 0) {
errsv = errno;
_LOGW ("could not load plugin '%s' from file '%s': %s", pname, path, nm_strerror_native (errsv));
return TRUE;
}
if (!S_ISREG (st.st_mode)) {
_LOGW ("could not load plugin '%s' from file '%s': not a file", pname, path);
return TRUE;
}
if (st.st_uid != 0) {
_LOGW ("could not load plugin '%s' from file '%s': file must be owned by root", pname, path);
return TRUE;
}
if (st.st_mode & (S_IWGRP | S_IWOTH | S_ISUID)) {
_LOGW ("could not load plugin '%s' from file '%s': invalid file permissions", pname, path);
return TRUE;
}
module = g_module_open (path, G_MODULE_BIND_LOCAL);
if (!module) {
_LOGW ("could not load plugin '%s' from file '%s': %s",
pname, path, g_module_error ());
return TRUE;
}
/* errors after this point are fatal, because we loaded the shared library already. */
if (!g_module_symbol (module, "nm_settings_plugin_factory", (gpointer) (&factory_func))) {
g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
"Could not find plugin '%s' factory function.",
pname);
g_module_close (module);
return FALSE;
}
/* after accessing the plugin we cannot unload it anymore, because the glib
* types cannot be properly unregistered. */
g_module_make_resident (module);
plugin = (*factory_func) ();
if (!NM_IS_SETTINGS_PLUGIN (plugin)) {
g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
"plugin '%s' returned invalid settings plugin",
pname);
return FALSE;
}
add_plugin (self, NM_SETTINGS_PLUGIN (plugin), pname, path);
return TRUE;
}
static void
add_plugin_keyfile (NMSettings *self)
{
NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
if (priv->keyfile_plugin)
return;
priv->keyfile_plugin = nms_keyfile_plugin_new ();
add_plugin (self, NM_SETTINGS_PLUGIN (priv->keyfile_plugin), "keyfile", NULL);
}
static gboolean
load_plugins (NMSettings *self, const char *const*plugins, GError **error)
{
const char *const*iter;
gboolean success = TRUE;
for (iter = plugins; iter && *iter; iter++) {
const char *pname = *iter;
if (!*pname || strchr (pname, '/')) {
_LOGW ("ignore invalid plugin \"%s\"", pname);
continue;
}
if (NM_IN_STRSET (pname, "ifcfg-suse", "ifnet", "ibft", "no-ibft")) {
_LOGW ("skipping deprecated plugin %s", pname);
continue;
}
/* keyfile plugin is built-in now */
if (nm_streq (pname, "keyfile")) {
add_plugin_keyfile (self);
continue;
}
if (nm_utils_strv_find_first ((char **) plugins,
iter - plugins,
pname) >= 0) {
/* the plugin is already mentioned in the list previously.
* Don't load a duplicate. */
continue;
}
success = add_plugin_load_file (self, pname, error);
if (!success)
break;
}
/* If keyfile plugin was not among configured plugins, add it as the last one */
if (success)
add_plugin_keyfile (self);
return success;
}
/*****************************************************************************/
static void
pk_hostname_cb (NMAuthChain *chain,
GDBusMethodInvocation *context,
gpointer user_data)
{
NMSettings *self = NM_SETTINGS (user_data);
NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
NMAuthCallResult result;
GError *error = NULL;
const char *hostname;
nm_assert (G_IS_DBUS_METHOD_INVOCATION (context));
c_list_unlink (nm_auth_chain_parent_lst_list (chain));
result = nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_SETTINGS_MODIFY_HOSTNAME);
/* If our NMSettingsConnection is already gone, do nothing */
if (result != NM_AUTH_CALL_RESULT_YES) {
error = g_error_new_literal (NM_SETTINGS_ERROR,
NM_SETTINGS_ERROR_PERMISSION_DENIED,
"Insufficient privileges.");
} else {
hostname = nm_auth_chain_get_data (chain, "hostname");
if (!nm_hostname_manager_write_hostname (priv->hostname_manager, hostname)) {
error = g_error_new_literal (NM_SETTINGS_ERROR,
NM_SETTINGS_ERROR_FAILED,
"Saving the hostname failed.");
}
}
if (error)
g_dbus_method_invocation_take_error (context, error);
else
g_dbus_method_invocation_return_value (context, NULL);
}
static void
impl_settings_save_hostname (NMDBusObject *obj,
const NMDBusInterfaceInfoExtended *interface_info,
const NMDBusMethodInfoExtended *method_info,
GDBusConnection *connection,
const char *sender,
GDBusMethodInvocation *invocation,
GVariant *parameters)
{
NMSettings *self = NM_SETTINGS (obj);
NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
NMAuthChain *chain;
const char *hostname;
g_variant_get (parameters, "(&s)", &hostname);
/* Minimal validation of the hostname */
if (!nm_hostname_manager_validate_hostname (hostname)) {
g_dbus_method_invocation_return_error_literal (invocation,
NM_SETTINGS_ERROR,
NM_SETTINGS_ERROR_INVALID_HOSTNAME,
"The hostname was too long or contained invalid characters.");
return;
}
chain = nm_auth_chain_new_context (invocation, pk_hostname_cb, self);
if (!chain) {
g_dbus_method_invocation_return_error_literal (invocation,
NM_SETTINGS_ERROR,
NM_SETTINGS_ERROR_PERMISSION_DENIED,
"Unable to authenticate the request.");
return;
}
c_list_link_tail (&priv->auth_lst_head, nm_auth_chain_parent_lst_list (chain));
nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_SETTINGS_MODIFY_HOSTNAME, TRUE);
nm_auth_chain_set_data (chain, "hostname", g_strdup (hostname), g_free);
}
/*****************************************************************************/
static void
_hostname_changed_cb (NMHostnameManager *hostname_manager,
GParamSpec *pspec,
gpointer user_data)
{
_notify (user_data, PROP_HOSTNAME);
}
/*****************************************************************************/
static gboolean
have_connection_for_device (NMSettings *self, NMDevice *device)
{
NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
NMSettingWired *s_wired;
const char *setting_hwaddr;
const char *perm_hw_addr;
NMSettingsConnection *sett_conn;
g_return_val_if_fail (NM_IS_SETTINGS (self), FALSE);
perm_hw_addr = nm_device_get_permanent_hw_address (device);
/* Find a wired connection locked to the given MAC address, if any */
c_list_for_each_entry (sett_conn, &priv->connections_lst_head, _connections_lst) {
NMConnection *connection = nm_settings_connection_get_connection (sett_conn);
NMSettingConnection *s_con = nm_connection_get_setting_connection (connection);
const char *ctype;
const char *iface;
ctype = nm_setting_connection_get_connection_type (s_con);
if (!NM_IN_STRSET (ctype, NM_SETTING_WIRED_SETTING_NAME,
NM_SETTING_PPPOE_SETTING_NAME))
continue;
if (!nm_device_check_connection_compatible (device, connection, NULL))
continue;
if (nm_settings_connection_default_wired_get_device (sett_conn))
continue;
if (NM_FLAGS_ANY (nm_settings_connection_get_flags (sett_conn),
NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE))
continue;
iface = nm_setting_connection_get_interface_name (s_con);
if (!nm_streq0 (iface, nm_device_get_iface (device)))
continue;
s_wired = nm_connection_get_setting_wired (connection);
if ( !s_wired
&& nm_streq (ctype, NM_SETTING_PPPOE_SETTING_NAME)) {
/* No wired setting; therefore the PPPoE connection applies to any device */
return TRUE;
}
setting_hwaddr = nm_setting_wired_get_mac_address (s_wired);
if (setting_hwaddr) {
/* A connection mac-locked to this device */
if ( perm_hw_addr
&& nm_utils_hwaddr_matches (setting_hwaddr, -1, perm_hw_addr, -1))
return TRUE;
} else {
/* A connection that applies to any wired device */
return TRUE;
}
}
/* See if there's a known non-NetworkManager configuration for the device */
if (nm_device_spec_match_list (device, priv->unrecognized_specs))
return TRUE;
return FALSE;
}
static void
default_wired_clear_tag (NMSettings *self,
NMDevice *device,
NMSettingsConnection *sett_conn,
gboolean add_to_no_auto_default)
{
nm_assert (NM_IS_SETTINGS (self));
nm_assert (NM_IS_DEVICE (device));
nm_assert (NM_IS_SETTINGS_CONNECTION (sett_conn));
nm_assert (device == nm_settings_connection_default_wired_get_device (sett_conn));
nm_assert (sett_conn == g_object_get_qdata (G_OBJECT (device), _default_wired_connection_quark ()));
_LOGT ("auto-default: forget association between %s (%s) and device %s (%s)",
nm_settings_connection_get_uuid (sett_conn),
nm_settings_connection_get_id (sett_conn),
nm_device_get_iface (device),
add_to_no_auto_default ? "persisted" : "temporary");
nm_settings_connection_default_wired_set_device (sett_conn, NULL);
g_object_set_qdata (G_OBJECT (device), _default_wired_connection_quark (), NULL);
if (add_to_no_auto_default)
nm_config_set_no_auto_default_for_device (NM_SETTINGS_GET_PRIVATE (self)->config, device);
}
static void
device_realized (NMDevice *device, GParamSpec *pspec, NMSettings *self)
{
gs_unref_object NMConnection *connection = NULL;
NMSettingsPrivate *priv;
NMSettingsConnection *added;
GError *error = NULL;
if (!nm_device_is_real (device))
return;
g_signal_handlers_disconnect_by_func (device,
G_CALLBACK (device_realized),
self);
priv = NM_SETTINGS_GET_PRIVATE (self);
/* If the device isn't managed or it already has a default wired connection,
* ignore it.
*/
if ( !nm_device_get_managed (device, FALSE)
|| g_object_get_qdata (G_OBJECT (device), _default_wired_connection_quark ())
|| have_connection_for_device (self, device)
|| nm_config_get_no_auto_default_for_device (priv->config, device))
return;
connection = nm_device_new_default_connection (device);
if (!connection)
return;
_LOGT ("auto-default: creating in-memory connection %s (%s) for device %s",
nm_connection_get_uuid (connection),
nm_connection_get_id (connection),
nm_device_get_iface (device));
nm_settings_add_connection (self,
connection,
NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY,
NM_SETTINGS_CONNECTION_ADD_REASON_NONE,
NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED,
&added,
&error);
if (!added) {
if (!g_error_matches (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_UUID_EXISTS)) {
_LOGW ("(%s) couldn't create default wired connection: %s",
nm_device_get_iface (device),
error->message);
}
g_clear_error (&error);
return;
}
nm_settings_connection_default_wired_set_device (added, device);
g_object_set_qdata (G_OBJECT (device), _default_wired_connection_quark (), added);
_LOGI ("(%s): created default wired connection '%s'",
nm_device_get_iface (device),
nm_settings_connection_get_id (added));
}
void
nm_settings_device_added (NMSettings *self, NMDevice *device)
{
if (nm_device_is_real (device))
device_realized (device, NULL, self);
else {
/* FIXME(shutdown): we need to disconnect this signal handler during
* shutdown. */
g_signal_connect_after (device, "notify::" NM_DEVICE_REAL,
G_CALLBACK (device_realized),
self);
}
}
void
nm_settings_device_removed (NMSettings *self, NMDevice *device, gboolean quitting)
{
NMSettingsConnection *connection;
g_signal_handlers_disconnect_by_func (device,
G_CALLBACK (device_realized),
self);
connection = g_object_get_qdata (G_OBJECT (device), _default_wired_connection_quark ());
if (connection) {
default_wired_clear_tag (self, device, connection, FALSE);
/* Don't delete the default wired connection on shutdown, so that it
* remains up and can be assumed if NM starts again.
*/
if (quitting == FALSE)
nm_settings_connection_delete (connection, TRUE);
}
}
/*****************************************************************************/
static void
session_monitor_changed_cb (NMSessionMonitor *session_monitor,
NMSettings *self)
{
NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
NMSettingsConnection *const*list;
guint i, len;
guint generation;
again:
list = nm_settings_get_connections (self, &len);
generation = priv->connections_generation;
for (i = 0; i < len; i++) {
gboolean is_visible;
is_visible = nm_settings_connection_check_visibility (list[i],
session_monitor);
nm_settings_connection_set_flags (list[i],
NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE,
is_visible);
if (generation != priv->connections_generation) {
/* the cached list was invalidated. Start again.
*
* Note that nm_settings_connection_recheck_visibility() will do nothing
* if the visibility didn't change (including emitting no signals,
* and not invalidating the list).
*
* Hence, for this to be an endless loop, the settings would have
* to constantly change the visibility flag and also invalidate the list. */
goto again;
}
}
}
/*****************************************************************************/
G_GNUC_PRINTF (4, 5)
static void
_kf_db_log_fcn (NMKeyFileDB *kf_db,
int syslog_level,
gpointer user_data,
const char *fmt,
...)
{
NMSettings *self = user_data;
NMLogLevel level = nm_log_level_from_syslog (syslog_level);
if (_NMLOG_ENABLED (level)) {
NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
gs_free char *msg = NULL;
va_list ap;
const char *prefix;
va_start (ap, fmt);
msg = g_strdup_vprintf (fmt, ap);
va_end (ap);
if (priv->kf_db_timestamps == kf_db)
prefix = "timestamps";
else if (priv->kf_db_seen_bssids == kf_db)
prefix = "seen-bssids";
else {
nm_assert_not_reached ();
prefix = "???";
}
_NMLOG (level, "[%s-keyfile]: %s", prefix, msg);
}
}
static gboolean
_kf_db_got_dirty_flush (NMSettings *self,
gboolean is_timestamps)
{
NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
const char *prefix;
NMKeyFileDB *kf_db;
if (is_timestamps) {
prefix = "timestamps";
kf_db = priv->kf_db_timestamps;
priv->kf_db_flush_idle_id_timestamps = 0;
} else {
prefix = "seen-bssids";
kf_db = priv->kf_db_seen_bssids;
priv->kf_db_flush_idle_id_seen_bssids = 0;
}
if (nm_key_file_db_is_dirty (kf_db))
nm_key_file_db_to_file (kf_db, FALSE);
else {
_LOGT ("[%s-keyfile]: skip saving changes to \"%s\"",
prefix,
nm_key_file_db_get_filename (kf_db));
}
return G_SOURCE_REMOVE;
}
static gboolean
_kf_db_got_dirty_flush_timestamps_cb (gpointer user_data)
{
return _kf_db_got_dirty_flush (user_data,
TRUE);
}
static gboolean
_kf_db_got_dirty_flush_seen_bssids_cb (gpointer user_data)
{
return _kf_db_got_dirty_flush (user_data,
FALSE);
}
static void
_kf_db_got_dirty_fcn (NMKeyFileDB *kf_db,
gpointer user_data)
{
NMSettings *self = user_data;
NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
GSourceFunc idle_func;
guint *p_id;
const char *prefix;
if (priv->kf_db_timestamps == kf_db) {
prefix = "timestamps";
p_id = &priv->kf_db_flush_idle_id_timestamps;
idle_func = _kf_db_got_dirty_flush_timestamps_cb;
} else if (priv->kf_db_seen_bssids == kf_db) {
prefix = "seen-bssids";
p_id = &priv->kf_db_flush_idle_id_seen_bssids;
idle_func = _kf_db_got_dirty_flush_seen_bssids_cb;
} else {
nm_assert_not_reached ();
return;
}
if (*p_id != 0)
return;
_LOGT ("[%s-keyfile]: schedule flushing changes to disk", prefix);
*p_id = g_idle_add_full (G_PRIORITY_LOW, idle_func, self, NULL);
}
void
nm_settings_kf_db_write (NMSettings *self)
{
NMSettingsPrivate *priv;
g_return_if_fail (NM_IS_SETTINGS (self));
priv = NM_SETTINGS_GET_PRIVATE (self);
if (priv->kf_db_timestamps)
nm_key_file_db_to_file (priv->kf_db_timestamps, TRUE);
if (priv->kf_db_seen_bssids)
nm_key_file_db_to_file (priv->kf_db_seen_bssids, TRUE);
}
/*****************************************************************************/
gboolean
nm_settings_start (NMSettings *self, GError **error)
{
NMSettingsPrivate *priv;
gs_strfreev char **plugins = NULL;
GSList *iter;
priv = NM_SETTINGS_GET_PRIVATE (self);
nm_assert (!priv->started);
priv->hostname_manager = g_object_ref (nm_hostname_manager_get ());
priv->kf_db_timestamps = nm_key_file_db_new (NMSTATEDIR "/timestamps",
"timestamps",
_kf_db_log_fcn,
_kf_db_got_dirty_fcn,
self);
priv->kf_db_seen_bssids = nm_key_file_db_new (NMSTATEDIR "/seen-bssids",
"seen-bssids",
_kf_db_log_fcn,
_kf_db_got_dirty_fcn,
self);
nm_key_file_db_start (priv->kf_db_timestamps);
nm_key_file_db_start (priv->kf_db_seen_bssids);
/* Load the plugins; fail if a plugin is not found. */
plugins = nm_config_data_get_plugins (nm_config_get_data_orig (priv->config), TRUE);
if (!load_plugins (self, (const char *const*) plugins, error))
return FALSE;
for (iter = priv->plugins; iter; iter = iter->next) {
NMSettingsPlugin *plugin = NM_SETTINGS_PLUGIN (iter->data);
g_signal_connect (plugin, NM_SETTINGS_PLUGIN_UNMANAGED_SPECS_CHANGED,
G_CALLBACK (_plugin_unmanaged_specs_changed), self);
g_signal_connect (plugin, NM_SETTINGS_PLUGIN_UNRECOGNIZED_SPECS_CHANGED,
G_CALLBACK (_plugin_unrecognized_specs_changed), self);
}
_plugin_unmanaged_specs_changed (NULL, self);
_plugin_unrecognized_specs_changed (NULL, self);
_plugin_connections_reload (self);
g_signal_connect (priv->hostname_manager,
"notify::"NM_HOSTNAME_MANAGER_HOSTNAME,
G_CALLBACK (_hostname_changed_cb),
self);
if (nm_hostname_manager_get_hostname (priv->hostname_manager))
_notify (self, PROP_HOSTNAME);
priv->started = TRUE;
_startup_complete_check (self, 0);
/* FIXME(shutdown): we also need a nm_settings_stop() during shutdown.
*
* In particular, we need to remove all in-memory keyfiles from /run that are nm-generated.
* alternatively, the nm-generated flag must also be persisted and loaded to /run. */
return TRUE;
}
/*****************************************************************************/
static void
get_property (GObject *object, guint prop_id,
GValue *value, GParamSpec *pspec)
{
NMSettings *self = NM_SETTINGS (object);
NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
const char **strv;
switch (prop_id) {
case PROP_UNMANAGED_SPECS:
g_value_take_boxed (value,
_nm_utils_slist_to_strv (nm_settings_get_unmanaged_specs (self),
TRUE));
break;
case PROP_HOSTNAME:
g_value_set_string (value,
priv->hostname_manager
? nm_hostname_manager_get_hostname (priv->hostname_manager)
: NULL);
break;
case PROP_CAN_MODIFY:
g_value_set_boolean (value, TRUE);
break;
case PROP_CONNECTIONS:
strv = nm_dbus_utils_get_paths_for_clist (&priv->connections_lst_head,
priv->connections_len,
G_STRUCT_OFFSET (NMSettingsConnection, _connections_lst),
TRUE);
g_value_take_boxed (value, nm_utils_strv_make_deep_copied (strv));
break;
case PROP_STARTUP_COMPLETE:
g_value_set_boolean (value, !nm_settings_get_startup_complete_blocked_reason (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
/*****************************************************************************/
static void
nm_settings_init (NMSettings *self)
{
NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
c_list_init (&priv->auth_lst_head);
c_list_init (&priv->connections_lst_head);
c_list_init (&priv->sce_dirty_lst_head);
priv->sce_idx = g_hash_table_new_full (nm_pstr_hash, nm_pstr_equal,
NULL, (GDestroyNotify) _sett_conn_entry_free);
priv->config = g_object_ref (nm_config_get ());
priv->agent_mgr = g_object_ref (nm_agent_manager_get ());
priv->platform = g_object_ref (NM_PLATFORM_GET);
priv->session_monitor = g_object_ref (nm_session_monitor_get ());
g_signal_connect (priv->session_monitor,
NM_SESSION_MONITOR_CHANGED,
G_CALLBACK (session_monitor_changed_cb),
self);
}
NMSettings *
nm_settings_new (void)
{
return g_object_new (NM_TYPE_SETTINGS, NULL);
}
static void
dispose (GObject *object)
{
NMSettings *self = NM_SETTINGS (object);
NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
CList *iter;
nm_assert (c_list_is_empty (&priv->sce_dirty_lst_head));
nm_assert (g_hash_table_size (priv->sce_idx) == 0);
nm_clear_g_source (&priv->startup_complete_timeout_id);
nm_clear_g_signal_handler (priv->platform, &priv->startup_complete_platform_change_id);
nm_clear_pointer (&priv->startup_complete_idx, g_hash_table_destroy);
g_clear_object (&priv->startup_complete_blocked_by);
while ((iter = c_list_first (&priv->auth_lst_head)))
nm_auth_chain_destroy (nm_auth_chain_parent_lst_entry (iter));
if (priv->hostname_manager) {
g_signal_handlers_disconnect_by_func (priv->hostname_manager,
G_CALLBACK (_hostname_changed_cb),
self);
g_clear_object (&priv->hostname_manager);
}
if (priv->session_monitor) {
g_signal_handlers_disconnect_by_func (priv->session_monitor,
G_CALLBACK (session_monitor_changed_cb),
self);
g_clear_object (&priv->session_monitor);
}
G_OBJECT_CLASS (nm_settings_parent_class)->dispose (object);
}
static void
finalize (GObject *object)
{
NMSettings *self = NM_SETTINGS (object);
NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
GSList *iter;
_clear_connections_cached_list (priv);
nm_assert (c_list_is_empty (&priv->connections_lst_head));
nm_assert (c_list_is_empty (&priv->sce_dirty_lst_head));
nm_assert (g_hash_table_size (priv->sce_idx) == 0);
nm_clear_pointer (&priv->sce_idx, g_hash_table_destroy);
g_slist_free_full (priv->unmanaged_specs, g_free);
g_slist_free_full (priv->unrecognized_specs, g_free);
while ((iter = priv->plugins)) {
gs_unref_object NMSettingsPlugin *plugin = iter->data;
priv->plugins = g_slist_delete_link (priv->plugins, iter);
g_signal_handlers_disconnect_by_data (plugin, self);
}
g_clear_object (&priv->keyfile_plugin);
g_clear_object (&priv->agent_mgr);
nm_clear_g_source (&priv->kf_db_flush_idle_id_timestamps);
nm_clear_g_source (&priv->kf_db_flush_idle_id_seen_bssids);
nm_key_file_db_to_file (priv->kf_db_timestamps, FALSE);
nm_key_file_db_to_file (priv->kf_db_seen_bssids, FALSE);
nm_key_file_db_destroy (priv->kf_db_timestamps);
nm_key_file_db_destroy (priv->kf_db_seen_bssids);
G_OBJECT_CLASS (nm_settings_parent_class)->finalize (object);
g_clear_object (&priv->config);
g_clear_object (&priv->platform);
}
static const GDBusSignalInfo signal_info_new_connection = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT (
"NewConnection",
.args = NM_DEFINE_GDBUS_ARG_INFOS (
NM_DEFINE_GDBUS_ARG_INFO ("connection", "o"),
),
);
static const GDBusSignalInfo signal_info_connection_removed = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT (
"ConnectionRemoved",
.args = NM_DEFINE_GDBUS_ARG_INFOS (
NM_DEFINE_GDBUS_ARG_INFO ("connection", "o"),
),
);
static const NMDBusInterfaceInfoExtended interface_info_settings = {
.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
NM_DBUS_INTERFACE_SETTINGS,
.methods = NM_DEFINE_GDBUS_METHOD_INFOS (
NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
NM_DEFINE_GDBUS_METHOD_INFO_INIT (
"ListConnections",
.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
NM_DEFINE_GDBUS_ARG_INFO ("connections", "ao"),
),
),
.handle = impl_settings_list_connections,
),
NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
NM_DEFINE_GDBUS_METHOD_INFO_INIT (
"GetConnectionByUuid",
.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
NM_DEFINE_GDBUS_ARG_INFO ("uuid", "s"),
),
.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
NM_DEFINE_GDBUS_ARG_INFO ("connection", "o"),
),
),
.handle = impl_settings_get_connection_by_uuid,
),
NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
NM_DEFINE_GDBUS_METHOD_INFO_INIT (
"AddConnection",
.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
NM_DEFINE_GDBUS_ARG_INFO ("connection", "a{sa{sv}}"),
),
.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
NM_DEFINE_GDBUS_ARG_INFO ("path", "o"),
),
),
.handle = impl_settings_add_connection,
),
NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
NM_DEFINE_GDBUS_METHOD_INFO_INIT (
"AddConnectionUnsaved",
.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
NM_DEFINE_GDBUS_ARG_INFO ("connection", "a{sa{sv}}"),
),
.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
NM_DEFINE_GDBUS_ARG_INFO ("path", "o"),
),
),
.handle = impl_settings_add_connection_unsaved,
),
NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
NM_DEFINE_GDBUS_METHOD_INFO_INIT (
"AddConnection2",
.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
NM_DEFINE_GDBUS_ARG_INFO ("settings", "a{sa{sv}}"),
NM_DEFINE_GDBUS_ARG_INFO ("flags", "u"),
NM_DEFINE_GDBUS_ARG_INFO ("args", "a{sv}"),
),
.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
NM_DEFINE_GDBUS_ARG_INFO ("path", "o"),
NM_DEFINE_GDBUS_ARG_INFO ("result", "a{sv}"),
),
),
.handle = impl_settings_add_connection2,
),
NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
NM_DEFINE_GDBUS_METHOD_INFO_INIT (
"LoadConnections",
.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
NM_DEFINE_GDBUS_ARG_INFO ("filenames", "as"),
),
.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
NM_DEFINE_GDBUS_ARG_INFO ("status", "b"),
NM_DEFINE_GDBUS_ARG_INFO ("failures", "as"),
),
),
.handle = impl_settings_load_connections,
),
NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
NM_DEFINE_GDBUS_METHOD_INFO_INIT (
"ReloadConnections",
.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
NM_DEFINE_GDBUS_ARG_INFO ("status", "b"),
),
),
.handle = impl_settings_reload_connections,
),
NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
NM_DEFINE_GDBUS_METHOD_INFO_INIT (
"SaveHostname",
.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
NM_DEFINE_GDBUS_ARG_INFO ("hostname", "s"),
),
),
.handle = impl_settings_save_hostname,
),
),
.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
&nm_signal_info_property_changed_legacy,
&signal_info_new_connection,
&signal_info_connection_removed,
),
.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Connections", "ao", NM_SETTINGS_CONNECTIONS),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Hostname", "s", NM_SETTINGS_HOSTNAME),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("CanModify", "b", NM_SETTINGS_CAN_MODIFY),
),
),
.legacy_property_changed = TRUE,
};
static void
nm_settings_class_init (NMSettingsClass *class)
{
GObjectClass *object_class = G_OBJECT_CLASS (class);
NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (class);
dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_STATIC (NM_DBUS_PATH_SETTINGS);
dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_settings);
object_class->get_property = get_property;
object_class->dispose = dispose;
object_class->finalize = finalize;
obj_properties[PROP_UNMANAGED_SPECS] =
g_param_spec_boxed (NM_SETTINGS_UNMANAGED_SPECS, "", "",
G_TYPE_STRV,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS);
obj_properties[PROP_HOSTNAME] =
g_param_spec_string (NM_SETTINGS_HOSTNAME, "", "",
NULL,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS);
obj_properties[PROP_CAN_MODIFY] =
g_param_spec_boolean (NM_SETTINGS_CAN_MODIFY, "", "",
FALSE,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS);
obj_properties[PROP_CONNECTIONS] =
g_param_spec_boxed (NM_SETTINGS_CONNECTIONS, "", "",
G_TYPE_STRV,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS);
obj_properties[PROP_STARTUP_COMPLETE] =
g_param_spec_boolean (NM_SETTINGS_STARTUP_COMPLETE, "", "",
FALSE,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
signals[CONNECTION_ADDED] =
g_signal_new (NM_SETTINGS_SIGNAL_CONNECTION_ADDED,
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_FIRST,
0, NULL, NULL,
g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE, 1, NM_TYPE_SETTINGS_CONNECTION);
signals[CONNECTION_UPDATED] =
g_signal_new (NM_SETTINGS_SIGNAL_CONNECTION_UPDATED,
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_FIRST,
0, NULL, NULL,
NULL,
G_TYPE_NONE, 2, NM_TYPE_SETTINGS_CONNECTION, G_TYPE_UINT);
signals[CONNECTION_REMOVED] =
g_signal_new (NM_SETTINGS_SIGNAL_CONNECTION_REMOVED,
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_FIRST,
0, NULL, NULL,
g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE, 1, NM_TYPE_SETTINGS_CONNECTION);
signals[CONNECTION_FLAGS_CHANGED] =
g_signal_new (NM_SETTINGS_SIGNAL_CONNECTION_FLAGS_CHANGED,
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_FIRST,
0, NULL, NULL,
g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE, 1, NM_TYPE_SETTINGS_CONNECTION);
}