diff options
Diffstat (limited to 'src/core/settings/nm-settings.c')
| -rw-r--r-- | src/core/settings/nm-settings.c | 4144 |
1 files changed, 4144 insertions, 0 deletions
diff --git a/src/core/settings/nm-settings.c b/src/core/settings/nm-settings.c new file mode 100644 index 00000000..3d31fb8b --- /dev/null +++ b/src/core/settings/nm-settings.c @@ -0,0 +1,4144 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Søren Sandmann <sandmann@daimi.au.dk> + * Dan Williams <dcbw@redhat.com> + * Tambet Ingo <tambet@gmail.com> + * Copyright (C) 2007 - 2011 Red Hat, Inc. + * Copyright (C) 2008 Novell, Inc. + */ + +#include "src/core/nm-default-daemon.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-libnm-core-intern/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-manager.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); + +static NM_CACHED_QUARK_FCN("default-wired-connection-blocked", + _default_wired_connection_blocked_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; +} + +/** + * _sett_conn_entry_storage_find_conflicting_storage: + * @sett_conn_entry: the list of settings-storages for the given UUID. + * @target_plugin: the settings plugin to check + * @storage_check_including: (allow-none): optionally compare against this storage. + * @plugins: the list of plugins sorted in descending priority. This determines + * the priority and whether a storage conflicts. + * + * If we were to add the a storage to @target_plugin, then this function checks + * whether there are already other storages that would hide the storage after we + * add it. Those conflicting/hiding storages are a problem, because they have higher + * priority, so we cannot add the storage. + * + * @storage_check_including is optional, and if given then it checks whether updating + * the profile in this storage would result in confict. This is the check before + * update-connection. If this parameter is omitted, then it's about what happens + * when adding a new profile (add-connection). + * + * Returns: the conflicting storage or %NULL if there is none. + */ +static NMSettingsStorage * +_sett_conn_entry_storage_find_conflicting_storage(SettConnEntry * sett_conn_entry, + NMSettingsPlugin * target_plugin, + NMSettingsStorage *storage_check_including, + const GSList * plugins) +{ + StorageData *sd; + + if (!sett_conn_entry) + return NULL; + + if (storage_check_including && nm_settings_storage_is_keyfile_run(storage_check_including)) { + /* the storage we check against is in-memory. It always has highest + * priority, so there can be no other conflicting storages. */ + return NULL; + } + + /* Finds the first (highest priority) storage that has a connection. + * Note that due to tombstones (that have a high priority), the connection + * may not actually be exposed. This is to find hidden/shadowed storages + * that provide a connection. */ + c_list_for_each_entry (sd, &sett_conn_entry->sd_lst_head, sd_lst) { + nm_assert(NM_IS_SETTINGS_STORAGE(sd->storage)); + + if (!sd->connection) { + /* We only consider storages with connection. In particular, + * tombstones are not relevant, because we can delete them to + * resolve the conflict. */ + continue; + } + + if (sd->storage == storage_check_including) { + /* ok, the storage is the one we are about to check. All other + * storages are lower priority, so there is no storage that hides + * our storage_check_including. */ + return NULL; + } + + if (nm_settings_plugin_cmp_by_priority(nm_settings_storage_get_plugin(sd->storage), + target_plugin, + plugins) + <= 0) { + /* the plugin of the existing storage is less important than @target_plugin. + * We have no conflicting/hiding storage. */ + return NULL; + } + + /* Found. If we would add the profile to @target_plugin, then it would be hidden + * by existing_storage. */ + return sd->storage; + } + + return NULL; +} + +static NMSettingsStorage * +_sett_conn_entry_find_shadowed_storage(SettConnEntry * sett_conn_entry, + const char * shadowed_storage_filename, + NMSettingsStorage *blacklisted_storage) +{ + StorageData *sd; + + if (!shadowed_storage_filename) + return NULL; + + c_list_for_each_entry (sd, &sett_conn_entry->sd_lst_head, sd_lst) { + nm_assert(NM_IS_SETTINGS_STORAGE(sd->storage)); + + if (!sd->connection) + continue; + + if (blacklisted_storage == sd->storage) + continue; + + if (!nm_streq0(nm_settings_storage_get_filename_for_shadowed_storage(sd->storage), + shadowed_storage_filename)) + continue; + + return sd->storage; + } + + return NULL; +} + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMSettings, + PROP_MANAGER, + 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; + + NMManager *manager; + + 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; + + gint64 startup_complete_start_timestamp_msec; + GHashTable *startup_complete_idx; + CList startup_complete_scd_lst_head; + 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_msec); + +/*****************************************************************************/ + +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; + CList scd_lst; + gint64 timeout_msec; +} StartupCompleteData; + +static void +_startup_complete_data_destroy(StartupCompleteData *scd) +{ + c_list_unlink_stale(&scd->scd_lst); + g_object_unref(scd->sett_conn); + nm_g_slice_free(scd); +} + +static gboolean +_startup_complete_check_is_ready(NMSettings * self, + NMSettingsConnection *sett_conn, + gboolean ignore_pending_actions) +{ + NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self); + NMConnection * conn; + const CList * tmp_lst; + NMDevice * device; + + if (!priv->manager) + return TRUE; + + conn = nm_settings_connection_get_connection(sett_conn); + + nm_manager_for_each_device (priv->manager, device, tmp_lst) { + if (!nm_device_is_real(device)) + continue; + + if (nm_device_get_state(device) < NM_DEVICE_STATE_UNAVAILABLE + || (!ignore_pending_actions && nm_device_has_pending_action(device))) { + /* while a device is not yet available and still has a pending + * action itself, it's not a suitable candidate. */ + continue; + } + + if (!nm_device_check_connection_compatible(device, conn, NULL)) + continue; + + return TRUE; + } + + return FALSE; +} + +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_check(NMSettings *self, gint64 now_msec) +{ + NMSettingsPrivate * priv = NM_SETTINGS_GET_PRIVATE(self); + StartupCompleteData *scd_not_ready; + StartupCompleteData *scd_safe; + StartupCompleteData *scd; + gint64 elapsed_msec; + CList ready_lst; + + if (priv->startup_complete_start_timestamp_msec == 0) { + /* we are already done for good or didn't start yet. */ + return; + } + + if (!priv->started) { + /* before we are started there is no need to evaluate our list because + * we are anyway blocking startup-complete. */ + return; + } + + if (c_list_is_empty(&priv->startup_complete_scd_lst_head)) + goto ready; + + nm_utils_get_monotonic_timestamp_msec_cached(&now_msec); + + elapsed_msec = now_msec - priv->startup_complete_start_timestamp_msec; + + /* We search the entire list whether they all timed-out or found a compatible device. + * We do that by appending elements that are ready to the end of the list, so that + * we hopefully keep testing the elements that are ready already (and can shortcut + * the test in common cases). + * + * Note that all profiles that we wait for need to have their dependencies satisfied + * at the same time. For example, consider connection A is waiting for device A' which is ready. + * Connection B waits for device B', which isn't ready. Once B'/B becomes ready, A/A' must + * still be ready. Otherwise, we would wait for A/A' to become ready again. */ + scd_not_ready = NULL; + c_list_init(&ready_lst); + c_list_for_each_entry_safe (scd, scd_safe, &priv->startup_complete_scd_lst_head, scd_lst) { + if (scd->timeout_msec <= elapsed_msec) + goto next_with_ready; + + if (_startup_complete_check_is_ready(self, scd->sett_conn, FALSE)) + goto next_with_ready; + + scd_not_ready = scd; + break; + +next_with_ready: + /* this element is ready. We move it to a temporary list, so that we + * can reorder the list (to next time evaluate the non-ready element first). */ + nm_c_list_move_tail(&ready_lst, &scd->scd_lst); + } + c_list_splice(&priv->startup_complete_scd_lst_head, &ready_lst); + + nm_clear_g_source(&priv->startup_complete_timeout_id); + + if (scd_not_ready) { + gint64 timeout_msec; + + timeout_msec = priv->startup_complete_start_timestamp_msec + scd_not_ready->timeout_msec + - nm_utils_get_monotonic_timestamp_msec(); + priv->startup_complete_timeout_id = + g_timeout_add(NM_CLAMP(0, timeout_msec, 60000), _startup_complete_timeout_cb, self); + _LOGT("startup-complete: wait for suitable device for connection \"%s\" (%s) which has " + "\"connection.wait-device-timeout\" set", + nm_settings_connection_get_id(scd_not_ready->sett_conn), + nm_settings_connection_get_uuid(scd_not_ready->sett_conn)); + return; + } + + if (_LOGW_ENABLED()) { + c_list_for_each_entry (scd, &priv->startup_complete_scd_lst_head, scd_lst) { + if (!_startup_complete_check_is_ready(self, scd->sett_conn, TRUE)) { + _LOGW("startup-complete: profile \"%s\" (%s) was waiting for non-existing device " + "(with timeout \"connection.wait-device-timeout=%" G_GINT64_FORMAT "\")", + nm_settings_connection_get_id(scd->sett_conn), + nm_settings_connection_get_uuid(scd->sett_conn), + scd->timeout_msec); + } + } + } + +ready: + nm_clear_pointer(&priv->startup_complete_idx, g_hash_table_destroy); + nm_assert(c_list_is_empty(&priv->startup_complete_scd_lst_head)); + nm_assert(priv->started); + _LOGT("startup-complete: ready, no more profiles to wait for"); + priv->startup_complete_start_timestamp_msec = 0; + nm_assert(!priv->startup_complete_idx); + nm_assert(priv->startup_complete_timeout_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); + StartupCompleteData *scd; + gint64 timeout_msec; + gint64 now_msec = 0; + NMSettingConnection *s_con; + gint32 v; + + nm_assert(priv->startup_complete_start_timestamp_msec != 0); + + if (forget) { + if (!priv->startup_complete_idx) + return; + if (!g_hash_table_remove(priv->startup_complete_idx, &sett_conn)) + return; + goto check; + } + + 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) + timeout_msec = v; + else + timeout_msec = 0; + + if (!priv->startup_complete_idx) { + nm_assert(!priv->started); + + if (timeout_msec == 0) + return; + + 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) { + if (timeout_msec == 0) + return; + scd = g_slice_new(StartupCompleteData); + *scd = (StartupCompleteData){ + .sett_conn = g_object_ref(sett_conn), + .timeout_msec = timeout_msec, + }; + g_hash_table_add(priv->startup_complete_idx, scd); + c_list_link_tail(&priv->startup_complete_scd_lst_head, &scd->scd_lst); + } else { + scd->timeout_msec = timeout_msec; + nm_c_list_move_front(&priv->startup_complete_scd_lst_head, &scd->scd_lst); + } + +check: + _startup_complete_check(self, now_msec); +} + +const char * +nm_settings_get_startup_complete_blocked_reason(NMSettings *self, gboolean force_reload) +{ + NMSettingsPrivate * priv = NM_SETTINGS_GET_PRIVATE(self); + StartupCompleteData *scd; + const char * uuid; + + if (priv->startup_complete_start_timestamp_msec == 0) + goto out_done; + + if (force_reload) + _startup_complete_check(self, 0); + + if (c_list_is_empty(&priv->startup_complete_scd_lst_head)) + goto out_done; + + scd = c_list_first_entry(&priv->startup_complete_scd_lst_head, StartupCompleteData, scd_lst); + + nm_assert(scd); + nm_assert(NM_IS_SETTINGS_CONNECTION(scd->sett_conn)); + nm_assert(scd == nm_g_hash_table_lookup(priv->startup_complete_idx, &scd->sett_conn)); + + uuid = nm_settings_connection_get_uuid(scd->sett_conn); + if (uuid) + return uuid; + + g_return_val_if_reached("settings-starting"); + +out_done: + if (!priv->started) + return "settings-starting"; + return NULL; +} + +/*****************************************************************************/ + +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_ascending(const StorageData *sd_a, + const StorageData *sd_b, + const GSList * plugins) +{ + const NMSettingsMetaData *meta_data_a; + const NMSettingsMetaData *meta_data_b; + bool is_keyfile_run_a; + bool is_keyfile_run_b; + + /* Sort storages by priority. More important storages are sorted + * higher (ascending sort). For example, if "sd_a" is more important than + * "sd_b" (sd_a>sd_b), a positive integer is returned. */ + + meta_data_a = nm_settings_storage_is_meta_data(sd_a->storage); + meta_data_b = nm_settings_storage_is_meta_data(sd_b->storage); + + /* runtime storages (both connections and meta-data) are always more + * important. */ + is_keyfile_run_a = nm_settings_storage_is_keyfile_run(sd_a->storage); + is_keyfile_run_b = nm_settings_storage_is_keyfile_run(sd_b->storage); + if (is_keyfile_run_a != is_keyfile_run_b) { + if (!meta_data_a && !meta_data_b) { + /* Ok, both are non-meta-data providing actual profiles. But one is in /run and one is in + * another storage. In this case we first honor whether one of the storages is explicitly + * prioritized. The prioritize flag is an in-memory hack to overwrite relative priorities + * contrary to what exists on-disk. + * + * This is done because when we use explicit D-Bus API (like update-connection) + * to update a profile, then we really want to prioritize the candidate + * despite having multiple other profiles. + * + * The example is if you have the same UUID twice in /run (one of them shadowed). + * If you move it to disk, then one of the profiles gets deleted and re-created + * on disk, but that on-disk profile must win against the remainging profile in + * /run. At least until the next reload/restart. */ + NM_CMP_FIELD_UNSAFE(sd_a, sd_b, prioritize); + } + + /* in-memory has higher priority. That is regardless of whether any of + * them is meta-data/tombstone or a profile. + * + * That works, because if any of them are tombstones/metadata, then we are in full + * control. There can by only one meta-data file, which is fully owned (and accordingly + * created/deleted) by NetworkManager. + * + * The only case where this might not be right is if we have profiles + * in /run that are shadowed. When we move such a profile to disk, then + * a conflict might arise. That is handled by "prioritize" above! */ + NM_CMP_DIRECT(is_keyfile_run_a, is_keyfile_run_b); + } + + /* After we determined that both profiles are either in /run or not, + * tombstones are always more important than non-tombstones. */ + NM_CMP_DIRECT(meta_data_a && meta_data_a->is_tombstone, + meta_data_b && meta_data_b->is_tombstone); + + /* Again, prioritized entries are sorted first (higher priority). */ + NM_CMP_FIELD_UNSAFE(sd_a, sd_b, prioritize); + + /* finally, compare the storages. This basically honors the timestamp + * of the profile and the relative order of the source plugin (via the + * @plugins list). */ + return nm_settings_storage_cmp(sd_a->storage, sd_b->storage, plugins); +} + +static int +_sett_conn_entry_sds_update_cmp(const CList *ls_a, const CList *ls_b, gconstpointer user_data) +{ + /* we sort highest priority storages first (descending). Hence, the order is swapped. */ + return _sett_conn_entry_sds_update_cmp_ascending(c_list_entry(ls_b, StorageData, sd_lst), + c_list_entry(ls_a, StorageData, sd_lst), + user_data); +} + +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, + * volatile, and external. */ + sett_mask |= (NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED + | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE + | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL); + sett_flags &= ~(NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED + | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE + | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL); + } + + 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->startup_complete_start_timestamp_msec != 0) { + 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 + | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL, + 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->startup_complete_start_timestamp_msec != 0) + _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; + const char * shadowed_storage; + gboolean shadowed_owned; + + filename = nm_settings_storage_get_filename(storage); + if (connection) { + shadowed_storage = nm_settings_storage_get_shadowed_storage(storage, &shadowed_owned); + _LOGT("storage[%s," NM_SETTINGS_STORAGE_PRINT_FMT + "]: change event with connection \"%s\"%s%s%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, "\")", ""), + NM_PRINT_FMT_QUOTED(shadowed_storage, + shadowed_owned ? " (owns \"" : " (shadows \"", + shadowed_storage, + "\")", + "")); + } else if ((meta_data = nm_settings_storage_is_meta_data(storage))) { + nm_assert(meta_data->is_tombstone); + shadowed_storage = nm_settings_storage_get_shadowed_storage(storage, &shadowed_owned); + _LOGT("storage[%s," NM_SETTINGS_STORAGE_PRINT_FMT + "]: change event for %shiding profile%s%s%s%s%s%s", + sett_conn_entry->uuid, + NM_SETTINGS_STORAGE_PRINT_ARG(storage), + nm_settings_storage_is_meta_data_alive(storage) ? "" : "dropping ", + NM_PRINT_FMT_QUOTED(filename, " (file \"", filename, "\")", ""), + NM_PRINT_FMT_QUOTED(shadowed_storage, + shadowed_owned ? " (owns \"" : " (shadows \"", + shadowed_storage, + "\")", + "")); + } 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, + SettConnEntry * sett_conn_entry, + NMConnection * new_connection, + gboolean in_memory, + NMSettingsConnectionIntFlags sett_flags, + const char * shadowed_storage, + gboolean shadowed_owned, + NMSettingsStorage ** out_new_storage, + NMConnection ** out_new_connection, + GError ** error) +{ + NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self); + gs_free_error 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 (!in_memory) { + NMSettingsStorage *conflicting_storage; + + conflicting_storage = _sett_conn_entry_storage_find_conflicting_storage(sett_conn_entry, + plugin, + NULL, + priv->plugins); + if (conflicting_storage) { + /* we have a connection provided by a plugin with higher priority than the one + * we would want to add the connection. We cannot do that, because doing so + * would result in adding a connection that gets hidden by the existing profile. + * Also, since we test the plugins in order of priority, all following plugins + * are unsuitable. + * + * Multiple connection plugins are so cumbersome, especially if they are unable + * to add the connection. I suggest to disable all plugins except keyfile. */ + _LOGT("add-connection: failed to add %s/'%s': there is an existing " + "storage " NM_SETTINGS_STORAGE_PRINT_FMT " with higher priority", + nm_connection_get_uuid(new_connection), + nm_connection_get_id(new_connection), + NM_SETTINGS_STORAGE_PRINT_ARG(conflicting_storage)); + nm_assert(first_error); + break; + } + } + + if (plugin == (NMSettingsPlugin *) priv->keyfile_plugin) { + success = nms_keyfile_plugin_add_connection( + priv->keyfile_plugin, + new_connection, + 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), + NM_FLAGS_HAS(sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL), + shadowed_storage, + shadowed_owned, + &storage, + &connection_to_add, + &add_error); + } else { + if (in_memory) + continue; + nm_assert(!NM_FLAGS_HAS(sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED)); + nm_assert(!NM_FLAGS_HAS(sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE)); + nm_assert(!NM_FLAGS_HAS(sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL)); + 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, g_steal_pointer(&first_error)); + return FALSE; +} + +static gboolean +_update_connection_to_plugin(NMSettings * self, + NMSettingsStorage * storage, + NMConnection * connection, + NMSettingsConnectionIntFlags sett_flags, + gboolean force_rename, + const char * shadowed_storage, + gboolean shadowed_owned, + NMSettingsStorage ** out_new_storage, + NMConnection ** out_new_connection, + GError ** error) +{ + NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self); + NMSettingsPlugin * plugin; + gboolean success; + + plugin = nm_settings_storage_get_plugin(storage); + + if (plugin == (NMSettingsPlugin *) priv->keyfile_plugin) { + success = nms_keyfile_plugin_update_connection( + priv->keyfile_plugin, + 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(sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL), + shadowed_storage, + shadowed_owned, + force_rename, + out_new_storage, + out_new_connection, + error); + } else { + nm_assert(!shadowed_storage); + nm_assert(!shadowed_owned); + success = nm_settings_plugin_update_connection(plugin, + storage, + connection, + out_new_storage, + out_new_connection, + error); + } + + return success; +} + +static void +_set_nmmeta_tombstone(NMSettings *self, + const char *uuid, + gboolean tombstone_on_disk, + gboolean tombstone_in_memory, + const char *shadowed_storage) +{ + NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self); + gs_unref_object NMSettingsStorage *tombstone_1_storage = NULL; + gs_unref_object NMSettingsStorage *tombstone_2_storage = NULL; + + if (tombstone_on_disk) { + if (!nms_keyfile_plugin_set_nmmeta_tombstone(priv->keyfile_plugin, + FALSE, + uuid, + FALSE, + TRUE, + NULL, + &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, + shadowed_storage, + &tombstone_2_storage, + NULL)) { + nms_keyfile_plugin_set_nmmeta_tombstone(priv->keyfile_plugin, + TRUE, + uuid, + TRUE, + TRUE, + shadowed_storage, + &tombstone_2_storage, + NULL); + } + _connection_changed_track(self, tombstone_2_storage, NULL, 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) +{ + NMSettingsPrivate *priv; + gs_unref_object NMConnection *connection_cloned_1 = NULL; + gs_unref_object NMConnection *new_connection = NULL; + gs_unref_object NMSettingsStorage *new_storage = NULL; + gs_unref_object NMSettingsStorage *shadowed_storage = NULL; + NMSettingsStorage * update_storage = NULL; + gs_free_error GError *local = NULL; + SettConnEntry * sett_conn_entry; + const char * uuid; + StorageData * sd; + gboolean new_in_memory; + gboolean success; + const char * shadowed_storage_filename = NULL; + + priv = NM_SETTINGS_GET_PRIVATE(self); + + nm_assert(NM_IN_SET(persist_mode, + NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK, + NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY)); + + new_in_memory = (persist_mode != NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK); + + nm_assert(!NM_FLAGS_ANY(sett_flags, ~_NM_SETTINGS_CONNECTION_INT_FLAGS_PERSISTENT_MASK)); + + if (NM_FLAGS_ANY(sett_flags, + NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED + | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE + | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL)) { + nm_assert(new_in_memory); + new_in_memory = TRUE; + } + + 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); + + sett_conn_entry = _sett_conn_entries_get(self, uuid); + if (_sett_conn_entry_get_conn(sett_conn_entry)) { + 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 (sett_conn_entry) { + c_list_for_each_entry (sd, &sett_conn_entry->sd_lst_head, sd_lst) { + if (!nm_settings_storage_is_meta_data(sd->storage)) + continue; + shadowed_storage = nm_g_object_ref(_sett_conn_entry_find_shadowed_storage( + sett_conn_entry, + nm_settings_storage_get_shadowed_storage(sd->storage, NULL), + NULL)); + if (shadowed_storage) { + /* We have a nmmeta tombstone that indicates that a storage is shadowed. + * + * This happens when deleting a in-memory profile that was decoupled from + * the persistent storage with NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED. + * We need to take over this storage again... */ + break; + } + } + } + + if (shadowed_storage && !new_in_memory) { + NMSettingsStorage *conflicting_storage; + + conflicting_storage = _sett_conn_entry_storage_find_conflicting_storage( + sett_conn_entry, + nm_settings_storage_get_plugin(shadowed_storage), + shadowed_storage, + priv->plugins); + if (conflicting_storage) { + /* We cannot add the profile as @shadowed_storage, because there is another, existing storage + * that would hide it. Just add it as new storage. In general, this leads to duplication of profiles, + * but the circumstances where this happens are very exotic (you need at least one additional settings + * plugin, then going through the paths of making shadowed_storage in-memory-detached and delete it, + * and finally adding the conflicting storage outside of NM and restart/reload). */ + _LOGT("ignore shadowed storage " NM_SETTINGS_STORAGE_PRINT_FMT + " due to conflicting storage " NM_SETTINGS_STORAGE_PRINT_FMT, + NM_SETTINGS_STORAGE_PRINT_ARG(shadowed_storage), + NM_SETTINGS_STORAGE_PRINT_ARG(conflicting_storage)); + } else + update_storage = shadowed_storage; + } + + shadowed_storage_filename = + (shadowed_storage && !update_storage) + ? nm_settings_storage_get_filename_for_shadowed_storage(shadowed_storage) + : NULL; + +again_add_connection: + + if (!update_storage) { + success = _add_connection_to_first_plugin(self, + sett_conn_entry, + connection, + new_in_memory, + sett_flags, + shadowed_storage_filename, + FALSE, + &new_storage, + &new_connection, + &local); + } else { + success = _update_connection_to_plugin(self, + update_storage, + connection, + sett_flags, + FALSE, + shadowed_storage_filename, + FALSE, + &new_storage, + &new_connection, + &local); + if (!success) { + if (!NMS_IS_KEYFILE_STORAGE(update_storage)) { + /* hm, the intended storage is not keyfile (it's ifcfg-rh). This settings + * plugin may not support the new connection. So step back and retry adding + * the profile anew. */ + _LOGT("failure to add profile as existing storage \"%s\": %s", + nm_settings_storage_get_filename(update_storage), + local->message); + update_storage = NULL; + g_clear_object(&shadowed_storage); + shadowed_storage_filename = NULL; + g_clear_error(&local); + goto again_add_connection; + } + } + } + + if (!success) { + if (!update_storage) { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, + "failure adding connection: %s", + local->message); + } else { + g_set_error(error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, + "failure writing connection to existing storage \"%s\": %s", + nm_settings_storage_get_filename(update_storage), + 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; + gs_unref_object NMSettingsStorage *new_tombstone_storage = NULL; + gboolean in_memory; + gboolean simulate; + + 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)) + in_memory = TRUE; + 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; + } + in_memory = FALSE; + } + + simulate = FALSE; +again_delete_tombstone: + if (!nms_keyfile_plugin_set_nmmeta_tombstone(priv->keyfile_plugin, + simulate, + uuid, + in_memory, + FALSE, + NULL, + &new_tombstone_storage, + NULL)) { + /* Ups, something went wrong. We really need to get rid of the tombstone. At least + * forget about it in-memory. Upong next restart/reload, this might be reverted + * however :( .*/ + if (!simulate) { + simulate = TRUE; + goto again_delete_tombstone; + } + } + if (new_tombstone_storage) + _connection_changed_track(self, new_tombstone_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) +{ + 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; + NMSettingsStorage * drop_storage = NULL; + SettConnEntry * sett_conn_entry; + gboolean cur_in_memory; + gboolean new_in_memory; + const char * uuid; + gboolean tombstone_in_memory = FALSE; + gboolean tombstone_on_disk = FALSE; + + 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, + NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED, + NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY)); + + 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)); + + sett_conn_entry = _sett_conn_entries_get(self, uuid); + + nm_assert(_sett_conn_entry_get_conn(sett_conn_entry) == 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 + : 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_HAS(nm_settings_connection_get_flags(sett_conn), + NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED)); + + default_wired_clear_tag(self, device, sett_conn, FALSE); + + if (NM_IN_SET(persist_mode, NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST)) { + /* making a default-wired-connection a regular connection implies persisting + * it to disk (unless specified differently). + * + * Actually, this line is probably unreached, because we should not use + * NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST to toggle the nm-generated + * flag. */ + nm_assert_not_reached(); + 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_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL) + && 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 + | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL)) { + /* we update the nm-generated/volatile setting of a profile (which is inherently + * 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; + } + + 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, + 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 + | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL)); + sett_mask |= NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED + | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE + | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL; + sett_flags &= ~(NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED + | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE + | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL); + } + + if (persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST) { + new_storage = g_object_ref(cur_storage); + new_connection_real = 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 { + NMSettingsStorage *shadowed_storage; + const char * cur_shadowed_storage_filename; + const char * new_shadowed_storage_filename = NULL; + gboolean cur_shadowed_owned; + gboolean new_shadowed_owned = FALSE; + NMSettingsStorage *update_storage = NULL; + gs_free_error GError *local = NULL; + gboolean success; + + cur_shadowed_storage_filename = + nm_settings_storage_get_shadowed_storage(cur_storage, &cur_shadowed_owned); + + shadowed_storage = _sett_conn_entry_find_shadowed_storage(sett_conn_entry, + cur_shadowed_storage_filename, + cur_storage); + if (!shadowed_storage) { + cur_shadowed_storage_filename = NULL; + cur_shadowed_owned = FALSE; + } + + if (new_in_memory && persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY) { + if (cur_in_memory) { + drop_storage = shadowed_storage; + update_storage = cur_storage; + } else + drop_storage = cur_storage; + } else if (!new_in_memory && cur_in_memory && shadowed_storage) { + drop_storage = cur_storage; + update_storage = shadowed_storage; + } else if (new_in_memory != cur_in_memory) { + if (!new_in_memory) + drop_storage = cur_storage; + else if (persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY) + drop_storage = cur_storage; + else { + nm_assert(NM_IN_SET(persist_mode, + NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY, + NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED)); + } + } else if (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. */ + } else + update_storage = cur_storage; + + if (new_in_memory) { + if (persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY) { + /* pass */ + } else if (!cur_in_memory) { + new_shadowed_storage_filename = + nm_settings_storage_get_filename_for_shadowed_storage(cur_storage); + if (new_shadowed_storage_filename + && persist_mode != NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED) + new_shadowed_owned = TRUE; + } else { + new_shadowed_storage_filename = cur_shadowed_storage_filename; + if (new_shadowed_storage_filename + && persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY) + new_shadowed_owned = TRUE; + } + } + + if (!update_storage) { + success = _add_connection_to_first_plugin(self, + sett_conn_entry, + connection, + new_in_memory, + sett_flags, + new_shadowed_storage_filename, + new_shadowed_owned, + &new_storage, + &new_connection, + &local); + } else { + success = _update_connection_to_plugin(self, + update_storage, + connection, + sett_flags, + update_reason, + new_shadowed_storage_filename, + new_shadowed_owned, + &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 " : "", + update_storage ? "update" : "write", + 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", + update_storage ? "update" : "write", + local->message); + return FALSE; + } + + new_storage = g_object_ref(cur_storage); + new_connection_real = connection; + } else { + gs_unref_variant GVariant *agent_owned_secrets = NULL; + + _LOGT("update[%s]: %s: %s profile \"%s\"", + nm_settings_storage_get_uuid(cur_storage), + log_context_name, + update_storage ? "update" : "write", + 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))); + + 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_SETTINGS_STORAGE(new_storage)); + nm_assert(NM_IS_CONNECTION(new_connection_real)); + + _connection_changed_track(self, new_storage, new_connection_real, TRUE); + + if (drop_storage && drop_storage != new_storage) { + gs_free_error GError *local = NULL; + + if (!nm_settings_plugin_delete_connection(nm_settings_storage_get_plugin(drop_storage), + drop_storage, + &local)) { + const char *filename; + + filename = nm_settings_storage_get_filename(drop_storage); + _LOGT("update[%s]: failed to delete moved storage " NM_SETTINGS_STORAGE_PRINT_FMT + "%s%s%s: %s", + nm_settings_storage_get_uuid(drop_storage), + NM_SETTINGS_STORAGE_PRINT_ARG(drop_storage), + NM_PRINT_FMT_QUOTED(filename, " (file \"", filename, "\")", ""), + local->message); + /* there is no aborting back form this. We must get rid of the connection and + * cannot do better than log a message. Proceed, but remember to write tombstones. */ + if (nm_settings_storage_is_keyfile_run(cur_storage)) + tombstone_in_memory = TRUE; + else + tombstone_on_disk = TRUE; + } else + _connection_changed_track(self, drop_storage, NULL, FALSE); + } + + _set_nmmeta_tombstone(self, uuid, tombstone_on_disk, tombstone_in_memory, NULL); + + _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) +{ + NMSettingsStorage *cur_storage; + NMSettingsStorage *shadowed_storage; + NMSettingsStorage *shadowed_storage_unowned = NULL; + NMSettingsStorage *drop_storages[2] = {}; + gs_free_error GError *local = NULL; + SettConnEntry * sett_conn_entry; + const char * cur_shadowed_storage_filename; + const char * new_shadowed_storage_filename = NULL; + gboolean cur_shadowed_owned; + const char * uuid; + gboolean tombstone_in_memory = FALSE; + gboolean tombstone_on_disk = FALSE; + int i; + + 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)); + + 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)) + drop_storages[0] = cur_storage; + else + tombstone_on_disk = TRUE; + } else + drop_storages[0] = cur_storage; + + cur_shadowed_storage_filename = + nm_settings_storage_get_shadowed_storage(cur_storage, &cur_shadowed_owned); + + shadowed_storage = _sett_conn_entry_find_shadowed_storage(sett_conn_entry, + cur_shadowed_storage_filename, + cur_storage); + if (shadowed_storage) { + if (!cur_shadowed_owned) + shadowed_storage_unowned = g_steal_pointer(&shadowed_storage); + } + drop_storages[1] = shadowed_storage; + + for (i = 0; i < (int) G_N_ELEMENTS(drop_storages); i++) { + NMSettingsStorage *storage; + StorageData * sd; + + storage = drop_storages[i]; + if (!storage) + continue; + + if (!nm_settings_plugin_delete_connection(nm_settings_storage_get_plugin(storage), + storage, + &local)) { + _LOGT("delete-connection: failed to delete storage " NM_SETTINGS_STORAGE_PRINT_FMT + ": %s", + NM_SETTINGS_STORAGE_PRINT_ARG(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 log a message. Proceed, but remember to write tombstones. */ + if (nm_settings_storage_is_keyfile_run(cur_storage)) + tombstone_in_memory = TRUE; + else + tombstone_on_disk = TRUE; + sett_conn_entry = _sett_conn_entries_get(self, uuid); + } else + sett_conn_entry = _connection_changed_track(self, storage, NULL, FALSE); + + c_list_for_each_entry (sd, &sett_conn_entry->sd_lst_head, sd_lst) { + if (NM_IN_SET(sd->storage, drop_storages[0], drop_storages[1])) + continue; + if (!_storage_data_is_alive(sd)) + continue; + if (nm_settings_storage_is_meta_data(sd->storage)) + continue; + + if (sd->storage == shadowed_storage_unowned) { + /* this only happens if we leak a profile on disk after NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED. + * We need to write a tombstone and remember the shadowed-storage. */ + tombstone_in_memory = TRUE; + new_shadowed_storage_filename = + nm_settings_storage_get_filename(shadowed_storage_unowned); + 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; + } + } + + _set_nmmeta_tombstone(self, + uuid, + tombstone_on_disk, + tombstone_in_memory, + new_shadowed_storage_filename); + + _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, + NM_UTILS_ERROR_MSG_INSUFF_PRIV); + } 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; + } + + 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; + + chain = nm_auth_chain_new_subject(subject, context, pk_add_cb, self); + + 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_dbus_manager_new_auth_subject_from_context(context); + if (!subject) { + g_dbus_method_invocation_return_error_literal(context, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_PERMISSION_DENIED, + NM_UTILS_ERROR_MSG_REQ_UID_UKNOWN); + 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); + nm_assert(!entry->error); + } 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); + + /* We MUST return %TRUE here, otherwise older libnm versions might misbehave. */ + 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, + 0x43, + 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_dbus_manager_new_auth_subject_from_context(invocation); + if (!subject) { + error = g_error_new_literal(NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_PERMISSION_DENIED, + NM_UTILS_ERROR_MSG_REQ_UID_UKNOWN); + 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_object_full(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); + hostname = nm_auth_chain_get_data(chain, "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, + NM_UTILS_ERROR_MSG_INSUFF_PRIV); + } else { + 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."); + } + } + + nm_audit_log_control_op(NM_AUDIT_OP_HOSTNAME_SAVE, + hostname, + !error, + nm_auth_chain_get_subject(chain), + error ? error->message : NULL); + + 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; + const char * error_reason; + int error_code; + + g_variant_get(parameters, "(&s)", &hostname); + + /* Minimal validation of the hostname */ + if (!nm_hostname_manager_validate_hostname(hostname)) { + error_code = NM_SETTINGS_ERROR_INVALID_HOSTNAME; + error_reason = "The hostname was too long or contained invalid characters"; + goto err; + } + + chain = nm_auth_chain_new_context(invocation, pk_hostname_cb, self); + if (!chain) { + error_code = NM_SETTINGS_ERROR_PERMISSION_DENIED; + error_reason = NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED; + goto err; + } + + 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); + return; +err: + nm_audit_log_control_op(NM_AUDIT_OP_HOSTNAME_SAVE, hostname, FALSE, invocation, error_reason); + g_dbus_method_invocation_return_error_literal(invocation, + NM_SETTINGS_ERROR, + error_code, + error_reason); +} + +/*****************************************************************************/ + +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); + NMSettingsConnection *sett_conn; + + g_return_val_if_fail(NM_IS_SETTINGS(self), FALSE); + + /* Find a wired connection matching for the device, if any */ + c_list_for_each_entry (sett_conn, &priv->connections_lst_head, _connections_lst) { + NMConnection *connection = nm_settings_connection_get_connection(sett_conn); + + 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 + | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL)) + continue; + + 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_CLASS(device)->new_default_connection + || !nm_device_get_managed(device, FALSE) + || g_object_get_qdata(G_OBJECT(device), _default_wired_connection_blocked_quark())) + return; + + /* we only check once whether to create the auto-default connection. If we reach this point, + * we mark the creation of the default-wired-connection as blocked. */ + g_object_set_qdata(G_OBJECT(device), _default_wired_connection_blocked_quark(), device); + + if (nm_config_get_no_auto_default_for_device(priv->config, device)) { + _LOGT("auto-default: cannot create auto-default connection for device %s: disabled by " + "\"no-auto-default\"", + nm_device_get_iface(device)); + return; + } + + if (have_connection_for_device(self, device)) { + _LOGT("auto-default: cannot create auto-default connection for device %s: already has a " + "profile", + nm_device_get_iface(device)); + return; + } + + connection = nm_device_new_default_connection(device); + if (!connection) { + _LOGT("auto-default: cannot create auto-default connection for device %s", + nm_device_get_iface(device)); + 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->startup_complete_start_timestamp_msec = nm_utils_get_monotonic_timestamp_msec(); + + 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, FALSE)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettings * self = NM_SETTINGS(object); + NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self); + + switch (prop_id) { + case PROP_MANAGER: + /* construct-only */ + priv->manager = g_value_get_pointer(value); + nm_assert(NM_IS_MANAGER(priv->manager)); + g_object_add_weak_pointer(G_OBJECT(priv->manager), (gpointer *) &priv->manager); + 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->startup_complete_scd_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(NMManager *manager) +{ + nm_assert(NM_IS_MANAGER(manager)); + + return g_object_new(NM_TYPE_SETTINGS, NM_SETTINGS_MANAGER, manager, 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_pointer(&priv->startup_complete_idx, g_hash_table_destroy); + nm_assert(c_list_is_empty(&priv->startup_complete_scd_lst_head)); + + 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); + + if (priv->manager) { + g_object_remove_weak_pointer(G_OBJECT(priv->manager), (gpointer *) &priv->manager); + priv->manager = NULL; + } +} + +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->set_property = set_property; + object_class->dispose = dispose; + object_class->finalize = finalize; + + obj_properties[PROP_MANAGER] = + g_param_spec_pointer(NM_SETTINGS_MANAGER, + "", + "", + G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS); + + 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); +} |