diff options
| author | Michael Biebl <biebl@debian.org> | 2021-02-11 18:11:46 +0100 |
|---|---|---|
| committer | Michael Biebl <biebl@debian.org> | 2021-02-11 18:11:46 +0100 |
| commit | 80ec1decc49c72efec2a8b87c06245c92c0ab807 (patch) | |
| tree | e3b229aa94e8dcf0590f2317664176e7b8f7607b /src/core/nm-manager.c | |
| parent | 65f86e8f56267192d42f2b629fc6b0c99fb9cd0c (diff) | |
New upstream version 1.29.90 upstream/1.29.90
Diffstat (limited to 'src/core/nm-manager.c')
| -rw-r--r-- | src/core/nm-manager.c | 8696 |
1 files changed, 8696 insertions, 0 deletions
diff --git a/src/core/nm-manager.c b/src/core/nm-manager.c new file mode 100644 index 00000000..5a6e05a9 --- /dev/null +++ b/src/core/nm-manager.c @@ -0,0 +1,8696 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2007 - 2009 Novell, Inc. + * Copyright (C) 2007 - 2017 Red Hat, Inc. + */ + +#include "src/core/nm-default-daemon.h" + +#include "nm-manager.h" + +#include <stdlib.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/sendfile.h> +#include <limits.h> + +#include "nm-glib-aux/nm-c-list.h" + +#include "nm-libnm-core-intern/nm-common-macros.h" +#include "nm-dbus-manager.h" +#include "vpn/nm-vpn-manager.h" +#include "devices/nm-device.h" +#include "devices/nm-device-generic.h" +#include "platform/nm-platform.h" +#include "platform/nmp-object.h" +#include "nm-hostname-manager.h" +#include "nm-keep-alive.h" +#include "nm-rfkill-manager.h" +#include "dhcp/nm-dhcp-manager.h" +#include "settings/nm-settings.h" +#include "settings/nm-settings-connection.h" +#include "nm-auth-utils.h" +#include "nm-auth-manager.h" +#include "NetworkManagerUtils.h" +#include "devices/nm-device-factory.h" +#include "nm-sleep-monitor.h" +#include "nm-connectivity.h" +#include "nm-policy.h" +#include "nm-session-monitor.h" +#include "nm-act-request.h" +#include "nm-core-internal.h" +#include "nm-config.h" +#include "nm-audit-manager.h" +#include "nm-std-aux/nm-dbus-compat.h" +#include "nm-checkpoint.h" +#include "nm-checkpoint-manager.h" +#include "nm-dbus-object.h" +#include "nm-dispatcher.h" +#include "NetworkManagerUtils.h" + +#define DEVICE_STATE_PRUNE_RATELIMIT_MAX 100u + +/*****************************************************************************/ + +typedef struct { + gboolean user_enabled; + gboolean sw_enabled; + gboolean hw_enabled; + RfKillType rtype; + NMConfigRunStatePropertyType key; + const char * desc; + const char * prop; + const char * hw_prop; +} RadioState; + +typedef enum { + ASYNC_OP_TYPE_AC_AUTH_ACTIVATE_INTERNAL, + ASYNC_OP_TYPE_AC_AUTH_ACTIVATE_USER, + ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE, + ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE2, +} AsyncOpType; + +typedef struct { + CList async_op_lst; + NMManager * self; + AsyncOpType async_op_type; + union { + struct { + NMActiveConnection *active; + union { + struct { + GDBusMethodInvocation *invocation; + } activate_user; + struct { + GDBusMethodInvocation * invocation; + NMConnection * connection; + NMSettingsConnectionPersistMode persist_mode; + bool is_volatile : 1; + } add_and_activate; + }; + } ac_auth; + }; +} AsyncOpData; + +enum { + DEVICE_ADDED, + INTERNAL_DEVICE_ADDED, + DEVICE_REMOVED, + INTERNAL_DEVICE_REMOVED, + ACTIVE_CONNECTION_ADDED, + ACTIVE_CONNECTION_REMOVED, + CONFIGURE_QUIT, + DEVICE_IFINDEX_CHANGED, + + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = {0}; + +NM_GOBJECT_PROPERTIES_DEFINE(NMManager, + PROP_VERSION, + PROP_CAPABILITIES, + PROP_STATE, + PROP_STARTUP, + PROP_NETWORKING_ENABLED, + PROP_WIRELESS_ENABLED, + PROP_WIRELESS_HARDWARE_ENABLED, + PROP_WWAN_ENABLED, + PROP_WWAN_HARDWARE_ENABLED, + PROP_WIMAX_ENABLED, + PROP_WIMAX_HARDWARE_ENABLED, + PROP_ACTIVE_CONNECTIONS, + PROP_CONNECTIVITY, + PROP_CONNECTIVITY_CHECK_AVAILABLE, + PROP_CONNECTIVITY_CHECK_ENABLED, + PROP_CONNECTIVITY_CHECK_URI, + PROP_PRIMARY_CONNECTION, + PROP_PRIMARY_CONNECTION_TYPE, + PROP_ACTIVATING_CONNECTION, + PROP_DEVICES, + PROP_METERED, + PROP_GLOBAL_DNS_CONFIGURATION, + PROP_ALL_DEVICES, + PROP_CHECKPOINTS, + + /* Not exported */ + PROP_SLEEPING, ); + +typedef struct { + NMPlatform *platform; + + GArray *capabilities; + + CList active_connections_lst_head; /* Oldest ACs at the beginning */ + CList async_op_lst_head; + guint ac_cleanup_id; + NMActiveConnection *primary_connection; + NMActiveConnection *activating_connection; + NMMetered metered; + + CList devices_lst_head; + + NMState state; + NMConfig * config; + NMConnectivity * concheck_mgr; + NMPolicy * policy; + NMHostnameManager *hostname_manager; + + struct { + GDBusConnection *connection; + guint id; + } prop_filter; + NMRfkillManager *rfkill_mgr; + + CList link_cb_lst; + + NMCheckpointManager *checkpoint_mgr; + + NMSettings *settings; + + RadioState radio_states[RFKILL_TYPE_MAX]; + NMVpnManager *vpn_manager; + + NMSleepMonitor *sleep_monitor; + + NMAuthManager *auth_mgr; + + GHashTable *device_route_metrics; + + CList auth_lst_head; + + GHashTable *sleep_devices; + + /* Firmware dir monitor */ + GFileMonitor *fw_monitor; + guint fw_changed_id; + + guint timestamp_update_id; + + guint devices_inited_id; + + NMConnectivityState connectivity_state; + + guint8 device_state_prune_ratelimit_count; + + bool startup : 1; + bool devices_inited : 1; + + bool sleeping : 1; + bool net_enabled : 1; + + unsigned connectivity_check_enabled_last : 2; + + guint delete_volatile_connection_idle_id; + CList delete_volatile_connection_lst_head; +} NMManagerPrivate; + +struct _NMManager { + NMDBusObject parent; + NMManagerPrivate _priv; +}; + +typedef struct { + NMDBusObjectClass parent; +} NMManagerClass; + +G_DEFINE_TYPE(NMManager, nm_manager, NM_TYPE_DBUS_OBJECT) + +#define NM_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMManager, NM_IS_MANAGER) + +/*****************************************************************************/ + +NM_DEFINE_SINGLETON_INSTANCE(NMManager); + +/*****************************************************************************/ + +#define _NMLOG_PREFIX_NAME "manager" +#define _NMLOG(level, domain, ...) \ + G_STMT_START \ + { \ + const NMLogLevel _level = (level); \ + const NMLogDomain _domain = (domain); \ + \ + if (nm_logging_enabled(_level, _domain)) { \ + const NMManager *const _self = (self); \ + char _sbuf[32]; \ + \ + _nm_log(_level, \ + _domain, \ + 0, \ + NULL, \ + NULL, \ + "%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME, \ + ((_self && _self != singleton_instance) ? nm_sprintf_buf(_sbuf, "[%p]", _self) \ + : "") \ + _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } \ + G_STMT_END + +#define _NMLOG2(level, domain, device, ...) \ + G_STMT_START \ + { \ + const NMLogLevel _level = (level); \ + const NMLogDomain _domain = (domain); \ + \ + if (nm_logging_enabled(_level, _domain)) { \ + const NMManager *const _self = (self); \ + const char *const _ifname = _nm_device_get_iface(device); \ + char _sbuf[32]; \ + \ + _nm_log(_level, \ + _domain, \ + 0, \ + _ifname, \ + NULL, \ + "%s%s: %s%s%s" _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME, \ + ((_self && _self != singleton_instance) ? nm_sprintf_buf(_sbuf, "[%p]", _self) \ + : ""), \ + NM_PRINT_FMT_QUOTED(_ifname, "(", _ifname, "): ", "") \ + _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } \ + G_STMT_END + +#define _NMLOG3(level, domain, connection, ...) \ + G_STMT_START \ + { \ + const NMLogLevel _level = (level); \ + const NMLogDomain _domain = (domain); \ + \ + if (nm_logging_enabled(_level, _domain)) { \ + const NMManager *const _self = (self); \ + NMConnection *const _connection = (connection); \ + const char *const _con_id = _nm_connection_get_id(_connection); \ + char _sbuf[32]; \ + \ + _nm_log(_level, \ + _domain, \ + 0, \ + NULL, \ + _nm_connection_get_uuid(_connection), \ + "%s%s: %s%s%s" _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME, \ + ((_self && _self != singleton_instance) ? nm_sprintf_buf(_sbuf, "[%p]", _self) \ + : ""), \ + NM_PRINT_FMT_QUOTED(_con_id, "(", _con_id, ") ", "") \ + _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } \ + G_STMT_END + +/*****************************************************************************/ + +static const NMDBusInterfaceInfoExtended interface_info_manager; +static const GDBusSignalInfo signal_info_check_permissions; +static const GDBusSignalInfo signal_info_state_changed; +static const GDBusSignalInfo signal_info_device_added; +static const GDBusSignalInfo signal_info_device_removed; + +static void update_connectivity_value(NMManager *self); + +static gboolean add_device(NMManager *self, NMDevice *device, GError **error); + +static void _emit_device_added_removed(NMManager *self, NMDevice *device, gboolean is_added); + +static NMActiveConnection *_new_active_connection(NMManager * self, + gboolean is_vpn, + NMSettingsConnection * sett_conn, + NMConnection * incompl_conn, + NMConnection * applied, + const char * specific_object, + NMDevice * device, + NMAuthSubject * subject, + NMActivationType activation_type, + NMActivationReason activation_reason, + NMActivationStateFlags initial_state_flags, + GError ** error); + +static void policy_activating_ac_changed(GObject *object, GParamSpec *pspec, gpointer user_data); + +static gboolean find_master(NMManager * self, + NMConnection * connection, + NMDevice * device, + NMSettingsConnection **out_master_connection, + NMDevice ** out_master_device, + NMActiveConnection ** out_master_ac, + GError ** error); + +static void nm_manager_update_state(NMManager *manager); + +static void connection_changed(NMManager *self, NMSettingsConnection *sett_conn); +static void device_sleep_cb(NMDevice *device, GParamSpec *pspec, NMManager *self); + +static void +settings_startup_complete_changed(NMSettings *settings, GParamSpec *pspec, NMManager *self); + +static void retry_connections_for_parent_device(NMManager *self, NMDevice *device); + +static void +active_connection_state_changed(NMActiveConnection *active, GParamSpec *pspec, NMManager *self); +static void +active_connection_default_changed(NMActiveConnection *active, GParamSpec *pspec, NMManager *self); +static void active_connection_parent_active(NMActiveConnection *active, + NMActiveConnection *parent_ac, + NMManager * self); + +static NMActiveConnection *active_connection_find(NMManager * self, + NMSettingsConnection * sett_conn, + const char * uuid, + NMActiveConnectionState max_state, + GPtrArray ** out_all_matching); + +static NMConnectivity *concheck_get_mgr(NMManager *self); + +static void _internal_activation_auth_done(NMManager * self, + NMActiveConnection *active, + gboolean success, + const char * error_desc); +static void _add_and_activate_auth_done(NMManager * self, + AsyncOpType async_op_type, + NMActiveConnection * active, + NMConnection * connection, + GDBusMethodInvocation * invocation, + NMSettingsConnectionPersistMode persist_mode, + gboolean is_volatile, + gboolean success, + const char * error_desc); +static void _activation_auth_done(NMManager * self, + NMActiveConnection * active, + GDBusMethodInvocation *invocation, + gboolean success, + const char * error_desc); + +/*****************************************************************************/ + +static NM_CACHED_QUARK_FCN("autoconnect-root", autoconnect_root_quark); + +/*****************************************************************************/ + +static gboolean +_connection_is_vpn(NMConnection *connection) +{ + const char *type; + + type = nm_connection_get_connection_type(connection); + if (type) + return nm_streq(type, NM_SETTING_VPN_SETTING_NAME); + + /* we have an incomplete (invalid) connection at hand. That can only + * happen during AddAndActivate. Determine whether it's VPN type based + * on the existence of a [vpn] section. */ + return !!nm_connection_get_setting_vpn(connection); +} + +/*****************************************************************************/ + +static gboolean +concheck_enabled(NMManager *self, gboolean *out_changed) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + guint check_enabled; + + check_enabled = nm_connectivity_check_enabled(concheck_get_mgr(self)) ? 1 : 2; + if (priv->connectivity_check_enabled_last == check_enabled) + NM_SET_OUT(out_changed, FALSE); + else { + NM_SET_OUT(out_changed, TRUE); + priv->connectivity_check_enabled_last = check_enabled; + } + return check_enabled == 1; +} + +static void +concheck_config_changed_cb(NMConnectivity *connectivity, NMManager *self) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMDevice * device; + gboolean changed; + + concheck_enabled(self, &changed); + if (changed) + _notify(self, PROP_CONNECTIVITY_CHECK_ENABLED); + + c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) + nm_device_check_connectivity_update_interval(device); +} + +static NMConnectivity * +concheck_get_mgr(NMManager *self) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + + if (G_UNLIKELY(!priv->concheck_mgr)) { + priv->concheck_mgr = g_object_ref(nm_connectivity_get()); + g_signal_connect(priv->concheck_mgr, + NM_CONNECTIVITY_CONFIG_CHANGED, + G_CALLBACK(concheck_config_changed_cb), + self); + } + return priv->concheck_mgr; +} + +/*****************************************************************************/ + +static AsyncOpData * +_async_op_data_new_authorize_activate_internal(NMManager *self, NMActiveConnection *active_take) +{ + AsyncOpData *async_op_data; + + async_op_data = g_slice_new0(AsyncOpData); + async_op_data->async_op_type = ASYNC_OP_TYPE_AC_AUTH_ACTIVATE_INTERNAL; + async_op_data->self = g_object_ref(self); + async_op_data->ac_auth.active = active_take; + c_list_link_tail(&NM_MANAGER_GET_PRIVATE(self)->async_op_lst_head, + &async_op_data->async_op_lst); + return async_op_data; +} + +static AsyncOpData * +_async_op_data_new_ac_auth_activate_user(NMManager * self, + NMActiveConnection * active_take, + GDBusMethodInvocation *invocation_take) +{ + AsyncOpData *async_op_data; + + async_op_data = g_slice_new0(AsyncOpData); + async_op_data->async_op_type = ASYNC_OP_TYPE_AC_AUTH_ACTIVATE_USER; + async_op_data->self = g_object_ref(self); + async_op_data->ac_auth.active = active_take; + async_op_data->ac_auth.activate_user.invocation = invocation_take; + c_list_link_tail(&NM_MANAGER_GET_PRIVATE(self)->async_op_lst_head, + &async_op_data->async_op_lst); + return async_op_data; +} + +static AsyncOpData * +_async_op_data_new_ac_auth_add_and_activate(NMManager * self, + AsyncOpType async_op_type, + NMActiveConnection * active_take, + GDBusMethodInvocation * invocation_take, + NMConnection * connection_take, + NMSettingsConnectionPersistMode persist_mode, + gboolean is_volatile) +{ + AsyncOpData *async_op_data; + + nm_assert(NM_IN_SET(async_op_type, + ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE, + ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE2)); + + async_op_data = g_slice_new0(AsyncOpData); + async_op_data->async_op_type = async_op_type; + async_op_data->self = g_object_ref(self); + async_op_data->ac_auth.active = active_take; + async_op_data->ac_auth.add_and_activate.invocation = invocation_take; + async_op_data->ac_auth.add_and_activate.connection = connection_take; + async_op_data->ac_auth.add_and_activate.persist_mode = persist_mode; + async_op_data->ac_auth.add_and_activate.is_volatile = is_volatile; + c_list_link_tail(&NM_MANAGER_GET_PRIVATE(self)->async_op_lst_head, + &async_op_data->async_op_lst); + return async_op_data; +} + +static void +_async_op_complete_ac_auth_cb(NMActiveConnection *active, + gboolean success, + const char * error_desc, + gpointer user_data) +{ + AsyncOpData *async_op_data = user_data; + + nm_assert(async_op_data); + nm_assert(NM_IS_MANAGER(async_op_data->self)); + nm_assert( + nm_c_list_contains_entry(&NM_MANAGER_GET_PRIVATE(async_op_data->self)->async_op_lst_head, + async_op_data, + async_op_lst)); + nm_assert(NM_IS_ACTIVE_CONNECTION(active)); + nm_assert(active == async_op_data->ac_auth.active); + + c_list_unlink(&async_op_data->async_op_lst); + + switch (async_op_data->async_op_type) { + case ASYNC_OP_TYPE_AC_AUTH_ACTIVATE_INTERNAL: + _internal_activation_auth_done(async_op_data->self, + async_op_data->ac_auth.active, + success, + error_desc); + break; + case ASYNC_OP_TYPE_AC_AUTH_ACTIVATE_USER: + _activation_auth_done(async_op_data->self, + async_op_data->ac_auth.active, + async_op_data->ac_auth.activate_user.invocation, + success, + error_desc); + break; + case ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE: + case ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE2: + _add_and_activate_auth_done(async_op_data->self, + async_op_data->async_op_type, + async_op_data->ac_auth.active, + async_op_data->ac_auth.add_and_activate.connection, + async_op_data->ac_auth.add_and_activate.invocation, + async_op_data->ac_auth.add_and_activate.persist_mode, + async_op_data->ac_auth.add_and_activate.is_volatile, + success, + error_desc); + g_object_unref(async_op_data->ac_auth.add_and_activate.connection); + break; + default: + nm_assert_not_reached(); + break; + } + + g_object_unref(async_op_data->ac_auth.active); + g_object_unref(async_op_data->self); + g_slice_free(AsyncOpData, async_op_data); +} + +/*****************************************************************************/ + +typedef struct { + int ifindex; + guint32 aspired_metric; + guint32 effective_metric; +} DeviceRouteMetricData; + +static DeviceRouteMetricData * +_device_route_metric_data_new(int ifindex, guint32 aspired_metric, guint32 effective_metric) +{ + DeviceRouteMetricData *data; + + nm_assert(ifindex > 0); + + /* For IPv4, metrics can use the entire uint32 bit range. For IPv6, + * zero is treated like 1024. Since we handle IPv4 and IPv6 identically, + * we cannot allow a zero metric here. + */ + nm_assert(aspired_metric > 0); + nm_assert(effective_metric == 0 || aspired_metric <= effective_metric); + + data = g_slice_new0(DeviceRouteMetricData); + data->ifindex = ifindex; + data->aspired_metric = aspired_metric; + data->effective_metric = effective_metric ?: aspired_metric; + return data; +} + +static guint +_device_route_metric_data_by_ifindex_hash(gconstpointer p) +{ + const DeviceRouteMetricData *data = p; + NMHashState h; + + nm_hash_init(&h, 1030338191); + nm_hash_update_vals(&h, data->ifindex); + return nm_hash_complete(&h); +} + +static gboolean +_device_route_metric_data_by_ifindex_equal(gconstpointer pa, gconstpointer pb) +{ + const DeviceRouteMetricData *a = pa; + const DeviceRouteMetricData *b = pb; + + return a->ifindex == b->ifindex; +} + +static guint32 +_device_route_metric_get(NMManager * self, + int ifindex, + NMDeviceType device_type, + gboolean lookup_only, + guint32 * out_aspired_metric) +{ + NMManagerPrivate * priv; + const DeviceRouteMetricData *d2; + DeviceRouteMetricData * data; + DeviceRouteMetricData data_lookup; + const NMDedupMultiHeadEntry *all_links_head; + NMPObject links_needle; + guint n_links; + gboolean cleaned = FALSE; + GHashTableIter h_iter; + guint32 metric; + + g_return_val_if_fail(NM_IS_MANAGER(self), 0); + + NM_SET_OUT(out_aspired_metric, 0); + + if (ifindex <= 0) { + if (lookup_only) + return 0; + metric = nm_device_get_route_metric_default(device_type); + NM_SET_OUT(out_aspired_metric, metric); + return metric; + } + + priv = NM_MANAGER_GET_PRIVATE(self); + + if (lookup_only && !priv->device_route_metrics) + return 0; + + if (G_UNLIKELY(!priv->device_route_metrics)) { + const GHashTable * h; + const NMConfigDeviceStateData *device_state; + + priv->device_route_metrics = + g_hash_table_new_full(_device_route_metric_data_by_ifindex_hash, + _device_route_metric_data_by_ifindex_equal, + NULL, + nm_g_slice_free_fcn(DeviceRouteMetricData)); + cleaned = TRUE; + + /* we need to pre-populate the cache for all (still existing) devices from the state-file */ + h = nm_config_device_state_get_all(priv->config); + if (!h) + goto initited; + + g_hash_table_iter_init(&h_iter, (GHashTable *) h); + while (g_hash_table_iter_next(&h_iter, NULL, (gpointer *) &device_state)) { + if (!device_state->route_metric_default_effective) + continue; + if (!nm_platform_link_get(priv->platform, device_state->ifindex)) { + /* we have the entry in the state file, but (currently) no such + * ifindex exists in platform. Most likely the entry is obsolete, + * hence we skip it. */ + continue; + } + if (!g_hash_table_add( + priv->device_route_metrics, + _device_route_metric_data_new(device_state->ifindex, + device_state->route_metric_default_aspired, + device_state->route_metric_default_effective))) + nm_assert_not_reached(); + } + } + +initited: + data_lookup.ifindex = ifindex; + + data = g_hash_table_lookup(priv->device_route_metrics, &data_lookup); + if (data) + goto out; + if (lookup_only) + return 0; + + if (!cleaned) { + /* get the number of all links in the platform cache. */ + all_links_head = nm_platform_lookup_all(priv->platform, + NMP_CACHE_ID_TYPE_OBJECT_TYPE, + nmp_object_stackinit_id_link(&links_needle, 1)); + n_links = all_links_head ? all_links_head->len : 0; + + /* on systems where a lot of devices are created and go away, the index contains + * a lot of stale entries. We must from time to time clean them up. + * + * Do do this cleanup, whenever we have more entries then 2 times the number of links. */ + if (G_UNLIKELY(g_hash_table_size(priv->device_route_metrics) > NM_MAX(20, n_links * 2))) { + /* from time to time, we need to do some house-keeping and prune stale entries. + * Otherwise, on a system where interfaces frequently come and go (docker), we + * keep growing this cache for ifindexes that no longer exist. */ + g_hash_table_iter_init(&h_iter, priv->device_route_metrics); + while (g_hash_table_iter_next(&h_iter, NULL, (gpointer *) &d2)) { + if (!nm_platform_link_get(priv->platform, d2->ifindex)) + g_hash_table_iter_remove(&h_iter); + } + cleaned = TRUE; + } + } + + data = + _device_route_metric_data_new(ifindex, nm_device_get_route_metric_default(device_type), 0); + + /* unfortunately, there is no stright forward way to lookup all reserved metrics. + * Note, that we don't only have to know which metrics are currently reserved, + * but also, which metrics are now seemingly un-used but caused another reserved + * metric to be bumped. Hence, the naive O(n^2) search :( + * + * Well, technically, since we limit bumping the metric to 50, this entire + * loop runs at most 50 times, so it's still O(n). Let's just say, it's not + * very efficient. */ +again: + g_hash_table_iter_init(&h_iter, priv->device_route_metrics); + while (g_hash_table_iter_next(&h_iter, NULL, (gpointer *) &d2)) { + if (data->effective_metric < d2->aspired_metric + || data->effective_metric > d2->effective_metric) { + /* no overlap. Skip. */ + continue; + } + if (!cleaned && !nm_platform_link_get(priv->platform, d2->ifindex)) { + /* the metric seems taken, but there is no such interface. This entry + * is stale, forget about it. */ + g_hash_table_iter_remove(&h_iter); + continue; + } + + if (d2->effective_metric == G_MAXUINT32) { + /* we cannot bump the metric any further. Done. + * + * Actually, this can currently not happen because the aspired_metric + * are small numbers and we limit the bumping to 50. Still, for + * completeness... */ + data->effective_metric = G_MAXUINT32; + break; + } + + if (d2->effective_metric - data->aspired_metric >= 50) { + /* as one active interface reserves an entire range of metrics + * (from aspired_metric to effective_metric), that means if you + * alternatingly activate two interfaces, their metric will + * bump each other. + * + * Limit this, bump the metric at most 50 points. */ + data->effective_metric = data->aspired_metric + 50; + break; + } + + /* bump the metric, and search again. */ + data->effective_metric = d2->effective_metric + 1; + goto again; + } + + _LOGT(LOGD_DEVICE, + "default-route-metric: ifindex %d reserves metric %u (aspired %u)", + data->ifindex, + data->effective_metric, + data->aspired_metric); + + if (!g_hash_table_add(priv->device_route_metrics, data)) + nm_assert_not_reached(); + +out: + NM_SET_OUT(out_aspired_metric, data->aspired_metric); + return data->effective_metric; +} + +guint32 +nm_manager_device_route_metric_reserve(NMManager *self, int ifindex, NMDeviceType device_type) +{ + guint32 metric; + + metric = _device_route_metric_get(self, ifindex, device_type, FALSE, NULL); + nm_assert(metric != 0); + return metric; +} + +void +nm_manager_device_route_metric_clear(NMManager *self, int ifindex) +{ + NMManagerPrivate * priv; + DeviceRouteMetricData data_lookup; + + priv = NM_MANAGER_GET_PRIVATE(self); + + if (!priv->device_route_metrics) + return; + data_lookup.ifindex = ifindex; + if (g_hash_table_remove(priv->device_route_metrics, &data_lookup)) { + _LOGT(LOGD_DEVICE, "default-route-metric: ifindex %d released", ifindex); + } +} + +/*****************************************************************************/ + +static void +_delete_volatile_connection_do(NMManager *self, NMSettingsConnection *connection) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + + if (!NM_FLAGS_ANY(nm_settings_connection_get_flags(connection), + NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE + | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL)) + return; + if (!nm_settings_has_connection(priv->settings, connection)) + return; + if (active_connection_find(self, + connection, + NULL, + NM_ACTIVE_CONNECTION_STATE_DEACTIVATED, + NULL)) + return; + + _LOGD(LOGD_DEVICE, + "volatile connection disconnected. Deleting connection '%s' (%s)", + nm_settings_connection_get_id(connection), + nm_settings_connection_get_uuid(connection)); + nm_settings_connection_delete(connection, FALSE); +} + +/* Returns: whether to notify D-Bus of the removal or not */ +static gboolean +active_connection_remove(NMManager *self, NMActiveConnection *active) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + gs_unref_object NMSettingsConnection *connection = NULL; + gboolean notify; + + nm_assert(NM_IS_ACTIVE_CONNECTION(active)); + nm_assert(c_list_contains(&priv->active_connections_lst_head, &active->active_connections_lst)); + + notify = nm_dbus_object_is_exported(NM_DBUS_OBJECT(active)); + + c_list_unlink(&active->active_connections_lst); + g_signal_emit(self, signals[ACTIVE_CONNECTION_REMOVED], 0, active); + g_signal_handlers_disconnect_by_func(active, active_connection_state_changed, self); + g_signal_handlers_disconnect_by_func(active, active_connection_default_changed, self); + g_signal_handlers_disconnect_by_func(active, active_connection_parent_active, self); + + connection = nm_g_object_ref(nm_active_connection_get_settings_connection(active)); + + nm_dbus_object_clear_and_unexport(&active); + + if (connection) + _delete_volatile_connection_do(self, connection); + + return notify; +} + +static gboolean +_active_connection_cleanup(gpointer user_data) +{ + NMManager * self = NM_MANAGER(user_data); + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + NMActiveConnection *ac, *ac_safe; + + priv->ac_cleanup_id = 0; + + g_object_freeze_notify(G_OBJECT(self)); + c_list_for_each_entry_safe (ac, + ac_safe, + &priv->active_connections_lst_head, + active_connections_lst) { + if (nm_active_connection_get_state(ac) == NM_ACTIVE_CONNECTION_STATE_DEACTIVATED) { + if (active_connection_remove(self, ac)) + _notify(self, PROP_ACTIVE_CONNECTIONS); + } + } + g_object_thaw_notify(G_OBJECT(self)); + + return FALSE; +} + +static void +active_connection_state_changed(NMActiveConnection *active, GParamSpec *pspec, NMManager *self) +{ + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + NMActiveConnectionState state; + NMSettingsConnection * con; + + state = nm_active_connection_get_state(active); + if (state == NM_ACTIVE_CONNECTION_STATE_DEACTIVATED) { + /* Destroy active connections from an idle handler to ensure that + * their last property change notifications go out, which wouldn't + * happen if we destroyed them immediately when their state was set + * to DEACTIVATED. + */ + if (!priv->ac_cleanup_id) + priv->ac_cleanup_id = g_idle_add(_active_connection_cleanup, self); + + con = nm_active_connection_get_settings_connection(active); + if (con) + g_object_set_qdata(G_OBJECT(con), autoconnect_root_quark(), NULL); + } + + nm_manager_update_state(self); +} + +static void +active_connection_default_changed(NMActiveConnection *active, GParamSpec *pspec, NMManager *self) +{ + nm_manager_update_state(self); +} + +/** + * active_connection_add(): + * @self: the #NMManager + * @active: the #NMActiveConnection to manage + * + * Begins to track and manage @active. Increases the refcount of @active. + */ +static void +active_connection_add(NMManager *self, NMActiveConnection *active) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + + nm_assert(NM_IS_ACTIVE_CONNECTION(active)); + nm_assert(!c_list_is_linked(&active->active_connections_lst)); + + c_list_link_tail(&priv->active_connections_lst_head, &active->active_connections_lst); + g_object_ref(active); + + g_signal_connect(active, + "notify::" NM_ACTIVE_CONNECTION_STATE, + G_CALLBACK(active_connection_state_changed), + self); + g_signal_connect(active, + "notify::" NM_ACTIVE_CONNECTION_DEFAULT, + G_CALLBACK(active_connection_default_changed), + self); + g_signal_connect(active, + "notify::" NM_ACTIVE_CONNECTION_DEFAULT6, + G_CALLBACK(active_connection_default_changed), + self); + + if (!nm_dbus_object_is_exported(NM_DBUS_OBJECT(active))) + nm_dbus_object_export(NM_DBUS_OBJECT(active)); + + g_signal_emit(self, signals[ACTIVE_CONNECTION_ADDED], 0, active); + + _notify(self, PROP_ACTIVE_CONNECTIONS); +} + +const CList * +nm_manager_get_active_connections(NMManager *manager) +{ + return &NM_MANAGER_GET_PRIVATE(manager)->active_connections_lst_head; +} + +static NMActiveConnection * +active_connection_find( + NMManager * self, + NMSettingsConnection * sett_conn, + const char * uuid, + NMActiveConnectionState max_state /* candidates in state @max_state will be found */, + GPtrArray ** out_all_matching) +{ + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + NMActiveConnection *ac; + NMActiveConnection *best_ac = NULL; + GPtrArray * all = NULL; + + nm_assert(!sett_conn || NM_IS_SETTINGS_CONNECTION(sett_conn)); + nm_assert(!out_all_matching || !*out_all_matching); + + c_list_for_each_entry_prev (ac, &priv->active_connections_lst_head, active_connections_lst) { + NMSettingsConnection *ac_conn; + + ac_conn = nm_active_connection_get_settings_connection(ac); + if (sett_conn && sett_conn != ac_conn) + continue; + if (uuid && !nm_streq0(uuid, nm_settings_connection_get_uuid(ac_conn))) + continue; + if (nm_active_connection_get_state(ac) > max_state) + continue; + + if (!out_all_matching) + return ac; + + if (!best_ac) { + best_ac = ac; + continue; + } + + if (!all) { + all = g_ptr_array_new_with_free_func(g_object_unref); + g_ptr_array_add(all, g_object_ref(best_ac)); + } + g_ptr_array_add(all, g_object_ref(ac)); + } + + if (!best_ac) { + AsyncOpData *async_op_data; + + c_list_for_each_entry (async_op_data, &priv->async_op_lst_head, async_op_lst) { + NMSettingsConnection *ac_conn; + + ac = async_op_data->ac_auth.active; + ac_conn = nm_active_connection_get_settings_connection(ac); + if (sett_conn && sett_conn != ac_conn) + continue; + if (uuid && !nm_streq0(uuid, nm_settings_connection_get_uuid(ac_conn))) + continue; + + if (!out_all_matching) + return ac; + + if (!best_ac) { + best_ac = ac; + continue; + } + + if (!all) { + all = g_ptr_array_new_with_free_func(g_object_unref); + g_ptr_array_add(all, g_object_ref(best_ac)); + } + g_ptr_array_add(all, g_object_ref(ac)); + } + + if (!best_ac) + return NULL; + } + + /* as an optimization, we only allocate out_all_matching, if there are more + * than one result. If there is only one result, we only return the single + * element and don't bother allocating an array. That's the common case. + * + * Also, in case we have multiple results, we return the *first* one + * as @best_ac. */ + nm_assert(!all || (all->len >= 2 && all->pdata[0] == best_ac)); + + *out_all_matching = all; + return best_ac; +} + +static NMActiveConnection * +active_connection_find_by_connection(NMManager * self, + NMSettingsConnection * sett_conn, + NMConnection * connection, + NMActiveConnectionState max_state, + GPtrArray ** out_all_matching) +{ + nm_assert(NM_IS_MANAGER(self)); + nm_assert(!sett_conn || NM_IS_SETTINGS_CONNECTION(sett_conn)); + nm_assert(!connection || NM_IS_CONNECTION(connection)); + nm_assert(sett_conn || connection); + nm_assert(!connection || !sett_conn + || connection == nm_settings_connection_get_connection(sett_conn)); + + /* Depending on whether connection is a settings connection, + * either lookup by object-identity of @connection, or compare the UUID */ + return active_connection_find(self, + sett_conn, + sett_conn ? NULL : nm_connection_get_uuid(connection), + max_state, + out_all_matching); +} + +typedef struct { + NMManager *self; + gboolean for_auto_activation; +} GetActivatableConnectionsFilterData; + +static gboolean +_get_activatable_connections_filter(NMSettings * settings, + NMSettingsConnection *sett_conn, + gpointer user_data) +{ + const GetActivatableConnectionsFilterData *d = user_data; + NMConnectionMultiConnect multi_connect; + + if (NM_FLAGS_ANY(nm_settings_connection_get_flags(sett_conn), + NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE + | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL)) + return FALSE; + + multi_connect = + _nm_connection_get_multi_connect(nm_settings_connection_get_connection(sett_conn)); + if (multi_connect == NM_CONNECTION_MULTI_CONNECT_MULTIPLE + || (multi_connect == NM_CONNECTION_MULTI_CONNECT_MANUAL_MULTIPLE + && !d->for_auto_activation)) + return TRUE; + + /* the connection is activatable, if it has no active-connections that are in state + * activated, activating, or waiting to be activated. */ + return !active_connection_find(d->self, + sett_conn, + NULL, + NM_ACTIVE_CONNECTION_STATE_ACTIVATED, + NULL); +} + +NMSettingsConnection ** +nm_manager_get_activatable_connections(NMManager *manager, + gboolean for_auto_activation, + gboolean sort, + guint * out_len) +{ + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(manager); + const GetActivatableConnectionsFilterData d = { + .self = manager, + .for_auto_activation = for_auto_activation, + }; + + return nm_settings_get_connections_clone( + priv->settings, + out_len, + _get_activatable_connections_filter, + (gpointer) &d, + sort ? nm_settings_connection_cmp_autoconnect_priority_p_with_data : NULL, + NULL); +} + +static NMActiveConnection * +active_connection_get_by_path(NMManager *self, const char *path) +{ + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + NMActiveConnection *ac; + + ac = nm_dbus_manager_lookup_object(nm_dbus_object_get_manager(NM_DBUS_OBJECT(self)), path); + if (!ac || !NM_IS_ACTIVE_CONNECTION(ac) || c_list_is_empty(&ac->active_connections_lst)) + return NULL; + + nm_assert(c_list_contains(&priv->active_connections_lst_head, &ac->active_connections_lst)); + return ac; +} + +/*****************************************************************************/ + +static void +_config_changed_cb(NMConfig * config, + NMConfigData * config_data, + NMConfigChangeFlags changes, + NMConfigData * old_data, + NMManager * self) +{ + g_object_freeze_notify(G_OBJECT(self)); + + if (NM_FLAGS_HAS(changes, NM_CONFIG_CHANGE_GLOBAL_DNS_CONFIG)) + _notify(self, PROP_GLOBAL_DNS_CONFIGURATION); + + if (!nm_streq0(nm_config_data_get_connectivity_uri(config_data), + nm_config_data_get_connectivity_uri(old_data))) { + if ((!nm_config_data_get_connectivity_uri(config_data)) + != (!nm_config_data_get_connectivity_uri(old_data))) + _notify(self, PROP_CONNECTIVITY_CHECK_AVAILABLE); + _notify(self, PROP_CONNECTIVITY_CHECK_URI); + } + + g_object_thaw_notify(G_OBJECT(self)); +} + +static void +_reload_auth_cb(NMAuthChain *chain, GDBusMethodInvocation *context, gpointer user_data) +{ + NMManager * self = NM_MANAGER(user_data); + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + GError * ret_error = NULL; + NMAuthCallResult result; + guint32 flags; + NMAuthSubject * subject; + char s_buf[60]; + NMConfigChangeFlags reload_type = NM_CONFIG_CHANGE_NONE; + + nm_assert(G_IS_DBUS_METHOD_INVOCATION(context)); + + c_list_unlink(nm_auth_chain_parent_lst_list(chain)); + flags = GPOINTER_TO_UINT(nm_auth_chain_get_data(chain, "flags")); + + subject = nm_auth_chain_get_subject(chain); + + result = nm_auth_chain_get_result(chain, NM_AUTH_PERMISSION_RELOAD); + if (result != NM_AUTH_CALL_RESULT_YES) { + ret_error = g_error_new_literal(NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + "Not authorized to reload configuration"); + } else { + if (NM_FLAGS_ANY(flags, ~NM_MANAGER_RELOAD_FLAG_ALL)) { + /* invalid flags */ + } else if (flags == 0) + reload_type = NM_CONFIG_CHANGE_CAUSE_SIGHUP; + else { + if (NM_FLAGS_HAS(flags, NM_MANAGER_RELOAD_FLAG_CONF)) + reload_type |= NM_CONFIG_CHANGE_CAUSE_CONF; + if (NM_FLAGS_HAS(flags, NM_MANAGER_RELOAD_FLAG_DNS_RC)) + reload_type |= NM_CONFIG_CHANGE_CAUSE_DNS_RC; + if (NM_FLAGS_HAS(flags, NM_MANAGER_RELOAD_FLAG_DNS_FULL)) + reload_type |= NM_CONFIG_CHANGE_CAUSE_DNS_FULL; + } + + if (reload_type == NM_CONFIG_CHANGE_NONE) { + ret_error = g_error_new_literal(NM_MANAGER_ERROR, + NM_MANAGER_ERROR_INVALID_ARGUMENTS, + "Invalid flags for reload"); + } + } + + nm_audit_log_control_op(NM_AUDIT_OP_RELOAD, + nm_sprintf_buf(s_buf, "%u", flags), + ret_error == NULL, + subject, + ret_error ? ret_error->message : NULL); + + if (ret_error) { + g_dbus_method_invocation_take_error(context, ret_error); + return; + } + + nm_config_reload(priv->config, reload_type, TRUE); + g_dbus_method_invocation_return_value(context, NULL); +} + +static void +impl_manager_reload(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMManager * self = NM_MANAGER(obj); + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMAuthChain * chain; + guint32 flags; + + g_variant_get(parameters, "(u)", &flags); + + chain = nm_auth_chain_new_context(invocation, _reload_auth_cb, self); + if (!chain) { + g_dbus_method_invocation_return_error_literal(invocation, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED); + return; + } + + c_list_link_tail(&priv->auth_lst_head, nm_auth_chain_parent_lst_list(chain)); + nm_auth_chain_set_data(chain, "flags", GUINT_TO_POINTER(flags), NULL); + nm_auth_chain_add_call(chain, NM_AUTH_PERMISSION_RELOAD, TRUE); +} + +/*****************************************************************************/ + +NMDevice * +nm_manager_get_device_by_path(NMManager *self, const char *path) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMDevice * device; + + g_return_val_if_fail(path, NULL); + + device = nm_dbus_manager_lookup_object(nm_dbus_object_get_manager(NM_DBUS_OBJECT(self)), path); + if (!device || !NM_IS_DEVICE(device) || c_list_is_empty(&device->devices_lst)) + return NULL; + + nm_assert(c_list_contains(&priv->devices_lst_head, &device->devices_lst)); + return device; +} + +NMDevice * +nm_manager_get_device_by_ifindex(NMManager *self, int ifindex) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMDevice * device; + + if (ifindex > 0) { + c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) { + if (nm_device_get_ifindex(device) == ifindex) + return device; + } + } + + return NULL; +} + +static NMDevice * +find_device_by_permanent_hw_addr(NMManager *self, const char *hwaddr) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMDevice * device; + const char * device_addr; + guint8 hwaddr_bin[NM_UTILS_HWADDR_LEN_MAX]; + gsize hwaddr_len; + + g_return_val_if_fail(hwaddr != NULL, NULL); + + if (!_nm_utils_hwaddr_aton(hwaddr, hwaddr_bin, sizeof(hwaddr_bin), &hwaddr_len)) + return NULL; + + c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) { + device_addr = nm_device_get_permanent_hw_address(device); + if (device_addr && nm_utils_hwaddr_matches(hwaddr_bin, hwaddr_len, device_addr, -1)) + return device; + } + return NULL; +} + +static NMDevice * +find_device_by_ip_iface(NMManager *self, const char *iface) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMDevice * device; + + g_return_val_if_fail(iface, NULL); + + c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) { + if (nm_device_is_real(device) && nm_streq0(nm_device_get_ip_iface(device), iface)) + return device; + } + return NULL; +} + +/** + * find_device_by_iface: + * @self: the #NMManager + * @iface: the device interface to find + * @connection: a connection to ensure the returned device is compatible with + * @slave: a slave connection to ensure a master is compatible with + * + * Finds a device by interface name, preferring realized devices. If @slave + * is given, this function will only return master devices and will ensure + * @slave, when activated, can be a slave of the returned master device. If + * @connection is given, this function will only consider devices that are + * compatible with @connection. + * + * Returns: the matching #NMDevice + */ +static NMDevice * +find_device_by_iface(NMManager * self, + const char * iface, + NMConnection *connection, + NMConnection *slave) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMDevice * fallback = NULL; + NMDevice * candidate; + + g_return_val_if_fail(iface != NULL, NULL); + + c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) { + if (!nm_streq(nm_device_get_iface(candidate), iface)) + continue; + if (connection && !nm_device_check_connection_compatible(candidate, connection, NULL)) + continue; + if (slave) { + if (!nm_device_is_master(candidate)) + continue; + if (!nm_device_check_slave_connection_compatible(candidate, slave)) + continue; + } + + if (nm_device_is_real(candidate)) + return candidate; + else if (!fallback) + fallback = candidate; + } + return fallback; +} + +static gboolean +manager_sleeping(NMManager *self) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + + if (priv->sleeping || !priv->net_enabled) + return TRUE; + return FALSE; +} + +static const char * +_nm_state_to_string(NMState state) +{ + switch (state) { + case NM_STATE_ASLEEP: + return "ASLEEP"; + case NM_STATE_DISCONNECTED: + return "DISCONNECTED"; + case NM_STATE_DISCONNECTING: + return "DISCONNECTING"; + case NM_STATE_CONNECTING: + return "CONNECTING"; + case NM_STATE_CONNECTED_LOCAL: + return "CONNECTED_LOCAL"; + case NM_STATE_CONNECTED_SITE: + return "CONNECTED_SITE"; + case NM_STATE_CONNECTED_GLOBAL: + return "CONNECTED_GLOBAL"; + case NM_STATE_UNKNOWN: + default: + return "UNKNOWN"; + } +} + +static NMState +find_best_device_state(NMManager *manager) +{ + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(manager); + NMState best_state = NM_STATE_DISCONNECTED; + NMActiveConnection *ac; + + c_list_for_each_entry (ac, &priv->active_connections_lst_head, active_connections_lst) { + NMActiveConnectionState ac_state = nm_active_connection_get_state(ac); + + switch (ac_state) { + case NM_ACTIVE_CONNECTION_STATE_ACTIVATED: + if (nm_active_connection_get_default(ac, AF_UNSPEC)) { + if (priv->connectivity_state == NM_CONNECTIVITY_FULL) + return NM_STATE_CONNECTED_GLOBAL; + + best_state = NM_STATE_CONNECTED_SITE; + } else { + if (best_state < NM_STATE_CONNECTING) + best_state = NM_STATE_CONNECTED_LOCAL; + } + break; + case NM_ACTIVE_CONNECTION_STATE_ACTIVATING: + if (!NM_IN_SET(nm_active_connection_get_activation_type(ac), + NM_ACTIVATION_TYPE_EXTERNAL, + NM_ACTIVATION_TYPE_ASSUME)) { + if (best_state != NM_STATE_CONNECTED_GLOBAL) + best_state = NM_STATE_CONNECTING; + } + break; + case NM_ACTIVE_CONNECTION_STATE_DEACTIVATING: + if (!NM_IN_SET(nm_active_connection_get_activation_type(ac), + NM_ACTIVATION_TYPE_EXTERNAL, + NM_ACTIVATION_TYPE_ASSUME)) { + if (best_state < NM_STATE_DISCONNECTING) + best_state = NM_STATE_DISCONNECTING; + } + break; + default: + break; + } + } + + return best_state; +} + +static void +nm_manager_update_metered(NMManager *self) +{ + NMManagerPrivate *priv; + NMDevice * device; + NMMetered value = NM_METERED_UNKNOWN; + + g_return_if_fail(NM_IS_MANAGER(self)); + priv = NM_MANAGER_GET_PRIVATE(self); + + if (priv->primary_connection) { + device = nm_active_connection_get_device(priv->primary_connection); + if (device) + value = nm_device_get_metered(device); + } + + if (value != priv->metered) { + priv->metered = value; + _LOGD(LOGD_CORE, "new metered value: %d", (int) priv->metered); + _notify(self, PROP_METERED); + } +} + +NMMetered +nm_manager_get_metered(NMManager *self) +{ + g_return_val_if_fail(NM_IS_MANAGER(self), NM_METERED_UNKNOWN); + + return NM_MANAGER_GET_PRIVATE(self)->metered; +} + +static void +nm_manager_update_state(NMManager *self) +{ + NMManagerPrivate *priv; + NMState new_state = NM_STATE_DISCONNECTED; + + g_return_if_fail(NM_IS_MANAGER(self)); + + priv = NM_MANAGER_GET_PRIVATE(self); + + if (manager_sleeping(self)) + new_state = NM_STATE_ASLEEP; + else + new_state = find_best_device_state(self); + + if (new_state >= NM_STATE_CONNECTED_LOCAL && priv->connectivity_state == NM_CONNECTIVITY_FULL) { + new_state = NM_STATE_CONNECTED_GLOBAL; + } + + if (priv->state == new_state) + return; + + priv->state = new_state; + + _LOGI(LOGD_CORE, "NetworkManager state is now %s", _nm_state_to_string(new_state)); + + _notify(self, PROP_STATE); + nm_dbus_object_emit_signal(NM_DBUS_OBJECT(self), + &interface_info_manager, + &signal_info_state_changed, + "(u)", + (guint32) priv->state); +} + +static void +manager_device_state_changed(NMDevice * device, + NMDeviceState new_state, + NMDeviceState old_state, + NMDeviceStateReason reason, + gpointer user_data) +{ + NMManager * self = NM_MANAGER(user_data); + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + + if (old_state == NM_DEVICE_STATE_UNMANAGED && new_state > NM_DEVICE_STATE_UNMANAGED) + retry_connections_for_parent_device(self, device); + + if (NM_IN_SET(new_state, + NM_DEVICE_STATE_UNMANAGED, + NM_DEVICE_STATE_UNAVAILABLE, + NM_DEVICE_STATE_DISCONNECTED, + NM_DEVICE_STATE_PREPARE, + NM_DEVICE_STATE_FAILED)) + _notify(self, PROP_ACTIVE_CONNECTIONS); + + if (NM_IN_SET(new_state, + NM_DEVICE_STATE_UNMANAGED, + NM_DEVICE_STATE_DISCONNECTED, + NM_DEVICE_STATE_ACTIVATED)) { + nm_manager_write_device_state(self, device, NULL); + + G_STATIC_ASSERT_EXPR(DEVICE_STATE_PRUNE_RATELIMIT_MAX < G_MAXUINT8); + if (priv->device_state_prune_ratelimit_count++ > DEVICE_STATE_PRUNE_RATELIMIT_MAX) { + /* We write the device state to /run. The state files are named after the + * ifindex (which is assumed to be unique and not repeat -- in practice + * it may repeat). So from time to time, we prune device state files + * for interfaces that no longer exist. + * + * Otherwise, the files might pile up if you create (and destroy) a large + * number of software devices. */ + priv->device_state_prune_ratelimit_count = 0; + nm_config_device_state_prune_stale(NULL, priv->platform); + } + } + + if (NM_IN_SET(new_state, NM_DEVICE_STATE_UNAVAILABLE, NM_DEVICE_STATE_DISCONNECTED)) + nm_settings_device_added(priv->settings, device); +} + +static void device_has_pending_action_changed(NMDevice *device, GParamSpec *pspec, NMManager *self); + +static void +check_if_startup_complete(NMManager *self) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMDevice * device; + const char * reason; + + if (!priv->startup) + return; + + if (!priv->devices_inited) + return; + + c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) { + reason = nm_device_has_pending_action_reason(device); + if (reason) { + _LOGD(LOGD_CORE, + "startup complete is waiting for device '%s' (%s)", + nm_device_get_iface(device), + reason); + return; + } + } + + /* All NMDevice must be ready. But also NMSettings tracks profiles that wait for + * ready devices via "connection.wait-device-timeout". + * + * Note that we only re-check nm_settings_get_startup_complete_blocked_reason() when + * all of the devices become ready (again). + * + * For example, assume we have device "eth1" and "profile-eth2" which waits for "eth2". + * If "eth1" is ready (no pending action), we only need to re-evaluate "profile-eth2" + * if we have another device ("eth2"), that becomes non-ready (had pending actions) + * and again become ready. We don't need to check "profile-eth2" until "eth2" becomes + * non-ready. + * That is why nm_settings_get_startup_complete_blocked_reason() only has any significance + * if all devices are ready too. It allows us to cut down the number of checks whether + * NMSettings is ready. That's because we don't need to re-evaluate on minor changes of + * a device, only when all devices become managed and ready. */ + + g_signal_handlers_block_by_func(priv->settings, settings_startup_complete_changed, self); + reason = nm_settings_get_startup_complete_blocked_reason(priv->settings, TRUE); + g_signal_handlers_unblock_by_func(priv->settings, settings_startup_complete_changed, self); + if (reason) { + _LOGD(LOGD_CORE, "startup complete is waiting for connection (%s)", reason); + return; + } + + _LOGI(LOGD_CORE, "startup complete"); + + priv->startup = FALSE; + + /* we no longer care about these signals. Startup-complete only + * happens once. */ + g_signal_handlers_disconnect_by_func(priv->settings, + G_CALLBACK(settings_startup_complete_changed), + self); + c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) { + g_signal_handlers_disconnect_by_func(device, + G_CALLBACK(device_has_pending_action_changed), + self); + } + + _notify(self, PROP_STARTUP); + + if (nm_config_get_configure_and_quit(priv->config)) + g_signal_emit(self, signals[CONFIGURE_QUIT], 0); +} + +static void +device_has_pending_action_changed(NMDevice *device, GParamSpec *pspec, NMManager *self) +{ + check_if_startup_complete(self); +} + +static void +settings_startup_complete_changed(NMSettings *settings, GParamSpec *pspec, NMManager *self) +{ + check_if_startup_complete(self); +} + +static void +_parent_notify_changed(NMManager *self, NMDevice *device, gboolean device_removed) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMDevice * candidate; + + nm_assert(NM_IS_DEVICE(device)); + +again: + c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) { + if (nm_device_parent_notify_changed(candidate, device, device_removed)) { + /* in the unlikely event that this changes anything, we start iterating + * again, to be sure that the device list is up-to-date. */ + goto again; + } + } +} + +static gboolean +device_is_wake_on_lan(NMPlatform *platform, NMDevice *device) +{ + int ifindex; + + ifindex = nm_device_get_ip_ifindex(device); + if (ifindex <= 0) + return FALSE; + return nm_platform_link_get_wake_on_lan(platform, ifindex); +} + +static void +remove_device(NMManager *self, NMDevice *device, gboolean quitting) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + gboolean unmanage = FALSE; + + _LOG2D(LOGD_DEVICE, + device, + "removing device (managed %d, wol %d)", + nm_device_get_managed(device, FALSE), + device_is_wake_on_lan(priv->platform, device)); + + if (nm_device_get_managed(device, FALSE)) { + if (quitting) { + /* Leave configured if wo(w)lan and quitting */ + if (device_is_wake_on_lan(priv->platform, device)) + unmanage = FALSE; + else + unmanage = nm_device_unmanage_on_quit(device); + } else { + /* the device is already gone. Unmanage it. */ + unmanage = TRUE; + } + + if (unmanage) { + if (quitting) + nm_device_set_unmanaged_by_quitting(device); + else { + nm_device_sys_iface_state_set(device, NM_DEVICE_SYS_IFACE_STATE_REMOVED); + nm_device_set_unmanaged_by_flags(device, + NM_UNMANAGED_PLATFORM_INIT, + TRUE, + NM_DEVICE_STATE_REASON_REMOVED); + } + } else if (quitting + && nm_config_get_configure_and_quit(priv->config) + == NM_CONFIG_CONFIGURE_AND_QUIT_ENABLED) { + nm_device_spawn_iface_helper(device); + } + } + + g_signal_handlers_disconnect_matched(device, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, self); + + nm_settings_device_removed(priv->settings, device, quitting); + + c_list_unlink(&device->devices_lst); + + _parent_notify_changed(self, device, TRUE); + + if (nm_device_is_real(device)) { + gboolean unconfigure_ip_config = !quitting || unmanage; + + /* When we don't unmanage the device on shutdown, we want to preserve the DNS + * configuration in resolv.conf. For that, we must leak the configuration + * in NMPolicy/NMDnsManager. We do that, by emitting the device-removed signal + * with device's ip-config object still uncleared. In that case, NMPolicy + * never learns to unconfigure the ip-config objects and does not remove them + * from DNS on shutdown (which is ugly, because we don't cleanup the memory + * properly). + * + * Control that by passing @unconfigure_ip_config. */ + nm_device_removed(device, unconfigure_ip_config); + + _emit_device_added_removed(self, device, FALSE); + } else { + /* unrealize() does not release a slave device from master and + * clear IP configurations, do it here */ + nm_device_removed(device, TRUE); + } + + g_signal_emit(self, signals[INTERNAL_DEVICE_REMOVED], 0, device); + _notify(self, PROP_ALL_DEVICES); + + update_connectivity_value(self); + + nm_dbus_object_clear_and_unexport(&device); + + check_if_startup_complete(self); +} + +static void +device_removed_cb(NMDevice *device, gpointer user_data) +{ + remove_device(NM_MANAGER(user_data), device, FALSE); +} + +NMState +nm_manager_get_state(NMManager *manager) +{ + g_return_val_if_fail(NM_IS_MANAGER(manager), NM_STATE_UNKNOWN); + + return NM_MANAGER_GET_PRIVATE(manager)->state; +} + +/*****************************************************************************/ + +static NMDevice * +find_parent_device_for_connection(NMManager * self, + NMConnection * connection, + NMDeviceFactory *cached_factory, + const char ** out_parent_spec) +{ + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + NMDeviceFactory * factory; + const char * parent_name = NULL; + NMSettingsConnection *parent_connection; + NMDevice * parent, *first_compatible = NULL; + NMDevice * candidate; + + g_return_val_if_fail(NM_IS_CONNECTION(connection), NULL); + NM_SET_OUT(out_parent_spec, NULL); + + if (!cached_factory) { + factory = nm_device_factory_manager_find_factory_for_connection(connection); + if (!factory) + return NULL; + } else + factory = cached_factory; + + parent_name = nm_device_factory_get_connection_parent(factory, connection); + if (!parent_name) + return NULL; + + NM_SET_OUT(out_parent_spec, parent_name); + + /* Try as an interface name of a parent device */ + parent = find_device_by_iface(self, parent_name, NULL, NULL); + if (parent) + return parent; + + /* Maybe a hardware address */ + parent = find_device_by_permanent_hw_addr(self, parent_name); + if (parent) + return parent; + + /* Maybe a connection UUID */ + parent_connection = nm_settings_get_connection_by_uuid(priv->settings, parent_name); + if (!parent_connection) + return NULL; + + /* Check if the parent connection is currently activated or is compatible + * with some known device. + */ + c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) { + /* Unmanaged devices are not compatible with any connection */ + if (!nm_device_get_managed(candidate, FALSE)) + continue; + + if (nm_device_get_settings_connection(candidate) == parent_connection) + return candidate; + + if (!first_compatible + && nm_device_check_connection_compatible( + candidate, + nm_settings_connection_get_connection(parent_connection), + NULL)) + first_compatible = candidate; + } + + return first_compatible; +} + +/** + * nm_manager_get_connection_iface: + * @self: the #NMManager + * @connection: the #NMConnection to get the interface for + * @out_parent: on success, the parent device if any + * @out_parent_spec: on return, a string specifying the parent device + * in the connection. This can be a device name, a MAC address or a + * connection UUID. + * @error: an error if determining the virtual interface name failed + * + * Given @connection, returns the interface name that the connection + * would need to use when activated. %NULL is returned if the name + * is not specified in connection or a the name for a virtual device + * could not be generated. + * + * Returns: the expected interface name (caller takes ownership), or %NULL + */ +char * +nm_manager_get_connection_iface(NMManager * self, + NMConnection *connection, + NMDevice ** out_parent, + const char ** out_parent_spec, + GError ** error) +{ + NMDeviceFactory *factory; + char * iface = NULL; + NMDevice * parent = NULL; + + NM_SET_OUT(out_parent, NULL); + NM_SET_OUT(out_parent_spec, NULL); + + factory = nm_device_factory_manager_find_factory_for_connection(connection); + if (!factory) { + if (nm_streq0(nm_connection_get_connection_type(connection), + NM_SETTING_GENERIC_SETTING_NAME)) { + /* the generic type doesn't have a factory. */ + goto return_ifname_fom_connection; + } + + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_FAILED, + "NetworkManager plugin for '%s' unavailable", + nm_connection_get_connection_type(connection)); + return NULL; + } + + if (!out_parent && !NM_DEVICE_FACTORY_GET_CLASS(factory)->get_connection_iface) { + /* optimization. Shortcut lookup of the partent device. */ + goto return_ifname_fom_connection; + } + + parent = find_parent_device_for_connection(self, connection, factory, out_parent_spec); + iface = nm_device_factory_get_connection_iface(factory, + connection, + parent ? nm_device_get_ip_iface(parent) : NULL, + error); + if (!iface) + return NULL; + + if (out_parent) + *out_parent = parent; + return iface; + +return_ifname_fom_connection: + iface = g_strdup(nm_connection_get_interface_name(connection)); + if (!iface) { + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_FAILED, + "failed to determine interface name: error determine name for %s", + nm_connection_get_connection_type(connection)); + } + return iface; +} + +/** + * nm_manager_iface_for_uuid: + * @self: the #NMManager + * @uuid: the connection uuid + * + * Gets a link name for the given UUID. Useful for the settings plugins that + * wish to write configuration files compatible with tooling that can't + * interpret our UUIDs. + * + * Returns: An interface name; %NULL if none matches + */ +const char * +nm_manager_iface_for_uuid(NMManager *self, const char *uuid) +{ + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + NMSettingsConnection *sett_conn; + + sett_conn = nm_settings_get_connection_by_uuid(priv->settings, uuid); + if (!sett_conn) + return NULL; + + return nm_connection_get_interface_name(nm_settings_connection_get_connection(sett_conn)); +} + +NMDevice * +nm_manager_get_device(NMManager *self, const char *ifname, NMDeviceType device_type) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMDevice * device; + + g_return_val_if_fail(ifname, NULL); + g_return_val_if_fail(device_type != NM_DEVICE_TYPE_UNKNOWN, NULL); + + c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) { + if (nm_device_get_device_type(device) == device_type + && nm_streq0(nm_device_get_iface(device), ifname)) + return device; + } + + return NULL; +} + +gboolean +nm_manager_remove_device(NMManager *self, const char *ifname, NMDeviceType device_type) +{ + NMDevice *d; + + d = nm_manager_get_device(self, ifname, device_type); + if (!d) + return FALSE; + + remove_device(self, d, FALSE); + return TRUE; +} + +/** + * system_create_virtual_device: + * @self: the #NMManager + * @connection: the connection which might require a virtual device + * + * If @connection requires a virtual device and one does not yet exist for it, + * creates that device. + * + * Returns: A #NMDevice that was just realized; %NULL if none + */ +static NMDevice * +system_create_virtual_device(NMManager *self, NMConnection *connection) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMDeviceFactory * factory; + gs_free NMSettingsConnection **connections = NULL; + guint i; + gs_free char * iface = NULL; + const char * parent_spec; + NMDevice * device = NULL, *parent = NULL; + NMDevice * dev_candidate; + GError * error = NULL; + NMLogLevel log_level; + + g_return_val_if_fail(NM_IS_MANAGER(self), NULL); + g_return_val_if_fail(NM_IS_CONNECTION(connection), NULL); + + iface = nm_manager_get_connection_iface(self, connection, &parent, &parent_spec, &error); + if (!iface) { + _LOG3D(LOGD_DEVICE, connection, "can't get a name of a virtual device: %s", error->message); + g_error_free(error); + return NULL; + } + + if (parent_spec && !parent) { + /* parent is not ready, wait */ + return NULL; + } + + /* See if there's a device that is already compatible with this connection */ + c_list_for_each_entry (dev_candidate, &priv->devices_lst_head, devices_lst) { + if (nm_device_check_connection_compatible(dev_candidate, connection, NULL)) { + if (nm_device_is_real(dev_candidate)) { + _LOG3D(LOGD_DEVICE, connection, "already created virtual interface name %s", iface); + return NULL; + } + + device = dev_candidate; + break; + } + } + + if (!device) { + /* No matching device found. Proceed creating a new one. */ + + factory = nm_device_factory_manager_find_factory_for_connection(connection); + if (!factory) { + _LOG3E(LOGD_DEVICE, + connection, + "(%s) NetworkManager plugin for '%s' unavailable", + iface, + nm_connection_get_connection_type(connection)); + return NULL; + } + + device = nm_device_factory_create_device(factory, iface, NULL, connection, NULL, &error); + if (!device) { + _LOG3W(LOGD_DEVICE, connection, "factory can't create the device: %s", error->message); + g_error_free(error); + return NULL; + } + + _LOG3D(LOGD_DEVICE, connection, "create virtual device %s", nm_device_get_iface(device)); + + if (!add_device(self, device, &error)) { + _LOG3W(LOGD_DEVICE, + connection, + "can't register the device with manager: %s", + error->message); + g_error_free(error); + g_object_unref(device); + return NULL; + } + + /* Add device takes a reference that NMManager still owns, so it's + * safe to unref here and still return @device. + */ + g_object_unref(device); + } + + if (!nm_device_check_unrealized_device_managed(device)) { + _LOG3D(LOGD_DEVICE, + connection, + "skip activation because virtual device '%s' is unmanaged", + nm_device_get_iface(device)); + return device; + } + + if (!find_master(self, connection, device, NULL, NULL, NULL, &error)) { + _LOG3D(LOGD_DEVICE, connection, "skip activation: %s", error->message); + g_error_free(error); + return device; + } + + /* Create backing resources if the device has any autoconnect connections */ + connections = nm_settings_get_connections_clone( + priv->settings, + NULL, + NULL, + NULL, + nm_settings_connection_cmp_autoconnect_priority_p_with_data, + NULL); + for (i = 0; connections[i]; i++) { + NMConnection * candidate = nm_settings_connection_get_connection(connections[i]); + NMSettingConnection *s_con; + + if (!nm_device_check_connection_compatible(device, candidate, NULL)) + continue; + + s_con = nm_connection_get_setting_connection(candidate); + g_assert(s_con); + if (!nm_setting_connection_get_autoconnect(s_con) + || nm_settings_connection_autoconnect_is_blocked(connections[i])) + continue; + + /* Create any backing resources the device needs */ + if (!nm_device_create_and_realize(device, connection, parent, &error)) { + log_level = + g_error_matches(error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_MISSING_DEPENDENCIES) + ? LOGL_DEBUG + : LOGL_ERR; + _NMLOG3(log_level, + LOGD_DEVICE, + connection, + "couldn't create the device: %s", + error->message); + g_error_free(error); + return NULL; + } + + retry_connections_for_parent_device(self, device); + break; + } + + return device; +} + +static void +retry_connections_for_parent_device(NMManager *self, NMDevice *device) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + gs_free NMSettingsConnection **connections = NULL; + guint i; + + g_return_if_fail(device); + + connections = nm_settings_get_connections_clone( + priv->settings, + NULL, + NULL, + NULL, + nm_settings_connection_cmp_autoconnect_priority_p_with_data, + NULL); + for (i = 0; connections[i]; i++) { + NMSettingsConnection *sett_conn = connections[i]; + NMConnection * connection = nm_settings_connection_get_connection(sett_conn); + gs_free_error GError *error = NULL; + gs_free char * ifname = NULL; + NMDevice * parent; + + parent = find_parent_device_for_connection(self, connection, NULL, NULL); + if (parent == device) { + /* Only try to activate devices that don't already exist */ + ifname = nm_manager_get_connection_iface(self, connection, &parent, NULL, &error); + if (ifname) { + if (!nm_platform_link_get_by_ifname(NM_PLATFORM_GET, ifname)) + connection_changed(self, sett_conn); + } + } + } +} + +static void +connection_changed(NMManager *self, NMSettingsConnection *sett_conn) +{ + NMConnection *connection; + NMDevice * device; + + if (NM_FLAGS_ANY(nm_settings_connection_get_flags(sett_conn), + NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE + | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL)) + return; + + connection = nm_settings_connection_get_connection(sett_conn); + + if (!nm_connection_is_virtual(connection)) + return; + + device = system_create_virtual_device(self, connection); + if (!device) + return; + + /* Maybe the device that was created was needed by some other + * connection's device (parent of a VLAN). Let the connections + * can use the newly created device as a parent know. */ + retry_connections_for_parent_device(self, device); +} + +static void +connection_added_cb(NMSettings *settings, NMSettingsConnection *sett_conn, NMManager *self) +{ + connection_changed(self, sett_conn); +} + +static void +connection_updated_cb(NMSettings * settings, + NMSettingsConnection *sett_conn, + guint update_reason_u, + NMManager * self) +{ + connection_changed(self, sett_conn); +} + +/*****************************************************************************/ + +static void +_delete_volatile_connection_all(NMManager *self, gboolean do_delete) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMCListElem * elem; + + while ( + (elem = c_list_first_entry(&priv->delete_volatile_connection_lst_head, NMCListElem, lst))) { + gs_unref_object NMSettingsConnection *connection = NULL; + + connection = nm_c_list_elem_free_steal(elem); + if (do_delete) + _delete_volatile_connection_do(self, connection); + } +} + +static gboolean +_delete_volatile_connection_cb(gpointer user_data) +{ + NMManager * self = user_data; + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + + priv->delete_volatile_connection_idle_id = 0; + _delete_volatile_connection_all(self, TRUE); + return G_SOURCE_REMOVE; +} + +static void +connection_flags_changed(NMSettings *settings, NMSettingsConnection *connection, gpointer user_data) +{ + NMManager * self = user_data; + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + + if (!NM_FLAGS_ANY(nm_settings_connection_get_flags(connection), + NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE + | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL)) + return; + + if (active_connection_find(self, + connection, + NULL, + NM_ACTIVE_CONNECTION_STATE_DEACTIVATED, + NULL)) { + /* the connection still has an active-connection. It will be purged + * when the active connection(s) get(s) removed. */ + return; + } + + c_list_link_tail(&priv->delete_volatile_connection_lst_head, + &nm_c_list_elem_new_stale(g_object_ref(connection))->lst); + if (!priv->delete_volatile_connection_idle_id) + priv->delete_volatile_connection_idle_id = g_idle_add(_delete_volatile_connection_cb, self); +} + +/*****************************************************************************/ + +static void +system_unmanaged_devices_changed_cb(NMSettings *settings, GParamSpec *pspec, gpointer user_data) +{ + NMManager * self = NM_MANAGER(user_data); + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMDevice * device; + + c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) + nm_device_set_unmanaged_by_user_settings(device); +} + +static void +hostname_changed_cb(NMHostnameManager *hostname_manager, GParamSpec *pspec, NMManager *self) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + const char * hostname; + + hostname = nm_hostname_manager_get_hostname(priv->hostname_manager); + + nm_dispatcher_call_hostname(NULL, NULL, NULL); + nm_dhcp_manager_set_default_hostname(nm_dhcp_manager_get(), hostname); +} + +/*****************************************************************************/ +/* General NMManager stuff */ +/*****************************************************************************/ + +static gboolean +radio_enabled_for_rstate(RadioState *rstate, gboolean check_changeable) +{ + gboolean enabled; + + enabled = rstate->user_enabled && rstate->hw_enabled; + if (check_changeable) + enabled &= rstate->sw_enabled; + return enabled; +} + +static gboolean +radio_enabled_for_type(NMManager *self, RfKillType rtype, gboolean check_changeable) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + + return radio_enabled_for_rstate(&priv->radio_states[rtype], check_changeable); +} + +static void +manager_update_radio_enabled(NMManager *self, RadioState *rstate, gboolean enabled) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMDevice * device; + + /* Do nothing for radio types not yet implemented */ + if (!rstate->prop) + return; + + g_object_notify(G_OBJECT(self), rstate->prop); + + /* Don't touch devices if asleep/networking disabled */ + if (manager_sleeping(self)) + return; + + /* enable/disable wireless devices as required */ + c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) { + if (nm_device_get_rfkill_type(device) == rstate->rtype) { + _LOG2D(LOGD_RFKILL, + device, + "rfkill: setting radio %s", + enabled ? "enabled" : "disabled"); + nm_device_set_enabled(device, enabled); + } + } +} + +static void +update_rstate_from_rfkill(NMRfkillManager *rfkill_mgr, RadioState *rstate) +{ + switch (nm_rfkill_manager_get_rfkill_state(rfkill_mgr, rstate->rtype)) { + case RFKILL_UNBLOCKED: + rstate->sw_enabled = TRUE; + rstate->hw_enabled = TRUE; + break; + case RFKILL_SOFT_BLOCKED: + rstate->sw_enabled = FALSE; + rstate->hw_enabled = TRUE; + break; + case RFKILL_HARD_BLOCKED: + rstate->sw_enabled = FALSE; + rstate->hw_enabled = FALSE; + break; + default: + g_warn_if_reached(); + break; + } +} + +static void +manager_rfkill_update_one_type(NMManager *self, RadioState *rstate, RfKillType rtype) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + gboolean old_enabled, new_enabled, old_rfkilled, new_rfkilled, old_hwe; + + old_enabled = radio_enabled_for_rstate(rstate, TRUE); + old_rfkilled = rstate->hw_enabled && rstate->sw_enabled; + old_hwe = rstate->hw_enabled; + + /* recheck kernel rfkill state */ + update_rstate_from_rfkill(priv->rfkill_mgr, rstate); + + /* Print out all states affecting device enablement */ + if (rstate->desc) { + _LOGD(LOGD_RFKILL, + "rfkill: %s hw-enabled %d sw-enabled %d", + rstate->desc, + rstate->hw_enabled, + rstate->sw_enabled); + } + + /* Log new killswitch state */ + new_rfkilled = rstate->hw_enabled && rstate->sw_enabled; + if (old_rfkilled != new_rfkilled) { + _LOGI(LOGD_RFKILL, + "rfkill: %s now %s by radio killswitch", + rstate->desc, + new_rfkilled ? "enabled" : "disabled"); + } + + /* Send out property changed signal for HW enabled */ + if (rstate->hw_enabled != old_hwe) { + if (rstate->hw_prop) + g_object_notify(G_OBJECT(self), rstate->hw_prop); + } + + /* And finally update the actual device radio state itself; respect the + * daemon state here because this is never called from user-triggered + * radio changes and we only want to ignore the daemon enabled state when + * handling user radio change requests. + */ + new_enabled = radio_enabled_for_rstate(rstate, TRUE); + if (new_enabled != old_enabled) + manager_update_radio_enabled(self, rstate, new_enabled); +} + +static void +nm_manager_rfkill_update(NMManager *self, RfKillType rtype) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + guint i; + + if (rtype != RFKILL_TYPE_UNKNOWN) + manager_rfkill_update_one_type(self, &priv->radio_states[rtype], rtype); + else { + /* Otherwise, sync all radio types */ + for (i = 0; i < RFKILL_TYPE_MAX; i++) + manager_rfkill_update_one_type(self, &priv->radio_states[i], i); + } +} + +static void +device_auth_done_cb(NMAuthChain *chain, GDBusMethodInvocation *context, gpointer user_data) +{ + NMManager * self = NM_MANAGER(user_data); + gs_free_error GError * error = NULL; + NMAuthCallResult result; + NMDevice * device; + GCancellable * cancellable; + const char * permission; + NMManagerDeviceAuthRequestFunc callback; + NMAuthSubject * subject; + + nm_assert(G_IS_DBUS_METHOD_INVOCATION(context)); + + c_list_unlink(nm_auth_chain_parent_lst_list(chain)); + + permission = nm_auth_chain_get_data(chain, "perm"); + nm_assert(permission); + callback = nm_auth_chain_get_data(chain, "callback"); + nm_assert(callback); + device = nm_auth_chain_get_data(chain, "device"); + nm_assert(NM_IS_DEVICE(device)); + + cancellable = nm_auth_chain_get_cancellable(chain); + nm_assert(!cancellable || G_IS_CANCELLABLE(cancellable)); + + result = nm_auth_chain_get_result(chain, permission); + subject = nm_auth_chain_get_subject(chain); + + if (cancellable && g_cancellable_set_error_if_cancelled(cancellable, &error)) { + /* pass. */ + } else { + if (result != NM_AUTH_CALL_RESULT_YES) { + _LOGD(LOGD_CORE, "%s request failed: not authorized", permission); + error = g_error_new(NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + "%s request failed: not authorized", + permission); + } + + nm_assert(error || (result == NM_AUTH_CALL_RESULT_YES)); + } + + callback(device, context, subject, error, nm_auth_chain_get_data(chain, "user-data")); +} + +static void +_device_auth_done_fail_on_idle(gpointer user_data, GCancellable *cancellable) +{ + gs_unref_object NMManager *self = NULL; + gs_unref_object NMDevice *device = NULL; + gs_unref_object GDBusMethodInvocation *context = NULL; + gs_unref_object NMAuthSubject *subject = NULL; + gs_free_error GError *error_original = NULL; + gs_free_error GError * error_cancelled = NULL; + NMManagerDeviceAuthRequestFunc callback; + gpointer callback_user_data; + + nm_utils_user_data_unpack(user_data, + &self, + &device, + &context, + &subject, + &error_original, + &callback, + &callback_user_data); + + g_cancellable_set_error_if_cancelled(cancellable, &error_cancelled); + + callback(device, context, subject, error_cancelled ?: error_original, callback_user_data); +} + +void +nm_manager_device_auth_request(NMManager * self, + NMDevice * device, + GDBusMethodInvocation * context, + NMConnection * connection, + const char * permission, + gboolean allow_interaction, + GCancellable * cancellable, + NMManagerDeviceAuthRequestFunc callback, + gpointer user_data) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + gs_free_error GError *error = NULL; + gs_unref_object NMAuthSubject *subject = NULL; + NMAuthChain * chain; + char * permission_dup; + + /* Validate the caller */ + subject = nm_dbus_manager_new_auth_subject_from_context(context); + if (!subject) { + g_set_error_literal(&error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + NM_UTILS_ERROR_MSG_REQ_UID_UKNOWN); + goto fail_on_idle; + } + + /* Ensure the subject has permissions for this connection */ + if (connection + && !nm_auth_is_subject_in_acl_set_error(connection, + subject, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + &error)) + goto fail_on_idle; + + chain = nm_auth_chain_new_subject(subject, context, device_auth_done_cb, self); + if (cancellable) + nm_auth_chain_set_cancellable(chain, cancellable); + + permission_dup = g_strdup(permission); + + c_list_link_tail(&priv->auth_lst_head, nm_auth_chain_parent_lst_list(chain)); + nm_auth_chain_set_data(chain, "device", g_object_ref(device), g_object_unref); + nm_auth_chain_set_data(chain, "callback", callback, NULL); + nm_auth_chain_set_data(chain, "user-data", user_data, NULL); + nm_auth_chain_set_data(chain, "perm", permission_dup /* transfer ownership */, g_free); + nm_auth_chain_add_call_unsafe(chain, permission_dup, allow_interaction); + return; + +fail_on_idle: + nm_utils_invoke_on_idle(cancellable, + _device_auth_done_fail_on_idle, + nm_utils_user_data_pack(g_object_ref(self), + g_object_ref(device), + g_object_ref(context), + g_steal_pointer(&subject), + g_steal_pointer(&error), + callback, + user_data)); +} + +static gboolean +new_activation_allowed_for_connection(NMManager *self, NMSettingsConnection *connection) +{ + if (NM_IN_SET( + _nm_connection_get_multi_connect(nm_settings_connection_get_connection(connection)), + NM_CONNECTION_MULTI_CONNECT_MANUAL_MULTIPLE, + NM_CONNECTION_MULTI_CONNECT_MULTIPLE)) + return TRUE; + + return !active_connection_find(self, + connection, + NULL, + NM_ACTIVE_CONNECTION_STATE_ACTIVATED, + NULL); +} + +/** + * get_existing_connection: + * @manager: #NMManager instance + * @device: #NMDevice instance + * @out_generated: (allow-none): return TRUE, if the connection was generated. + * + * Returns: a #NMSettingsConnection to be assumed by the device, or %NULL if + * the device does not support assuming existing connections. + */ +static NMSettingsConnection * +get_existing_connection(NMManager *self, NMDevice *device, gboolean *out_generated) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + gs_unref_object NMConnection *connection = NULL; + NMSettingsConnection * added; + GError * error = NULL; + gs_free_error GError *gen_error = NULL; + NMDevice * master = NULL; + int ifindex = nm_device_get_ifindex(device); + NMSettingsConnection *matched = NULL; + NMSettingsConnection *connection_checked = NULL; + gboolean assume_state_guess_assume = FALSE; + const char * assume_state_connection_uuid = NULL; + gboolean maybe_later, only_by_uuid = FALSE; + + if (out_generated) + *out_generated = FALSE; + + nm_device_capture_initial_config(device); + + if (ifindex) { + int master_ifindex = nm_platform_link_get_master(priv->platform, ifindex); + + /* Check that the master is activating before assuming a + * slave connection. However, ignore ovs-system master as + * we never manage it. + */ + if (master_ifindex + && nm_platform_link_get_type(priv->platform, master_ifindex) + != NM_LINK_TYPE_OPENVSWITCH) { + master = nm_manager_get_device_by_ifindex(self, master_ifindex); + if (!master) { + _LOG2D(LOGD_DEVICE, + device, + "assume: don't assume because " + "cannot generate connection for slave before its master (%s/%d)", + nm_platform_link_get_name(priv->platform, master_ifindex), + master_ifindex); + return NULL; + } + if (!nm_device_get_act_request(master)) { + _LOG2D(LOGD_DEVICE, + device, + "assume: don't assume because " + "cannot generate connection for slave before master %s activates", + nm_device_get_iface(master)); + return NULL; + } + } + } + + /* The core of the API is nm_device_generate_connection() function and + * update_connection() virtual method and the convenient connection_type + * class attribute. Subclasses supporting the new API must have + * update_connection() implemented, otherwise nm_device_generate_connection() + * returns NULL. + */ + connection = nm_device_generate_connection(device, master, &maybe_later, &gen_error); + if (!connection) { + if (maybe_later) { + /* The device can generate a connection, but it failed for now. + * Give it a chance to match a connection from the state file. */ + only_by_uuid = TRUE; + } else { + nm_device_assume_state_reset(device); + _LOG2D(LOGD_DEVICE, + device, + "assume: cannot generate connection: %s", + gen_error->message); + return NULL; + } + } + + nm_device_assume_state_get(device, &assume_state_guess_assume, &assume_state_connection_uuid); + + /* Now we need to compare the generated connection to each configured + * connection. The comparison function is the heart of the connection + * assumption implementation and it must compare the connections very + * carefully to sort out various corner cases. Also, the comparison is + * not entirely symmetric. + * + * When no configured connection matches the generated connection, we keep + * the generated connection instead. + */ + if (assume_state_connection_uuid + && (connection_checked = + nm_settings_get_connection_by_uuid(priv->settings, assume_state_connection_uuid)) + && new_activation_allowed_for_connection(self, connection_checked) + && nm_device_check_connection_compatible( + device, + nm_settings_connection_get_connection(connection_checked), + NULL)) { + if (connection) { + NMConnection *con = nm_settings_connection_get_connection(connection_checked); + + if (nm_utils_match_connection((NMConnection *[]){con, NULL}, + connection, + TRUE, + nm_device_has_carrier(device), + nm_device_get_route_metric(device, AF_INET), + nm_device_get_route_metric(device, AF_INET6), + NULL, + NULL)) + matched = connection_checked; + } else + matched = connection_checked; + } + + if (!matched && only_by_uuid) { + _LOG2D(LOGD_DEVICE, device, "assume: cannot generate connection: %s", gen_error->message); + return NULL; + } + + if (!matched && assume_state_guess_assume) { + gs_free NMSettingsConnection **sett_conns = NULL; + guint len, i, j; + + /* the state file doesn't indicate a connection UUID to assume. Search the + * persistent connections for a matching candidate. */ + sett_conns = nm_manager_get_activatable_connections(self, FALSE, FALSE, &len); + if (len > 0) { + for (i = 0, j = 0; i < len; i++) { + NMSettingsConnection *sett_conn = sett_conns[i]; + + if (sett_conn != connection_checked + && nm_device_check_connection_compatible( + device, + nm_settings_connection_get_connection(sett_conn), + NULL)) + sett_conns[j++] = sett_conn; + } + sett_conns[j] = NULL; + len = j; + if (len > 0) { + gs_free NMConnection **conns = NULL; + NMConnection * con; + + g_qsort_with_data(sett_conns, + len, + sizeof(sett_conns[0]), + nm_settings_connection_cmp_timestamp_p_with_data, + NULL); + + conns = nm_settings_connections_array_to_connections(sett_conns, len); + + con = nm_utils_match_connection(conns, + connection, + FALSE, + nm_device_has_carrier(device), + nm_device_get_route_metric(device, AF_INET), + nm_device_get_route_metric(device, AF_INET6), + NULL, + NULL); + if (con) { + for (i = 0; i < len; i++) { + if (conns[i] == con) { + matched = sett_conns[i]; + break; + } + } + nm_assert(matched); + } + } + } + } + + if (matched) { + if (NM_FLAGS_HAS(nm_settings_connection_get_flags(matched), + NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL)) { + _LOG2D(LOGD_DEVICE, + device, + "assume: take over previous connection '%s' (%s)", + nm_settings_connection_get_id(matched), + nm_settings_connection_get_uuid(matched)); + NM_SET_OUT(out_generated, TRUE); + } else { + _LOG2I(LOGD_DEVICE, + device, + "assume: will attempt to assume matching connection '%s' (%s)%s", + nm_settings_connection_get_id(matched), + nm_settings_connection_get_uuid(matched), + assume_state_connection_uuid + && nm_streq(assume_state_connection_uuid, + nm_settings_connection_get_uuid(matched)) + ? " (indicated)" + : " (guessed)"); + } + nm_device_assume_state_reset(device); + return matched; + } + + _LOG2D(LOGD_DEVICE, + device, + "assume: generated connection '%s' (%s)", + nm_connection_get_id(connection), + nm_connection_get_uuid(connection)); + + nm_device_assume_state_reset(device); + + if (!nm_settings_add_connection(priv->settings, + connection, + NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY, + NM_SETTINGS_CONNECTION_ADD_REASON_NONE, + NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED + | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE + | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL, + &added, + &error)) { + _LOG2W(LOGD_SETTINGS, + device, + "assume: failure to save generated connection '%s': %s", + nm_connection_get_id(connection), + error->message); + g_error_free(error); + return NULL; + } + + NM_SET_OUT(out_generated, TRUE); + return added; +} + +static gboolean +copy_lease(const char *src, const char *dst) +{ + nm_auto_close int src_fd = -1; + int dst_fd; + ssize_t res, size = SSIZE_MAX; + + src_fd = open(src, O_RDONLY | O_CLOEXEC); + if (src_fd < 0) + return FALSE; + + dst_fd = open(dst, O_CREAT | O_EXCL | O_CLOEXEC | O_WRONLY, 0644); + if (dst_fd < 0) + return FALSE; + + while ((res = sendfile(dst_fd, src_fd, NULL, size)) > 0) + size -= res; + + nm_close(dst_fd); + + if (res != 0) { + unlink(dst); + return FALSE; + } + + return TRUE; +} + +static gboolean +recheck_assume_connection(NMManager *self, NMDevice *device) +{ + NMSettingsConnection *sett_conn; + gboolean was_unmanaged = FALSE; + gboolean generated = FALSE; + NMDeviceState state; + gboolean activation_type_assume; + + g_return_val_if_fail(NM_IS_MANAGER(self), FALSE); + g_return_val_if_fail(NM_IS_DEVICE(device), FALSE); + + if (!nm_device_get_managed(device, FALSE)) { + nm_device_assume_state_reset(device); + _LOG2D(LOGD_DEVICE, device, "assume: don't assume because %s", "not managed"); + return FALSE; + } + + state = nm_device_get_state(device); + if (state > NM_DEVICE_STATE_DISCONNECTED) { + nm_device_assume_state_reset(device); + _LOG2D(LOGD_DEVICE, + device, + "assume: don't assume due to device state %s", + nm_device_state_to_str(state)); + return FALSE; + } + + sett_conn = get_existing_connection(self, device, &generated); + /* log no reason. get_existing_connection() already does it. */ + if (!sett_conn) + return FALSE; + + activation_type_assume = !generated; + + if (state == NM_DEVICE_STATE_UNMANAGED) { + gs_free char *initramfs_lease = + g_strdup_printf(RUNSTATEDIR "/initramfs/net.%s.lease", nm_device_get_iface(device)); + gs_free char *connection_lease = g_strdup_printf(NMRUNDIR "/dhclient-%s-%s.lease", + nm_settings_connection_get_uuid(sett_conn), + nm_device_get_iface(device)); + + if (copy_lease(initramfs_lease, connection_lease)) { + unlink(initramfs_lease); + /* + * We've managed to steal the lease used by initramfs before it + * killed off the dhclient. We need to take ownership of the configured + * connection and act like the device was configured by us. + * Otherwise, the address would just expire. + */ + _LOG2I(LOGD_DEVICE, device, "assume: taking over an initramfs-configured connection"); + activation_type_assume = TRUE; + + if (generated) { + gs_unref_object NMConnection *con2 = NULL; + + con2 = nm_simple_connection_new_clone( + nm_settings_connection_get_connection(sett_conn)); + + /* Reset the IPv4 setting to empty method=auto, regardless of what assumption guessed. */ + nm_connection_add_setting(con2, + g_object_new(NM_TYPE_SETTING_IP4_CONFIG, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_AUTO, + NULL)); + + nm_settings_connection_update(sett_conn, + con2, + NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP, + NM_SETTINGS_CONNECTION_INT_FLAGS_NONE, + NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE + | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL, + NM_SETTINGS_CONNECTION_UPDATE_REASON_NONE, + "assume-initrd", + NULL); + } + } + } + + nm_device_sys_iface_state_set(device, + activation_type_assume ? NM_DEVICE_SYS_IFACE_STATE_ASSUME + : NM_DEVICE_SYS_IFACE_STATE_EXTERNAL); + + /* Move device to DISCONNECTED to activate the connection */ + if (state == NM_DEVICE_STATE_UNMANAGED) { + was_unmanaged = TRUE; + nm_device_state_changed(device, + NM_DEVICE_STATE_UNAVAILABLE, + NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED); + } + if (nm_device_get_state(device) == NM_DEVICE_STATE_UNAVAILABLE) { + nm_device_state_changed(device, + NM_DEVICE_STATE_DISCONNECTED, + NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED); + } + + g_return_val_if_fail(nm_device_get_state(device) >= NM_DEVICE_STATE_DISCONNECTED, FALSE); + + { + gs_unref_object NMActiveConnection *active = NULL; + gs_unref_object NMAuthSubject *subject = NULL; + NMActiveConnection * master_ac; + GError * error = NULL; + + subject = nm_auth_subject_new_internal(); + + /* Note: the lifetime of the activation connection is always bound to the profiles visibility + * via NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY. + * + * This only makes a difference, if the profile actually has "connection.permissions" + * set to limit visibility (which is not the case for externally managed, generated profiles). + * + * If we assume a previously active connection whose lifetime was unbound, we now bind it + * after restart. That is not correct, and can mean that the profile becomes subject to + * deactivation after restart (if the user logs out). + * + * This should be improved, but it's unclear how. */ + active = _new_active_connection( + self, + FALSE, + sett_conn, + NULL, + NULL, + NULL, + device, + subject, + activation_type_assume ? NM_ACTIVATION_TYPE_ASSUME : NM_ACTIVATION_TYPE_EXTERNAL, + activation_type_assume ? NM_ACTIVATION_REASON_ASSUME : NM_ACTIVATION_REASON_EXTERNAL, + NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY, + &error); + + if (!active) { + _LOGW(LOGD_DEVICE, + "assume: assumed connection %s failed to activate: %s", + nm_dbus_object_get_path(NM_DBUS_OBJECT(sett_conn)), + error->message); + g_error_free(error); + + if (was_unmanaged) { + nm_device_state_changed(device, + NM_DEVICE_STATE_UNAVAILABLE, + NM_DEVICE_STATE_REASON_CONFIG_FAILED); + } + + if (generated && !activation_type_assume) { + _LOG2D(LOGD_DEVICE, + device, + "assume: deleting generated connection after assuming failed"); + nm_settings_connection_delete(sett_conn, FALSE); + } else { + if (nm_device_sys_iface_state_get(device) == NM_DEVICE_SYS_IFACE_STATE_ASSUME) + nm_device_sys_iface_state_set(device, NM_DEVICE_SYS_IFACE_STATE_EXTERNAL); + } + return FALSE; + } + + /* If the device is a slave or VLAN, find the master ActiveConnection */ + master_ac = NULL; + if (find_master(self, + nm_settings_connection_get_connection(sett_conn), + device, + NULL, + NULL, + &master_ac, + NULL) + && master_ac) + nm_active_connection_set_master(active, master_ac); + + active_connection_add(self, active); + nm_device_queue_activation(device, NM_ACT_REQUEST(active)); + } + + return TRUE; +} + +static void +recheck_assume_connection_cb(NMManager *self, NMDevice *device) +{ + recheck_assume_connection(self, device); +} + +static void +device_ifindex_changed(NMDevice *device, GParamSpec *pspec, NMManager *self) +{ + _parent_notify_changed(self, device, FALSE); +} + +static void +device_ip_iface_changed(NMDevice *device, GParamSpec *pspec, NMManager *self) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + const char * ip_iface = nm_device_get_ip_iface(device); + NMDeviceType device_type = nm_device_get_device_type(device); + NMDevice * candidate; + + /* Remove NMDevice objects that are actually child devices of others, + * when the other device finally knows its IP interface name. For example, + * remove the PPP interface that's a child of a WWAN device, since it's + * not really a standalone NMDevice. + */ + c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) { + if (candidate != device && nm_streq0(nm_device_get_iface(candidate), ip_iface) + && nm_device_get_device_type(candidate) == device_type + && nm_device_is_real(candidate)) { + remove_device(self, candidate, FALSE); + break; + } + } +} + +static void +device_iface_changed(NMDevice *device, GParamSpec *pspec, NMManager *self) +{ + /* Virtual connections may refer to the new device name as + * parent device, retry to activate them. + */ + retry_connections_for_parent_device(self, device); +} + +static void +_emit_device_added_removed(NMManager *self, NMDevice *device, gboolean is_added) +{ + nm_dbus_object_emit_signal(NM_DBUS_OBJECT(self), + &interface_info_manager, + is_added ? &signal_info_device_added : &signal_info_device_removed, + "(o)", + nm_dbus_object_get_path(NM_DBUS_OBJECT(device))); + g_signal_emit(self, signals[is_added ? DEVICE_ADDED : DEVICE_REMOVED], 0, device); + _notify(self, PROP_DEVICES); +} + +static void +device_realized(NMDevice *device, GParamSpec *pspec, NMManager *self) +{ + _emit_device_added_removed(self, device, nm_device_is_real(device)); +} + +static NMConnectivityState +_get_best_connectivity(NMManager *self, int addr_family) +{ + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + NMConnectivityState best_state; + NMDevice * dev; + gint64 best_metric; + + if (addr_family == AF_UNSPEC) { + best_state = _get_best_connectivity(self, AF_INET); + if (nm_connectivity_state_cmp(best_state, NM_CONNECTIVITY_FULL) >= 0) { + /* already FULL IPv4 connectivity. No need to check IPv6, it doesn't get + * better. */ + return best_state; + } + return NM_MAX_WITH_CMP(nm_connectivity_state_cmp, + best_state, + _get_best_connectivity(self, AF_INET6)); + } + + nm_assert_addr_family(addr_family); + + best_state = NM_CONNECTIVITY_UNKNOWN; + best_metric = G_MAXINT64; + c_list_for_each_entry (dev, &priv->devices_lst_head, devices_lst) { + const NMPObject * r; + NMConnectivityState state; + gint64 metric; + + r = nm_device_get_best_default_route(dev, addr_family); + if (r) + metric = NMP_OBJECT_CAST_IP_ROUTE(r)->metric; + else { + /* if all devices have no default-route, we still include the best + * of all connectivity state of all the devices. */ + metric = G_MAXINT64; + } + + if (metric > best_metric) { + /* we already have a default route with better metric. The connectivity state + * of this device is irreleavnt. */ + continue; + } + + state = nm_device_get_connectivity_state(dev, addr_family); + if (metric < best_metric) { + /* this device has a better default route. It wins. */ + best_metric = metric; + best_state = state; + } else { + best_state = NM_MAX_WITH_CMP(nm_connectivity_state_cmp, best_state, state); + } + + if (nm_connectivity_state_cmp(best_state, NM_CONNECTIVITY_FULL) >= 0) { + /* it doesn't get better than FULL. We are done. */ + break; + } + } + + return best_state; +} + +static void +device_connectivity_changed(NMDevice *device, GParamSpec *pspec, NMManager *self) +{ + update_connectivity_value(self); +} + +static void +update_connectivity_value(NMManager *self) +{ + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + NMConnectivityState best_state; + + best_state = _get_best_connectivity(self, AF_UNSPEC); + if (best_state == priv->connectivity_state) + return; + + priv->connectivity_state = best_state; + + _LOGD(LOGD_CORE, + "connectivity checking indicates %s", + nm_connectivity_state_to_string(priv->connectivity_state)); + + nm_manager_update_state(self); + _notify(self, PROP_CONNECTIVITY); + nm_dispatcher_call_connectivity(priv->connectivity_state, NULL, NULL, NULL); +} + +static void +_device_realize_finish(NMManager *self, NMDevice *device, const NMPlatformLink *plink) +{ + g_return_if_fail(NM_IS_MANAGER(self)); + g_return_if_fail(NM_IS_DEVICE(device)); + + nm_device_realize_finish(device, plink); + + if (!nm_device_get_managed(device, FALSE)) { + nm_device_assume_state_reset(device); + return; + } + + if (recheck_assume_connection(self, device)) + return; + + /* if we failed to assume a connection for the managed device, but the device + * is still unavailable. Set UNAVAILABLE state again, this time with NOW_MANAGED. */ + nm_device_state_changed(device, + NM_DEVICE_STATE_UNAVAILABLE, + NM_DEVICE_STATE_REASON_NOW_MANAGED); + nm_device_emit_recheck_auto_activate(device); +} + +/** + * add_device: + * @self: the #NMManager + * @device: the #NMDevice to add + * @error: (out): the #GError + * + * If successful, this function will increase the references count of @device. + * Callers should decrease the reference count. + */ +static gboolean +add_device(NMManager *self, NMDevice *device, GError **error) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + const char * iface, *type_desc; + RfKillType rtype; + GSList * iter, *remove = NULL; + int ifindex; + const char * dbus_path; + NMDevice * candidate; + + /* No duplicates */ + ifindex = nm_device_get_ifindex(device); + if (ifindex > 0 && nm_manager_get_device_by_ifindex(self, ifindex)) { + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_FAILED, + "A device with ifindex %d already exists", + ifindex); + return FALSE; + } + + /* Remove existing devices owned by the new device; eg remove ethernet + * ports that are owned by a WWAN modem, since udev may announce them + * before the modem is fully discovered. + * + * FIXME: use parent/child device relationships instead of removing + * the child NMDevice entirely + */ + c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) { + if (nm_device_is_real(candidate) && (iface = nm_device_get_ip_iface(candidate)) + && nm_device_owns_iface(device, iface)) + remove = g_slist_prepend(remove, candidate); + } + for (iter = remove; iter; iter = iter->next) + remove_device(self, NM_DEVICE(iter->data), FALSE); + g_slist_free(remove); + + g_object_ref(device); + + nm_assert(c_list_is_empty(&device->devices_lst)); + c_list_link_tail(&priv->devices_lst_head, &device->devices_lst); + + g_signal_connect(device, + NM_DEVICE_STATE_CHANGED, + G_CALLBACK(manager_device_state_changed), + self); + + g_signal_connect(device, NM_DEVICE_REMOVED, G_CALLBACK(device_removed_cb), self); + + g_signal_connect_data(device, + NM_DEVICE_RECHECK_ASSUME, + G_CALLBACK(recheck_assume_connection_cb), + self, + NULL, + G_CONNECT_SWAPPED); + + g_signal_connect(device, + "notify::" NM_DEVICE_IP_IFACE, + G_CALLBACK(device_ip_iface_changed), + self); + + g_signal_connect(device, + "notify::" NM_DEVICE_IFINDEX, + G_CALLBACK(device_ifindex_changed), + self); + + g_signal_connect(device, "notify::" NM_DEVICE_IFACE, G_CALLBACK(device_iface_changed), self); + + g_signal_connect(device, "notify::" NM_DEVICE_REAL, G_CALLBACK(device_realized), self); + + g_signal_connect(device, + "notify::" NM_DEVICE_IP4_CONNECTIVITY, + G_CALLBACK(device_connectivity_changed), + self); + g_signal_connect(device, + "notify::" NM_DEVICE_IP6_CONNECTIVITY, + G_CALLBACK(device_connectivity_changed), + self); + + if (priv->startup) { + g_signal_connect(device, + "notify::" NM_DEVICE_HAS_PENDING_ACTION, + G_CALLBACK(device_has_pending_action_changed), + self); + } + + /* Update global rfkill state for this device type with the device's + * rfkill state, and then set this device's rfkill state based on the + * global state. + */ + rtype = nm_device_get_rfkill_type(device); + if (rtype != RFKILL_TYPE_UNKNOWN) { + nm_manager_rfkill_update(self, rtype); + nm_device_set_enabled(device, radio_enabled_for_type(self, rtype, TRUE)); + } + + iface = nm_device_get_iface(device); + g_assert(iface); + type_desc = nm_device_get_type_desc(device); + g_assert(type_desc); + + nm_device_set_unmanaged_by_user_settings(device); + + nm_device_set_unmanaged_flags(device, NM_UNMANAGED_SLEEPING, manager_sleeping(self)); + + dbus_path = nm_dbus_object_export(NM_DBUS_OBJECT(device)); + _LOG2I(LOGD_DEVICE, device, "new %s device (%s)", type_desc, dbus_path); + + nm_settings_device_added(priv->settings, device); + g_signal_emit(self, signals[INTERNAL_DEVICE_ADDED], 0, device); + _notify(self, PROP_ALL_DEVICES); + + _parent_notify_changed(self, device, FALSE); + + return TRUE; +} + +/*****************************************************************************/ + +static void +factory_device_added_cb(NMDeviceFactory *factory, NMDevice *device, gpointer user_data) +{ + NMManager *self = user_data; + GError * error = NULL; + + g_return_if_fail(NM_IS_MANAGER(self)); + + if (nm_device_realize_start(device, + NULL, + FALSE, /* assume_state_guess_assume */ + NULL, /* assume_state_connection_uuid */ + FALSE, /* set_nm_owned */ + NM_UNMAN_FLAG_OP_FORGET, + NULL, + &error)) { + add_device(self, device, NULL); + _device_realize_finish(self, device, NULL); + retry_connections_for_parent_device(self, device); + } else { + _LOG2W(LOGD_DEVICE, device, "failed to realize device: %s", error->message); + g_error_free(error); + } +} + +static void +_register_device_factory(NMDeviceFactory *factory, gpointer user_data) +{ + NMManager *self = NM_MANAGER(user_data); + + g_signal_connect(factory, + NM_DEVICE_FACTORY_DEVICE_ADDED, + G_CALLBACK(factory_device_added_cb), + self); +} + +/*****************************************************************************/ + +void +nm_manager_notify_device_availability_maybe_changed(NMManager *self) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMDevice * device; + + c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) + nm_device_notify_availability_maybe_changed(device); +} + +/*****************************************************************************/ + +static void +platform_link_added(NMManager * self, + int ifindex, + const NMPlatformLink * plink, + gboolean guess_assume, + const NMConfigDeviceStateData *dev_state) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMDeviceFactory * factory; + NMDevice * device = NULL; + NMDevice * candidate; + + g_return_if_fail(ifindex > 0); + + if (nm_manager_get_device_by_ifindex(self, ifindex)) + return; + + /* Let unrealized devices try to realize themselves with the link */ + c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) { + gboolean compatible = TRUE; + gs_free_error GError *error = NULL; + + if (nm_device_get_link_type(candidate) != plink->type) + continue; + + if (!nm_streq(nm_device_get_iface(candidate), plink->name)) + continue; + + if (nm_device_is_real(candidate)) { + /* There's already a realized device with the link's name + * and a different ifindex. + */ + if (nm_device_get_ifindex(candidate) <= 0) + nm_device_update_from_platform_link(candidate, plink); + else { + /* The ifindex of a device can't be changed after + * initialization because it is used as a key by + * the dns-manager. + */ + _LOGD(LOGD_DEVICE, + "(%s): removing old device %p after ifindex change from %d to %d", + plink->name, + candidate, + nm_device_get_ifindex(candidate), + ifindex); + remove_device(self, candidate, FALSE); + goto add; + } + return; + } else if (nm_device_realize_start(candidate, + plink, + FALSE, /* assume_state_guess_assume */ + NULL, /* assume_state_connection_uuid */ + FALSE, /* set_nm_owned */ + NM_UNMAN_FLAG_OP_FORGET, + &compatible, + &error)) { + _device_realize_finish(self, candidate, plink); + return; + } + + _LOGD(LOGD_DEVICE, "(%s): failed to realize from plink: '%s'", plink->name, error->message); + + /* Try next unrealized device */ + } + +add: + /* Try registered device factories */ + factory = nm_device_factory_manager_find_factory_for_link_type(plink->type); + if (factory) { + gboolean ignore = FALSE; + gs_free_error GError *error = NULL; + + device = + nm_device_factory_create_device(factory, plink->name, plink, NULL, &ignore, &error); + if (!device) { + if (!ignore) { + _LOGW(LOGD_PLATFORM, + "%s: factory failed to create device: %s", + plink->name, + error->message); + } else { + _LOGD(LOGD_PLATFORM, + "%s: factory failed to create device: %s", + plink->name, + error->message); + } + return; + } + } + + if (device == NULL) { + gboolean nm_plugin_missing = FALSE; + + switch (plink->type) { + case NM_LINK_TYPE_WWAN_NET: + case NM_LINK_TYPE_BNEP: + case NM_LINK_TYPE_OLPC_MESH: + case NM_LINK_TYPE_TEAM: + case NM_LINK_TYPE_WIFI: + _LOGI(LOGD_PLATFORM, + "(%s): '%s' plugin not available; creating generic device", + plink->name, + nm_link_type_to_string(plink->type)); + nm_plugin_missing = TRUE; + /* fall-through */ + default: + device = nm_device_generic_new(plink, nm_plugin_missing); + break; + } + } + + if (device) { + gs_free_error GError *error = NULL; + NMUnmanFlagOp unmanaged_user_explicit = NM_UNMAN_FLAG_OP_FORGET; + + if (dev_state) { + switch (dev_state->managed) { + case NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_MANAGED: + unmanaged_user_explicit = NM_UNMAN_FLAG_OP_SET_MANAGED; + break; + case NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_UNMANAGED: + unmanaged_user_explicit = NM_UNMAN_FLAG_OP_SET_UNMANAGED; + break; + case NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_UNKNOWN: + break; + } + } + + if (nm_device_realize_start(device, + plink, + guess_assume, + dev_state ? dev_state->connection_uuid : NULL, + dev_state ? (dev_state->nm_owned == 1) : FALSE, + unmanaged_user_explicit, + NULL, + &error)) { + add_device(self, device, NULL); + _device_realize_finish(self, device, plink); + retry_connections_for_parent_device(self, device); + } else { + _LOGW(LOGD_DEVICE, "%s: failed to realize device: %s", plink->name, error->message); + } + g_object_unref(device); + } +} + +typedef struct { + CList lst; + NMManager *self; + int ifindex; + guint idle_id; +} PlatformLinkCbData; + +static gboolean +_platform_link_cb_idle(PlatformLinkCbData *data) +{ + int ifindex = data->ifindex; + NMManager * self = data->self; + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + const NMPlatformLink *plink; + + c_list_unlink_stale(&data->lst); + g_slice_free(PlatformLinkCbData, data); + + plink = nm_platform_link_get(priv->platform, ifindex); + if (plink) { + const NMPObject *plink_keep_alive = nmp_object_ref(NMP_OBJECT_UP_CAST(plink)); + + platform_link_added(self, ifindex, plink, FALSE, NULL); + nmp_object_unref(plink_keep_alive); + } else { + NMDevice *device; + GError * error = NULL; + + device = nm_manager_get_device_by_ifindex(self, ifindex); + if (device) { + if (nm_device_is_software(device)) { + nm_device_sys_iface_state_set(device, NM_DEVICE_SYS_IFACE_STATE_REMOVED); + /* Our software devices stick around until their connection is removed */ + if (!nm_device_unrealize(device, FALSE, &error)) { + _LOG2W(LOGD_DEVICE, device, "failed to unrealize: %s", error->message); + g_clear_error(&error); + remove_device(self, device, FALSE); + } else { + nm_device_update_from_platform_link(device, NULL); + } + } else { + /* Hardware and external devices always get removed when their kernel link is gone */ + remove_device(self, device, FALSE); + } + } + } + + return G_SOURCE_REMOVE; +} + +static void +platform_link_cb(NMPlatform * platform, + int obj_type_i, + int ifindex, + NMPlatformLink *plink, + int change_type_i, + gpointer user_data) +{ + NMManager * self; + NMManagerPrivate * priv; + const NMPlatformSignalChangeType change_type = change_type_i; + PlatformLinkCbData * data; + + switch (change_type) { + case NM_PLATFORM_SIGNAL_ADDED: + case NM_PLATFORM_SIGNAL_REMOVED: + self = NM_MANAGER(user_data); + priv = NM_MANAGER_GET_PRIVATE(self); + + data = g_slice_new(PlatformLinkCbData); + data->self = self; + data->ifindex = ifindex; + c_list_link_tail(&priv->link_cb_lst, &data->lst); + data->idle_id = g_idle_add((GSourceFunc) _platform_link_cb_idle, data); + break; + default: + break; + } +} + +static void +platform_query_devices(NMManager *self) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + gs_unref_ptrarray GPtrArray *links = NULL; + int i; + gboolean guess_assume; + gs_free char * order = NULL; + + guess_assume = nm_config_get_first_start(nm_config_get()); + order = nm_config_data_get_value(NM_CONFIG_GET_DATA, + NM_CONFIG_KEYFILE_GROUP_MAIN, + NM_CONFIG_KEYFILE_KEY_MAIN_SLAVES_ORDER, + NM_CONFIG_GET_VALUE_STRIP); + links = nm_platform_link_get_all(priv->platform, !nm_streq0(order, "index")); + if (!links) + return; + for (i = 0; i < links->len; i++) { + const NMPlatformLink * link = NMP_OBJECT_CAST_LINK(links->pdata[i]); + const NMConfigDeviceStateData *dev_state; + + dev_state = nm_config_device_state_get(priv->config, link->ifindex); + platform_link_added(self, + link->ifindex, + link, + guess_assume && (!dev_state || !dev_state->connection_uuid), + dev_state); + } +} + +static void +rfkill_manager_rfkill_changed_cb(NMRfkillManager *rfkill_mgr, + RfKillType rtype, + RfKillState udev_state, + gpointer user_data) +{ + nm_manager_rfkill_update(NM_MANAGER(user_data), rtype); +} + +const CList * +nm_manager_get_devices(NMManager *manager) +{ + g_return_val_if_fail(NM_IS_MANAGER(manager), NULL); + + return &NM_MANAGER_GET_PRIVATE(manager)->devices_lst_head; +} + +typedef enum { + DEVICE_ACTIVATION_PRIO_NONE, + DEVICE_ACTIVATION_PRIO_UNMANAGED, + DEVICE_ACTIVATION_PRIO_UNAVAILABLE, + DEVICE_ACTIVATION_PRIO_DEACTIVATING, + DEVICE_ACTIVATION_PRIO_ACTIVATING, + DEVICE_ACTIVATION_PRIO_ACTIVATED, + DEVICE_ACTIVATION_PRIO_DISCONNECTED, + + _DEVICE_ACTIVATION_PRIO_BEST = DEVICE_ACTIVATION_PRIO_DISCONNECTED, +} DeviceActivationPrio; + +static DeviceActivationPrio +_device_get_activation_prio(NMDevice *device) +{ + if (!nm_device_get_managed(device, TRUE)) + return DEVICE_ACTIVATION_PRIO_NONE; + + switch (nm_device_get_state(device)) { + case NM_DEVICE_STATE_DISCONNECTED: + return DEVICE_ACTIVATION_PRIO_DISCONNECTED; + case NM_DEVICE_STATE_ACTIVATED: + return DEVICE_ACTIVATION_PRIO_ACTIVATED; + case NM_DEVICE_STATE_PREPARE: + case NM_DEVICE_STATE_CONFIG: + case NM_DEVICE_STATE_NEED_AUTH: + case NM_DEVICE_STATE_IP_CONFIG: + case NM_DEVICE_STATE_IP_CHECK: + case NM_DEVICE_STATE_SECONDARIES: + return DEVICE_ACTIVATION_PRIO_ACTIVATING; + case NM_DEVICE_STATE_DEACTIVATING: + case NM_DEVICE_STATE_FAILED: + return DEVICE_ACTIVATION_PRIO_DEACTIVATING; + case NM_DEVICE_STATE_UNAVAILABLE: + return DEVICE_ACTIVATION_PRIO_UNAVAILABLE; + case NM_DEVICE_STATE_UNKNOWN: + case NM_DEVICE_STATE_UNMANAGED: + return DEVICE_ACTIVATION_PRIO_UNMANAGED; + } + + g_return_val_if_reached(DEVICE_ACTIVATION_PRIO_UNAVAILABLE); +} + +static NMDevice * +nm_manager_get_best_device_for_connection(NMManager * self, + NMSettingsConnection *sett_conn, + NMConnection * connection, + gboolean for_user_request, + GHashTable * unavailable_devices, + GError ** error) +{ + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + NMActiveConnectionState ac_state; + NMActiveConnection * ac; + NMDevice * ac_device; + NMDevice * device; + struct { + NMDevice * device; + DeviceActivationPrio prio; + } best = { + .device = NULL, + .prio = DEVICE_ACTIVATION_PRIO_NONE, + }; + NMDeviceCheckConAvailableFlags flags; + gs_unref_ptrarray GPtrArray *all_ac_arr = NULL; + gs_free_error GError * local_best = NULL; + NMConnectionMultiConnect multi_connect; + + nm_assert(!sett_conn || NM_IS_SETTINGS_CONNECTION(sett_conn)); + nm_assert(!connection || NM_IS_CONNECTION(connection)); + nm_assert(sett_conn || connection); + nm_assert(!connection || !sett_conn + || connection == nm_settings_connection_get_connection(sett_conn)); + + if (!connection) + connection = nm_settings_connection_get_connection(sett_conn); + + multi_connect = _nm_connection_get_multi_connect(connection); + + if (!for_user_request) + flags = NM_DEVICE_CHECK_CON_AVAILABLE_NONE; + else { + /* if the profile is multi-connect=single, we also consider devices which + * are marked as unmanaged. And explicit user-request shows sufficient user + * intent to make the device managed. + * That is also, because we expect that such profile is suitably tied + * to the intended device. So when an unmanaged device matches, the user's + * intent is clear. + * + * For multi-connect != single devices that is different. The profile + * is not restricted to a particular device. + * For that reason, plain `nmcli connection up "$MULIT_PROFILE"` seems + * less suitable for multi-connect profiles, because the target device is + * left unspecified. Anyway, if a user issues + * + * $ nmcli device set "$DEVICE" managed no + * $ nmcli connection up "$MULIT_PROFILE" + * + * then it is reasonable for multi-connect profiles to not consider + * the device a suitable candidate. + * + * This may be seen inconsistent, but I think that it makes a lot of + * sense. Also note that "connection.multi-connect" work quite differently + * in aspects like activation. E.g. `nmcli connection up` of multi-connect + * "single" profile, will deactivate the profile if it is active already. + * That is different from multi-connect profiles, where it will aim to + * activate the profile one more time on an hitherto disconnected device. + */ + if (multi_connect == NM_CONNECTION_MULTI_CONNECT_SINGLE) + flags = NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST; + else + flags = NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST + & ~_NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_OVERRULE_UNMANAGED; + } + + if (multi_connect == NM_CONNECTION_MULTI_CONNECT_SINGLE + && (ac = active_connection_find_by_connection(self, + sett_conn, + connection, + NM_ACTIVE_CONNECTION_STATE_DEACTIVATING, + &all_ac_arr))) { + /* if we have a profile which may activate on only one device (multi-connect single), then + * we prefer the device on which the profile is already active. It means to reactivate + * the profile on the same device. + * + * If the profile can be activated on multiple devices, we don't do this. In fact, the + * check below for the DeviceActivationPrio will prefer devices which are not already + * activated (with this or another) profile. */ + + ac_device = nm_active_connection_get_device(ac); + if (ac_device + && ((unavailable_devices && g_hash_table_contains(unavailable_devices, ac_device)) + || !nm_device_check_connection_available(ac_device, connection, flags, NULL, NULL))) + ac_device = NULL; + + if (all_ac_arr) { + guint i; + + ac_state = nm_active_connection_get_state(ac); + + /* we found several active connections. See which one is the most suitable... */ + nm_assert(ac == all_ac_arr->pdata[0]); + for (i = 1; i < all_ac_arr->len; i++) { + NMActiveConnection * ac2 = all_ac_arr->pdata[i]; + NMDevice * ac_device2 = nm_active_connection_get_device(ac2); + NMActiveConnectionState ac_state2; + + if (!ac_device2 + || (unavailable_devices + && g_hash_table_contains(unavailable_devices, ac_device2)) + || !nm_device_check_connection_available(ac_device2, + connection, + flags, + NULL, + NULL)) + continue; + + ac_state2 = nm_active_connection_get_state(ac2); + + if (!ac_device) + goto found_better; + + if (ac_state == ac_state2) { + /* active-connections are in their list in the order in which they are connected. + * If we have two with same state, the later (newer) one is preferred. */ + goto found_better; + } + + switch (ac_state) { + case NM_ACTIVE_CONNECTION_STATE_UNKNOWN: + if (NM_IN_SET(ac_state2, + NM_ACTIVE_CONNECTION_STATE_ACTIVATING, + NM_ACTIVE_CONNECTION_STATE_ACTIVATED, + NM_ACTIVE_CONNECTION_STATE_DEACTIVATING)) + goto found_better; + break; + case NM_ACTIVE_CONNECTION_STATE_ACTIVATING: + if (NM_IN_SET(ac_state2, NM_ACTIVE_CONNECTION_STATE_ACTIVATED)) + goto found_better; + break; + case NM_ACTIVE_CONNECTION_STATE_ACTIVATED: + break; + case NM_ACTIVE_CONNECTION_STATE_DEACTIVATING: + if (NM_IN_SET(ac_state2, + NM_ACTIVE_CONNECTION_STATE_ACTIVATING, + NM_ACTIVE_CONNECTION_STATE_ACTIVATED)) + goto found_better; + break; + default: + nm_assert_not_reached(); + goto found_better; + } + + continue; +found_better: + ac = ac2; + ac_state = ac_state2; + ac_device = ac_device2; + } + } + + if (ac_device) + return ac_device; + } + + /* Pick the first device that's compatible with the connection. */ + c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) { + GError * local = NULL; + DeviceActivationPrio prio; + + if (unavailable_devices && g_hash_table_contains(unavailable_devices, device)) + continue; + + /* determine the priority of this device. Currently, this priority is independent + * of the profile (connection) and the device's details (aside the state). + * + * Maybe nm_device_check_connection_available() should instead return a priority, + * as it has more information available. + * + * For example, if you have multiple Wi-Fi devices, currently a user-request would + * also select the device if the AP is not visible. Optimally, if one of the two + * devices sees the AP and the other one doesn't, the former would be preferred. + * For that, the priority would need to be determined by nm_device_check_connection_available(). */ + prio = _device_get_activation_prio(device); + if (prio <= best.prio && best.device) { + /* we already have a matching device with a better priority. This candidate + * cannot be better. Skip the check. + * + * Also note, that below we collect the best error message @local_best. + * Since we already have best.device, the error message does not matter + * either, and we can skip nm_device_check_connection_available() altogether. */ + continue; + } + + if (nm_device_check_connection_available(device, + connection, + flags, + NULL, + error ? &local : NULL)) { + if (prio == _DEVICE_ACTIVATION_PRIO_BEST) { + /* this device already has the best priority. It cannot get better + * and finish the search. */ + return device; + } + best.prio = prio; + best.device = device; + continue; + } + + if (error) { + gboolean reset_error; + + if (!local_best) + reset_error = TRUE; + else if (local_best->domain != NM_UTILS_ERROR) + reset_error = (local->domain == NM_UTILS_ERROR); + else { + reset_error = (local->domain == NM_UTILS_ERROR && local_best->code < local->code); + } + + if (reset_error) { + g_clear_error(&local_best); + g_set_error(&local_best, + local->domain, + local->code, + "device %s not available because %s", + nm_device_get_iface(device), + local->message); + } + g_error_free(local); + } + } + + if (best.device) + return best.device; + + if (error) { + if (local_best) + g_propagate_error(error, g_steal_pointer(&local_best)); + else { + nm_utils_error_set_literal(error, NM_UTILS_ERROR_UNKNOWN, "no suitable device found"); + } + } + return NULL; +} + +static const char ** +_get_devices_paths(NMManager *self, gboolean all_devices) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + const char ** paths = NULL; + guint i; + NMDevice * device; + + paths = g_new(const char *, c_list_length(&priv->devices_lst_head) + 1); + + i = 0; + c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) { + const char *path; + + path = nm_dbus_object_get_path(NM_DBUS_OBJECT(device)); + if (!path) + continue; + + if (!all_devices && !nm_device_is_real(device)) + continue; + + paths[i++] = path; + } + paths[i++] = NULL; + + return paths; +} + +static void +impl_manager_get_devices(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMManager * self = NM_MANAGER(obj); + gs_free const char **paths = NULL; + + paths = _get_devices_paths(self, FALSE); + g_dbus_method_invocation_return_value(invocation, g_variant_new("(^ao)", (char **) paths)); +} + +static void +impl_manager_get_all_devices(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMManager * self = NM_MANAGER(obj); + gs_free const char **paths = NULL; + + paths = _get_devices_paths(self, TRUE); + g_dbus_method_invocation_return_value(invocation, g_variant_new("(^ao)", (char **) paths)); +} + +static void +impl_manager_get_device_by_ip_iface(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMManager * self = NM_MANAGER(obj); + NMDevice * device; + const char *path = NULL; + const char *iface; + + g_variant_get(parameters, "(&s)", &iface); + + device = find_device_by_ip_iface(self, iface); + if (device) + path = nm_dbus_object_get_path(NM_DBUS_OBJECT(device)); + + if (!path) { + g_dbus_method_invocation_return_error(invocation, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_UNKNOWN_DEVICE, + "No device found for the requested iface."); + return; + } + + g_dbus_method_invocation_return_value(invocation, g_variant_new("(o)", path)); +} + +static gboolean +is_compatible_with_slave(NMConnection *master, NMConnection *slave) +{ + NMSettingConnection *s_con; + + g_return_val_if_fail(master, FALSE); + g_return_val_if_fail(slave, FALSE); + + s_con = nm_connection_get_setting_connection(slave); + g_assert(s_con); + + return nm_connection_is_type(master, nm_setting_connection_get_slave_type(s_con)); +} + +/** + * find_master: + * @self: #NMManager object + * @connection: the #NMConnection to find the master connection and device for + * @device: the #NMDevice, if any, which will activate @connection + * @out_master_connection: on success, the master connection of @connection if + * that master connection was found + * @out_master_device: on success, the master device of @connection if that + * master device was found + * @out_master_ac: on success, the master ActiveConnection of @connection if + * there already is one + * @error: the error, if an error occurred + * + * Given an #NMConnection, attempts to find its master. If @connection has + * no master, this will return %TRUE and @out_master_connection and + * @out_master_device will be untouched. + * + * If @connection does have a master, then the outputs depend on what is in its + * #NMSettingConnection:master property: + * + * If "master" is the ifname of an existing #NMDevice, and that device has a + * compatible master connection activated or activating on it, then + * @out_master_device, @out_master_connection, and @out_master_ac will all be + * set. If the device exists and is idle, only @out_master_device will be set. + * If the device exists and has an incompatible connection on it, an error + * will be returned. + * + * If "master" is the ifname of a non-existent device, then @out_master_device + * will be %NULL, and @out_master_connection will be a connection whose + * activation would cause the creation of that device. @out_master_ac MAY be + * set in this case as well (if the connection has started activating, but has + * not yet created its device). + * + * If "master" is the UUID of a compatible master connection, then + * @out_master_connection will be the identified connection, and @out_master_device + * and/or @out_master_ac will be set if the connection is currently activating. + * (@out_master_device will not be set if the device exists but does not have + * @out_master_connection active/activating on it.) + * + * Returns: %TRUE if the master device and/or connection could be found or if + * the connection did not require a master, %FALSE otherwise + **/ +static gboolean +find_master(NMManager * self, + NMConnection * connection, + NMDevice * device, + NMSettingsConnection **out_master_connection, + NMDevice ** out_master_device, + NMActiveConnection ** out_master_ac, + GError ** error) +{ + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + NMSettingConnection * s_con; + const char * master; + NMDevice * master_device = NULL; + NMSettingsConnection *master_connection; + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + master = nm_setting_connection_get_master(s_con); + + if (master == NULL) + return TRUE; /* success, but no master */ + + /* Try as an interface name first */ + master_device = find_device_by_iface(self, master, NULL, connection); + if (master_device) { + if (master_device == device) { + g_set_error_literal(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_DEPENDENCY_FAILED, + "Device cannot be its own master"); + return FALSE; + } + + master_connection = nm_device_get_settings_connection(master_device); + if (master_connection + && !is_compatible_with_slave(nm_settings_connection_get_connection(master_connection), + connection)) { + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_DEPENDENCY_FAILED, + "The active connection on %s is not compatible", + nm_device_get_iface(master_device)); + return FALSE; + } + } else { + /* Try master as a connection UUID */ + master_connection = nm_settings_get_connection_by_uuid(priv->settings, master); + if (master_connection) { + NMDevice *candidate; + + /* Check if the master connection is activated on some device already */ + c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) { + if (candidate == device) + continue; + + if (nm_device_get_settings_connection(candidate) == master_connection) { + master_device = candidate; + break; + } + } + } + } + + if (out_master_connection) + *out_master_connection = master_connection; + if (out_master_device) + *out_master_device = master_device; + if (out_master_ac && master_connection) { + *out_master_ac = active_connection_find(self, + master_connection, + NULL, + NM_ACTIVE_CONNECTION_STATE_DEACTIVATING, + NULL); + } + + if (master_device || master_connection) + return TRUE; + else { + g_set_error_literal(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_UNKNOWN_DEVICE, + "Master connection not found or invalid"); + return FALSE; + } +} + +/** + * ensure_master_active_connection: + * @self: the #NMManager + * @subject: the #NMAuthSubject representing the requestor of this activation + * @connection: the connection that should depend on @master_connection + * @device: the #NMDevice, if any, which will activate @connection + * @master_connection: the master connection, or %NULL + * @master_device: the master device, or %NULL + * @activation_reason: the reason for activation + * @error: the error, if an error occurred + * + * Determines whether a given #NMConnection depends on another connection to + * be activated, and if so, finds that master connection or creates it. + * + * If @master_device and @master_connection are both set then @master_connection + * MUST already be activated or activating on @master_device, and the function will + * return the existing #NMActiveConnection. + * + * If only @master_device is set, and it has an #NMActiveConnection, then the + * function will return it if it is a compatible master, or an error if not. If it + * doesn't have an AC, then the function will create one if a compatible master + * connection exists, or return an error if not. + * + * If only @master_connection is set, then this will try to find or create a compatible + * #NMDevice, and either activate @master_connection on that device or return an error. + * + * Returns: the master #NMActiveConnection that the caller should depend on, or + * %NULL if an error occurred + */ +static NMActiveConnection * +ensure_master_active_connection(NMManager * self, + NMAuthSubject * subject, + NMConnection * connection, + NMDevice * device, + NMSettingsConnection *master_connection, + NMDevice * master_device, + NMActivationReason activation_reason, + GError ** error) +{ + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + NMActiveConnection *ac; + NMActiveConnection *master_ac = NULL; + NMDeviceState master_state; + gboolean bind_lifetime_to_profile_visibility; + + g_return_val_if_fail(connection, NULL); + g_return_val_if_fail(master_connection || master_device, FALSE); + + bind_lifetime_to_profile_visibility = + NM_FLAGS_HAS(nm_device_get_activation_state_flags(device), + NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY); + + /* If the master device isn't activated then we need to activate it using + * compatible connection. If it's already activating we can just proceed. + */ + if (master_device) { + NMSettingsConnection *device_connection = nm_device_get_settings_connection(master_device); + + /* If we're passed a connection and a device, we require that connection + * be already activated on the device, eg returned from find_master(). + */ + g_assert(!master_connection || master_connection == device_connection); + if (device_connection + && !is_compatible_with_slave(nm_settings_connection_get_connection(device_connection), + connection)) { + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_DEPENDENCY_FAILED, + "The active connection %s is not compatible", + nm_connection_get_id(connection)); + return NULL; + } + + master_state = nm_device_get_state(master_device); + if ((master_state == NM_DEVICE_STATE_ACTIVATED) || nm_device_is_activating(master_device)) { + /* Device already using master_connection */ + ac = NM_ACTIVE_CONNECTION(nm_device_get_act_request(master_device)); + g_return_val_if_fail(device_connection, ac); + + if (!bind_lifetime_to_profile_visibility) { + /* unbind the lifetime. */ + nm_active_connection_set_state_flags_clear( + ac, + NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY); + } + + return ac; + } + + /* If the device is disconnected, find a compatible connection and + * activate it on the device. + */ + if (master_state == NM_DEVICE_STATE_DISCONNECTED || !nm_device_is_real(master_device)) { + gs_free NMSettingsConnection **connections = NULL; + guint i; + + g_assert(master_connection == NULL); + + /* Find a compatible connection and activate this device using it */ + connections = nm_manager_get_activatable_connections(self, FALSE, TRUE, NULL); + for (i = 0; connections[i]; i++) { + NMSettingsConnection *candidate = connections[i]; + NMConnection * cand_conn = nm_settings_connection_get_connection(candidate); + + /* Ensure eg bond/team slave and the candidate master is a + * bond/team master + */ + if (!is_compatible_with_slave(cand_conn, connection)) + continue; + + if (nm_device_check_connection_available( + master_device, + cand_conn, + NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST, + NULL, + NULL)) { + master_ac = nm_manager_activate_connection( + self, + candidate, + NULL, + NULL, + master_device, + subject, + NM_ACTIVATION_TYPE_MANAGED, + activation_reason, + bind_lifetime_to_profile_visibility + ? NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY + : NM_ACTIVATION_STATE_FLAG_NONE, + error); + return master_ac; + } + } + + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_UNKNOWN_CONNECTION, + "No compatible connection found."); + return NULL; + } + + /* Otherwise, the device is unmanaged, unavailable, or disconnecting */ + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_DEPENDENCY_FAILED, + "Device unmanaged or not available for activation"); + } else if (master_connection) { + NMDevice *candidate; + + /* Find a compatible device and activate it using this connection */ + c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) { + if (candidate == device) { + /* A device obviously can't be its own master */ + continue; + } + + if (!nm_device_check_connection_available( + candidate, + nm_settings_connection_get_connection(master_connection), + NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST, + NULL, + NULL)) + continue; + + if (!nm_device_is_software(candidate)) { + master_state = nm_device_get_state(candidate); + if (nm_device_is_real(candidate) && master_state != NM_DEVICE_STATE_DISCONNECTED) + continue; + } + + master_ac = nm_manager_activate_connection( + self, + master_connection, + NULL, + NULL, + candidate, + subject, + NM_ACTIVATION_TYPE_MANAGED, + activation_reason, + bind_lifetime_to_profile_visibility + ? NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY + : NM_ACTIVATION_STATE_FLAG_NONE, + error); + return master_ac; + } + + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_UNKNOWN_DEVICE, + "No device available"); + } else + g_assert_not_reached(); + + return NULL; +} + +typedef struct { + NMSettingsConnection *connection; + NMDevice * device; +} SlaveConnectionInfo; + +/** + * find_slaves: + * @manager: #NMManager object + * @sett_conn: the master #NMSettingsConnection to find slave connections for + * @device: the master #NMDevice for the @sett_conn + * @out_n_slaves: on return, the number of slaves found + * + * Given an #NMSettingsConnection, attempts to find its slaves. If @sett_conn is not + * master, or has not any slaves, this will return %NULL. + * + * Returns: an array of #SlaveConnectionInfo for given master @sett_conn, or %NULL + **/ +static SlaveConnectionInfo * +find_slaves(NMManager * manager, + NMSettingsConnection *sett_conn, + NMDevice * device, + guint * out_n_slaves, + gboolean for_user_request) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(manager); + gs_free NMSettingsConnection **all_connections = NULL; + guint n_all_connections; + guint i; + SlaveConnectionInfo * slaves = NULL; + guint n_slaves = 0; + NMSettingConnection * s_con; + gs_unref_hashtable GHashTable *devices = NULL; + + nm_assert(out_n_slaves); + + s_con = nm_connection_get_setting_connection(nm_settings_connection_get_connection(sett_conn)); + g_return_val_if_fail(s_con, NULL); + + devices = g_hash_table_new(nm_direct_hash, NULL); + + /* Search through all connections, not only inactive ones, because + * even if a slave was already active, it might be deactivated during + * master reactivation. + */ + all_connections = nm_settings_get_connections_clone( + priv->settings, + &n_all_connections, + NULL, + NULL, + nm_settings_connection_cmp_autoconnect_priority_p_with_data, + NULL); + for (i = 0; i < n_all_connections; i++) { + NMSettingsConnection *master_connection = NULL; + NMDevice * master_device = NULL, *slave_device; + NMSettingsConnection *candidate = all_connections[i]; + + find_master(manager, + nm_settings_connection_get_connection(candidate), + NULL, + &master_connection, + &master_device, + NULL, + NULL); + if ((master_connection && master_connection == sett_conn) + || (master_device && master_device == device)) { + slave_device = nm_manager_get_best_device_for_connection(manager, + candidate, + NULL, + for_user_request, + devices, + NULL); + + if (!slaves) { + /* what we allocate is quite likely much too large. Don't bother, it is only + * a temporary buffer. */ + slaves = g_new(SlaveConnectionInfo, n_all_connections); + } + + nm_assert(n_slaves < n_all_connections); + slaves[n_slaves].connection = candidate, slaves[n_slaves].device = slave_device, + n_slaves++; + + if (slave_device) + g_hash_table_add(devices, slave_device); + } + } + + *out_n_slaves = n_slaves; + + /* Warning: returns NULL if n_slaves is zero. */ + return slaves; +} + +static gboolean +should_connect_slaves(NMConnection *connection, NMDevice *device) +{ + NMSettingConnection * s_con; + NMSettingConnectionAutoconnectSlaves val; + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + + val = nm_setting_connection_get_autoconnect_slaves(s_con); + if (val != NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_DEFAULT) + goto out; + + val = + nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA, + NM_CON_DEFAULT("connection.autoconnect-slaves"), + device, + 0, + 1, + -1); + +out: + if (val == NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_NO) + return FALSE; + if (val == NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_YES) + return TRUE; + return FALSE; +} + +static int +compare_slaves(gconstpointer a, gconstpointer b, gpointer sort_by_name) +{ + const SlaveConnectionInfo *a_info = a; + const SlaveConnectionInfo *b_info = b; + + /* Slaves without a device at the end */ + if (!a_info->device) + return 1; + if (!b_info->device) + return -1; + + if (GPOINTER_TO_INT(sort_by_name)) { + return nm_strcmp0(nm_device_get_iface(a_info->device), nm_device_get_iface(b_info->device)); + } + + return nm_device_get_ifindex(a_info->device) - nm_device_get_ifindex(b_info->device); +} + +static void +autoconnect_slaves(NMManager * self, + NMSettingsConnection *master_connection, + NMDevice * master_device, + NMAuthSubject * subject, + gboolean for_user_request) +{ + GError *local_err = NULL; + + if (should_connect_slaves(nm_settings_connection_get_connection(master_connection), + master_device)) { + gs_free SlaveConnectionInfo *slaves = NULL; + guint i, n_slaves = 0; + gboolean bind_lifetime_to_profile_visibility; + + slaves = find_slaves(self, master_connection, master_device, &n_slaves, for_user_request); + if (n_slaves > 1) { + gs_free char *value = NULL; + + value = nm_config_data_get_value(NM_CONFIG_GET_DATA, + NM_CONFIG_KEYFILE_GROUP_MAIN, + NM_CONFIG_KEYFILE_KEY_MAIN_SLAVES_ORDER, + NM_CONFIG_GET_VALUE_STRIP); + g_qsort_with_data(slaves, + n_slaves, + sizeof(slaves[0]), + compare_slaves, + GINT_TO_POINTER(!nm_streq0(value, "index"))); + } + + bind_lifetime_to_profile_visibility = + n_slaves > 0 + && NM_FLAGS_HAS(nm_device_get_activation_state_flags(master_device), + NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY); + + for (i = 0; i < n_slaves; i++) { + SlaveConnectionInfo *slave = &slaves[i]; + const char * uuid; + + /* To avoid loops when autoconnecting slaves, we propagate + * the UUID of the initial connection down to slaves until + * the same connection is found. + */ + uuid = g_object_get_qdata(G_OBJECT(master_connection), autoconnect_root_quark()); + if (nm_streq0(nm_settings_connection_get_uuid(slave->connection), uuid)) { + _LOGI(LOGD_CORE, + "will NOT activate slave connection '%s' (%s) as a dependency for master " + "'%s' (%s): " + "circular dependency detected", + nm_settings_connection_get_id(slave->connection), + nm_settings_connection_get_uuid(slave->connection), + nm_settings_connection_get_id(master_connection), + nm_settings_connection_get_uuid(master_connection)); + continue; + } + + if (!uuid) + uuid = nm_settings_connection_get_uuid(master_connection); + g_object_set_qdata_full(G_OBJECT(slave->connection), + autoconnect_root_quark(), + g_strdup(uuid), + g_free); + + if (!slave->device) { + _LOGD(LOGD_CORE, + "will NOT activate slave connection '%s' (%s) as a dependency for master " + "'%s' (%s): " + "no compatible device found", + nm_settings_connection_get_id(slave->connection), + nm_settings_connection_get_uuid(slave->connection), + nm_settings_connection_get_id(master_connection), + nm_settings_connection_get_uuid(master_connection)); + continue; + } + + _LOGD(LOGD_CORE, + "will activate slave connection '%s' (%s) as a dependency for master '%s' (%s)", + nm_settings_connection_get_id(slave->connection), + nm_settings_connection_get_uuid(slave->connection), + nm_settings_connection_get_id(master_connection), + nm_settings_connection_get_uuid(master_connection)); + + /* Schedule slave activation */ + nm_manager_activate_connection( + self, + slave->connection, + NULL, + NULL, + slave->device, + subject, + NM_ACTIVATION_TYPE_MANAGED, + NM_ACTIVATION_REASON_AUTOCONNECT_SLAVES, + bind_lifetime_to_profile_visibility + ? NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY + : NM_ACTIVATION_STATE_FLAG_NONE, + &local_err); + if (local_err) { + _LOGW(LOGD_CORE, "Slave connection activation failed: %s", local_err->message); + g_clear_error(&local_err); + } + } + } +} + +static gboolean +_internal_activate_vpn(NMManager *self, NMActiveConnection *active, GError **error) +{ + nm_assert(NM_IS_VPN_CONNECTION(active)); + + nm_dbus_object_export(NM_DBUS_OBJECT(active)); + if (!nm_vpn_manager_activate_connection(NM_MANAGER_GET_PRIVATE(self)->vpn_manager, + NM_VPN_CONNECTION(active), + error)) { + nm_dbus_object_unexport(NM_DBUS_OBJECT(active)); + return FALSE; + } + + active_connection_add(self, active); + return TRUE; +} + +/* Traverse the device to disconnected state. This means that the device is ready + * for connection and will proceed activating if there's an activation request + * enqueued. + */ +static void +unmanaged_to_disconnected(NMDevice *device) +{ + /* when creating the software device, it can happen that the device is + * still unmanaged by NM_UNMANAGED_PLATFORM_INIT because we didn't yet + * get the udev event. At this point, we can no longer delay the activation + * and force the device to be managed. */ + nm_device_set_unmanaged_by_flags(device, + NM_UNMANAGED_PLATFORM_INIT, + FALSE, + NM_DEVICE_STATE_REASON_USER_REQUESTED); + + nm_device_set_unmanaged_by_flags(device, + NM_UNMANAGED_USER_EXPLICIT, + FALSE, + NM_DEVICE_STATE_REASON_USER_REQUESTED); + + if (!nm_device_get_managed(device, FALSE)) { + /* the device is still marked as unmanaged. Nothing to do. */ + return; + } + + if (nm_device_get_state(device) == NM_DEVICE_STATE_UNMANAGED) { + nm_device_state_changed(device, + NM_DEVICE_STATE_UNAVAILABLE, + NM_DEVICE_STATE_REASON_USER_REQUESTED); + } + + if (nm_device_get_state(device) == NM_DEVICE_STATE_UNAVAILABLE + && nm_device_is_available(device, NM_DEVICE_CHECK_DEV_AVAILABLE_FOR_USER_REQUEST)) { + nm_device_state_changed(device, + NM_DEVICE_STATE_DISCONNECTED, + NM_DEVICE_STATE_REASON_USER_REQUESTED); + } +} + +static NMActivationStateFlags +_activation_bind_lifetime_to_profile_visibility(NMAuthSubject *subject) +{ + if (nm_auth_subject_get_subject_type(subject) == NM_AUTH_SUBJECT_TYPE_INTERNAL + || nm_auth_subject_get_unix_process_uid(subject) == 0) { + /* internal requests and requests from root are always unbound. */ + return NM_ACTIVATION_STATE_FLAG_NONE; + } + + /* if the activation was not done by internal decision nor root, there + * are the following cases: + * + * - the connection has "connection.permissions" unset and the profile + * is not restricted to a user and commonly always visible. It does + * not hurt to bind the lifetime, because we expect the profile to be + * visible at the moment. If the profile changes (while still being active), + * we want to pick-up changes to the visibility and possibly disconnect. + * + * - the connection has "connection.permissions" set, and the current user + * is the owner: + * + * - Usually, we would expect that the profile is visible at the moment, + * and of course we want to bind the lifetime. The moment the user + * logs out, the connection becomes invisible and disconnects. + * + * - the profile at this time could already be invisible (e.g. if the + * user didn't create a proper session (sudo) and manually activates + * an invisible profile. In this case, we still want to bind the + * lifetime, and it will disconnect after the user logs in and logs + * out again. NMKeepAlive takes care of that. + */ + return NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY; +} + +/* The parent connection is ready; we can proceed realizing the device and + * progressing the device to disconencted state. + */ +static void +active_connection_parent_active(NMActiveConnection *active, + NMActiveConnection *parent_ac, + NMManager * self) +{ + NMDevice * device = nm_active_connection_get_device(active); + GError * error = NULL; + NMSettingsConnection *sett_conn; + NMDevice * parent; + + g_signal_handlers_disconnect_by_func(active, (GCallback) active_connection_parent_active, self); + + if (!parent_ac) { + _LOGW(LOGD_CORE, + "The parent connection device '%s' depended on disappeared.", + nm_device_get_iface(device)); + nm_active_connection_set_state_fail(active, + NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_REMOVED, + "parent device disappeared"); + return; + } + + sett_conn = nm_active_connection_get_settings_connection(active); + parent = nm_active_connection_get_device(parent_ac); + + if (!nm_device_create_and_realize(device, + nm_settings_connection_get_connection(sett_conn), + parent, + &error)) { + _LOGW(LOGD_CORE, + "Could not realize device '%s': %s", + nm_device_get_iface(device), + error->message); + nm_active_connection_set_state_fail(active, + NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_REALIZE_FAILED, + "failure to realize device"); + return; + } + + /* We can now proceed to disconnected state so that activation proceeds. */ + unmanaged_to_disconnected(device); +} + +static gboolean +_internal_activate_device(NMManager *self, NMActiveConnection *active, GError **error) +{ + NMDevice * device, *master_device = NULL; + NMConnection * applied; + NMSettingsConnection * sett_conn; + NMSettingsConnection * master_connection = NULL; + NMConnection * existing_connection = NULL; + NMActiveConnection * master_ac = NULL; + NMAuthSubject * subject; + GError * local = NULL; + NMConnectionMultiConnect multi_connect; + const char * parent_spec; + + g_return_val_if_fail(NM_IS_MANAGER(self), FALSE); + g_return_val_if_fail(NM_IS_ACTIVE_CONNECTION(active), FALSE); + g_return_val_if_fail(error == NULL || *error == NULL, FALSE); + + g_assert(NM_IS_VPN_CONNECTION(active) == FALSE); + + device = nm_active_connection_get_device(active); + g_return_val_if_fail(device != NULL, FALSE); + + sett_conn = nm_active_connection_get_settings_connection(active); + nm_assert(sett_conn); + + applied = nm_active_connection_get_applied_connection(active); + + /* If the device is active and its connection is not visible to the + * user that's requesting this new activation, fail, since other users + * should not be allowed to implicitly deactivate private connections + * by activating a connection of their own. + */ + existing_connection = nm_device_get_applied_connection(device); + subject = nm_active_connection_get_subject(active); + if (existing_connection + && !nm_auth_is_subject_in_acl_set_error(existing_connection, + subject, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + error)) { + g_prefix_error(error, "Private connection already active on the device: "); + return FALSE; + } + + /* Final connection must be available on device */ + if (!nm_device_check_connection_available(device, + applied, + NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST, + NULL, + &local)) { + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_UNKNOWN_CONNECTION, + "Connection '%s' is not available on device %s because %s", + nm_settings_connection_get_id(sett_conn), + nm_device_get_iface(device), + local->message); + g_error_free(local); + return FALSE; + } + + if (nm_active_connection_get_activation_type(active) == NM_ACTIVATION_TYPE_MANAGED) + nm_device_sys_iface_state_set(device, NM_DEVICE_SYS_IFACE_STATE_MANAGED); + + /* Try to find the master connection/device if the connection has a dependency */ + if (!find_master(self, + applied, + device, + &master_connection, + &master_device, + &master_ac, + error)) { + g_prefix_error(error, + "Can not find a master for %s: ", + nm_settings_connection_get_id(sett_conn)); + return FALSE; + } + + /* Create any backing resources the device needs */ + if (!nm_device_is_real(device)) { + NMDevice *parent; + + parent = find_parent_device_for_connection(self, + nm_settings_connection_get_connection(sett_conn), + NULL, + &parent_spec); + + if (parent_spec && !parent) { + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_DEPENDENCY_FAILED, + "parent device '%s' not found", + parent_spec); + return FALSE; + } + + if (parent && !nm_device_is_real(parent)) { + NMSettingsConnection *parent_con; + NMActiveConnection * parent_ac; + + parent_con = nm_device_get_best_connection(parent, NULL, error); + if (!parent_con) { + g_prefix_error(error, "%s failed to create parent: ", nm_device_get_iface(device)); + return FALSE; + } + + if (nm_active_connection_get_activation_reason(active) + == NM_ACTIVATION_REASON_AUTOCONNECT + && NM_FLAGS_HAS(nm_settings_connection_autoconnect_blocked_reason_get(parent_con), + NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST)) { + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_DEPENDENCY_FAILED, + "the parent connection of %s cannot autoactivate because it is blocked " + "due to user request", + nm_device_get_iface(device)); + return FALSE; + } + + parent_ac = nm_manager_activate_connection( + self, + parent_con, + NULL, + NULL, + parent, + subject, + NM_ACTIVATION_TYPE_MANAGED, + nm_active_connection_get_activation_reason(active), + nm_active_connection_get_state_flags(active) + & NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY, + error); + if (!parent_ac) { + g_prefix_error(error, + "%s failed to activate parent: ", + nm_device_get_iface(device)); + return FALSE; + } + + /* We can't realize now; defer until the parent device is ready. */ + g_signal_connect(active, + NM_ACTIVE_CONNECTION_PARENT_ACTIVE, + (GCallback) active_connection_parent_active, + self); + nm_active_connection_set_parent(active, parent_ac); + } else { + /* We can realize now; no need to wait for a parent device. */ + if (!nm_device_create_and_realize(device, + nm_settings_connection_get_connection(sett_conn), + parent, + error)) { + g_prefix_error(error, + "%s failed to create resources: ", + nm_device_get_iface(device)); + return FALSE; + } + } + } + + /* Ensure there's a master active connection the new connection we're + * activating can depend on. + */ + if (master_connection || master_device) { + if (master_connection) { + _LOGD(LOGD_CORE, + "Activation of '%s' requires master connection '%s'", + nm_settings_connection_get_id(sett_conn), + nm_settings_connection_get_id(master_connection)); + } + if (master_device) { + _LOGD(LOGD_CORE, + "Activation of '%s' requires master device '%s'", + nm_settings_connection_get_id(sett_conn), + nm_device_get_ip_iface(master_device)); + } + + /* Ensure eg bond slave and the candidate master is a bond master */ + if (master_connection + && !is_compatible_with_slave(nm_settings_connection_get_connection(master_connection), + applied)) { + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_DEPENDENCY_FAILED, + "The master connection '%s' is not compatible with '%s'", + nm_settings_connection_get_id(master_connection), + nm_settings_connection_get_id(sett_conn)); + return FALSE; + } + + if (!master_ac) { + master_ac = + ensure_master_active_connection(self, + nm_active_connection_get_subject(active), + applied, + device, + master_connection, + master_device, + nm_active_connection_get_activation_reason(active), + error); + if (!master_ac) { + if (master_device) { + g_prefix_error(error, + "Master device '%s' can't be activated: ", + nm_device_get_ip_iface(master_device)); + } else { + g_prefix_error(error, + "Master connection '%s' can't be activated: ", + nm_settings_connection_get_id(master_connection)); + } + return FALSE; + } + } + + /* Now that we're activating a slave for that master, make sure the master just + * decides to go unmanaged while we're activating (perhaps because other slaves + * go away leaving him with no kids). + */ + if (master_device) { + nm_device_set_unmanaged_by_flags(master_device, + NM_UNMANAGED_EXTERNAL_DOWN, + NM_UNMAN_FLAG_OP_FORGET, + NM_DEVICE_STATE_REASON_USER_REQUESTED); + } + + nm_active_connection_set_master(active, master_ac); + _LOGD(LOGD_CORE, + "Activation of '%s' depends on active connection %p %s", + nm_settings_connection_get_id(sett_conn), + master_ac, + nm_dbus_object_get_path(NM_DBUS_OBJECT(master_ac)) ?: ""); + } + + /* Check slaves for master connection and possibly activate them */ + autoconnect_slaves(self, + sett_conn, + device, + nm_active_connection_get_subject(active), + nm_active_connection_get_activation_reason(active) + == NM_ACTIVATION_REASON_USER_REQUEST); + + multi_connect = + _nm_connection_get_multi_connect(nm_settings_connection_get_connection(sett_conn)); + if (multi_connect == NM_CONNECTION_MULTI_CONNECT_MULTIPLE + || (multi_connect == NM_CONNECTION_MULTI_CONNECT_MANUAL_MULTIPLE + && NM_IN_SET(nm_active_connection_get_activation_reason(active), + NM_ACTIVATION_REASON_ASSUME, + NM_ACTIVATION_REASON_AUTOCONNECT_SLAVES, + NM_ACTIVATION_REASON_USER_REQUEST))) { + /* the profile can be activated multiple times. Proceed. */ + } else { + gs_unref_ptrarray GPtrArray *all_ac_arr = NULL; + NMActiveConnection * ac; + guint i, n_all; + + /* Disconnect the connection if already connected or queued for activation. + * The connection cannot be active multiple times (at the same time). */ + ac = active_connection_find(self, + sett_conn, + NULL, + NM_ACTIVE_CONNECTION_STATE_ACTIVATED, + &all_ac_arr); + if (ac) { + n_all = all_ac_arr ? all_ac_arr->len : ((guint) 1); + for (i = 0; i < n_all; i++) { + nm_device_disconnect_active_connection(all_ac_arr ? all_ac_arr->pdata[i] : ac, + NM_DEVICE_STATE_REASON_NEW_ACTIVATION, + NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN); + } + } + } + + /* If the device is there, we can ready it for the activation. */ + if (nm_device_is_real(device)) { + unmanaged_to_disconnected(device); + + if (!nm_device_get_managed(device, FALSE)) { + /* Unexpectedly, the device is still unmanaged. That can happen for example, + * if the device is forcibly unmanaged due to NM_UNMANAGED_USER_SETTINGS. */ + g_set_error_literal(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_DEPENDENCY_FAILED, + "Activation failed because the device is unmanaged"); + return FALSE; + } + } + + /* Export the new ActiveConnection to clients and start it on the device */ + active_connection_add(self, active); + nm_device_queue_activation(device, NM_ACT_REQUEST(active)); + return TRUE; +} + +static gboolean +_internal_activate_generic(NMManager *self, NMActiveConnection *active, GError **error) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + gboolean success = FALSE; + + /* Ensure activation request is still valid, eg that its device hasn't gone + * away or that some other dependency has not failed. + */ + if (nm_active_connection_get_state(active) >= NM_ACTIVE_CONNECTION_STATE_DEACTIVATING) { + g_set_error_literal(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_DEPENDENCY_FAILED, + "Activation failed because dependencies failed."); + return FALSE; + } + + if (NM_IS_VPN_CONNECTION(active)) + success = _internal_activate_vpn(self, active, error); + else + success = _internal_activate_device(self, active, error); + + if (success) { + /* Force an update of the Manager's activating-connection property. + * The device changes state before the AC gets exported, which causes + * the manager's 'activating-connection' property to be NULL since the + * AC only gets a D-Bus path when it's exported. So now that the AC + * is exported, make sure the manager's activating-connection property + * is up-to-date. + */ + policy_activating_ac_changed(G_OBJECT(priv->policy), NULL, self); + } + + return success; +} + +static NMActiveConnection * +_new_active_connection(NMManager * self, + gboolean is_vpn, + NMSettingsConnection * sett_conn, + NMConnection * incompl_conn, + NMConnection * applied, + const char * specific_object, + NMDevice * device, + NMAuthSubject * subject, + NMActivationType activation_type, + NMActivationReason activation_reason, + NMActivationStateFlags initial_state_flags, + GError ** error) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMDevice * parent_device; + + nm_assert(!sett_conn || NM_IS_SETTINGS_CONNECTION(sett_conn)); + nm_assert(!incompl_conn || NM_IS_CONNECTION(incompl_conn)); + nm_assert((!incompl_conn) ^ (!sett_conn)); + nm_assert(NM_IS_AUTH_SUBJECT(subject)); + nm_assert(is_vpn + == _connection_is_vpn(sett_conn ? nm_settings_connection_get_connection(sett_conn) + : incompl_conn)); + nm_assert(is_vpn || NM_IS_DEVICE(device)); + nm_assert(!nm_streq0(specific_object, "/")); + nm_assert(!applied || NM_IS_CONNECTION(applied)); + nm_assert(!is_vpn || !applied); + + if (is_vpn) { + NMActiveConnection *parent; + + /* FIXME: for VPN connections, we don't allow re-activating an + * already active connection. It's a bug, and should be fixed together + * when reworking VPN handling. */ + if (active_connection_find_by_connection(self, + sett_conn, + incompl_conn, + NM_ACTIVE_CONNECTION_STATE_ACTIVATED, + NULL)) { + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_CONNECTION_ALREADY_ACTIVE, + "Connection '%s' is already active", + sett_conn ? nm_settings_connection_get_id(sett_conn) + : nm_connection_get_id(incompl_conn)); + return NULL; + } + + if (activation_type != NM_ACTIVATION_TYPE_MANAGED) + g_return_val_if_reached(NULL); + + if (specific_object) { + /* Find the specific connection the client requested we use */ + parent = active_connection_get_by_path(self, specific_object); + if (!parent) { + g_set_error_literal(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_CONNECTION_NOT_ACTIVE, + "Base connection for VPN connection not active."); + return NULL; + } + } else + parent = priv->primary_connection; + + if (!parent) { + g_set_error_literal(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_UNKNOWN_CONNECTION, + "Could not find source connection."); + return NULL; + } + + parent_device = nm_active_connection_get_device(parent); + if (!parent_device) { + g_set_error_literal(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_UNKNOWN_DEVICE, + "Source connection had no active device"); + return NULL; + } + + if (device && device != parent_device) { + g_set_error_literal(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_UNKNOWN_DEVICE, + "The device doesn't match the active connection."); + return NULL; + } + + return (NMActiveConnection *) nm_vpn_connection_new( + sett_conn, + parent_device, + nm_dbus_object_get_path(NM_DBUS_OBJECT(parent)), + activation_reason, + initial_state_flags, + subject); + } + + return (NMActiveConnection *) nm_act_request_new(sett_conn, + applied, + specific_object, + subject, + activation_type, + activation_reason, + initial_state_flags, + device); +} + +static void +_internal_activation_auth_done(NMManager * self, + NMActiveConnection *active, + gboolean success, + const char * error_desc) +{ + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + NMActiveConnection *ac; + gs_free_error GError *error = NULL; + + nm_assert(NM_IS_ACTIVE_CONNECTION(active)); + + if (!success) + goto fail; + + /* Don't continue with an autoconnect-activation if a more important activation + * already exists. + * We also check this earlier, but there we may fail to detect a duplicate + * if the existing active connection was undergoing authorization. + */ + if (NM_IN_SET(nm_active_connection_get_activation_reason(active), + NM_ACTIVATION_REASON_EXTERNAL, + NM_ACTIVATION_REASON_ASSUME, + NM_ACTIVATION_REASON_AUTOCONNECT)) { + c_list_for_each_entry (ac, &priv->active_connections_lst_head, active_connections_lst) { + if (nm_active_connection_get_device(ac) == nm_active_connection_get_device(active) + && nm_active_connection_get_settings_connection(ac) + == nm_active_connection_get_settings_connection(active) + && nm_active_connection_get_state(ac) <= NM_ACTIVE_CONNECTION_STATE_ACTIVATED) { + g_set_error(&error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_CONNECTION_ALREADY_ACTIVE, + "Connection '%s' is already active", + nm_active_connection_get_settings_connection_id(active)); + goto fail; + } + } + } + + if (_internal_activate_generic(self, active, &error)) + return; + +fail: + _delete_volatile_connection_do(self, nm_active_connection_get_settings_connection(active)); + nm_assert(error_desc || error); + nm_active_connection_set_state_fail(active, + NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN, + error_desc ?: error->message); +} + +/** + * nm_manager_activate_connection(): + * @self: the #NMManager + * @sett_conn: the #NMSettingsConnection to activate on @device + * @applied: (allow-none): the applied connection to activate on @device + * @specific_object: the specific object path, if any, for the activation + * @device: the #NMDevice to activate @sett_conn on. Can be %NULL for VPNs. + * @subject: the subject which requested activation + * @activation_type: whether to assume the connection. That is, take over gracefully, + * non-destructible. + * @activation_reason: the reason for activation + * @initial_state_flags: the initial state flags for the activation. + * @error: return location for an error + * + * Begins a new internally-initiated activation of @sett_conn on @device. + * @subject should be the subject of the activation that triggered this + * one, or if this is an autoconnect request, a new internal subject. + * The returned #NMActiveConnection is owned by the Manager and should be + * referenced by the caller if the caller continues to use it. If @applied + * is supplied, it shall not be modified by the caller afterwards. + * + * Returns: (transfer none): the new #NMActiveConnection that tracks + * activation of @sett_conn on @device + */ +NMActiveConnection * +nm_manager_activate_connection(NMManager * self, + NMSettingsConnection * sett_conn, + NMConnection * applied, + const char * specific_object, + NMDevice * device, + NMAuthSubject * subject, + NMActivationType activation_type, + NMActivationReason activation_reason, + NMActivationStateFlags initial_state_flags, + GError ** error) +{ + NMManagerPrivate * priv; + NMActiveConnection *active; + AsyncOpData * async_op_data; + gboolean is_vpn; + + g_return_val_if_fail(NM_IS_MANAGER(self), NULL); + g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(sett_conn), NULL); + is_vpn = _connection_is_vpn(nm_settings_connection_get_connection(sett_conn)); + g_return_val_if_fail(is_vpn || NM_IS_DEVICE(device), NULL); + g_return_val_if_fail(!error || !*error, NULL); + nm_assert(!nm_streq0(specific_object, "/")); + + priv = NM_MANAGER_GET_PRIVATE(self); + + if (!nm_auth_is_subject_in_acl_set_error(nm_settings_connection_get_connection(sett_conn), + subject, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + error)) + return NULL; + + /* Look for a active connection that's equivalent and is already pending authorization + * and eventual activation. This is used to de-duplicate concurrent activations which would + * otherwise race and cause the device to disconnect and reconnect repeatedly. + * In particular, this allows the master and multiple slaves to concurrently auto-activate + * while all the slaves would use the same active-connection. */ + c_list_for_each_entry (async_op_data, &priv->async_op_lst_head, async_op_lst) { + if (async_op_data->async_op_type != ASYNC_OP_TYPE_AC_AUTH_ACTIVATE_INTERNAL) + continue; + + active = async_op_data->ac_auth.active; + if (sett_conn == nm_active_connection_get_settings_connection(active) + && nm_streq0(nm_active_connection_get_specific_object(active), specific_object) + && (!device || nm_active_connection_get_device(active) == device) + && nm_auth_subject_get_subject_type(nm_active_connection_get_subject(active)) + == NM_AUTH_SUBJECT_TYPE_INTERNAL + && nm_auth_subject_get_subject_type(subject) == NM_AUTH_SUBJECT_TYPE_INTERNAL + && nm_active_connection_get_activation_reason(active) == activation_reason) + return active; + } + + active = _new_active_connection(self, + is_vpn, + sett_conn, + NULL, + applied, + specific_object, + device, + subject, + activation_type, + activation_reason, + initial_state_flags, + error); + if (!active) + return NULL; + + nm_active_connection_authorize(active, + NULL, + _async_op_complete_ac_auth_cb, + _async_op_data_new_authorize_activate_internal(self, active)); + return active; +} + +/** + * validate_activation_request: + * @self: the #NMManager + * @context: the D-Bus context of the requestor + * @sett_conn: the #NMSettingsConnection to be activated, or %NULL if there + * is only a partial activation. + * @connection: the partial #NMConnection to be activated (if @sett_conn is unspecified) + * @device_path: the object path of the device to be activated, or NULL + * @out_device: on successful return, the #NMDevice to be activated with @connection + * The caller may pass in a device which shortcuts the lookup by path. + * In this case, the passed in device must have the matching @device_path + * already. + * @out_is_vpn: on successful return, %TRUE if @connection is a VPN connection + * @error: location to store an error on failure + * + * Performs basic validation on an activation request, including ensuring that + * the requestor is a valid Unix process, is not disallowed in @connection + * permissions, and that a device exists that can activate @connection. + * + * Returns: on success, the #NMAuthSubject representing the requestor, or + * %NULL on error + */ +static NMAuthSubject * +validate_activation_request(NMManager * self, + GDBusMethodInvocation *context, + NMSettingsConnection * sett_conn, + NMConnection * connection, + const char * device_path, + NMDevice ** out_device, + gboolean * out_is_vpn, + GError ** error) +{ + NMDevice * device = NULL; + gboolean is_vpn = FALSE; + gs_unref_object NMAuthSubject *subject = NULL; + + nm_assert(!sett_conn || NM_IS_SETTINGS_CONNECTION(sett_conn)); + nm_assert(!connection || NM_IS_CONNECTION(connection)); + nm_assert(sett_conn || connection); + nm_assert(!connection || !sett_conn + || connection == nm_settings_connection_get_connection(sett_conn)); + nm_assert(out_device); + nm_assert(out_is_vpn); + + if (!connection) + connection = nm_settings_connection_get_connection(sett_conn); + + /* Validate the caller */ + subject = nm_dbus_manager_new_auth_subject_from_context(context); + if (!subject) { + g_set_error_literal(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + NM_UTILS_ERROR_MSG_REQ_UID_UKNOWN); + return NULL; + } + + if (!nm_auth_is_subject_in_acl_set_error(connection, + subject, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + error)) + return NULL; + + is_vpn = _connection_is_vpn(connection); + + if (*out_device) { + device = *out_device; + nm_assert(NM_IS_DEVICE(device)); + nm_assert(device_path); + nm_assert(nm_streq0(device_path, nm_dbus_object_get_path(NM_DBUS_OBJECT(device)))); + nm_assert(device == nm_manager_get_device_by_path(self, device_path)); + } else if (device_path) { + device = nm_manager_get_device_by_path(self, device_path); + if (!device) { + g_set_error_literal(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_UNKNOWN_DEVICE, + "Device not found"); + return NULL; + } + } else if (!is_vpn) { + gs_free_error GError *local = NULL; + + device = nm_manager_get_best_device_for_connection(self, + sett_conn, + connection, + TRUE, + NULL, + &local); + if (!device) { + gs_free char *iface = NULL; + + /* VPN and software-device connections don't need a device yet, + * but non-virtual connections do ... */ + if (!nm_connection_is_virtual(connection)) { + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_UNKNOWN_DEVICE, + "No suitable device found for this connection (%s).", + local->message); + return NULL; + } + + /* Look for an existing device with the connection's interface name */ + iface = nm_manager_get_connection_iface(self, connection, NULL, NULL, error); + if (!iface) + return NULL; + + device = find_device_by_iface(self, iface, connection, NULL); + if (!device) { + g_set_error_literal(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_UNKNOWN_DEVICE, + "Failed to find a compatible device for this connection"); + return NULL; + } + } + } + + nm_assert(is_vpn || NM_IS_DEVICE(device)); + + *out_device = device; + *out_is_vpn = is_vpn; + return g_steal_pointer(&subject); +} + +/*****************************************************************************/ + +static void +_activation_auth_done(NMManager * self, + NMActiveConnection * active, + GDBusMethodInvocation *invocation, + gboolean success, + const char * error_desc) +{ + GError * error = NULL; + NMAuthSubject * subject; + NMSettingsConnection *connection; + + subject = nm_active_connection_get_subject(active); + connection = nm_active_connection_get_settings_connection(active); + + if (!success) { + error = + g_error_new_literal(NM_MANAGER_ERROR, NM_MANAGER_ERROR_PERMISSION_DENIED, error_desc); + goto fail; + } + + if (!_internal_activate_generic(self, active, &error)) + goto fail; + + nm_settings_connection_autoconnect_blocked_reason_set( + connection, + NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST, + FALSE); + g_dbus_method_invocation_return_value( + invocation, + g_variant_new("(o)", nm_dbus_object_get_path(NM_DBUS_OBJECT(active)))); + nm_audit_log_connection_op(NM_AUDIT_OP_CONN_ACTIVATE, connection, TRUE, NULL, subject, NULL); + return; + +fail: + _delete_volatile_connection_do(self, connection); + + nm_audit_log_connection_op(NM_AUDIT_OP_CONN_ACTIVATE, + connection, + FALSE, + NULL, + subject, + error->message); + nm_active_connection_set_state_fail(active, + NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN, + error->message); + + g_dbus_method_invocation_take_error(invocation, error); +} + +static void +impl_manager_activate_connection(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * dbus_connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMManager * self = NM_MANAGER(obj); + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + gs_unref_object NMActiveConnection *active = NULL; + gs_unref_object NMAuthSubject *subject = NULL; + NMSettingsConnection * sett_conn = NULL; + NMDevice * device = NULL; + gboolean is_vpn = FALSE; + GError * error = NULL; + const char * connection_path; + const char * device_path; + const char * specific_object_path; + + g_variant_get(parameters, "(&o&o&o)", &connection_path, &device_path, &specific_object_path); + + connection_path = nm_dbus_path_not_empty(connection_path); + specific_object_path = nm_dbus_path_not_empty(specific_object_path); + device_path = nm_dbus_path_not_empty(device_path); + + /* If the connection path is given and valid, that connection is activated. + * Otherwise, the "best" connection for the device is chosen and activated, + * regardless of whether that connection is autoconnect-enabled or not + * (since this is an explicit request, not an auto-activation request). + */ + if (connection_path) { + sett_conn = nm_settings_get_connection_by_path(priv->settings, connection_path); + if (!sett_conn) { + error = g_error_new_literal(NM_MANAGER_ERROR, + NM_MANAGER_ERROR_UNKNOWN_CONNECTION, + "Connection could not be found."); + goto error; + } + } else { + /* If no connection is given, find a suitable connection for the given device path */ + if (!device_path) { + error = g_error_new_literal( + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_UNKNOWN_DEVICE, + "Only devices may be activated without a specifying a connection"); + goto error; + } + device = nm_manager_get_device_by_path(self, device_path); + if (!device) { + error = g_error_new(NM_MANAGER_ERROR, + NM_MANAGER_ERROR_UNKNOWN_DEVICE, + "Can not activate an unknown device '%s'", + device_path); + goto error; + } + + sett_conn = nm_device_get_best_connection(device, specific_object_path, &error); + if (!sett_conn) + goto error; + } + + subject = validate_activation_request(self, + invocation, + sett_conn, + NULL, + device_path, + &device, + &is_vpn, + &error); + if (!subject) + goto error; + + active = _new_active_connection(self, + is_vpn, + sett_conn, + NULL, + NULL, + specific_object_path, + device, + subject, + NM_ACTIVATION_TYPE_MANAGED, + NM_ACTIVATION_REASON_USER_REQUEST, + _activation_bind_lifetime_to_profile_visibility(subject), + &error); + if (!active) + goto error; + + nm_active_connection_authorize( + active, + NULL, + _async_op_complete_ac_auth_cb, + _async_op_data_new_ac_auth_activate_user(self, active, invocation)); + + /* we passed the pointer on to _async_op_data_new_ac_auth_activate_user() */ + g_steal_pointer(&active); + + return; + +error: + if (sett_conn) { + nm_audit_log_connection_op(NM_AUDIT_OP_CONN_ACTIVATE, + sett_conn, + FALSE, + NULL, + subject, + error->message); + } + g_dbus_method_invocation_take_error(invocation, error); +} + +/*****************************************************************************/ + +static void +activation_add_done(NMSettings * settings, + NMSettingsConnection * new_connection, + GError * error, + GDBusMethodInvocation *context, + NMAuthSubject * subject, + gpointer user_data) +{ + NMManager * self; + gs_unref_object NMActiveConnection *active = NULL; + gs_free_error GError *local = NULL; + gpointer async_op_type_ptr; + AsyncOpType async_op_type; + GVariant * result_floating; + + nm_utils_user_data_unpack(user_data, &self, &active, &async_op_type_ptr); + async_op_type = GPOINTER_TO_INT(async_op_type_ptr); + + if (error) + goto fail; + + nm_active_connection_set_settings_connection(active, new_connection); + + if (!_internal_activate_generic(self, active, &local)) + goto fail; + + if (async_op_type == ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE) { + result_floating = g_variant_new("(oo)", + nm_dbus_object_get_path(NM_DBUS_OBJECT(new_connection)), + nm_dbus_object_get_path(NM_DBUS_OBJECT(active))); + } else { + result_floating = g_variant_new("(oo@a{sv})", + nm_dbus_object_get_path(NM_DBUS_OBJECT(new_connection)), + nm_dbus_object_get_path(NM_DBUS_OBJECT(active)), + g_variant_new_array(G_VARIANT_TYPE("{sv}"), NULL, 0)); + } + g_dbus_method_invocation_return_value(context, result_floating); + + nm_audit_log_connection_op(NM_AUDIT_OP_CONN_ADD_ACTIVATE, + nm_active_connection_get_settings_connection(active), + TRUE, + NULL, + nm_active_connection_get_subject(active), + NULL); + return; + +fail: + if (local) { + nm_assert(!error); + error = local; + } else + nm_assert(error); + + nm_active_connection_set_state_fail(active, + NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN, + error->message); + if (new_connection) + nm_settings_connection_delete(new_connection, FALSE); + g_dbus_method_invocation_return_gerror(context, error); + nm_audit_log_connection_op(NM_AUDIT_OP_CONN_ADD_ACTIVATE, + NULL, + FALSE, + NULL, + nm_active_connection_get_subject(active), + error->message); +} + +static void +_add_and_activate_auth_done(NMManager * self, + AsyncOpType async_op_type, + NMActiveConnection * active, + NMConnection * connection, + GDBusMethodInvocation * invocation, + NMSettingsConnectionPersistMode persist_mode, + gboolean is_volatile, + gboolean success, + const char * error_desc) +{ + NMManagerPrivate *priv; + GError * error = NULL; + + if (!success) { + error = + g_error_new_literal(NM_MANAGER_ERROR, NM_MANAGER_ERROR_PERMISSION_DENIED, error_desc); + nm_audit_log_connection_op(NM_AUDIT_OP_CONN_ADD_ACTIVATE, + NULL, + FALSE, + NULL, + nm_active_connection_get_subject(active), + error->message); + g_dbus_method_invocation_take_error(invocation, error); + return; + } + + priv = NM_MANAGER_GET_PRIVATE(self); + + /* FIXME(shutdown): nm_settings_add_connection_dbus() cannot be cancelled. It should be made + * cancellable and tracked via AsyncOpData to be able to do a clean + * shutdown. */ + nm_settings_add_connection_dbus( + priv->settings, + connection, + persist_mode, + NM_SETTINGS_CONNECTION_ADD_REASON_NONE, + (is_volatile ? NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE + : NM_SETTINGS_CONNECTION_INT_FLAGS_NONE), + nm_active_connection_get_subject(active), + invocation, + activation_add_done, + nm_utils_user_data_pack(self, g_object_ref(active), GINT_TO_POINTER(async_op_type))); +} + +static void +impl_manager_add_and_activate_connection(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * dbus_connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMManager * self = NM_MANAGER(obj); + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + gs_unref_object NMConnection *incompl_conn = NULL; + gs_unref_object NMActiveConnection *active = NULL; + gs_unref_object NMAuthSubject *subject = NULL; + GError * error = NULL; + NMDevice * device = NULL; + gboolean is_vpn = FALSE; + gs_unref_variant GVariant *settings = NULL; + gs_unref_variant GVariant *options = NULL; + const char * device_path; + const char * specific_object_path; + gs_free NMConnection ** conns = NULL; + NMSettingsConnectionPersistMode persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK; + gboolean is_volatile = FALSE; + gboolean bind_dbus_client = FALSE; + AsyncOpType async_op_type; + + if (nm_streq(method_info->parent.name, "AddAndActivateConnection2")) { + async_op_type = ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE2; + g_variant_get(parameters, + "(@a{sa{sv}}&o&o@a{sv})", + &settings, + &device_path, + &specific_object_path, + &options); + } else { + nm_assert(nm_streq(method_info->parent.name, "AddAndActivateConnection")); + async_op_type = ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE; + g_variant_get(parameters, + "(@a{sa{sv}}&o&o)", + &settings, + &device_path, + &specific_object_path); + } + + if (options) { + GVariantIter iter; + const char * option_name; + GVariant * option_value; + + g_variant_iter_init(&iter, options); + while (g_variant_iter_next(&iter, "{&sv}", &option_name, &option_value)) { + gs_unref_variant GVariant *option_value_free = NULL; + const char * s; + + option_value_free = option_value; + + if (nm_streq(option_name, "persist") + && g_variant_is_of_type(option_value, G_VARIANT_TYPE_STRING)) { + s = g_variant_get_string(option_value, NULL); + + is_volatile = FALSE; + persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK; + + if (nm_streq(s, "volatile")) { + persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY; + is_volatile = TRUE; + } else if (nm_streq(s, "memory")) + persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY; + else if (nm_streq(s, "disk")) { + /* pass */ + } else { + error = g_error_new_literal( + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_INVALID_ARGUMENTS, + "Option \"persist\" must be one of \"volatile\", \"memory\" or \"disk\""); + goto error; + } + } else if (nm_streq(option_name, "bind-activation") + && g_variant_is_of_type(option_value, G_VARIANT_TYPE_STRING)) { + s = g_variant_get_string(option_value, NULL); + + if (nm_streq(s, "dbus-client")) + bind_dbus_client = TRUE; + else if (nm_streq(s, "none")) + bind_dbus_client = FALSE; + else { + error = g_error_new_literal( + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_INVALID_ARGUMENTS, + "Option \"bind-activation\" must be one of \"dbus-client\" or \"none\""); + goto error; + } + } else { + error = g_error_new_literal(NM_MANAGER_ERROR, + NM_MANAGER_ERROR_INVALID_ARGUMENTS, + "Unknown extra option passed"); + goto error; + } + } + } + + specific_object_path = nm_dbus_path_not_empty(specific_object_path); + device_path = nm_dbus_path_not_empty(device_path); + + /* Try to create a new connection with the given settings. + * We allow empty settings for AddAndActivateConnection(). In that case, + * the connection will be completed in nm_utils_complete_generic() or + * nm_device_complete_connection() below. Just make sure we don't expect + * specific data being in the connection till then (especially in + * validate_activation_request()). + */ + incompl_conn = nm_simple_connection_new(); + if (settings && g_variant_n_children(settings)) + _nm_connection_replace_settings(incompl_conn, + settings, + NM_SETTING_PARSE_FLAGS_STRICT, + NULL); + + subject = validate_activation_request(self, + invocation, + NULL, + incompl_conn, + device_path, + &device, + &is_vpn, + &error); + if (!subject) + goto error; + + if (is_vpn) { + /* Try to fill the VPN's connection setting and name at least */ + if (!nm_connection_get_setting_vpn(incompl_conn)) { + error = g_error_new_literal(NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_SETTING, + "VPN connections require a 'vpn' setting"); + g_prefix_error(&error, "%s: ", NM_SETTING_VPN_SETTING_NAME); + goto error; + } + + conns = nm_settings_connections_array_to_connections( + nm_settings_get_connections(priv->settings, NULL), + -1); + + nm_utils_complete_generic(priv->platform, + incompl_conn, + NM_SETTING_VPN_SETTING_NAME, + conns, + NULL, + _("VPN connection"), + NULL, + NULL, + FALSE); /* No IPv6 by default for now */ + } else { + conns = nm_settings_connections_array_to_connections( + nm_settings_get_connections(priv->settings, NULL), + -1); + /* Let each device subclass complete the connection */ + if (!nm_device_complete_connection(device, + incompl_conn, + specific_object_path, + conns, + &error)) + goto error; + } + + nm_assert(_nm_connection_verify(incompl_conn, NULL) == NM_SETTING_VERIFY_SUCCESS); + + active = _new_active_connection(self, + is_vpn, + NULL, + incompl_conn, + NULL, + specific_object_path, + device, + subject, + NM_ACTIVATION_TYPE_MANAGED, + NM_ACTIVATION_REASON_USER_REQUEST, + _activation_bind_lifetime_to_profile_visibility(subject), + &error); + if (!active) + goto error; + + if (bind_dbus_client) { + NMKeepAlive *keep_alive; + + keep_alive = nm_active_connection_get_keep_alive(active); + nm_keep_alive_set_dbus_client_watch(keep_alive, dbus_connection, sender); + nm_keep_alive_arm(keep_alive); + } + + nm_active_connection_authorize(active, + incompl_conn, + _async_op_complete_ac_auth_cb, + _async_op_data_new_ac_auth_add_and_activate(self, + async_op_type, + active, + invocation, + incompl_conn, + persist_mode, + is_volatile)); + + /* we passed the pointers on to _async_op_data_new_ac_auth_add_and_activate() */ + g_steal_pointer(&incompl_conn); + g_steal_pointer(&active); + return; + +error: + nm_audit_log_connection_op(NM_AUDIT_OP_CONN_ADD_ACTIVATE, + NULL, + FALSE, + NULL, + subject, + error->message); + g_dbus_method_invocation_take_error(invocation, error); +} + +/*****************************************************************************/ + +gboolean +nm_manager_deactivate_connection(NMManager * manager, + NMActiveConnection *active, + NMDeviceStateReason reason, + GError ** error) +{ + if (NM_IS_VPN_CONNECTION(active)) { + NMActiveConnectionStateReason vpn_reason = + NM_ACTIVE_CONNECTION_STATE_REASON_USER_DISCONNECTED; + + if (nm_device_state_reason_check(reason) == NM_DEVICE_STATE_REASON_CONNECTION_REMOVED) + vpn_reason = NM_ACTIVE_CONNECTION_STATE_REASON_CONNECTION_REMOVED; + + if (!nm_vpn_connection_deactivate(NM_VPN_CONNECTION(active), vpn_reason, FALSE)) { + g_set_error_literal(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_CONNECTION_NOT_ACTIVE, + "The VPN connection was not active."); + return FALSE; + } + } else { + nm_assert(NM_IS_ACT_REQUEST(active)); + nm_device_disconnect_active_connection(active, + reason, + NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN); + } + + _notify(manager, PROP_ACTIVE_CONNECTIONS); + return TRUE; +} + +static void +deactivate_net_auth_done_cb(NMAuthChain *chain, GDBusMethodInvocation *context, gpointer user_data) +{ + NMManager * self = NM_MANAGER(user_data); + GError * error = NULL; + NMAuthCallResult result; + NMActiveConnection *active; + char * path; + + nm_assert(G_IS_DBUS_METHOD_INVOCATION(context)); + + c_list_unlink(nm_auth_chain_parent_lst_list(chain)); + + path = nm_auth_chain_get_data(chain, "path"); + result = nm_auth_chain_get_result(chain, NM_AUTH_PERMISSION_NETWORK_CONTROL); + active = active_connection_get_by_path(self, path); + + if (result != NM_AUTH_CALL_RESULT_YES) { + error = g_error_new_literal(NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + "Not authorized to deactivate connections"); + } else if (!active) { + error = g_error_new_literal(NM_MANAGER_ERROR, + NM_MANAGER_ERROR_CONNECTION_NOT_ACTIVE, + "The connection was not active."); + } else { + /* success; deactivation allowed */ + if (!nm_manager_deactivate_connection(self, + active, + NM_DEVICE_STATE_REASON_USER_REQUESTED, + &error)) + nm_assert(error); + } + + if (active) { + nm_audit_log_connection_op(NM_AUDIT_OP_CONN_DEACTIVATE, + nm_active_connection_get_settings_connection(active), + !error, + NULL, + nm_auth_chain_get_subject(chain), + error ? error->message : NULL); + } + + if (error) + g_dbus_method_invocation_take_error(context, error); + else + g_dbus_method_invocation_return_value(context, NULL); +} + +static void +impl_manager_deactivate_connection(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * dbus_connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMManager * self = NM_MANAGER(obj); + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + NMActiveConnection * ac; + NMSettingsConnection *sett_conn = NULL; + GError * error = NULL; + NMAuthSubject * subject = NULL; + NMAuthChain * chain; + const char * active_path; + + g_variant_get(parameters, "(&o)", &active_path); + + /* Find the connection by its object path */ + ac = active_connection_get_by_path(self, active_path); + if (ac) + sett_conn = nm_active_connection_get_settings_connection(ac); + + if (!sett_conn) { + error = g_error_new_literal(NM_MANAGER_ERROR, + NM_MANAGER_ERROR_CONNECTION_NOT_ACTIVE, + "The connection was not active."); + goto done; + } + + /* Validate the caller */ + subject = nm_dbus_manager_new_auth_subject_from_context(invocation); + if (!subject) { + error = g_error_new_literal(NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + NM_UTILS_ERROR_MSG_REQ_UID_UKNOWN); + goto done; + } + + if (!nm_auth_is_subject_in_acl_set_error(nm_settings_connection_get_connection(sett_conn), + subject, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + &error)) + goto done; + + chain = nm_auth_chain_new_subject(subject, invocation, deactivate_net_auth_done_cb, self); + c_list_link_tail(&priv->auth_lst_head, nm_auth_chain_parent_lst_list(chain)); + nm_auth_chain_set_data(chain, "path", g_strdup(active_path), g_free); + nm_auth_chain_add_call(chain, NM_AUTH_PERMISSION_NETWORK_CONTROL, TRUE); + +done: + if (error) { + if (sett_conn) { + nm_audit_log_connection_op(NM_AUDIT_OP_CONN_DEACTIVATE, + sett_conn, + FALSE, + NULL, + subject, + error->message); + } + g_dbus_method_invocation_take_error(invocation, error); + } + g_clear_object(&subject); +} + +static gboolean +sleep_devices_add(NMManager *self, NMDevice *device, gboolean suspending) +{ + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + NMSleepMonitorInhibitorHandle *handle = NULL; + + if (g_hash_table_lookup_extended(priv->sleep_devices, device, NULL, (gpointer *) &handle)) { + if (suspending) { + /* if we are suspending, always insert a new handle in sleep_devices. + * Even if we had an old handle, it might be stale by now. */ + g_hash_table_insert(priv->sleep_devices, + device, + nm_sleep_monitor_inhibit_take(priv->sleep_monitor)); + if (handle) + nm_sleep_monitor_inhibit_release(priv->sleep_monitor, handle); + } + return FALSE; + } + + g_hash_table_insert(priv->sleep_devices, + g_object_ref(device), + suspending ? nm_sleep_monitor_inhibit_take(priv->sleep_monitor) : NULL); + g_signal_connect(device, "notify::" NM_DEVICE_STATE, (GCallback) device_sleep_cb, self); + return TRUE; +} + +static gboolean +sleep_devices_remove(NMManager *self, NMDevice *device) +{ + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + NMSleepMonitorInhibitorHandle *handle; + + if (!g_hash_table_lookup_extended(priv->sleep_devices, device, NULL, (gpointer *) &handle)) + return FALSE; + + if (handle) + nm_sleep_monitor_inhibit_release(priv->sleep_monitor, handle); + + /* Remove device from hash */ + g_signal_handlers_disconnect_by_func(device, device_sleep_cb, self); + g_hash_table_remove(priv->sleep_devices, device); + g_object_unref(device); + return TRUE; +} + +static void +sleep_devices_clear(NMManager *self) +{ + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + NMDevice * device; + NMSleepMonitorInhibitorHandle *handle; + GHashTableIter iter; + + if (!priv->sleep_devices) + return; + + g_hash_table_iter_init(&iter, priv->sleep_devices); + while (g_hash_table_iter_next(&iter, (gpointer *) &device, (gpointer *) &handle)) { + g_signal_handlers_disconnect_by_func(device, device_sleep_cb, self); + if (handle) + nm_sleep_monitor_inhibit_release(priv->sleep_monitor, handle); + g_object_unref(device); + g_hash_table_iter_remove(&iter); + } +} + +static void +device_sleep_cb(NMDevice *device, GParamSpec *pspec, NMManager *self) +{ + switch (nm_device_get_state(device)) { + case NM_DEVICE_STATE_DISCONNECTED: + _LOGD(LOGD_SUSPEND, "sleep: unmanaging device %s", nm_device_get_ip_iface(device)); + nm_device_set_unmanaged_by_flags_queue(device, + NM_UNMANAGED_SLEEPING, + TRUE, + NM_DEVICE_STATE_REASON_SLEEPING); + break; + case NM_DEVICE_STATE_UNMANAGED: + _LOGD(LOGD_SUSPEND, "sleep: device %s is ready", nm_device_get_ip_iface(device)); + + if (!sleep_devices_remove(self, device)) + g_return_if_reached(); + + break; + default: + return; + } +} + +static void +do_sleep_wake(NMManager *self, gboolean sleeping_changed) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + gboolean suspending, waking_from_suspend; + NMDevice * device; + + suspending = sleeping_changed && priv->sleeping; + waking_from_suspend = sleeping_changed && !priv->sleeping; + + if (manager_sleeping(self)) { + _LOGD(LOGD_SUSPEND, "sleep: %s...", suspending ? "sleeping" : "disabling"); + + /* FIXME: are there still hardware devices that need to be disabled around + * suspend/resume? + */ + c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) { + if (nm_device_is_software(device)) { + /* If a user disables networking we consider that as an + * indication that also software devices must be disconnected. + * But we don't want to destroy them for external events as + * a system suspend. + */ + if (suspending) + continue; + } + /* Wake-on-LAN devices will be taken down post-suspend rather than pre- */ + if (suspending && device_is_wake_on_lan(priv->platform, device)) { + _LOGD(LOGD_SUSPEND, + "sleep: device %s has wake-on-lan, skipping", + nm_device_get_ip_iface(device)); + continue; + } + + if (nm_device_is_activating(device) + || nm_device_get_state(device) == NM_DEVICE_STATE_ACTIVATED) { + _LOGD(LOGD_SUSPEND, + "sleep: wait disconnection of device %s", + nm_device_get_ip_iface(device)); + + if (sleep_devices_add(self, device, suspending)) + nm_device_queue_state(device, + NM_DEVICE_STATE_DEACTIVATING, + NM_DEVICE_STATE_REASON_SLEEPING); + } else { + nm_device_set_unmanaged_by_flags(device, + NM_UNMANAGED_SLEEPING, + TRUE, + NM_DEVICE_STATE_REASON_SLEEPING); + } + } + } else { + _LOGD(LOGD_SUSPEND, "sleep: %s...", waking_from_suspend ? "waking up" : "re-enabling"); + + sleep_devices_clear(self); + + if (waking_from_suspend) { + c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) { + if (nm_device_is_software(device)) + continue; + + /* Belatedly take down Wake-on-LAN devices; ideally we wouldn't have to do this + * but for now it's the only way to make sure we re-check their connectivity. + */ + if (device_is_wake_on_lan(priv->platform, device)) + nm_device_set_unmanaged_by_flags(device, + NM_UNMANAGED_SLEEPING, + TRUE, + NM_DEVICE_STATE_REASON_SLEEPING); + + /* Check if the device is unmanaged but the state transition is still pending. + * If so, change state now so that later we re-manage the device forcing a + * re-check of available connections. + */ + if (!nm_device_get_managed(device, FALSE) + && nm_device_get_state(device) != NM_DEVICE_STATE_UNMANAGED) { + nm_device_state_changed(device, + NM_DEVICE_STATE_UNMANAGED, + NM_DEVICE_STATE_REASON_SLEEPING); + } + } + } + + /* Ensure rfkill state is up-to-date since we don't respond to state + * changes during sleep. + */ + nm_manager_rfkill_update(self, RFKILL_TYPE_UNKNOWN); + + /* Re-manage managed devices */ + c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) { + guint i; + + if (nm_device_is_software(device) + && !nm_device_get_unmanaged_flags(device, NM_UNMANAGED_SLEEPING)) { + /* DHCP leases of software devices could have gone stale + * so we need to renew them. */ + nm_device_update_dynamic_ip_setup(device); + continue; + } + + /* enable/disable wireless devices since that we don't respond + * to killswitch changes during sleep. + */ + for (i = 0; i < RFKILL_TYPE_MAX; i++) { + RadioState *rstate = &priv->radio_states[i]; + gboolean enabled = radio_enabled_for_rstate(rstate, TRUE); + + if (rstate->desc) { + _LOGD(LOGD_RFKILL, + "rfkill: %s %s devices (hw_enabled %d, sw_enabled %d, user_enabled %d)", + enabled ? "enabling" : "disabling", + rstate->desc, + rstate->hw_enabled, + rstate->sw_enabled, + rstate->user_enabled); + } + if (nm_device_get_rfkill_type(device) == rstate->rtype) + nm_device_set_enabled(device, enabled); + } + + nm_device_set_unmanaged_by_flags(device, + NM_UNMANAGED_SLEEPING, + FALSE, + NM_DEVICE_STATE_REASON_NOW_MANAGED); + } + } + + nm_manager_update_state(self); +} + +static void +_internal_sleep(NMManager *self, gboolean do_sleep) +{ + NMManagerPrivate *priv; + + g_return_if_fail(NM_IS_MANAGER(self)); + + priv = NM_MANAGER_GET_PRIVATE(self); + + if (priv->sleeping == do_sleep) + return; + + _LOGI(LOGD_SUSPEND, + "sleep: %s requested (sleeping: %s enabled: %s)", + do_sleep ? "sleep" : "wake", + priv->sleeping ? "yes" : "no", + priv->net_enabled ? "yes" : "no"); + + priv->sleeping = do_sleep; + + do_sleep_wake(self, TRUE); + + _notify(self, PROP_SLEEPING); +} + +static void +impl_manager_sleep(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMManager * self = NM_MANAGER(obj); + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + GError * error = NULL; + gs_unref_object NMAuthSubject *subject = NULL; + gboolean do_sleep; + + g_variant_get(parameters, "(b)", &do_sleep); + + subject = nm_dbus_manager_new_auth_subject_from_context(invocation); + + if (priv->sleeping == do_sleep) { + error = g_error_new(NM_MANAGER_ERROR, + NM_MANAGER_ERROR_ALREADY_ASLEEP_OR_AWAKE, + "Already %s", + do_sleep ? "asleep" : "awake"); + nm_audit_log_control_op(NM_AUDIT_OP_SLEEP_CONTROL, + do_sleep ? "on" : "off", + FALSE, + subject, + error->message); + g_dbus_method_invocation_take_error(invocation, error); + return; + } + + /* Unconditionally allow the request. Previously it was polkit protected + * but unfortunately that doesn't work for short-lived processes like + * pm-utils. It uses dbus-send without --print-reply, which quits + * immediately after sending the request, and NM is unable to obtain the + * sender's UID as dbus-send has already dropped off the bus. Thus NM + * fails the request. Instead, don't validate the request, but rely on + * D-Bus permissions to restrict the call to root. + */ + _internal_sleep(self, do_sleep); + nm_audit_log_control_op(NM_AUDIT_OP_SLEEP_CONTROL, + do_sleep ? "on" : "off", + TRUE, + subject, + NULL); + g_dbus_method_invocation_return_value(invocation, NULL); + return; +} + +static void +sleeping_cb(NMSleepMonitor *monitor, gboolean is_about_to_suspend, gpointer user_data) +{ + NMManager *self = user_data; + + _LOGT(LOGD_SUSPEND, "sleep: received %s signal", is_about_to_suspend ? "sleeping" : "resuming"); + _internal_sleep(self, is_about_to_suspend); +} + +static void +_internal_enable(NMManager *self, gboolean enable) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + + nm_config_state_set(priv->config, + TRUE, + FALSE, + NM_CONFIG_STATE_PROPERTY_NETWORKING_ENABLED, + enable); + + _LOGI(LOGD_SUSPEND, + "%s requested (sleeping: %s enabled: %s)", + enable ? "enable" : "disable", + priv->sleeping ? "yes" : "no", + priv->net_enabled ? "yes" : "no"); + + priv->net_enabled = enable; + + do_sleep_wake(self, FALSE); + + _notify(self, PROP_NETWORKING_ENABLED); +} + +static void +enable_net_done_cb(NMAuthChain *chain, GDBusMethodInvocation *context, gpointer user_data) +{ + NMManager * self = NM_MANAGER(user_data); + NMAuthCallResult result; + gboolean enable; + NMAuthSubject * subject; + + nm_assert(G_IS_DBUS_METHOD_INVOCATION(context)); + + c_list_unlink(nm_auth_chain_parent_lst_list(chain)); + enable = GPOINTER_TO_UINT(nm_auth_chain_get_data(chain, "enable")); + subject = nm_auth_chain_get_subject(chain); + + result = nm_auth_chain_get_result(chain, NM_AUTH_PERMISSION_ENABLE_DISABLE_NETWORK); + if (result != NM_AUTH_CALL_RESULT_YES) { + GError *ret_error; + + ret_error = g_error_new_literal(NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + "Not authorized to enable/disable networking"); + nm_audit_log_control_op(NM_AUDIT_OP_NET_CONTROL, + enable ? "on" : "off", + FALSE, + subject, + ret_error->message); + g_dbus_method_invocation_take_error(context, ret_error); + return; + } + + _internal_enable(self, enable); + g_dbus_method_invocation_return_value(context, NULL); + nm_audit_log_control_op(NM_AUDIT_OP_NET_CONTROL, enable ? "on" : "off", TRUE, subject, NULL); +} + +static void +impl_manager_enable(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMManager * self = NM_MANAGER(obj); + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMAuthChain * chain; + GError * error = NULL; + gboolean enable; + + g_variant_get(parameters, "(b)", &enable); + + if (priv->net_enabled == enable) { + error = g_error_new(NM_MANAGER_ERROR, + NM_MANAGER_ERROR_ALREADY_ENABLED_OR_DISABLED, + "Already %s", + enable ? "enabled" : "disabled"); + goto done; + } + + chain = nm_auth_chain_new_context(invocation, enable_net_done_cb, self); + if (!chain) { + error = g_error_new_literal(NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED); + goto done; + } + + c_list_link_tail(&priv->auth_lst_head, nm_auth_chain_parent_lst_list(chain)); + nm_auth_chain_set_data(chain, "enable", GUINT_TO_POINTER(enable), NULL); + nm_auth_chain_add_call(chain, NM_AUTH_PERMISSION_ENABLE_DISABLE_NETWORK, TRUE); + +done: + if (error) + g_dbus_method_invocation_take_error(invocation, error); +} + +/* Permissions */ + +static void +get_permissions_done_cb(NMAuthChain *chain, GDBusMethodInvocation *context, gpointer user_data) +{ + GVariantBuilder results; + int i; + + nm_assert(G_IS_DBUS_METHOD_INVOCATION(context)); + + c_list_unlink(nm_auth_chain_parent_lst_list(chain)); + + g_variant_builder_init(&results, G_VARIANT_TYPE("a{ss}")); + + for (i = 0; i < (int) G_N_ELEMENTS(nm_auth_permission_sorted); i++) { + const char *permission = nm_auth_permission_names_by_idx[nm_auth_permission_sorted[i] - 1]; + NMAuthCallResult result; + const char * result_str; + + result = nm_auth_chain_get_result(chain, permission); + result_str = nm_client_permission_result_to_string(nm_auth_call_result_to_client(result)); + g_variant_builder_add(&results, "{ss}", permission, result_str); + } + + g_dbus_method_invocation_return_value(context, g_variant_new("(a{ss})", &results)); +} + +static void +impl_manager_get_permissions(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMManager * self = NM_MANAGER(obj); + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMAuthChain * chain; + int i; + + chain = nm_auth_chain_new_context(invocation, get_permissions_done_cb, self); + if (!chain) { + g_dbus_method_invocation_return_error_literal(invocation, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED); + return; + } + + c_list_link_tail(&priv->auth_lst_head, nm_auth_chain_parent_lst_list(chain)); + + for (i = 0; i < (int) G_N_ELEMENTS(nm_auth_permission_sorted); i++) { + const char *permission = nm_auth_permission_names_by_idx[nm_auth_permission_sorted[i] - 1]; + + nm_auth_chain_add_call_unsafe(chain, permission, FALSE); + } +} + +static void +impl_manager_state(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMManager *self = NM_MANAGER(obj); + + nm_manager_update_state(self); + g_dbus_method_invocation_return_value( + invocation, + g_variant_new("(u)", NM_MANAGER_GET_PRIVATE(self)->state)); +} + +static void +impl_manager_set_logging(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMManager * self = NM_MANAGER(obj); + GError * error = NULL; + const char *level; + const char *domains; + + /* 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(NM_DBUS_OBJECT(self)), + invocation, + G_MAXULONG, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED)) + return; + + g_variant_get(parameters, "(&s&s)", &level, &domains); + + if (nm_logging_setup(level, domains, NULL, &error)) { + _LOGI(LOGD_CORE, + "logging: level '%s' domains '%s'", + nm_logging_level_to_string(), + nm_logging_domains_to_string()); + } + + if (error) + g_dbus_method_invocation_take_error(invocation, error); + else + g_dbus_method_invocation_return_value(invocation, NULL); +} + +static void +impl_manager_get_logging(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + g_dbus_method_invocation_return_value( + invocation, + g_variant_new("(ss)", nm_logging_level_to_string(), nm_logging_domains_to_string())); +} + +typedef struct { + NMManager * self; + GDBusMethodInvocation *context; + guint remaining; +} ConnectivityCheckData; + +static void +device_connectivity_done(NMDevice * device, + NMDeviceConnectivityHandle *handle, + NMConnectivityState state, + GError * error, + gpointer user_data) +{ + ConnectivityCheckData *data = user_data; + NMManager * self; + NMManagerPrivate * priv; + + nm_assert(data); + nm_assert(data->remaining > 0); + nm_assert(NM_IS_MANAGER(data->self)); + + data->remaining--; + + self = data->self; + priv = NM_MANAGER_GET_PRIVATE(self); + + if (data->context + && (data->remaining == 0 + || (state == NM_CONNECTIVITY_FULL + && priv->connectivity_state == NM_CONNECTIVITY_FULL))) { + /* despite having a @handle and @state returned by the requests, we always + * return the current connectivity_state. That is, because the connectivity_state + * and the answer to the connectivity check shall agree. + * + * However, if one of the requests (early) returns full connectivity and agrees with + * the accumulated connectivity state, we no longer have to wait. The result is set. + * + * This also works well, because NMDevice first emits change signals to its own + * connectivity state, which is then taken into account for the accumulated global + * state. All this happens, before the callback is invoked. */ + g_dbus_method_invocation_return_value( + g_steal_pointer(&data->context), + g_variant_new("(u)", (guint) priv->connectivity_state)); + } + + if (data->remaining == 0) { + g_object_unref(self); + g_slice_free(ConnectivityCheckData, data); + } +} + +static void +check_connectivity_auth_done_cb(NMAuthChain * chain, + GDBusMethodInvocation *context, + gpointer user_data) +{ + NMManager * self = NM_MANAGER(user_data); + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + GError * error = NULL; + NMAuthCallResult result; + ConnectivityCheckData *data; + NMDevice * device; + + c_list_unlink(nm_auth_chain_parent_lst_list(chain)); + + result = nm_auth_chain_get_result(chain, NM_AUTH_PERMISSION_NETWORK_CONTROL); + + if (result != NM_AUTH_CALL_RESULT_YES) { + error = g_error_new_literal(NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + "Not authorized to recheck connectivity"); + } + if (error) { + g_dbus_method_invocation_take_error(context, error); + return; + } + + data = g_slice_new(ConnectivityCheckData); + data->self = g_object_ref(self); + data->context = context; + data->remaining = 0; + + c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) { + if (nm_device_check_connectivity(device, AF_INET, device_connectivity_done, data)) + data->remaining++; + if (nm_device_check_connectivity(device, AF_INET6, device_connectivity_done, data)) + data->remaining++; + } + + if (data->remaining == 0) { + /* call the handler at least once. */ + data->remaining = 1; + device_connectivity_done(NULL, NULL, NM_CONNECTIVITY_UNKNOWN, NULL, data); + /* @data got destroyed. */ + } +} + +static void +impl_manager_check_connectivity(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMManager * self = NM_MANAGER(obj); + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMAuthChain * chain; + + chain = nm_auth_chain_new_context(invocation, check_connectivity_auth_done_cb, self); + if (!chain) { + g_dbus_method_invocation_return_error_literal(invocation, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED); + return; + } + + c_list_link_tail(&priv->auth_lst_head, nm_auth_chain_parent_lst_list(chain)); + nm_auth_chain_add_call(chain, NM_AUTH_PERMISSION_NETWORK_CONTROL, TRUE); +} + +static void +start_factory(NMDeviceFactory *factory, gpointer user_data) +{ + nm_device_factory_start(factory); +} + +gboolean +nm_manager_write_device_state(NMManager *self, NMDevice *device, int *out_ifindex) +{ + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + int ifindex; + gboolean managed; + NMConfigDeviceStateManagedType managed_type; + const char * uuid = NULL; + const char * perm_hw_addr_fake = NULL; + gboolean perm_hw_addr_is_fake; + guint32 route_metric_default_aspired; + guint32 route_metric_default_effective; + NMTernary nm_owned; + NMDhcpConfig * dhcp_config; + const char * next_server = NULL; + const char * root_path = NULL; + + NM_SET_OUT(out_ifindex, 0); + + ifindex = nm_device_get_ip_ifindex(device); + if (ifindex <= 0) + return FALSE; + if (ifindex == 1) { + /* ignore loopback */ + return FALSE; + } + + if (!nm_platform_link_get(priv->platform, ifindex)) + return FALSE; + + managed = nm_device_get_managed(device, FALSE); + if (managed) { + NMSettingsConnection *sett_conn = NULL; + + if (nm_device_get_state(device) <= NM_DEVICE_STATE_ACTIVATED) + sett_conn = nm_device_get_settings_connection(device); + if (sett_conn) + uuid = nm_settings_connection_get_uuid(sett_conn); + managed_type = NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_MANAGED; + } else if (nm_device_get_unmanaged_flags(device, NM_UNMANAGED_USER_EXPLICIT)) + managed_type = NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_UNMANAGED; + else + managed_type = NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_UNKNOWN; + + perm_hw_addr_fake = + nm_device_get_permanent_hw_address_full(device, FALSE, &perm_hw_addr_is_fake); + if (perm_hw_addr_fake && !perm_hw_addr_is_fake) + perm_hw_addr_fake = NULL; + + nm_owned = nm_device_is_software(device) ? nm_device_is_nm_owned(device) : NM_TERNARY_DEFAULT; + + route_metric_default_effective = _device_route_metric_get(self, + ifindex, + NM_DEVICE_TYPE_UNKNOWN, + TRUE, + &route_metric_default_aspired); + + dhcp_config = nm_device_get_dhcp_config(device, AF_INET); + if (dhcp_config) { + root_path = nm_dhcp_config_get_option(dhcp_config, "root_path"); + next_server = nm_dhcp_config_get_option(dhcp_config, "next_server"); + } + + if (!nm_config_device_state_write(ifindex, + managed_type, + perm_hw_addr_fake, + uuid, + nm_owned, + route_metric_default_aspired, + route_metric_default_effective, + next_server, + root_path)) + return FALSE; + + NM_SET_OUT(out_ifindex, ifindex); + return TRUE; +} + +void +nm_manager_write_device_state_all(NMManager *self) +{ + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + gs_unref_hashtable GHashTable *preserve_ifindexes = NULL; + NMDevice * device; + + preserve_ifindexes = g_hash_table_new(nm_direct_hash, NULL); + + c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) { + int ifindex; + + if (nm_manager_write_device_state(self, device, &ifindex)) { + g_hash_table_add(preserve_ifindexes, GINT_TO_POINTER(ifindex)); + } + } + + nm_config_device_state_prune_stale(preserve_ifindexes, NULL); +} + +static gboolean +devices_inited_cb(gpointer user_data) +{ + NMManager * self = user_data; + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + + priv->devices_inited_id = 0; + priv->devices_inited = TRUE; + check_if_startup_complete(self); + return G_SOURCE_REMOVE; +} + +gboolean +nm_manager_start(NMManager *self, GError **error) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + gs_free NMSettingsConnection **connections = NULL; + guint i; + + nm_device_factory_manager_load_factories(_register_device_factory, self); + + nm_device_factory_manager_for_each_factory(start_factory, NULL); + + /* Set initial radio enabled/disabled state */ + for (i = 0; i < RFKILL_TYPE_MAX; i++) { + RadioState *rstate = &priv->radio_states[i]; + gboolean enabled; + + if (!rstate->desc) + continue; + + /* recheck kernel rfkill state */ + update_rstate_from_rfkill(priv->rfkill_mgr, rstate); + + if (rstate->desc) { + _LOGI(LOGD_RFKILL, + "rfkill: %s %s by radio killswitch; %s by state file", + rstate->desc, + (rstate->hw_enabled && rstate->sw_enabled) ? "enabled" : "disabled", + rstate->user_enabled ? "enabled" : "disabled"); + } + enabled = radio_enabled_for_rstate(rstate, TRUE); + manager_update_radio_enabled(self, rstate, enabled); + } + + _LOGI(LOGD_CORE, "Networking is %s by state file", priv->net_enabled ? "enabled" : "disabled"); + + system_unmanaged_devices_changed_cb(priv->settings, NULL, self); + + hostname_changed_cb(priv->hostname_manager, NULL, self); + + if (!nm_settings_start(priv->settings, error)) + return FALSE; + + nm_platform_process_events(priv->platform); + + g_signal_connect(priv->platform, + NM_PLATFORM_SIGNAL_LINK_CHANGED, + G_CALLBACK(platform_link_cb), + self); + + platform_query_devices(self); + + /* Load VPN plugins */ + priv->vpn_manager = g_object_ref(nm_vpn_manager_get()); + + _LOGD(LOGD_CORE, "creating virtual devices..."); + g_signal_connect(priv->settings, + NM_SETTINGS_SIGNAL_CONNECTION_ADDED, + G_CALLBACK(connection_added_cb), + self); + g_signal_connect(priv->settings, + NM_SETTINGS_SIGNAL_CONNECTION_UPDATED, + G_CALLBACK(connection_updated_cb), + self); + connections = nm_settings_get_connections_clone( + priv->settings, + NULL, + NULL, + NULL, + nm_settings_connection_cmp_autoconnect_priority_p_with_data, + NULL); + for (i = 0; connections[i]; i++) + connection_changed(self, connections[i]); + + nm_clear_g_source(&priv->devices_inited_id); + priv->devices_inited_id = g_idle_add_full(G_PRIORITY_LOW + 10, devices_inited_cb, self, NULL); + + return TRUE; +} + +void +nm_manager_stop(NMManager *self) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMDevice * device; + + /* FIXME(shutdown): we don't do a proper shutdown yet: + * - need to ensure that all pending async operations are cancelled + * - e.g. operations in priv->async_op_lst_head + * - need to ensure that no more asynchronous requests are started, + * or that they complete quickly, or that they fail quickly. + * - note that cancelling some operations is not possible synchronously. + * Hence, stop() only prepares shutdown and tells everybody to not + * accept new work, and to complete in a timely manner. + * We need to still iterate the mainloop for a bit, to give everybody + * the chance to complete. + * - e.g. see comment at nm_auth_manager_force_shutdown() + */ + + nm_dbus_manager_stop(nm_dbus_object_get_manager(NM_DBUS_OBJECT(self))); + + while ((device = c_list_first_entry(&priv->devices_lst_head, NMDevice, devices_lst))) + remove_device(self, device, TRUE); + + _active_connection_cleanup(self); + + nm_clear_g_source(&priv->devices_inited_id); +} + +static gboolean +handle_firmware_changed(gpointer user_data) +{ + NMManager * self = NM_MANAGER(user_data); + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMDevice * device; + + priv->fw_changed_id = 0; + + /* Try to re-enable devices with missing firmware */ + c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) { + NMDeviceState state = nm_device_get_state(device); + + if (nm_device_get_firmware_missing(device) && (state == NM_DEVICE_STATE_UNAVAILABLE)) { + _LOG2I(LOGD_CORE, device, "firmware may now be available"); + + /* Re-set unavailable state to try bringing the device up again */ + nm_device_state_changed(device, + NM_DEVICE_STATE_UNAVAILABLE, + NM_DEVICE_STATE_REASON_NONE); + } + } + + return FALSE; +} + +static void +firmware_dir_changed(GFileMonitor * monitor, + GFile * file, + GFile * other_file, + GFileMonitorEvent event_type, + gpointer user_data) +{ + NMManager * self = NM_MANAGER(user_data); + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + + switch (event_type) { + case G_FILE_MONITOR_EVENT_CREATED: + case G_FILE_MONITOR_EVENT_CHANGED: + case G_FILE_MONITOR_EVENT_MOVED: + case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED: + case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT: + if (!priv->fw_changed_id) { + priv->fw_changed_id = g_timeout_add_seconds(4, handle_firmware_changed, self); + _LOGI(LOGD_CORE, "kernel firmware directory '%s' changed", KERNEL_FIRMWARE_DIR); + } + break; + default: + break; + } +} + +static void +connection_metered_changed(GObject *object, NMMetered metered, gpointer user_data) +{ + nm_manager_update_metered(NM_MANAGER(user_data)); +} + +static void +policy_default_ac_changed(GObject *object, GParamSpec *pspec, gpointer user_data) +{ + NMManager * self = NM_MANAGER(user_data); + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + NMActiveConnection *ac; + + /* Note: this assumes that it's not possible for the IP4 default + * route to be going over the default-ip6-device. If that changes, + * we need something more complicated here. + */ + ac = nm_policy_get_default_ip4_ac(priv->policy); + if (!ac) + ac = nm_policy_get_default_ip6_ac(priv->policy); + + if (ac != priv->primary_connection) { + if (priv->primary_connection) { + g_signal_handlers_disconnect_by_func(priv->primary_connection, + G_CALLBACK(connection_metered_changed), + self); + g_clear_object(&priv->primary_connection); + } + + priv->primary_connection = ac ? g_object_ref(ac) : NULL; + + if (priv->primary_connection) { + g_signal_connect(priv->primary_connection, + NM_ACTIVE_CONNECTION_DEVICE_METERED_CHANGED, + G_CALLBACK(connection_metered_changed), + self); + } + _LOGD(LOGD_CORE, + "PrimaryConnection now %s", + ac ? nm_active_connection_get_settings_connection_id(ac) : "(none)"); + _notify(self, PROP_PRIMARY_CONNECTION); + _notify(self, PROP_PRIMARY_CONNECTION_TYPE); + nm_manager_update_metered(self); + } +} + +static void +policy_activating_ac_changed(GObject *object, GParamSpec *pspec, gpointer user_data) +{ + NMManager * self = NM_MANAGER(user_data); + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + NMActiveConnection *activating, *best; + + /* We only look at activating-ip6-ac if activating-ip4-ac + * AND default-ip4-ac are NULL; if default-ip4-ac is + * non-NULL, then activating-ip6-ac is irrelevant, since while + * that AC might become the new default-ip6-ac, it can't + * become primary-connection while default-ip4-ac is set to + * something else. + */ + activating = nm_policy_get_activating_ip4_ac(priv->policy); + best = nm_policy_get_default_ip4_ac(priv->policy); + if (!activating && !best) + activating = nm_policy_get_activating_ip6_ac(priv->policy); + + if (nm_g_object_ref_set(&priv->activating_connection, activating)) { + _LOGD(LOGD_CORE, + "ActivatingConnection now %s", + activating ? nm_active_connection_get_settings_connection_id(activating) : "(none)"); + _notify(self, PROP_ACTIVATING_CONNECTION); + } +} + +/*****************************************************************************/ + +typedef struct { + NMManager * self; + NMDBusObject * obj; + const NMDBusInterfaceInfoExtended *interface_info; + const NMDBusPropertyInfoExtended * property_info; + GVariant * value; + guint64 export_version_id; +} DBusSetPropertyHandle; + +#define NM_PERM_DENIED_ERROR "org.freedesktop.NetworkManager.PermissionDenied" + +static void +_dbus_set_property_auth_cb(NMAuthChain * chain, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + DBusSetPropertyHandle *handle_data = user_data; + gs_unref_object NMDBusObject * obj = handle_data->obj; + const NMDBusInterfaceInfoExtended *interface_info = handle_data->interface_info; + const NMDBusPropertyInfoExtended * property_info = handle_data->property_info; + gs_unref_variant GVariant *value = handle_data->value; + guint64 export_version_id = handle_data->export_version_id; + gs_unref_object NMManager *self = handle_data->self; + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + NMAuthCallResult result; + gs_free_error GError *local = NULL; + const char * error_name = NULL; + const char * error_message = NULL; + GValue gvalue; + + g_slice_free(DBusSetPropertyHandle, handle_data); + + c_list_unlink(nm_auth_chain_parent_lst_list(chain)); + result = nm_auth_chain_get_result(chain, property_info->writable.permission); + + if (result != NM_AUTH_CALL_RESULT_YES) { + error_name = NM_PERM_DENIED_ERROR; + error_message = "Not authorized to perform this operation"; + goto out; + } + + if (export_version_id != nm_dbus_object_get_export_version_id(obj)) { + error_name = "org.freedesktop.DBus.Error.UnknownObject"; + error_message = "Object was deleted while authenticating"; + goto out; + } + + /* Handle some properties specially *sigh* */ + if (interface_info == &interface_info_manager + && nm_streq(property_info->property_name, NM_MANAGER_GLOBAL_DNS_CONFIGURATION)) { + const NMGlobalDnsConfig *global_dns; + + global_dns = nm_config_data_get_global_dns_config(nm_config_get_data(priv->config)); + if (global_dns && !nm_global_dns_config_is_internal(global_dns)) { + error_name = NM_PERM_DENIED_ERROR; + error_message = "Global DNS configuration already set via configuration file"; + goto out; + } + } + + g_dbus_gvariant_to_gvalue(value, &gvalue); + if (!nm_g_object_set_property(G_OBJECT(obj), property_info->property_name, &gvalue, &local)) { + error_name = "org.freedesktop.DBus.Error.InvalidArgs"; + error_message = local->message; + } + g_value_unset(&gvalue); + +out: + nm_audit_log_control_op(property_info->writable.audit_op, + property_info->property_name, + !error_message, + nm_auth_chain_get_subject(chain), + error_message); + if (error_message) + g_dbus_method_invocation_return_dbus_error(invocation, error_name, error_message); + else + g_dbus_method_invocation_return_value(invocation, NULL); +} + +void +nm_manager_dbus_set_property_handle(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusPropertyInfoExtended * property_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * value, + gpointer user_data) +{ + NMManager * self = user_data; + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMAuthChain * chain; + const char * error_message = NULL; + gs_unref_object NMAuthSubject *subject = NULL; + DBusSetPropertyHandle * handle_data; + + subject = nm_dbus_manager_new_auth_subject_from_context(invocation); + if (!subject) { + error_message = NM_UTILS_ERROR_MSG_REQ_UID_UKNOWN; + goto err; + } + + handle_data = g_slice_new0(DBusSetPropertyHandle); + handle_data->self = g_object_ref(self); + handle_data->obj = g_object_ref(obj); + handle_data->interface_info = interface_info; + handle_data->property_info = property_info; + handle_data->value = g_variant_ref(value); + handle_data->export_version_id = nm_dbus_object_get_export_version_id(obj); + + chain = nm_auth_chain_new_subject(subject, invocation, _dbus_set_property_auth_cb, handle_data); + c_list_link_tail(&priv->auth_lst_head, nm_auth_chain_parent_lst_list(chain)); + nm_auth_chain_add_call_unsafe(chain, property_info->writable.permission, TRUE); + return; + +err: + nm_audit_log_control_op(property_info->writable.audit_op, + property_info->property_name, + FALSE, + invocation, + error_message); + g_dbus_method_invocation_return_error_literal(invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_AUTH_FAILED, + error_message); +} + +/*****************************************************************************/ + +static NMCheckpointManager * +_checkpoint_mgr_get(NMManager *self, gboolean create_as_needed) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + + if (G_UNLIKELY(!priv->checkpoint_mgr) && create_as_needed) + priv->checkpoint_mgr = nm_checkpoint_manager_new(self, obj_properties[PROP_CHECKPOINTS]); + return priv->checkpoint_mgr; +} + +static void +checkpoint_auth_done_cb(NMAuthChain *chain, GDBusMethodInvocation *context, gpointer user_data) +{ + NMManager * self = NM_MANAGER(user_data); + char * op; + char * checkpoint_path = NULL; + char ** devices; + NMCheckpoint * checkpoint; + NMAuthCallResult result; + guint32 timeout, flags; + GVariant * variant = NULL; + GError * error = NULL; + const char * arg = NULL; + guint32 add_timeout; + + op = nm_auth_chain_get_data(chain, "audit-op"); + c_list_unlink(nm_auth_chain_parent_lst_list(chain)); + result = nm_auth_chain_get_result(chain, NM_AUTH_PERMISSION_CHECKPOINT_ROLLBACK); + + if (NM_IN_STRSET(op, + NM_AUDIT_OP_CHECKPOINT_DESTROY, + NM_AUDIT_OP_CHECKPOINT_ROLLBACK, + NM_AUDIT_OP_CHECKPOINT_ADJUST_ROLLBACK_TIMEOUT)) + arg = checkpoint_path = nm_auth_chain_get_data(chain, "checkpoint_path"); + + if (result != NM_AUTH_CALL_RESULT_YES) { + error = g_error_new_literal(NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + "Not authorized to checkpoint/rollback"); + } else { + if (nm_streq0(op, NM_AUDIT_OP_CHECKPOINT_CREATE)) { + timeout = GPOINTER_TO_UINT(nm_auth_chain_get_data(chain, "timeout")); + flags = GPOINTER_TO_UINT(nm_auth_chain_get_data(chain, "flags")); + devices = nm_auth_chain_get_data(chain, "devices"); + + checkpoint = nm_checkpoint_manager_create(_checkpoint_mgr_get(self, TRUE), + (const char *const *) devices, + timeout, + (NMCheckpointCreateFlags) flags, + &error); + if (checkpoint) { + arg = nm_dbus_object_get_path(NM_DBUS_OBJECT(checkpoint)); + variant = g_variant_new("(o)", arg); + } + } else if (nm_streq0(op, NM_AUDIT_OP_CHECKPOINT_DESTROY)) { + nm_checkpoint_manager_destroy(_checkpoint_mgr_get(self, TRUE), checkpoint_path, &error); + } else if (nm_streq0(op, NM_AUDIT_OP_CHECKPOINT_ROLLBACK)) { + nm_checkpoint_manager_rollback(_checkpoint_mgr_get(self, TRUE), + checkpoint_path, + &variant, + &error); + } else if (nm_streq0(op, NM_AUDIT_OP_CHECKPOINT_ADJUST_ROLLBACK_TIMEOUT)) { + add_timeout = GPOINTER_TO_UINT(nm_auth_chain_get_data(chain, "add_timeout")); + nm_checkpoint_manager_adjust_rollback_timeout(_checkpoint_mgr_get(self, TRUE), + checkpoint_path, + add_timeout, + &error); + } else + g_return_if_reached(); + } + + nm_audit_log_checkpoint_op(op, + arg ?: "", + !error, + nm_auth_chain_get_subject(chain), + error ? error->message : NULL); + + if (error) + g_dbus_method_invocation_take_error(context, error); + else + g_dbus_method_invocation_return_value(context, variant); +} + +static void +impl_manager_checkpoint_create(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMManager * self = NM_MANAGER(obj); + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMAuthChain * chain; + char ** devices; + guint32 rollback_timeout; + guint32 flags; + + G_STATIC_ASSERT_EXPR(sizeof(flags) <= sizeof(NMCheckpointCreateFlags)); + + chain = nm_auth_chain_new_context(invocation, checkpoint_auth_done_cb, self); + if (!chain) { + g_dbus_method_invocation_return_error_literal(invocation, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED); + return; + } + + g_variant_get(parameters, "(^aouu)", &devices, &rollback_timeout, &flags); + + c_list_link_tail(&priv->auth_lst_head, nm_auth_chain_parent_lst_list(chain)); + nm_auth_chain_set_data(chain, "audit-op", NM_AUDIT_OP_CHECKPOINT_CREATE, NULL); + nm_auth_chain_set_data(chain, "devices", devices, (GDestroyNotify) g_strfreev); + nm_auth_chain_set_data(chain, "flags", GUINT_TO_POINTER(flags), NULL); + nm_auth_chain_set_data(chain, "timeout", GUINT_TO_POINTER(rollback_timeout), NULL); + nm_auth_chain_add_call(chain, NM_AUTH_PERMISSION_CHECKPOINT_ROLLBACK, TRUE); +} + +static void +impl_manager_checkpoint_destroy(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMManager * self = NM_MANAGER(obj); + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMAuthChain * chain; + const char * checkpoint_path; + + chain = nm_auth_chain_new_context(invocation, checkpoint_auth_done_cb, self); + if (!chain) { + g_dbus_method_invocation_return_error_literal(invocation, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED); + return; + } + + g_variant_get(parameters, "(&o)", &checkpoint_path); + + c_list_link_tail(&priv->auth_lst_head, nm_auth_chain_parent_lst_list(chain)); + nm_auth_chain_set_data(chain, "audit-op", NM_AUDIT_OP_CHECKPOINT_DESTROY, NULL); + nm_auth_chain_set_data(chain, "checkpoint_path", g_strdup(checkpoint_path), g_free); + nm_auth_chain_add_call(chain, NM_AUTH_PERMISSION_CHECKPOINT_ROLLBACK, TRUE); +} + +static void +impl_manager_checkpoint_rollback(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMManager * self = NM_MANAGER(obj); + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMAuthChain * chain; + const char * checkpoint_path; + + chain = nm_auth_chain_new_context(invocation, checkpoint_auth_done_cb, self); + if (!chain) { + g_dbus_method_invocation_return_error_literal(invocation, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED); + return; + } + + g_variant_get(parameters, "(&o)", &checkpoint_path); + + c_list_link_tail(&priv->auth_lst_head, nm_auth_chain_parent_lst_list(chain)); + nm_auth_chain_set_data(chain, "audit-op", NM_AUDIT_OP_CHECKPOINT_ROLLBACK, NULL); + nm_auth_chain_set_data(chain, "checkpoint_path", g_strdup(checkpoint_path), g_free); + nm_auth_chain_add_call(chain, NM_AUTH_PERMISSION_CHECKPOINT_ROLLBACK, TRUE); +} + +static void +impl_manager_checkpoint_adjust_rollback_timeout(NMDBusObject * obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended * method_info, + GDBusConnection * connection, + const char * sender, + GDBusMethodInvocation * invocation, + GVariant * parameters) +{ + NMManager * self = NM_MANAGER(obj); + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + NMAuthChain * chain; + const char * checkpoint_path; + guint32 add_timeout; + + chain = nm_auth_chain_new_context(invocation, checkpoint_auth_done_cb, self); + if (!chain) { + g_dbus_method_invocation_return_error_literal(invocation, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_PERMISSION_DENIED, + NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED); + return; + } + + g_variant_get(parameters, "(&ou)", &checkpoint_path, &add_timeout); + + c_list_link_tail(&priv->auth_lst_head, nm_auth_chain_parent_lst_list(chain)); + nm_auth_chain_set_data(chain, "audit-op", NM_AUDIT_OP_CHECKPOINT_ADJUST_ROLLBACK_TIMEOUT, NULL); + nm_auth_chain_set_data(chain, "checkpoint_path", g_strdup(checkpoint_path), g_free); + nm_auth_chain_set_data(chain, "add_timeout", GUINT_TO_POINTER(add_timeout), NULL); + nm_auth_chain_add_call(chain, NM_AUTH_PERMISSION_CHECKPOINT_ROLLBACK, TRUE); +} + +/*****************************************************************************/ + +static void +auth_mgr_changed(NMAuthManager *auth_manager, gpointer user_data) +{ + /* Let clients know they should re-check their authorization */ + nm_dbus_object_emit_signal(user_data, + &interface_info_manager, + &signal_info_check_permissions, + "()"); +} + +#define KERN_RFKILL_OP_CHANGE_ALL 3 +#define KERN_RFKILL_TYPE_WLAN 1 +#define KERN_RFKILL_TYPE_WWAN 5 +struct rfkill_event { + uint32_t idx; + uint8_t type; + uint8_t op; + uint8_t soft; + uint8_t hard; +} _nm_packed; + +static void +rfkill_change(NMManager *self, const char *desc, RfKillType rtype, gboolean enabled) +{ + int fd; + struct rfkill_event event; + ssize_t len; + int errsv; + + g_return_if_fail(rtype == RFKILL_TYPE_WLAN || rtype == RFKILL_TYPE_WWAN); + + fd = open("/dev/rfkill", O_RDWR | O_CLOEXEC); + if (fd < 0) { + if (errno == EACCES) + _LOGW(LOGD_RFKILL, "rfkill: (%s): failed to open killswitch device", desc); + return; + } + + if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0) { + _LOGW(LOGD_RFKILL, + "rfkill: (%s): failed to set killswitch device for " + "non-blocking operation", + desc); + nm_close(fd); + return; + } + + memset(&event, 0, sizeof(event)); + event.op = KERN_RFKILL_OP_CHANGE_ALL; + switch (rtype) { + case RFKILL_TYPE_WLAN: + event.type = KERN_RFKILL_TYPE_WLAN; + break; + case RFKILL_TYPE_WWAN: + event.type = KERN_RFKILL_TYPE_WWAN; + break; + default: + g_assert_not_reached(); + } + event.soft = enabled ? 0 : 1; + + len = write(fd, &event, sizeof(event)); + if (len < 0) { + errsv = errno; + _LOGW(LOGD_RFKILL, + "rfkill: (%s): failed to change Wi-Fi killswitch state: (%d) %s", + desc, + errsv, + nm_strerror_native(errsv)); + } else if (len == sizeof(event)) { + _LOGI(LOGD_RFKILL, + "rfkill: %s hardware radio set %s", + desc, + enabled ? "enabled" : "disabled"); + } else { + /* Failed to write full structure */ + _LOGW(LOGD_RFKILL, "rfkill: (%s): failed to change Wi-Fi killswitch state", desc); + } + + nm_close(fd); +} + +static void +manager_radio_user_toggled(NMManager *self, RadioState *rstate, gboolean enabled) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + gboolean old_enabled, new_enabled; + + /* Don't touch devices if asleep/networking disabled */ + if (manager_sleeping(self)) + return; + + if (rstate->desc) { + _LOGD(LOGD_RFKILL, + "rfkill: (%s): setting radio %s by user", + rstate->desc, + enabled ? "enabled" : "disabled"); + } + + /* Update enabled key in state file */ + nm_config_state_set(priv->config, TRUE, FALSE, rstate->key, enabled); + + /* When the user toggles the radio, their request should override any + * daemon (like ModemManager) enabled state that can be changed. For WWAN + * for example, we want the WwanEnabled property to reflect the daemon state + * too so that users can toggle the modem powered, but we don't want that + * daemon state to affect whether or not the user *can* turn it on, which is + * what the kernel rfkill state does. So we ignore daemon enabled state + * when determining what the new state should be since it shouldn't block + * the user's request. + */ + old_enabled = radio_enabled_for_rstate(rstate, TRUE); + rstate->user_enabled = enabled; + new_enabled = radio_enabled_for_rstate(rstate, FALSE); + if (new_enabled != old_enabled) { + /* Try to change the kernel rfkill state */ + if (rstate->rtype == RFKILL_TYPE_WLAN || rstate->rtype == RFKILL_TYPE_WWAN) + rfkill_change(self, rstate->desc, rstate->rtype, new_enabled); + + manager_update_radio_enabled(self, rstate, new_enabled); + } +} + +static gboolean +periodic_update_active_connection_timestamps(gpointer user_data) +{ + NMManager * manager = NM_MANAGER(user_data); + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(manager); + NMActiveConnection *ac; + gboolean has_time = FALSE; + guint64 t = 0; + + c_list_for_each_entry (ac, &priv->active_connections_lst_head, active_connections_lst) { + if (nm_active_connection_get_state(ac) != NM_ACTIVE_CONNECTION_STATE_ACTIVATED) + continue; + + if (!has_time) { + t = time(NULL); + has_time = TRUE; + } + nm_settings_connection_update_timestamp(nm_active_connection_get_settings_connection(ac), + t); + } + return G_SOURCE_CONTINUE; +} + +void +nm_manager_unblock_failed_ovs_interfaces(NMManager *self) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + + nm_policy_unblock_failed_ovs_interfaces(priv->policy); +} + +/*****************************************************************************/ + +void +nm_manager_set_capability(NMManager *self, NMCapability cap) +{ + NMManagerPrivate *priv; + guint32 cap_i; + gssize idx; + + g_return_if_fail(NM_IS_MANAGER(self)); + if (cap < 1 || cap > _NM_CAPABILITY_MAX) + g_return_if_reached(); + + cap_i = (guint32) cap; + + priv = NM_MANAGER_GET_PRIVATE(self); + + idx = nm_utils_array_find_binary_search(&g_array_index(priv->capabilities, guint32, 0), + sizeof(guint32), + priv->capabilities->len, + &cap_i, + nm_cmp_uint32_p_with_data, + NULL); + if (idx >= 0) + return; + + nm_assert((~idx) <= (gssize) priv->capabilities->len); + + g_array_insert_val(priv->capabilities, ~idx, cap_i); + _notify(self, PROP_CAPABILITIES); +} + +void +nm_manager_emit_device_ifindex_changed(NMManager *self, NMDevice *device) +{ + g_signal_emit(self, signals[DEVICE_IFINDEX_CHANGED], 0, device); +} + +/*****************************************************************************/ + +NM_DEFINE_SINGLETON_REGISTER(NMManager); + +NMManager * +nm_manager_get(void) +{ + g_return_val_if_fail(singleton_instance, NULL); + return singleton_instance; +} + +NMSettings * +nm_settings_get(void) +{ + g_return_val_if_fail(singleton_instance, NULL); + + return NM_MANAGER_GET_PRIVATE(singleton_instance)->settings; +} + +NMManager * +nm_manager_setup(void) +{ + NMManager *self; + + g_return_val_if_fail(!singleton_instance, singleton_instance); + + self = g_object_new(NM_TYPE_MANAGER, NULL); + nm_assert(NM_IS_MANAGER(self)); + singleton_instance = self; + + nm_singleton_instance_register(); + nm_log_dbg(LOGD_CORE, + "setup %s singleton (" NM_HASH_OBFUSCATE_PTR_FMT ")", + "NMManager", + NM_HASH_OBFUSCATE_PTR(singleton_instance)); + + nm_dbus_object_export(NM_DBUS_OBJECT(self)); + return self; +} + +static void +constructed(GObject *object) +{ + NMManager * self = NM_MANAGER(object); + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + const NMConfigState *state; + + G_OBJECT_CLASS(nm_manager_parent_class)->constructed(object); + + priv->settings = nm_settings_new(self); + + nm_dbus_object_export(NM_DBUS_OBJECT(priv->settings)); + + g_signal_connect(priv->settings, + "notify::" NM_SETTINGS_STARTUP_COMPLETE, + G_CALLBACK(settings_startup_complete_changed), + self); + g_signal_connect(priv->settings, + "notify::" NM_SETTINGS_UNMANAGED_SPECS, + G_CALLBACK(system_unmanaged_devices_changed_cb), + self); + g_signal_connect(priv->settings, + NM_SETTINGS_SIGNAL_CONNECTION_FLAGS_CHANGED, + G_CALLBACK(connection_flags_changed), + self); + + priv->hostname_manager = g_object_ref(nm_hostname_manager_get()); + g_signal_connect(priv->hostname_manager, + "notify::" NM_HOSTNAME_MANAGER_HOSTNAME, + G_CALLBACK(hostname_changed_cb), + self); + + /* + * Do not delete existing virtual devices to keep connectivity up. + * Virtual devices are reused when NetworkManager is restarted. + * Hence, don't react on NM_SETTINGS_SIGNAL_CONNECTION_REMOVED. + */ + + priv->policy = nm_policy_new(self, priv->settings); + g_signal_connect(priv->policy, + "notify::" NM_POLICY_DEFAULT_IP4_AC, + G_CALLBACK(policy_default_ac_changed), + self); + g_signal_connect(priv->policy, + "notify::" NM_POLICY_DEFAULT_IP6_AC, + G_CALLBACK(policy_default_ac_changed), + self); + g_signal_connect(priv->policy, + "notify::" NM_POLICY_ACTIVATING_IP4_AC, + G_CALLBACK(policy_activating_ac_changed), + self); + g_signal_connect(priv->policy, + "notify::" NM_POLICY_ACTIVATING_IP6_AC, + G_CALLBACK(policy_activating_ac_changed), + self); + + priv->config = g_object_ref(nm_config_get()); + g_signal_connect(G_OBJECT(priv->config), + NM_CONFIG_SIGNAL_CONFIG_CHANGED, + G_CALLBACK(_config_changed_cb), + self); + + state = nm_config_state_get(priv->config); + + priv->net_enabled = state->net_enabled; + + priv->radio_states[RFKILL_TYPE_WLAN].user_enabled = state->wifi_enabled; + priv->radio_states[RFKILL_TYPE_WWAN].user_enabled = state->wwan_enabled; + + priv->rfkill_mgr = nm_rfkill_manager_new(); + g_signal_connect(priv->rfkill_mgr, + NM_RFKILL_MANAGER_SIGNAL_RFKILL_CHANGED, + G_CALLBACK(rfkill_manager_rfkill_changed_cb), + self); + + /* Force kernel Wi-Fi/WWAN rfkill state to follow NM saved Wi-Fi/WWAN state + * in case the BIOS doesn't save rfkill state, and to be consistent with user + * changes to the WirelessEnabled/WWANEnabled properties which toggle kernel + * rfkill. + */ + rfkill_change(self, + priv->radio_states[RFKILL_TYPE_WLAN].desc, + RFKILL_TYPE_WLAN, + priv->radio_states[RFKILL_TYPE_WLAN].user_enabled); + rfkill_change(self, + priv->radio_states[RFKILL_TYPE_WWAN].desc, + RFKILL_TYPE_WWAN, + priv->radio_states[RFKILL_TYPE_WWAN].user_enabled); +} + +static void +nm_manager_init(NMManager *self) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + guint i; + GFile * file; + + c_list_init(&priv->auth_lst_head); + c_list_init(&priv->link_cb_lst); + c_list_init(&priv->devices_lst_head); + c_list_init(&priv->active_connections_lst_head); + c_list_init(&priv->async_op_lst_head); + c_list_init(&priv->delete_volatile_connection_lst_head); + + priv->platform = g_object_ref(NM_PLATFORM_GET); + + priv->capabilities = g_array_new(FALSE, FALSE, sizeof(guint32)); + + /* Initialize rfkill structures and states */ + memset(priv->radio_states, 0, sizeof(priv->radio_states)); + + priv->radio_states[RFKILL_TYPE_WLAN].user_enabled = TRUE; + priv->radio_states[RFKILL_TYPE_WLAN].key = NM_CONFIG_STATE_PROPERTY_WIFI_ENABLED; + priv->radio_states[RFKILL_TYPE_WLAN].prop = NM_MANAGER_WIRELESS_ENABLED; + priv->radio_states[RFKILL_TYPE_WLAN].hw_prop = NM_MANAGER_WIRELESS_HARDWARE_ENABLED; + priv->radio_states[RFKILL_TYPE_WLAN].desc = "Wi-Fi"; + priv->radio_states[RFKILL_TYPE_WLAN].rtype = RFKILL_TYPE_WLAN; + + priv->radio_states[RFKILL_TYPE_WWAN].user_enabled = TRUE; + priv->radio_states[RFKILL_TYPE_WWAN].key = NM_CONFIG_STATE_PROPERTY_WWAN_ENABLED; + priv->radio_states[RFKILL_TYPE_WWAN].prop = NM_MANAGER_WWAN_ENABLED; + priv->radio_states[RFKILL_TYPE_WWAN].hw_prop = NM_MANAGER_WWAN_HARDWARE_ENABLED; + priv->radio_states[RFKILL_TYPE_WWAN].desc = "WWAN"; + priv->radio_states[RFKILL_TYPE_WWAN].rtype = RFKILL_TYPE_WWAN; + + for (i = 0; i < RFKILL_TYPE_MAX; i++) + priv->radio_states[i].hw_enabled = TRUE; + + priv->sleeping = FALSE; + priv->state = NM_STATE_DISCONNECTED; + priv->startup = TRUE; + + /* sleep/wake handling */ + priv->sleep_monitor = nm_sleep_monitor_new(); + g_signal_connect(priv->sleep_monitor, NM_SLEEP_MONITOR_SLEEPING, G_CALLBACK(sleeping_cb), self); + + /* Listen for authorization changes */ + priv->auth_mgr = g_object_ref(nm_auth_manager_get()); + g_signal_connect(priv->auth_mgr, + NM_AUTH_MANAGER_SIGNAL_CHANGED, + G_CALLBACK(auth_mgr_changed), + self); + + /* Monitor the firmware directory */ + if (strlen(KERNEL_FIRMWARE_DIR)) { + file = g_file_new_for_path(KERNEL_FIRMWARE_DIR "/"); + priv->fw_monitor = g_file_monitor_directory(file, G_FILE_MONITOR_NONE, NULL, NULL); + g_object_unref(file); + } + + if (priv->fw_monitor) { + g_signal_connect(priv->fw_monitor, "changed", G_CALLBACK(firmware_dir_changed), self); + _LOGI(LOGD_CORE, "monitoring kernel firmware directory '%s'.", KERNEL_FIRMWARE_DIR); + } else { + _LOGW(LOGD_CORE, "failed to monitor kernel firmware directory '%s'.", KERNEL_FIRMWARE_DIR); + } + + /* Update timestamps in active connections */ + priv->timestamp_update_id = + g_timeout_add_seconds(300, + (GSourceFunc) periodic_update_active_connection_timestamps, + self); + + priv->metered = NM_METERED_UNKNOWN; + priv->sleep_devices = g_hash_table_new(nm_direct_hash, NULL); +} + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMManager * self = NM_MANAGER(object); + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + NMConfigData * config_data; + const NMGlobalDnsConfig *dns_config; + const char * type; + const char * path; + NMActiveConnection * ac; + GPtrArray * ptrarr; + + switch (prop_id) { + case PROP_VERSION: + g_value_set_string(value, VERSION); + break; + case PROP_CAPABILITIES: + g_value_set_variant(value, + g_variant_new_fixed_array(G_VARIANT_TYPE("u"), + priv->capabilities->data, + priv->capabilities->len, + sizeof(guint32))); + break; + case PROP_STATE: + g_value_set_uint(value, priv->state); + break; + case PROP_STARTUP: + g_value_set_boolean(value, priv->startup); + break; + case PROP_NETWORKING_ENABLED: + g_value_set_boolean(value, priv->net_enabled); + break; + case PROP_WIRELESS_ENABLED: + g_value_set_boolean(value, radio_enabled_for_type(self, RFKILL_TYPE_WLAN, TRUE)); + break; + case PROP_WIRELESS_HARDWARE_ENABLED: + g_value_set_boolean(value, priv->radio_states[RFKILL_TYPE_WLAN].hw_enabled); + break; + case PROP_WWAN_ENABLED: + g_value_set_boolean(value, radio_enabled_for_type(self, RFKILL_TYPE_WWAN, TRUE)); + break; + case PROP_WWAN_HARDWARE_ENABLED: + g_value_set_boolean(value, priv->radio_states[RFKILL_TYPE_WWAN].hw_enabled); + break; + case PROP_WIMAX_ENABLED: + g_value_set_boolean(value, FALSE); + break; + case PROP_WIMAX_HARDWARE_ENABLED: + g_value_set_boolean(value, FALSE); + break; + case PROP_ACTIVE_CONNECTIONS: + ptrarr = g_ptr_array_new(); + c_list_for_each_entry_prev (ac, + &priv->active_connections_lst_head, + active_connections_lst) { + path = nm_dbus_object_get_path(NM_DBUS_OBJECT(ac)); + if (path) + g_ptr_array_add(ptrarr, g_strdup(path)); + } + g_ptr_array_add(ptrarr, NULL); + g_value_take_boxed(value, g_ptr_array_free(ptrarr, FALSE)); + break; + case PROP_CONNECTIVITY: + g_value_set_uint(value, priv->connectivity_state); + break; + case PROP_CONNECTIVITY_CHECK_AVAILABLE: + config_data = nm_config_get_data(priv->config); + g_value_set_boolean(value, nm_config_data_get_connectivity_uri(config_data) != NULL); + break; + case PROP_CONNECTIVITY_CHECK_ENABLED: + g_value_set_boolean(value, concheck_enabled(self, NULL)); + break; + case PROP_CONNECTIVITY_CHECK_URI: + config_data = nm_config_get_data(priv->config); + g_value_set_string(value, nm_config_data_get_connectivity_uri(config_data)); + break; + case PROP_PRIMARY_CONNECTION: + nm_dbus_utils_g_value_set_object_path(value, priv->primary_connection); + break; + case PROP_PRIMARY_CONNECTION_TYPE: + type = NULL; + if (priv->primary_connection) { + NMConnection *con; + + con = nm_active_connection_get_applied_connection(priv->primary_connection); + if (con) + type = nm_connection_get_connection_type(con); + } + g_value_set_string(value, type ?: ""); + break; + case PROP_ACTIVATING_CONNECTION: + nm_dbus_utils_g_value_set_object_path(value, priv->activating_connection); + break; + case PROP_SLEEPING: + g_value_set_boolean(value, priv->sleeping); + break; + case PROP_DEVICES: + g_value_take_boxed(value, nm_utils_strv_make_deep_copied(_get_devices_paths(self, FALSE))); + break; + case PROP_METERED: + g_value_set_uint(value, priv->metered); + break; + case PROP_GLOBAL_DNS_CONFIGURATION: + config_data = nm_config_get_data(priv->config); + dns_config = nm_config_data_get_global_dns_config(config_data); + nm_global_dns_config_to_dbus(dns_config, value); + break; + case PROP_ALL_DEVICES: + g_value_take_boxed(value, nm_utils_strv_make_deep_copied(_get_devices_paths(self, TRUE))); + break; + case PROP_CHECKPOINTS: + g_value_take_boxed( + value, + priv->checkpoint_mgr ? nm_utils_strv_make_deep_copied( + nm_checkpoint_manager_get_checkpoint_paths(priv->checkpoint_mgr, NULL)) + : NULL); + 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) +{ + NMManager * self = NM_MANAGER(object); + NMManagerPrivate * priv = NM_MANAGER_GET_PRIVATE(self); + NMGlobalDnsConfig *dns_config; + GError * error = NULL; + + switch (prop_id) { + case PROP_WIRELESS_ENABLED: + manager_radio_user_toggled(NM_MANAGER(object), + &priv->radio_states[RFKILL_TYPE_WLAN], + g_value_get_boolean(value)); + break; + case PROP_WWAN_ENABLED: + manager_radio_user_toggled(NM_MANAGER(object), + &priv->radio_states[RFKILL_TYPE_WWAN], + g_value_get_boolean(value)); + break; + case PROP_WIMAX_ENABLED: + /* WIMAX is deprecated. This does nothing. */ + break; + case PROP_CONNECTIVITY_CHECK_ENABLED: + nm_config_set_connectivity_check_enabled(priv->config, g_value_get_boolean(value)); + break; + case PROP_GLOBAL_DNS_CONFIGURATION: + dns_config = nm_global_dns_config_from_dbus(value, &error); + if (!error) + nm_config_set_global_dns(priv->config, dns_config, &error); + + nm_global_dns_config_free(dns_config); + + if (error) { + _LOGD(LOGD_CORE, "set global DNS failed with error: %s", error->message); + g_error_free(error); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +_deinit_device_factory(NMDeviceFactory *factory, gpointer user_data) +{ + g_signal_handlers_disconnect_matched(factory, + G_SIGNAL_MATCH_DATA, + 0, + 0, + NULL, + NULL, + NM_MANAGER(user_data)); +} + +static void +dispose(GObject *object) +{ + NMManager * self = NM_MANAGER(object); + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self); + CList * iter; + + nm_assert(c_list_is_empty(&priv->async_op_lst_head)); + + g_signal_handlers_disconnect_by_func(priv->platform, G_CALLBACK(platform_link_cb), self); + while ((iter = c_list_first(&priv->link_cb_lst))) { + PlatformLinkCbData *data = c_list_entry(iter, PlatformLinkCbData, lst); + + g_source_remove(data->idle_id); + c_list_unlink_stale(&data->lst); + g_slice_free(PlatformLinkCbData, data); + } + + while ((iter = c_list_first(&priv->auth_lst_head))) + nm_auth_chain_destroy(nm_auth_chain_parent_lst_entry(iter)); + + nm_clear_g_source(&priv->devices_inited_id); + + nm_clear_pointer(&priv->checkpoint_mgr, nm_checkpoint_manager_free); + + if (priv->concheck_mgr) { + g_signal_handlers_disconnect_by_func(priv->concheck_mgr, + G_CALLBACK(concheck_config_changed_cb), + self); + g_clear_object(&priv->concheck_mgr); + } + + if (priv->auth_mgr) { + g_signal_handlers_disconnect_by_func(priv->auth_mgr, G_CALLBACK(auth_mgr_changed), self); + g_clear_object(&priv->auth_mgr); + } + + nm_assert(c_list_is_empty(&priv->devices_lst_head)); + + nm_clear_g_source(&priv->ac_cleanup_id); + + while ((iter = c_list_first(&priv->active_connections_lst_head))) + active_connection_remove(self, + c_list_entry(iter, NMActiveConnection, active_connections_lst)); + + nm_assert(c_list_is_empty(&priv->active_connections_lst_head)); + g_clear_object(&priv->primary_connection); + g_clear_object(&priv->activating_connection); + + if (priv->config) { + g_signal_handlers_disconnect_by_func(priv->config, _config_changed_cb, self); + g_clear_object(&priv->config); + } + + if (priv->policy) { + g_signal_handlers_disconnect_by_func(priv->policy, policy_default_ac_changed, self); + g_signal_handlers_disconnect_by_func(priv->policy, policy_activating_ac_changed, self); + g_clear_object(&priv->policy); + } + + if (priv->settings) { + g_signal_handlers_disconnect_by_func(priv->settings, + settings_startup_complete_changed, + self); + g_signal_handlers_disconnect_by_func(priv->settings, + system_unmanaged_devices_changed_cb, + self); + g_signal_handlers_disconnect_by_func(priv->settings, connection_added_cb, self); + g_signal_handlers_disconnect_by_func(priv->settings, connection_updated_cb, self); + g_signal_handlers_disconnect_by_func(priv->settings, connection_flags_changed, self); + g_clear_object(&priv->settings); + } + + if (priv->hostname_manager) { + g_signal_handlers_disconnect_by_func(priv->hostname_manager, hostname_changed_cb, self); + g_clear_object(&priv->hostname_manager); + } + + g_clear_object(&priv->vpn_manager); + + sleep_devices_clear(self); + nm_clear_pointer(&priv->sleep_devices, g_hash_table_unref); + + if (priv->sleep_monitor) { + g_signal_handlers_disconnect_by_func(priv->sleep_monitor, sleeping_cb, self); + g_clear_object(&priv->sleep_monitor); + } + + if (priv->fw_monitor) { + g_signal_handlers_disconnect_by_func(priv->fw_monitor, firmware_dir_changed, self); + + nm_clear_g_source(&priv->fw_changed_id); + + g_file_monitor_cancel(priv->fw_monitor); + g_clear_object(&priv->fw_monitor); + } + + if (priv->rfkill_mgr) { + g_signal_handlers_disconnect_by_func(priv->rfkill_mgr, + rfkill_manager_rfkill_changed_cb, + self); + g_clear_object(&priv->rfkill_mgr); + } + + nm_clear_g_source(&priv->delete_volatile_connection_idle_id); + _delete_volatile_connection_all(self, FALSE); + nm_assert(!priv->delete_volatile_connection_idle_id); + nm_assert(c_list_is_empty(&priv->delete_volatile_connection_lst_head)); + + nm_device_factory_manager_for_each_factory(_deinit_device_factory, self); + + nm_clear_g_source(&priv->timestamp_update_id); + + nm_clear_pointer(&priv->device_route_metrics, g_hash_table_destroy); + + G_OBJECT_CLASS(nm_manager_parent_class)->dispose(object); +} + +static void +finalize(GObject *object) +{ + NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(object); + + g_array_free(priv->capabilities, TRUE); + + G_OBJECT_CLASS(nm_manager_parent_class)->finalize(object); + + g_object_unref(priv->platform); +} + +static const GDBusSignalInfo signal_info_check_permissions = + NM_DEFINE_GDBUS_SIGNAL_INFO_INIT("CheckPermissions", ); + +static const GDBusSignalInfo signal_info_state_changed = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT( + "StateChanged", + .args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("state", "u"), ), ); + +static const GDBusSignalInfo signal_info_device_added = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT( + "DeviceAdded", + .args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("device_path", "o"), ), ); + +static const GDBusSignalInfo signal_info_device_removed = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT( + "DeviceRemoved", + .args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("device_path", "o"), ), ); + +static const NMDBusInterfaceInfoExtended interface_info_manager = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE, + .methods = NM_DEFINE_GDBUS_METHOD_INFOS( + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT("Reload", + .in_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("flags", "u"), ), ), + .handle = impl_manager_reload, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "GetDevices", + .out_args = + NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("devices", "ao"), ), ), + .handle = impl_manager_get_devices, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "GetAllDevices", + .out_args = + NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("devices", "ao"), ), ), + .handle = impl_manager_get_all_devices, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "GetDeviceByIpIface", + .in_args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("iface", "s"), ), + .out_args = + NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("device", "o"), ), ), + .handle = impl_manager_get_device_by_ip_iface, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "ActivateConnection", + .in_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("connection", "o"), + NM_DEFINE_GDBUS_ARG_INFO("device", "o"), + NM_DEFINE_GDBUS_ARG_INFO("specific_object", "o"), ), + .out_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("active_connection", "o"), ), ), + .handle = impl_manager_activate_connection, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "AddAndActivateConnection", + .in_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("connection", "a{sa{sv}}"), + NM_DEFINE_GDBUS_ARG_INFO("device", "o"), + NM_DEFINE_GDBUS_ARG_INFO("specific_object", "o"), ), + .out_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("path", "o"), + NM_DEFINE_GDBUS_ARG_INFO("active_connection", "o"), ), ), + .handle = impl_manager_add_and_activate_connection, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "AddAndActivateConnection2", + .in_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("connection", "a{sa{sv}}"), + NM_DEFINE_GDBUS_ARG_INFO("device", "o"), + NM_DEFINE_GDBUS_ARG_INFO("specific_object", "o"), + NM_DEFINE_GDBUS_ARG_INFO("options", "a{sv}"), ), + .out_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("path", "o"), + NM_DEFINE_GDBUS_ARG_INFO("active_connection", "o"), + NM_DEFINE_GDBUS_ARG_INFO("result", "a{sv}"), ), ), + .handle = impl_manager_add_and_activate_connection, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "DeactivateConnection", + .in_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("active_connection", "o"), ), ), + .handle = impl_manager_deactivate_connection, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT("Sleep", + .in_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("sleep", "b"), ), ), + .handle = impl_manager_sleep, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT("Enable", + .in_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("enable", "b"), ), ), + .handle = impl_manager_enable, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "GetPermissions", + .out_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("permissions", "a{ss}"), ), ), + .handle = impl_manager_get_permissions, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT("SetLogging", + .in_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("level", "s"), + NM_DEFINE_GDBUS_ARG_INFO("domains", "s"), ), ), + .handle = impl_manager_set_logging, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT("GetLogging", + .out_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("level", "s"), + NM_DEFINE_GDBUS_ARG_INFO("domains", "s"), ), ), + .handle = impl_manager_get_logging, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "CheckConnectivity", + .out_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("connectivity", "u"), ), ), + .handle = impl_manager_check_connectivity, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT("state", + .out_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("state", "u"), ), ), + .handle = impl_manager_state, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "CheckpointCreate", + .in_args = + NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("devices", "ao"), + NM_DEFINE_GDBUS_ARG_INFO("rollback_timeout", "u"), + NM_DEFINE_GDBUS_ARG_INFO("flags", "u"), ), + .out_args = + NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("checkpoint", "o"), ), ), + .handle = impl_manager_checkpoint_create, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "CheckpointDestroy", + .in_args = + NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("checkpoint", "o"), ), ), + .handle = impl_manager_checkpoint_destroy, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "CheckpointRollback", + .in_args = + NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("checkpoint", "o"), ), + .out_args = + NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("result", "a{su}"), ), ), + .handle = impl_manager_checkpoint_rollback, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "CheckpointAdjustRollbackTimeout", + .in_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("checkpoint", "o"), + NM_DEFINE_GDBUS_ARG_INFO("add_timeout", "u"), ), ), + .handle = impl_manager_checkpoint_adjust_rollback_timeout, ), ), + .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, + &signal_info_check_permissions, + &signal_info_state_changed, + &signal_info_device_added, + &signal_info_device_removed, ), + .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Devices", "ao", NM_MANAGER_DEVICES), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("AllDevices", + "ao", + NM_MANAGER_ALL_DEVICES), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Checkpoints", + "ao", + NM_MANAGER_CHECKPOINTS), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("NetworkingEnabled", + "b", + NM_MANAGER_NETWORKING_ENABLED), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE_L( + "WirelessEnabled", + "b", + NM_MANAGER_WIRELESS_ENABLED, + NM_AUTH_PERMISSION_ENABLE_DISABLE_WIFI, + NM_AUDIT_OP_RADIO_CONTROL), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("WirelessHardwareEnabled", + "b", + NM_MANAGER_WIRELESS_HARDWARE_ENABLED), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE_L( + "WwanEnabled", + "b", + NM_MANAGER_WWAN_ENABLED, + NM_AUTH_PERMISSION_ENABLE_DISABLE_WWAN, + NM_AUDIT_OP_RADIO_CONTROL), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("WwanHardwareEnabled", + "b", + NM_MANAGER_WWAN_HARDWARE_ENABLED), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE_L( + "WimaxEnabled", + "b", + NM_MANAGER_WIMAX_ENABLED, + NM_AUTH_PERMISSION_ENABLE_DISABLE_WIMAX, + NM_AUDIT_OP_RADIO_CONTROL), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("WimaxHardwareEnabled", + "b", + NM_MANAGER_WIMAX_HARDWARE_ENABLED), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("ActiveConnections", + "ao", + NM_MANAGER_ACTIVE_CONNECTIONS), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("PrimaryConnection", + "o", + NM_MANAGER_PRIMARY_CONNECTION), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("PrimaryConnectionType", + "s", + NM_MANAGER_PRIMARY_CONNECTION_TYPE), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Metered", "u", NM_MANAGER_METERED), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("ActivatingConnection", + "o", + NM_MANAGER_ACTIVATING_CONNECTION), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Startup", "b", NM_MANAGER_STARTUP), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Version", "s", NM_MANAGER_VERSION), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Capabilities", + "au", + NM_MANAGER_CAPABILITIES), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("State", "u", NM_MANAGER_STATE), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Connectivity", + "u", + NM_MANAGER_CONNECTIVITY), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L( + "ConnectivityCheckAvailable", + "b", + NM_MANAGER_CONNECTIVITY_CHECK_AVAILABLE), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE_L( + "ConnectivityCheckEnabled", + "b", + NM_MANAGER_CONNECTIVITY_CHECK_ENABLED, + NM_AUTH_PERMISSION_ENABLE_DISABLE_CONNECTIVITY_CHECK, + NM_AUDIT_OP_NET_CONTROL), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("ConnectivityCheckUri", + "s", + NM_MANAGER_CONNECTIVITY_CHECK_URI), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE_L( + "GlobalDnsConfiguration", + "a{sv}", + NM_MANAGER_GLOBAL_DNS_CONFIGURATION, + NM_AUTH_PERMISSION_SETTINGS_MODIFY_GLOBAL_DNS, + NM_AUDIT_OP_NET_CONTROL), ), ), + .legacy_property_changed = TRUE, +}; + +static void +nm_manager_class_init(NMManagerClass *manager_class) +{ + GObjectClass * object_class = G_OBJECT_CLASS(manager_class); + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(manager_class); + + dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_STATIC(NM_DBUS_PATH); + dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_manager); + + object_class->constructed = constructed; + object_class->set_property = set_property; + object_class->get_property = get_property; + object_class->dispose = dispose; + object_class->finalize = finalize; + + obj_properties[PROP_VERSION] = g_param_spec_string(NM_MANAGER_VERSION, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_CAPABILITIES] = + g_param_spec_variant(NM_MANAGER_CAPABILITIES, + "", + "", + G_VARIANT_TYPE("au"), + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_STATE] = g_param_spec_uint(NM_MANAGER_STATE, + "", + "", + 0, + NM_STATE_DISCONNECTED, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_STARTUP] = g_param_spec_boolean(NM_MANAGER_STARTUP, + "", + "", + TRUE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_NETWORKING_ENABLED] = + g_param_spec_boolean(NM_MANAGER_NETWORKING_ENABLED, + "", + "", + TRUE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_WIRELESS_ENABLED] = + g_param_spec_boolean(NM_MANAGER_WIRELESS_ENABLED, + "", + "", + TRUE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_WIRELESS_HARDWARE_ENABLED] = + g_param_spec_boolean(NM_MANAGER_WIRELESS_HARDWARE_ENABLED, + "", + "", + TRUE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_WWAN_ENABLED] = + g_param_spec_boolean(NM_MANAGER_WWAN_ENABLED, + "", + "", + TRUE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_WWAN_HARDWARE_ENABLED] = + g_param_spec_boolean(NM_MANAGER_WWAN_HARDWARE_ENABLED, + "", + "", + TRUE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_WIMAX_ENABLED] = + g_param_spec_boolean(NM_MANAGER_WIMAX_ENABLED, + "", + "", + TRUE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_WIMAX_HARDWARE_ENABLED] = + g_param_spec_boolean(NM_MANAGER_WIMAX_HARDWARE_ENABLED, + "", + "", + TRUE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_ACTIVE_CONNECTIONS] = + g_param_spec_boxed(NM_MANAGER_ACTIVE_CONNECTIONS, + "", + "", + G_TYPE_STRV, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_CONNECTIVITY] = + g_param_spec_uint(NM_MANAGER_CONNECTIVITY, + "", + "", + NM_CONNECTIVITY_UNKNOWN, + NM_CONNECTIVITY_FULL, + NM_CONNECTIVITY_UNKNOWN, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_CONNECTIVITY_CHECK_AVAILABLE] = + g_param_spec_boolean(NM_MANAGER_CONNECTIVITY_CHECK_AVAILABLE, + "", + "", + TRUE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_CONNECTIVITY_CHECK_ENABLED] = + g_param_spec_boolean(NM_MANAGER_CONNECTIVITY_CHECK_ENABLED, + "", + "", + TRUE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_CONNECTIVITY_CHECK_URI] = + g_param_spec_string(NM_MANAGER_CONNECTIVITY_CHECK_URI, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_PRIMARY_CONNECTION] = + g_param_spec_string(NM_MANAGER_PRIMARY_CONNECTION, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_PRIMARY_CONNECTION_TYPE] = + g_param_spec_string(NM_MANAGER_PRIMARY_CONNECTION_TYPE, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_ACTIVATING_CONNECTION] = + g_param_spec_string(NM_MANAGER_ACTIVATING_CONNECTION, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + /* Sleeping is not exported over D-Bus */ + obj_properties[PROP_SLEEPING] = g_param_spec_boolean(NM_MANAGER_SLEEPING, + "", + "", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_DEVICES] = g_param_spec_boxed(NM_MANAGER_DEVICES, + "", + "", + G_TYPE_STRV, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMManager:metered: + * + * Whether the connectivity is metered. + * + * Since: 1.2 + **/ + obj_properties[PROP_METERED] = g_param_spec_uint(NM_MANAGER_METERED, + "", + "", + 0, + G_MAXUINT32, + NM_METERED_UNKNOWN, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMManager:global-dns-configuration: + * + * The global DNS configuration. + * + * Since: 1.2 + **/ + obj_properties[PROP_GLOBAL_DNS_CONFIGURATION] = + g_param_spec_variant(NM_MANAGER_GLOBAL_DNS_CONFIGURATION, + "", + "", + G_VARIANT_TYPE("a{sv}"), + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMManager:all-devices: + * + * All devices, including those that are not realized. + * + * Since: 1.2 + **/ + obj_properties[PROP_ALL_DEVICES] = + g_param_spec_boxed(NM_MANAGER_ALL_DEVICES, + "", + "", + G_TYPE_STRV, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_CHECKPOINTS] = + g_param_spec_boxed(NM_MANAGER_CHECKPOINTS, + "", + "", + G_TYPE_STRV, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + /* signals */ + + /* emitted only for realized devices */ + signals[DEVICE_ADDED] = g_signal_new(NM_MANAGER_DEVICE_ADDED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + NM_TYPE_DEVICE); + + /* Emitted for both realized devices and placeholder devices */ + signals[INTERNAL_DEVICE_ADDED] = g_signal_new(NM_MANAGER_INTERNAL_DEVICE_ADDED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + G_TYPE_OBJECT); + + /* emitted only for realized devices when a device + * becomes unrealized or removed */ + signals[DEVICE_REMOVED] = g_signal_new(NM_MANAGER_DEVICE_REMOVED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + NM_TYPE_DEVICE); + + /* Emitted for both realized devices and placeholder devices */ + signals[INTERNAL_DEVICE_REMOVED] = g_signal_new(NM_MANAGER_INTERNAL_DEVICE_REMOVED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + G_TYPE_OBJECT); + + signals[ACTIVE_CONNECTION_ADDED] = g_signal_new(NM_MANAGER_ACTIVE_CONNECTION_ADDED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + NM_TYPE_ACTIVE_CONNECTION); + + signals[ACTIVE_CONNECTION_REMOVED] = g_signal_new(NM_MANAGER_ACTIVE_CONNECTION_REMOVED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + NM_TYPE_ACTIVE_CONNECTION); + + signals[CONFIGURE_QUIT] = g_signal_new(NM_MANAGER_CONFIGURE_QUIT, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 0); + + signals[DEVICE_IFINDEX_CHANGED] = g_signal_new(NM_MANAGER_DEVICE_IFINDEX_CHANGED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + NM_TYPE_DEVICE); +} |