/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Søren Sandmann * Dan Williams * Tambet Ingo * Copyright (C) 2007 - 2011 Red Hat, Inc. * Copyright (C) 2008 Novell, Inc. */ #include "src/core/nm-default-daemon.h" #include "nm-settings.h" #include #include #include #include #if HAVE_SELINUX #include #endif #include "libnm-core-aux-intern/nm-common-macros.h" #include "libnm-glib-aux/nm-uuid.h" #include "libnm-glib-aux/nm-keyfile-aux.h" #include "libnm-core-intern/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 "libnm-core-intern/nm-core-internal.h" #include "libnm-std-aux/c-list-util.h" #include "libnm-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 "libnm-core-aux-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_uuid_is_normalized(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_uuid_is_normalized(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: (nullable): 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). * * @storage_check_ignore is optional, and if given then it skips this particular * storage. * * 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, NMSettingsStorage *storage_check_ignore, 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_ignore) { /* We ignore this one, because we're in the process of * replacing it. */ 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_STATIC_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; GCancellable *shutdown_cancellable; CList sce_dirty_lst_head; CList connections_lst_head; NMSettingsConnection **connections_cached_list; NMSettingsConnection **connections_cached_list_sorted_by_autoconnect_priority; GSList *unmanaged_specs; GSList *unrecognized_specs; gint64 startup_complete_start_timestamp_msec; GHashTable *startup_complete_idx; CList startup_complete_scd_lst_head; GSource *startup_complete_timeout_source; GSource *kf_db_flush_idle_source_timestamps; GSource *kf_db_flush_idle_source_seen_bssids; guint connections_len; guint connections_generation; bool kf_db_pruned_timestamps; bool kf_db_pruned_seen_bssid; bool started : 1; /* Whether NMSettingsConnections changed in a way that affects the comparison * with nm_settings_connection_cmp_autoconnect_priority_with_data(). In that case, * we may need to re-sort the connections_cached_list_sorted_by_autoconnect_priority * list. */ bool sorted_by_autoconnect_priority_maybe_changed : 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); /*****************************************************************************/ NMManager * nm_settings_get_manager(NMSettings *self) { g_return_val_if_fail(NM_IS_SETTINGS(self), NULL); return NM_SETTINGS_GET_PRIVATE(self)->manager; } /*****************************************************************************/ 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) { gs_free_error GError *error = NULL; 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; } /* Check that device is compatible with the device. We are also happy * with a device compatible but for which the connection is disallowed * by NM configuration. */ if (!nm_device_check_connection_compatible(device, conn, TRUE, &error) && !g_error_matches(error, NM_UTILS_ERROR, NM_UTILS_ERROR_CONNECTION_AVAILABLE_DISALLOWED)) 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); nm_clear_g_source_inst(&priv->startup_complete_timeout_source); _startup_complete_check(self, 0); return G_SOURCE_CONTINUE; } 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; } nm_clear_g_source_inst(&priv->startup_complete_timeout_source); 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); 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_source = nm_g_timeout_add_source(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_source); _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_AUTOCONNECT_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); nm_manager_notify_delete_settings_connections(priv->manager, sett_conn); _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))); nm_assert_connection_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 | NM_SETTINGS_CONNECTION_UPDATE_REASON_UPDATE_NON_SECRET); 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, const char *plugin_name, 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, NMSettingsStorage *drop_storage, GError **error) { NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self); gs_free_error GError *first_error = NULL; GSList *iter; const char *uuid; gboolean no_plugin = TRUE; uuid = nm_connection_get_uuid(new_connection); nm_assert(nm_uuid_is_normalized(uuid)); for (iter = priv->plugins; iter; iter = iter->next) { NMSettingsPlugin *plugin = NM_SETTINGS_PLUGIN(iter->data); gs_unref_object NMSettingsStorage *storage = NULL; gs_unref_object NMConnection *connection_to_add = NULL; gs_unref_object NMConnection *connection_to_add_cloned = NULL; NMConnection *connection_to_add_real = NULL; gs_unref_variant GVariant *agent_owned_secrets = NULL; gs_free_error GError *add_error = NULL; gboolean success; const char *filename; if (plugin_name && strcmp(plugin_name, nm_settings_plugin_get_plugin_name(plugin))) { /* Not the plugin we're confined to. Ignore. */ continue; } if (!in_memory) { NMSettingsStorage *conflicting_storage; conflicting_storage = _sett_conn_entry_storage_find_conflicting_storage(sett_conn_entry, plugin, NULL, drop_storage, 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); } no_plugin = FALSE; 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_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; } if (no_plugin) { nm_assert(plugin_name); nm_assert(!first_error); g_set_error(error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_ARGUMENTS, "a plugin by the name of '%s' is not available", plugin_name); } else { 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: (out) (optional) (nullable) (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, const char *plugin, 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, NULL, 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, plugin, sett_conn_entry, connection, new_in_memory, sett_flags, shadowed_storage_filename, FALSE, &new_storage, &new_connection, NULL, &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_CLEAR_AGENT_SECRETS | NM_SETTINGS_CONNECTION_UPDATE_REASON_UPDATE_NON_SECRET | (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, const char *plugin_name, 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; NMSettingsConnectionIntFlags new_flags; 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 && plugin_name) { NMSettingsPlugin *plugin = nm_settings_storage_get_plugin(update_storage); if (strcmp(plugin_name, nm_settings_plugin_get_plugin_name(plugin))) { /* We're updating a connection, we're confined to a particular * plugin, but the connection is currently using a different one. * We need to migrate. Drop the existing storage and look out for * a new one. */ drop_storage = update_storage; update_storage = NULL; } } new_flags = nm_settings_connection_get_flags(sett_conn); new_flags = NM_FLAGS_ASSIGN_MASK(new_flags, sett_mask, sett_flags); if (!update_storage) { success = _add_connection_to_first_plugin(self, plugin_name, sett_conn_entry, connection, new_in_memory, new_flags, new_shadowed_storage_filename, new_shadowed_owned, &new_storage, &new_connection, drop_storage, &local); } else { success = _update_connection_to_plugin(self, update_storage, connection, new_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_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_uuid_is_normalized(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, nm_auth_chain_get_data(chain, "plugin"), 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, const char *plugin, 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_set_data(chain, "plugin", g_strdup(plugin), g_free); 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, const char *plugin, 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, plugin, 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, NULL, 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, NULL, 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; gs_free char *plugin = NULL; NMSettingsAddConnection2Flags flags; const char *args_name; GVariant *args_value; 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, &args_value)) { if (plugin == NULL && nm_streq(args_name, "plugin") && g_variant_is_of_type(args_value, G_VARIANT_TYPE_STRING)) { plugin = g_variant_dup_string(args_value, NULL); continue; } 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, plugin, 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 | NM_SETTINGS_CONNECTION_UPDATE_REASON_UPDATE_NON_SECRET); 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)); } /*****************************************************************************/ void _nm_settings_notify_sorted_by_autoconnect_priority_maybe_changed(NMSettings *self) { NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self); priv->sorted_by_autoconnect_priority_maybe_changed = TRUE; } static void _clear_connections_cached_list(NMSettingsPrivate *priv) { if (priv->connections_cached_list) { 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); } if (priv->connections_cached_list_sorted_by_autoconnect_priority) { nm_assert(priv->connections_len == NM_PTRARRAY_LEN(priv->connections_cached_list_sorted_by_autoconnect_priority)); #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_sorted_by_autoconnect_priority, 0x42, sizeof(NMSettingsConnection *) * (priv->connections_len + 1)); #endif nm_clear_g_free(&priv->connections_cached_list_sorted_by_autoconnect_priority); } } 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) (optional): 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; } NMSettingsConnection *const * nm_settings_get_connections_sorted_by_autoconnect_priority(NMSettings *self, guint *out_len) { NMSettingsPrivate *priv; gboolean needs_sort = FALSE; 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)); nm_assert( !priv->connections_cached_list_sorted_by_autoconnect_priority || (priv->connections_len == NM_PTRARRAY_LEN(priv->connections_cached_list_sorted_by_autoconnect_priority))); if (!priv->connections_cached_list_sorted_by_autoconnect_priority) { NMSettingsConnection *const *list_cached; guint len; list_cached = nm_settings_get_connections(self, &len); priv->connections_cached_list_sorted_by_autoconnect_priority = nm_memdup(list_cached, sizeof(NMSettingsConnection *) * (len + 1)); needs_sort = (len > 1); } else if (priv->sorted_by_autoconnect_priority_maybe_changed) { if (!nm_utils_ptrarray_is_sorted( (gconstpointer *) priv->connections_cached_list_sorted_by_autoconnect_priority, priv->connections_len, FALSE, nm_settings_connection_cmp_autoconnect_priority_with_data, NULL)) { /* We cache the sorted list, but we don't monitor all entries whether they * get modified to invalidate the sort order. So every time we have to check * whether the sort order is still correct. The vast majority of the time it * is, and this check is faster than sorting anew. */ needs_sort = TRUE; } } else { nm_assert(nm_utils_ptrarray_is_sorted( (gconstpointer *) priv->connections_cached_list_sorted_by_autoconnect_priority, priv->connections_len, TRUE, nm_settings_connection_cmp_autoconnect_priority_with_data, NULL)); } priv->sorted_by_autoconnect_priority_maybe_changed = FALSE; if (needs_sort) { g_qsort_with_data(priv->connections_cached_list_sorted_by_autoconnect_priority, priv->connections_len, sizeof(NMSettingsConnection *), nm_settings_connection_cmp_autoconnect_priority_p_with_data, NULL); } NM_SET_OUT(out_len, priv->connections_len); return priv->connections_cached_list_sorted_by_autoconnect_priority; } /** * nm_settings_get_connections_clone: * @self: the #NMSetting * @out_len: (optional): optional output argument * @func: caller-supplied function for filtering connections * @func_data: caller-supplied data passed to @func * @sort_compare_func: (nullable): 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); if (sort_compare_func == nm_settings_connection_cmp_autoconnect_priority_p_with_data) { list_cached = nm_settings_get_connections_sorted_by_autoconnect_priority(self, &len); sort_compare_func = NULL; } else list_cached = nm_settings_get_connections(self, &len); #if NM_MORE_ASSERTS > 10 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_with_type(nm_dbus_object_get_manager(NM_DBUS_OBJECT(self)), NM_TYPE_SETTINGS_CONNECTION, path); if (!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, gboolean ignore_not_found, 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; if (!ignore_not_found) { _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_strv_find_first(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, FALSE, 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 _save_hostname_write_cb(GObject *source, GAsyncResult *result, gpointer user_data) { NMSettings *self; GDBusMethodInvocation *context; gs_free char *hostname = NULL; gs_unref_object NMAuthSubject *auth_subject = NULL; gs_unref_object GCancellable *cancellable = NULL; gs_free_error GError *error = NULL; nm_utils_user_data_unpack(user_data, &self, &context, &auth_subject, &hostname, &cancellable); nm_hostname_manager_set_static_hostname_finish(NM_HOSTNAME_MANAGER(source), result, &error); nm_audit_log_control_op(NM_AUDIT_OP_HOSTNAME_SAVE, hostname ?: "", !error, auth_subject, error ? error->message : NULL); if (nm_utils_error_is_cancelled(error)) { g_dbus_method_invocation_return_error_literal(context, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED, "NetworkManager is shutting down"); return; } if (error) { g_dbus_method_invocation_take_error(context, g_error_new(NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED, "Saving the hostname failed: %s", error->message)); return; } g_dbus_method_invocation_return_value(context, NULL); } static void _save_hostname_pk_cb(NMAuthChain *chain, GDBusMethodInvocation *context, gpointer user_data) { NMSettings *self = NM_SETTINGS(user_data); NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self); NMAuthCallResult result; gs_free char *hostname = NULL; 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_steal_data(chain, "hostname"); if (result != NM_AUTH_CALL_RESULT_YES) { nm_audit_log_control_op(NM_AUDIT_OP_HOSTNAME_SAVE, hostname ?: "", FALSE, nm_auth_chain_get_subject(chain), NM_UTILS_ERROR_MSG_INSUFF_PRIV); g_dbus_method_invocation_return_error_literal(context, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_PERMISSION_DENIED, NM_UTILS_ERROR_MSG_INSUFF_PRIV); return; } if (!priv->shutdown_cancellable) { /* we only keep a weak pointer on the cancellable, so we can * wrap it up after use. We almost never require this, because * SaveHostname is almost never called. */ priv->shutdown_cancellable = g_cancellable_new(); g_object_add_weak_pointer(G_OBJECT(priv->shutdown_cancellable), (gpointer *) &priv->shutdown_cancellable); } nm_hostname_manager_set_static_hostname( priv->hostname_manager, hostname, priv->shutdown_cancellable, _save_hostname_write_cb, nm_utils_user_data_pack(self, context, g_object_ref(nm_auth_chain_get_subject(chain)), hostname, g_object_ref(priv->shutdown_cancellable))); g_steal_pointer(&hostname); } 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_str_not_empty(hostname) && !nm_utils_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, _save_hostname_pk_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", nm_strdup_not_empty(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 _static_hostname_changed_cb(NMHostnameManager *hostname_manager, GParamSpec *pspec, gpointer user_data) { _notify(user_data, PROP_STATIC_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, TRUE, 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, NULL, 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; } } } /*****************************************************************************/ static gboolean _kf_db_prune_predicate(const char *uuid, gpointer user_data) { return !!nm_settings_get_connection_by_uuid(user_data, uuid); } static void _kf_db_to_file(NMSettings *self, gboolean is_timestamps, gboolean force_write) { NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self); NMKeyFileDB *kf_db; bool *p_kf_db_pruned; if (is_timestamps) { kf_db = priv->kf_db_timestamps; p_kf_db_pruned = &priv->kf_db_pruned_timestamps; } else { kf_db = priv->kf_db_seen_bssids; p_kf_db_pruned = &priv->kf_db_pruned_seen_bssid; } if (!*p_kf_db_pruned) { /* we only prune the DB once, because afterwards every * add/remove of an connection will lead to a direct update. */ *p_kf_db_pruned = TRUE; nm_key_file_db_prune(kf_db, _kf_db_prune_predicate, self); /* once we also go over the directory, and see whether we * have any left over temporary files to delete. */ nm_key_file_db_prune_tmp_files(kf_db); } nm_key_file_db_to_file(kf_db, force_write); } 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; nm_clear_g_source_inst(&priv->kf_db_flush_idle_source_timestamps); } else { prefix = "seen-bssids"; kf_db = priv->kf_db_seen_bssids; nm_clear_g_source_inst(&priv->kf_db_flush_idle_source_seen_bssids); } if (nm_key_file_db_is_dirty(kf_db)) _kf_db_to_file(self, is_timestamps, FALSE); else { _LOGT("[%s-keyfile]: skip saving changes to \"%s\"", prefix, nm_key_file_db_get_filename(kf_db)); } return G_SOURCE_CONTINUE; } 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; GSource **p_source; const char *prefix; if (priv->kf_db_timestamps == kf_db) { prefix = "timestamps"; p_source = &priv->kf_db_flush_idle_source_timestamps; idle_func = _kf_db_got_dirty_flush_timestamps_cb; } else if (priv->kf_db_seen_bssids == kf_db) { prefix = "seen-bssids"; p_source = &priv->kf_db_flush_idle_source_seen_bssids; idle_func = _kf_db_got_dirty_flush_seen_bssids_cb; } else { nm_assert_not_reached(); return; } if (*p_source) return; _LOGT("[%s-keyfile]: schedule flushing changes to disk", prefix); *p_source = nm_g_source_attach(nm_g_idle_source_new(G_PRIORITY_LOW, idle_func, self, NULL), NULL); } void nm_settings_kf_db_write(NMSettings *self) { g_return_if_fail(NM_IS_SETTINGS(self)); _kf_db_to_file(self, TRUE, TRUE); _kf_db_to_file(self, FALSE, 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 (plugins && plugins[0]) { if (!load_plugins(self, (const char *const *) plugins, error)) return FALSE; } else { add_plugin_keyfile(self); #if WITH_CONFIG_PLUGIN_IFCFG_RH add_plugin_load_file(self, "ifcfg-rh", TRUE, NULL); #endif #if WITH_CONFIG_PLUGIN_IFUPDOWN add_plugin_load_file(self, "ifupdown", TRUE, NULL); #endif } 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_STATIC_HOSTNAME, G_CALLBACK(_static_hostname_changed_cb), self); if (nm_hostname_manager_get_static_hostname(priv->hostname_manager)) _notify(self, PROP_STATIC_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_STATIC_HOSTNAME: g_value_set_string(value, priv->hostname_manager ? nm_hostname_manager_get_static_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_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_inst(&priv->startup_complete_timeout_source); 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(_static_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); } if (priv->shutdown_cancellable) { g_object_remove_weak_pointer(G_OBJECT(priv->shutdown_cancellable), (gpointer *) &priv->shutdown_cancellable); g_cancellable_cancel(g_steal_pointer(&priv->shutdown_cancellable)); } 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_inst(&priv->kf_db_flush_idle_source_timestamps); nm_clear_g_source_inst(&priv->kf_db_flush_idle_source_seen_bssids); _kf_db_to_file(self, TRUE, FALSE); _kf_db_to_file(self, FALSE, 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(&signal_info_new_connection, &signal_info_connection_removed, ), .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Connections", "ao", NM_SETTINGS_CONNECTIONS), NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Hostname", "s", NM_SETTINGS_STATIC_HOSTNAME), NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("CanModify", "b", NM_SETTINGS_CAN_MODIFY), ), ), }; 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_STATIC_HOSTNAME] = g_param_spec_string(NM_SETTINGS_STATIC_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); }