diff --git a/introspection/nm-ip4-config.xml b/introspection/nm-ip4-config.xml index 57ce29529d..70d3e1ce43 100644 --- a/introspection/nm-ip4-config.xml +++ b/introspection/nm-ip4-config.xml @@ -3,19 +3,26 @@ - Tuples of IPv4 address/prefix/gateway. + Array of tuples of IPv4 address/prefix/gateway. All 3 + elements of each tuple are in network byte order. Essentially: + [(addr, prefix, gateway), (addr, prefix, gateway), ...] + The nameservers in use. - The Windows Internet Name Service servers associated with the connection. + The Windows Internet Name Service servers associated with the connection. Each address is in network byte order. A list of domains this address belongs to. - Tuples of IPv4 route/prefix/next-hop/metric. + Tuples of IPv4 route/prefix/next-hop/metric. All 4 elements + of each tuple are in network byte order. 'route' and 'next hop' are IPv4 + addresses, while prefix and metric are simple unsigned integers. Essentially: + [(route, prefix, next-hop, metric), (route, prefix, next-hop, metric), ...] + diff --git a/introspection/nm-vpn-connection.xml b/introspection/nm-vpn-connection.xml index 233d8b0866..3a47cdf1b2 100644 --- a/introspection/nm-vpn-connection.xml +++ b/introspection/nm-vpn-connection.xml @@ -20,6 +20,17 @@ Whether this active connection is the default connection, i.e. whether it currently owns the default route. + + Whether this active connection is also a VPN connection. + + + + + + A dictionary mapping property names to variant boxed values + + + diff --git a/libnm-util/nm-setting-vpn.c b/libnm-util/nm-setting-vpn.c index def7432216..279fea8ebf 100644 --- a/libnm-util/nm-setting-vpn.c +++ b/libnm-util/nm-setting-vpn.c @@ -133,6 +133,10 @@ nm_setting_vpn_add_data_item (NMSettingVPN *setting, const char *item) { g_return_if_fail (NM_IS_SETTING_VPN (setting)); + g_return_if_fail (key != NULL); + g_return_if_fail (strlen (key) > 0); + g_return_if_fail (item != NULL); + g_return_if_fail (strlen (item) > 0); g_hash_table_insert (NM_SETTING_VPN_GET_PRIVATE (setting)->data, g_strdup (key), g_strdup (item)); @@ -171,6 +175,10 @@ nm_setting_vpn_add_secret (NMSettingVPN *setting, const char *secret) { g_return_if_fail (NM_IS_SETTING_VPN (setting)); + g_return_if_fail (key != NULL); + g_return_if_fail (strlen (key) > 0); + g_return_if_fail (secret != NULL); + g_return_if_fail (strlen (secret) > 0); g_hash_table_insert (NM_SETTING_VPN_GET_PRIVATE (setting)->secrets, g_strdup (key), g_strdup (secret)); diff --git a/libnm-util/tests/Makefile.am b/libnm-util/tests/Makefile.am index d0e3477df3..2d78b972d4 100644 --- a/libnm-util/tests/Makefile.am +++ b/libnm-util/tests/Makefile.am @@ -4,7 +4,11 @@ INCLUDES = \ -I$(top_srcdir)/include \ -I$(top_srcdir)/libnm-util -noinst_PROGRAMS = test-settings-defaults test-crypto test-need-secrets +noinst_PROGRAMS = \ + test-settings-defaults \ + test-crypto \ + test-need-secrets \ + test-general test_settings_defaults_SOURCES = \ test-settings-defaults.c @@ -42,11 +46,24 @@ test_need_secrets_LDADD = \ $(GLIB_LIBS) \ $(DBUS_LIBS) +test_general_SOURCES = \ + test-general.c + +test_general_CPPFLAGS = \ + $(GLIB_CFLAGS) \ + $(DBUS_CFLAGS) + +test_general_LDADD = \ + $(top_builddir)/libnm-util/libnm-util.la \ + $(GLIB_LIBS) \ + $(DBUS_LIBS) + if WITH_TESTS check-local: test-settings-defaults test-crypto test-need-secrets $(abs_builddir)/test-settings-defaults $(abs_builddir)/test-need-secrets + $(abs_builddir)/test-general # Cert with 8 bytes of tail padding $(abs_builddir)/test-crypto \ diff --git a/libnm-util/tests/test-general.c b/libnm-util/tests/test-general.c new file mode 100644 index 0000000000..d00ec994e1 --- /dev/null +++ b/libnm-util/tests/test-general.c @@ -0,0 +1,151 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2008 - 2009 Red Hat, Inc. + * + */ + +#include +#include +#include + +#include "nm-test-helpers.h" +#include + +#include "nm-setting-connection.h" +#include "nm-setting-vpn.h" + + +static void +vpn_check_func (const char *key, const char *value, gpointer user_data) +{ + const char *test = user_data; + + if (!strcmp (key, "foobar1")) { + ASSERT (strcmp (value, "blahblah1") == 0, + test, "unexpected vpn item '%s' / '%s'", key, value); + return; + } + + if (!strcmp (key, "foobar2")) { + ASSERT (strcmp (value, "blahblah2") == 0, + test, "unexpected vpn item '%s' / '%s'", key, value); + return; + } + + if (!strcmp (key, "foobar3")) { + ASSERT (strcmp (value, "blahblah3") == 0, + test, "unexpected vpn item '%s' / '%s'", key, value); + return; + } + + if (!strcmp (key, "foobar4")) { + ASSERT (strcmp (value, "blahblah4") == 0, + test, "unexpected vpn item '%s' / '%s'", key, value); + return; + } + + ASSERT (FALSE, test, "unexpected vpn item '%s'", key); +} + +static void +vpn_check_empty_func (const char *key, const char *value, gpointer user_data) +{ + const char *test = user_data; + + /* We don't expect any values */ + ASSERT (FALSE, test, "unexpected vpn item '%s'", key); +} + +static void +test_setting_vpn_items (void) +{ + NMSettingVPN *s_vpn; + + s_vpn = (NMSettingVPN *) nm_setting_vpn_new (); + ASSERT (s_vpn != NULL, + "vpn-items", + "error creating vpn setting"); + + nm_setting_vpn_add_data_item (s_vpn, "foobar1", "blahblah1"); + nm_setting_vpn_add_data_item (s_vpn, "foobar2", "blahblah2"); + nm_setting_vpn_add_data_item (s_vpn, "foobar3", "blahblah3"); + nm_setting_vpn_add_data_item (s_vpn, "foobar4", "blahblah4"); + + /* Ensure that added values are all present */ + nm_setting_vpn_foreach_data_item (s_vpn, vpn_check_func, "vpn-data"); + nm_setting_vpn_remove_data_item (s_vpn, "foobar1"); + nm_setting_vpn_remove_data_item (s_vpn, "foobar2"); + nm_setting_vpn_remove_data_item (s_vpn, "foobar3"); + nm_setting_vpn_remove_data_item (s_vpn, "foobar4"); + + nm_setting_vpn_add_secret (s_vpn, "foobar1", "blahblah1"); + nm_setting_vpn_add_secret (s_vpn, "foobar2", "blahblah2"); + nm_setting_vpn_add_secret (s_vpn, "foobar3", "blahblah3"); + nm_setting_vpn_add_secret (s_vpn, "foobar4", "blahblah4"); + + /* Ensure that added values are all present */ + nm_setting_vpn_foreach_secret (s_vpn, vpn_check_func, "vpn-secrets"); + nm_setting_vpn_remove_secret (s_vpn, "foobar1"); + nm_setting_vpn_remove_secret (s_vpn, "foobar2"); + nm_setting_vpn_remove_secret (s_vpn, "foobar3"); + nm_setting_vpn_remove_secret (s_vpn, "foobar4"); + + /* Try to add some blank values and make sure they are rejected */ + nm_setting_vpn_add_data_item (s_vpn, NULL, NULL); + nm_setting_vpn_add_data_item (s_vpn, "", ""); + nm_setting_vpn_add_data_item (s_vpn, "foobar1", NULL); + nm_setting_vpn_add_data_item (s_vpn, "foobar1", ""); + nm_setting_vpn_add_data_item (s_vpn, NULL, "blahblah1"); + nm_setting_vpn_add_data_item (s_vpn, "", "blahblah1"); + + nm_setting_vpn_foreach_data_item (s_vpn, vpn_check_empty_func, "vpn-data-empty"); + + /* Try to add some blank secrets and make sure they are rejected */ + nm_setting_vpn_add_secret (s_vpn, NULL, NULL); + nm_setting_vpn_add_secret (s_vpn, "", ""); + nm_setting_vpn_add_secret (s_vpn, "foobar1", NULL); + nm_setting_vpn_add_secret (s_vpn, "foobar1", ""); + nm_setting_vpn_add_secret (s_vpn, NULL, "blahblah1"); + nm_setting_vpn_add_secret (s_vpn, "", "blahblah1"); + + nm_setting_vpn_foreach_secret (s_vpn, vpn_check_empty_func, "vpn-secrets-empty"); + + g_object_unref (s_vpn); +} + +int main (int argc, char **argv) +{ + GError *error = NULL; + DBusGConnection *bus; + char *base; + + g_type_init (); + bus = dbus_g_bus_get (DBUS_BUS_SESSION, NULL); + + if (!nm_utils_init (&error)) + FAIL ("nm-utils-init", "failed to initialize libnm-util: %s", error->message); + + /* The tests */ + test_setting_vpn_items (); + + base = g_path_get_basename (argv[0]); + fprintf (stdout, "%s: SUCCESS\n", base); + g_free (base); + return 0; +} + diff --git a/src/NetworkManager.c b/src/NetworkManager.c index f14c6cbf07..a79d5c86ec 100644 --- a/src/NetworkManager.c +++ b/src/NetworkManager.c @@ -56,6 +56,7 @@ #define NM_DEFAULT_PID_FILE LOCALSTATEDIR"/run/NetworkManager.pid" #define NM_DEFAULT_SYSTEM_CONF_FILE SYSCONFDIR"/NetworkManager/nm-system-settings.conf" +#define NM_DEFAULT_SYSTEM_STATE_FILE LOCALSTATEDIR"/lib/NetworkManager/NetworkManager.state" /* * Globals @@ -227,22 +228,77 @@ setup_signals (void) sigaction (SIGUSR1, &action, NULL); } -static void +static gboolean write_pidfile (const char *pidfile) { char pid[16]; int fd; + gboolean success = FALSE; - if ((fd = open (pidfile, O_CREAT|O_WRONLY|O_TRUNC, 00644)) < 0) - { + if ((fd = open (pidfile, O_CREAT|O_WRONLY|O_TRUNC, 00644)) < 0) { nm_warning ("Opening %s failed: %s", pidfile, strerror (errno)); - return; + return FALSE; } + snprintf (pid, sizeof (pid), "%d", getpid ()); if (write (fd, pid, strlen (pid)) < 0) nm_warning ("Writing to %s failed: %s", pidfile, strerror (errno)); + else + success = TRUE; + if (close (fd)) nm_warning ("Closing %s failed: %s", pidfile, strerror (errno)); + + return success; +} + +/* Check whether the pidfile already exists and contains PID of a running NetworkManager + * Returns: FALSE - specified pidfile doesn't exist or doesn't contain PID of a running NM process + * TRUE - specified pidfile already exists and contains PID of a running NM process + */ +static gboolean +check_pidfile (const char *pidfile) +{ + char *contents = NULL; + gsize len = 0; + glong pid; + char *proc_cmdline = NULL; + gboolean nm_running = FALSE; + const char *process_name; + + if (!g_file_get_contents (pidfile, &contents, &len, NULL)) + return FALSE; + + if (len <= 0) + goto done; + + errno = 0; + pid = strtol (contents, NULL, 10); + if (pid <= 0 || pid > 65536 || errno) + goto done; + + g_free (contents); + proc_cmdline = g_strdup_printf ("/proc/%ld/cmdline", pid); + if (!g_file_get_contents (proc_cmdline, &contents, &len, NULL)) + goto done; + + process_name = strrchr (contents, '/'); + if (process_name) + process_name++; + else + process_name = contents; + if (strcmp (process_name, "NetworkManager") == 0) { + /* Check that the process exists */ + if (kill (pid, 0) == 0) { + g_warning ("NetworkManager is already running (pid %ld)", pid); + nm_running = TRUE; + } + } + +done: + g_free (proc_cmdline); + g_free (contents); + return nm_running; } static gboolean @@ -269,6 +325,94 @@ parse_config_file (const char *filename, char **plugins, GError **error) return TRUE; } +static gboolean +parse_state_file (const char *filename, + gboolean *net_enabled, + gboolean *wifi_enabled, + GError **error) +{ + GKeyFile *state_file; + GError *tmp_error = NULL; + gboolean wifi, net; + + g_return_val_if_fail (net_enabled != NULL, FALSE); + g_return_val_if_fail (wifi_enabled != NULL, FALSE); + + state_file = g_key_file_new (); + if (!state_file) { + g_set_error (error, 0, 0, + "Not enough memory to load state file."); + return FALSE; + } + + g_key_file_set_list_separator (state_file, ','); + if (!g_key_file_load_from_file (state_file, filename, G_KEY_FILE_KEEP_COMMENTS, &tmp_error)) { + /* This is kinda ugly; create the file and directory if it doesn't + * exist yet. We can't rely on distros necessarily creating the + * /var/lib/NetworkManager for us since we have to ensure that + * users upgrading NM get this working too. + */ + if ( tmp_error->domain == G_FILE_ERROR + && tmp_error->code == G_FILE_ERROR_NOENT) { + char *data, *dirname; + gsize len = 0; + gboolean ret = FALSE; + + /* try to create the directory if it doesn't exist */ + dirname = g_path_get_dirname (filename); + errno = 0; + if (mkdir (dirname, 0755) != 0) { + if (errno != EEXIST) { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_ACCES, + "Error creating state directory %s: %d", dirname, errno); + g_free (dirname); + return FALSE; + } + } + g_free (dirname); + + /* Write out the initial state to the state file */ + g_key_file_set_boolean (state_file, "main", "NetworkingEnabled", *net_enabled); + g_key_file_set_boolean (state_file, "main", "WirelessEnabled", *wifi_enabled); + + data = g_key_file_to_data (state_file, &len, NULL); + if (data) + ret = g_file_set_contents (filename, data, len, error); + g_free (data); + + return ret; + } else { + g_set_error_literal (error, tmp_error->domain, tmp_error->code, tmp_error->message); + g_clear_error (&tmp_error); + } + + /* Otherwise, file probably corrupt or inaccessible */ + return FALSE; + } + + /* Reading state bits of NetworkManager; an error leaves the passed-in state + * value unchanged. + */ + net = g_key_file_get_boolean (state_file, "main", "NetworkingEnabled", &tmp_error); + if (tmp_error) + g_set_error_literal (error, tmp_error->domain, tmp_error->code, tmp_error->message); + else + *net_enabled = net; + g_clear_error (&tmp_error); + + wifi = g_key_file_get_boolean (state_file, "main", "WirelessEnabled", error); + if (tmp_error) { + g_clear_error (error); + g_set_error_literal (error, tmp_error->domain, tmp_error->code, tmp_error->message); + } else + *wifi_enabled = wifi; + g_clear_error (&tmp_error); + + g_key_file_free (state_file); + + return TRUE; +} + /* * main * @@ -281,6 +425,8 @@ main (int argc, char *argv[]) gboolean g_fatal_warnings = FALSE; char *pidfile = NULL, *user_pidfile = NULL; char *config = NULL, *plugins = NULL; + char *state_file = NM_DEFAULT_SYSTEM_STATE_FILE; + gboolean wifi_enabled = TRUE, net_enabled = TRUE; gboolean success; NMPolicy *policy = NULL; NMVPNManager *vpn_manager = NULL; @@ -289,11 +435,13 @@ main (int argc, char *argv[]) NMSupplicantManager *sup_mgr = NULL; NMDHCPManager *dhcp_mgr = NULL; GError *error = NULL; + gboolean wrote_pidfile = FALSE; GOptionEntry options[] = { { "no-daemon", 0, 0, G_OPTION_ARG_NONE, &become_daemon, "Don't become a daemon", NULL }, { "g-fatal-warnings", 0, 0, G_OPTION_ARG_NONE, &g_fatal_warnings, "Make all warnings fatal", NULL }, { "pid-file", 0, 0, G_OPTION_ARG_FILENAME, &user_pidfile, "Specify the location of a PID file", "filename" }, + { "state-file", 0, 0, G_OPTION_ARG_FILENAME, &state_file, "State file location", "/path/to/state.file" }, { "config", 0, 0, G_OPTION_ARG_FILENAME, &config, "Config file location", "/path/to/config.file" }, { "plugins", 0, 0, G_OPTION_ARG_STRING, &plugins, "List of plugins separated by ,", "plugin1,plugin2" }, {NULL} @@ -331,6 +479,12 @@ main (int argc, char *argv[]) exit (1); } + pidfile = g_strdup (user_pidfile ? user_pidfile : NM_DEFAULT_PID_FILE); + + /* check pid file */ + if (check_pidfile (pidfile)) + exit (1); + /* Parse the config file */ if (config) { if (!parse_config_file (config, &plugins, &error)) { @@ -352,7 +506,16 @@ main (int argc, char *argv[]) } } - pidfile = g_strdup (user_pidfile ? user_pidfile : NM_DEFAULT_PID_FILE); + g_clear_error (&error); + + /* Parse the state file */ + if (!parse_state_file (state_file, &net_enabled, &wifi_enabled, &error)) { + g_warning ("State file %s parsing failed: (%d) %s.", + state_file, + error ? error->code : -1, + (error && error->message) ? error->message : "unknown"); + /* Not a hard failure */ + } /* Tricky: become_daemon is FALSE by default, so unless it's TRUE because * of a CLI option, it'll become TRUE after this @@ -368,7 +531,8 @@ main (int argc, char *argv[]) saved_errno); exit (1); } - write_pidfile (pidfile); + if (write_pidfile (pidfile)) + wrote_pidfile = TRUE; } if (g_fatal_warnings) { @@ -419,7 +583,7 @@ main (int argc, char *argv[]) goto done; } - manager = nm_manager_get (config, plugins, &error); + manager = nm_manager_get (config, plugins, state_file, net_enabled, wifi_enabled, &error); if (manager == NULL) { nm_error ("Failed to initialize the network manager: %s", error && error->message ? error->message : "(unknown)"); @@ -490,7 +654,7 @@ done: nm_logging_shutdown (); - if (pidfile) + if (pidfile && wrote_pidfile) unlink (pidfile); g_free (pidfile); diff --git a/src/NetworkManagerPolicy.c b/src/NetworkManagerPolicy.c index cd10b493f5..5b860aae0a 100644 --- a/src/NetworkManagerPolicy.c +++ b/src/NetworkManagerPolicy.c @@ -42,8 +42,6 @@ #include "nm-vpn-manager.h" #include "nm-modem.h" -#define STATE_IN_ACTIVATION_PHASE(state) ((state > NM_DEVICE_STATE_DISCONNECTED) && (state < NM_DEVICE_STATE_ACTIVATED)) - typedef struct LookupThread LookupThread; typedef void (*LookupCallback) (LookupThread *thread, gpointer user_data); @@ -711,6 +709,24 @@ hostname_changed (NMManager *manager, GParamSpec *pspec, gpointer user_data) update_system_hostname ((NMPolicy *) user_data, NULL); } +static void +sleeping_changed (NMManager *manager, GParamSpec *pspec, gpointer user_data) +{ + gboolean sleeping = FALSE; + GSList *connections, *iter; + + g_object_get (G_OBJECT (manager), NM_MANAGER_SLEEPING, &sleeping, NULL); + + /* Clear the invalid flag on all connections so they'll get retried on wakeup */ + if (sleeping) { + connections = nm_manager_get_connections (manager, NM_CONNECTION_SCOPE_SYSTEM); + connections = g_slist_concat (connections, nm_manager_get_connections (manager, NM_CONNECTION_SCOPE_USER)); + for (iter = connections; iter; iter = g_slist_next (iter)) + g_object_set_data (G_OBJECT (iter->data), INVALID_TAG, NULL); + g_slist_free (connections); + } +} + static void schedule_activate_check (NMPolicy *policy, NMDevice *device, guint delay_seconds) { @@ -770,7 +786,7 @@ device_state_changed (NMDevice *device, /* Mark the connection invalid if it failed during activation so that * it doesn't get automatically chosen over and over and over again. */ - if (connection && STATE_IN_ACTIVATION_PHASE (old_state)) { + if (connection && IS_ACTIVATING_STATE (old_state)) { g_object_set_data (G_OBJECT (connection), INVALID_TAG, GUINT_TO_POINTER (TRUE)); nm_info ("Marking connection '%s' invalid.", get_connection_id (connection)); nm_connection_clear_secrets (connection); @@ -992,10 +1008,14 @@ nm_policy_new (NMManager *manager, NMVPNManager *vpn_manager) G_CALLBACK (global_state_changed), policy); policy->signal_ids = g_slist_append (policy->signal_ids, (gpointer) id); - id = g_signal_connect (manager, "notify::hostname", + id = g_signal_connect (manager, "notify::" NM_MANAGER_HOSTNAME, G_CALLBACK (hostname_changed), policy); policy->signal_ids = g_slist_append (policy->signal_ids, (gpointer) id); + id = g_signal_connect (manager, "notify::" NM_MANAGER_SLEEPING, + G_CALLBACK (sleeping_changed), policy); + policy->signal_ids = g_slist_append (policy->signal_ids, (gpointer) id); + id = g_signal_connect (manager, "device-added", G_CALLBACK (device_added), policy); policy->signal_ids = g_slist_append (policy->signal_ids, (gpointer) id); diff --git a/src/modem-manager/nm-modem.c b/src/modem-manager/nm-modem.c index a2b37fb0d8..a5ed6d8482 100644 --- a/src/modem-manager/nm-modem.c +++ b/src/modem-manager/nm-modem.c @@ -135,6 +135,7 @@ ppp_ip4_config (NMPPPManager *ppp_manager, guint32 good_dns1 = htonl (0x04020201); /* GTE nameserver */ guint32 bad_dns2 = htonl (0x0A0B0C0E); guint32 good_dns2 = htonl (0x04020202); /* GTE nameserver */ + gboolean dns_workaround = FALSE; /* Work around a PPP bug (#1732) which causes many mobile broadband * providers to return 10.11.12.13 and 10.11.12.14 for the DNS servers. @@ -162,11 +163,13 @@ ppp_ip4_config (NMPPPManager *ppp_manager, * could actually be valid in some cases, so only substitute if ppp * returns *only* the two bad nameservers. */ - if (found1 && found2) { - nm_ip4_config_reset_nameservers (config); - nm_ip4_config_add_nameserver (config, good_dns1); - nm_ip4_config_add_nameserver (config, good_dns2); - } + dns_workaround = (found1 && found2); + } + + if (!num || dns_workaround) { + nm_ip4_config_reset_nameservers (config); + nm_ip4_config_add_nameserver (config, good_dns1); + nm_ip4_config_add_nameserver (config, good_dns2); } priv->pending_ip4_config = g_object_ref (config); diff --git a/src/named-manager/nm-named-manager.c b/src/named-manager/nm-named-manager.c index b89c80118c..c96a2e691b 100644 --- a/src/named-manager/nm-named-manager.c +++ b/src/named-manager/nm-named-manager.c @@ -388,14 +388,29 @@ update_resolv_conf (const char *domain, const char *iface, GError **error) { - const char *tmp_resolv_conf = RESOLV_CONF ".tmp"; - char tmp_resolv_conf_realpath [PATH_MAX]; + char *tmp_resolv_conf; + char *tmp_resolv_conf_realpath; + char *resolv_conf_realpath; FILE *f; int do_rename = 1; int old_errno = 0; - if (!realpath (tmp_resolv_conf, tmp_resolv_conf_realpath)) - strcpy (tmp_resolv_conf_realpath, tmp_resolv_conf); + g_return_val_if_fail (error != NULL, FALSE); + + /* Find the real path of resolv.conf; it could be a symlink to something */ + resolv_conf_realpath = realpath (RESOLV_CONF, NULL); + if (!resolv_conf_realpath) + resolv_conf_realpath = strdup (RESOLV_CONF); + + /* Build up the real path for the temp resolv.conf that we're about to + * write out. + */ + tmp_resolv_conf = g_strdup_printf ("%s.tmp", resolv_conf_realpath); + tmp_resolv_conf_realpath = realpath (tmp_resolv_conf, NULL); + if (!tmp_resolv_conf_realpath) + tmp_resolv_conf_realpath = strdup (tmp_resolv_conf); + g_free (tmp_resolv_conf); + tmp_resolv_conf = NULL; if ((f = fopen (tmp_resolv_conf_realpath, "w")) == NULL) { do_rename = 0; @@ -409,8 +424,11 @@ update_resolv_conf (const char *domain, g_strerror (old_errno), RESOLV_CONF, g_strerror (errno)); - return FALSE; + goto out; } + /* Update tmp_resolv_conf_realpath so the error message on fclose() + * failure will be correct. + */ strcpy (tmp_resolv_conf_realpath, RESOLV_CONF); } @@ -418,25 +436,34 @@ update_resolv_conf (const char *domain, if (fclose (f) < 0) { if (*error == NULL) { + /* only set an error here if write_resolv_conf() was successful, + * since its error is more important. + */ g_set_error (error, - NM_NAMED_MANAGER_ERROR, - NM_NAMED_MANAGER_ERROR_SYSTEM, - "Could not close %s: %s\n", - tmp_resolv_conf_realpath, - g_strerror (errno)); + NM_NAMED_MANAGER_ERROR, + NM_NAMED_MANAGER_ERROR_SYSTEM, + "Could not close %s: %s\n", + tmp_resolv_conf_realpath, + g_strerror (errno)); } } + /* Don't rename the tempfile over top of the existing resolv.conf if there + * was an error writing it out. + */ if (*error == NULL && do_rename) { - if (rename (tmp_resolv_conf, RESOLV_CONF) < 0) { + if (rename (tmp_resolv_conf_realpath, resolv_conf_realpath) < 0) { g_set_error (error, - NM_NAMED_MANAGER_ERROR, - NM_NAMED_MANAGER_ERROR_SYSTEM, - "Could not replace " RESOLV_CONF ": %s\n", - g_strerror (errno)); + NM_NAMED_MANAGER_ERROR, + NM_NAMED_MANAGER_ERROR_SYSTEM, + "Could not replace " RESOLV_CONF ": %s\n", + g_strerror (errno)); } } +out: + free (tmp_resolv_conf_realpath); + free (resolv_conf_realpath); return *error ? FALSE : TRUE; } diff --git a/src/nm-device-cdma.c b/src/nm-device-cdma.c index 78f5ed3351..4e8acb9ed8 100644 --- a/src/nm-device-cdma.c +++ b/src/nm-device-cdma.c @@ -120,6 +120,8 @@ real_connection_secrets_updated (NMDevice *device, NMDeviceCdmaPrivate *priv = NM_DEVICE_CDMA_GET_PRIVATE (device); NMActRequest *req; + g_return_if_fail (IS_ACTIVATING_STATE (nm_device_get_state (device))); + req = nm_device_get_act_request (device); g_assert (req); diff --git a/src/nm-device-ethernet.c b/src/nm-device-ethernet.c index 6a75cba1fd..672a902c28 100644 --- a/src/nm-device-ethernet.c +++ b/src/nm-device-ethernet.c @@ -632,8 +632,7 @@ real_connection_secrets_updated (NMDevice *dev, gboolean valid = FALSE; GSList *iter; - if (nm_device_get_state (dev) != NM_DEVICE_STATE_NEED_AUTH) - return; + g_return_if_fail (IS_ACTIVATING_STATE (nm_device_get_state (dev))); /* PPPoE? */ if (caller == SECRETS_CALLER_PPP) { @@ -663,6 +662,7 @@ real_connection_secrets_updated (NMDevice *dev, /* Only caller could be ourselves for 802.1x */ g_return_if_fail (caller == SECRETS_CALLER_ETHERNET); + g_return_if_fail (nm_device_get_state (dev) == NM_DEVICE_STATE_NEED_AUTH); for (iter = updated_settings; iter; iter = g_slist_next (iter)) { const char *setting_name = (const char *) iter->data; @@ -1850,7 +1850,7 @@ nm_device_ethernet_class_init (NMDeviceEthernetClass *klass) "Ifindex", "Interface index", 0, G_MAXUINT32, 0, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | NM_PROPERTY_PARAM_NO_EXPORT)); /* Signals */ signals[PROPERTIES_CHANGED] = diff --git a/src/nm-device-gsm.c b/src/nm-device-gsm.c index 4f738ec3a5..bd717c2ca1 100644 --- a/src/nm-device-gsm.c +++ b/src/nm-device-gsm.c @@ -120,6 +120,8 @@ real_connection_secrets_updated (NMDevice *device, NMDeviceGsmPrivate *priv = NM_DEVICE_GSM_GET_PRIVATE (device); NMActRequest *req; + g_return_if_fail (IS_ACTIVATING_STATE (nm_device_get_state (device))); + req = nm_device_get_act_request (device); g_assert (req); diff --git a/src/nm-device-interface.c b/src/nm-device-interface.c index c8475df4ca..f53a6028b8 100644 --- a/src/nm-device-interface.c +++ b/src/nm-device-interface.c @@ -151,7 +151,7 @@ nm_device_interface_init (gpointer g_iface) "DeviceType", "DeviceType", 0, G_MAXUINT32, NM_DEVICE_TYPE_UNKNOWN, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | NM_PROPERTY_PARAM_NO_EXPORT)); g_object_interface_install_property (g_iface, g_param_spec_boolean (NM_DEVICE_INTERFACE_MANAGED, diff --git a/src/nm-device-interface.h b/src/nm-device-interface.h index 0867bc2923..54b5c41a59 100644 --- a/src/nm-device-interface.h +++ b/src/nm-device-interface.h @@ -32,6 +32,9 @@ #define NM_IS_DEVICE_INTERFACE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DEVICE_INTERFACE)) #define NM_DEVICE_INTERFACE_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), NM_TYPE_DEVICE_INTERFACE, NMDeviceInterface)) +#define IS_ACTIVATING_STATE(state) \ + (state > NM_DEVICE_STATE_DISCONNECTED && state < NM_DEVICE_STATE_ACTIVATED) + typedef enum { NM_DEVICE_INTERFACE_ERROR_CONNECTION_ACTIVATING = 0, diff --git a/src/nm-device-olpc-mesh.c b/src/nm-device-olpc-mesh.c index 743f47cdf7..2eef45b216 100644 --- a/src/nm-device-olpc-mesh.c +++ b/src/nm-device-olpc-mesh.c @@ -909,7 +909,7 @@ check_companion_cb (gpointer user_data) if (priv->device_added_cb != 0) return FALSE; - manager = nm_manager_get (NULL, NULL, NULL); + manager = nm_manager_get (NULL, NULL, NULL, FALSE, FALSE, NULL); priv->device_added_cb = g_signal_connect (manager, "device-added", G_CALLBACK (device_added_cb), self); diff --git a/src/nm-device-wifi.c b/src/nm-device-wifi.c index f2c9350fd0..c9022f0645 100644 --- a/src/nm-device-wifi.c +++ b/src/nm-device-wifi.c @@ -3589,7 +3589,7 @@ nm_device_wifi_class_init (NMDeviceWifiClass *klass) "Ifindex", "Interface index", 0, G_MAXUINT32, 0, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | NM_PROPERTY_PARAM_NO_EXPORT)); g_object_class_install_property (object_class, PROP_SCANNING, g_param_spec_boolean (NM_DEVICE_WIFI_SCANNING, diff --git a/src/nm-manager.c b/src/nm-manager.c index 44e4b56e2a..0b0814d192 100644 --- a/src/nm-manager.c +++ b/src/nm-manager.c @@ -151,6 +151,7 @@ typedef struct { typedef struct { char *config_file; + char *state_file; GSList *devices; NMState state; @@ -216,6 +217,7 @@ enum { /* Not exported */ PROP_HOSTNAME, + PROP_SLEEPING, LAST_PROP }; @@ -1102,11 +1104,62 @@ nm_manager_name_owner_changed (NMDBusManager *mgr, } } +/* Store value into key-file; supported types: boolean, int, string */ +static gboolean +write_value_to_state_file (const char *filename, + const char *group, + const char *key, + GType value_type, + gpointer value, + GError **error) +{ + GKeyFile *key_file; + char *data; + gsize len = 0; + gboolean ret = FALSE; + + g_return_val_if_fail (filename != NULL, FALSE); + g_return_val_if_fail (group != NULL, FALSE); + g_return_val_if_fail (key != NULL, FALSE); + g_return_val_if_fail (value_type == G_TYPE_BOOLEAN || + value_type == G_TYPE_INT || + value_type == G_TYPE_STRING, + FALSE); + + key_file = g_key_file_new (); + if (!key_file) + return FALSE; + + g_key_file_set_list_separator (key_file, ','); + g_key_file_load_from_file (key_file, filename, G_KEY_FILE_KEEP_COMMENTS, NULL); + switch (value_type) { + case G_TYPE_BOOLEAN: + g_key_file_set_boolean (key_file, group, key, *((gboolean *) value)); + break; + case G_TYPE_INT: + g_key_file_set_integer (key_file, group, key, *((gint *) value)); + break; + case G_TYPE_STRING: + g_key_file_set_string (key_file, group, key, *((const gchar **) value)); + break; + } + + data = g_key_file_to_data (key_file, &len, NULL); + if (data) { + ret = g_file_set_contents (filename, data, len, error); + g_free (data); + } + g_key_file_free (key_file); + + return ret; +} + static void manager_set_wireless_enabled (NMManager *manager, gboolean enabled) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (manager); GSList *iter; + GError *error = NULL; if (priv->wireless_enabled == enabled) return; @@ -1119,6 +1172,19 @@ manager_set_wireless_enabled (NMManager *manager, gboolean enabled) g_object_notify (G_OBJECT (manager), NM_MANAGER_WIRELESS_ENABLED); + /* Update "WirelessEnabled" key in state file */ + if (priv->state_file) { + if (!write_value_to_state_file (priv->state_file, + "main", "WirelessEnabled", + G_TYPE_BOOLEAN, (gpointer) &priv->wireless_enabled, + &error)) { + g_warning ("Writing to state file %s failed: (%d) %s.", + priv->state_file, + error ? error->code : -1, + (error && error->message) ? error->message : "unknown"); + } + } + /* Don't touch devices if asleep/networking disabled */ if (priv->sleeping) return; @@ -2401,6 +2467,23 @@ impl_manager_sleep (NMManager *self, gboolean sleep, GError **error) priv->sleeping = sleep; + /* Update "NetworkingEnabled" key in state file */ + if (priv->state_file) { + GError *err = NULL; + gboolean networking_enabled = !sleep; + + if (!write_value_to_state_file (priv->state_file, + "main", "NetworkingEnabled", + G_TYPE_BOOLEAN, (gpointer) &networking_enabled, + &err)) { + g_warning ("Writing to state file %s failed: (%d) %s.", + priv->state_file, + err ? err->code : -1, + (err && err->message) ? err->message : "unknown"); + } + + } + if (sleep) { nm_info ("Sleeping..."); @@ -2432,6 +2515,8 @@ impl_manager_sleep (NMManager *self, gboolean sleep, GError **error) } nm_manager_update_state (self); + + g_object_notify (G_OBJECT (self), NM_MANAGER_SLEEPING); return TRUE; } @@ -2569,9 +2654,10 @@ nm_manager_start (NMManager *self) break; } - nm_info ("Wireless now %s by radio killswitch", - (priv->wireless_hw_enabled && we) ? "enabled" : "disabled"); - manager_set_wireless_enabled (self, we); + nm_info ("Wireless %s by radio killswitch; %s by state file", + (priv->wireless_hw_enabled && we) ? "enabled" : "disabled", + (priv->wireless_enabled) ? "enabled" : "disabled"); + manager_set_wireless_enabled (self, priv->wireless_enabled && we); system_unmanaged_devices_changed_cb (priv->sys_settings, NULL, self); system_hostname_changed_cb (priv->sys_settings, NULL, self); @@ -2589,7 +2675,12 @@ nm_manager_start (NMManager *self) } NMManager * -nm_manager_get (const char *config_file, const char *plugins, GError **error) +nm_manager_get (const char *config_file, + const char *plugins, + const char *state_file, + gboolean initial_net_enabled, + gboolean initial_wifi_enabled, + GError **error) { static NMManager *singleton = NULL; NMManagerPrivate *priv; @@ -2615,6 +2706,12 @@ nm_manager_get (const char *config_file, const char *plugins, GError **error) priv->config_file = g_strdup (config_file); + priv->state_file = g_strdup (state_file); + + priv->sleeping = !initial_net_enabled; + + priv->wireless_enabled = initial_wifi_enabled; + g_signal_connect (priv->sys_settings, "notify::" NM_SYSCONFIG_SETTINGS_UNMANAGED_SPECS, G_CALLBACK (system_unmanaged_devices_changed_cb), singleton); g_signal_connect (priv->sys_settings, "notify::" NM_SETTINGS_SYSTEM_INTERFACE_HOSTNAME, @@ -2764,6 +2861,9 @@ get_property (GObject *object, guint prop_id, case PROP_HOSTNAME: g_value_set_string (value, priv->hostname); break; + case PROP_SLEEPING: + g_value_set_boolean (value, priv->sleeping); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -2887,6 +2987,14 @@ nm_manager_class_init (NMManagerClass *manager_class) NULL, G_PARAM_READABLE | NM_PROPERTY_PARAM_NO_EXPORT)); + g_object_class_install_property + (object_class, PROP_SLEEPING, + g_param_spec_boolean (NM_MANAGER_SLEEPING, + "Sleeping", + "Sleeping", + FALSE, + G_PARAM_READABLE | NM_PROPERTY_PARAM_NO_EXPORT)); + /* signals */ signals[DEVICE_ADDED] = g_signal_new ("device-added", diff --git a/src/nm-manager.h b/src/nm-manager.h index 0c6da150d6..a17323d42f 100644 --- a/src/nm-manager.h +++ b/src/nm-manager.h @@ -42,6 +42,7 @@ /* Not exported */ #define NM_MANAGER_HOSTNAME "hostname" +#define NM_MANAGER_SLEEPING "sleeping" typedef struct { GObject parent; @@ -73,7 +74,12 @@ typedef struct { GType nm_manager_get_type (void); -NMManager *nm_manager_get (const char *config_file, const char *plugins, GError **error); +NMManager *nm_manager_get (const char *config_file, + const char *plugins, + const char *state_file, + gboolean initial_net_enabled, + gboolean initial_wifi_enabled, + GError **error); void nm_manager_start (NMManager *manager); diff --git a/src/system-settings/nm-sysconfig-connection.c b/src/system-settings/nm-sysconfig-connection.c index 11b022f9d0..7401da5571 100644 --- a/src/system-settings/nm-sysconfig-connection.c +++ b/src/system-settings/nm-sysconfig-connection.c @@ -42,6 +42,7 @@ G_DEFINE_TYPE_EXTENDED (NMSysconfigConnection, nm_sysconfig_connection, NM_TYPE_ typedef struct { PolkitAuthority *authority; + GSList *pk_calls; } NMSysconfigConnectionPrivate; /**************************************************************/ @@ -167,6 +168,7 @@ typedef struct { DBusGMethodInvocation *context; PolkitSubject *subject; GCancellable *cancellable; + gboolean disposed; /* Update */ NMConnection *connection; @@ -242,11 +244,26 @@ pk_update_cb (GObject *object, GAsyncResult *result, gpointer user_data) { PolkitCall *call = user_data; NMSysconfigConnection *self = call->self; - NMSysconfigConnectionPrivate *priv = NM_SYSCONFIG_CONNECTION_GET_PRIVATE (self); + NMSysconfigConnectionPrivate *priv; PolkitAuthorizationResult *pk_result; GError *error = NULL; GHashTable *settings; + /* If our NMSysconfigConnection is already gone, do nothing */ + if (call->disposed) { + error = g_error_new_literal (NM_SYSCONFIG_SETTINGS_ERROR, + NM_SYSCONFIG_SETTINGS_ERROR_GENERAL, + "Request was canceled."); + dbus_g_method_return_error (call->context, error); + g_error_free (error); + polkit_call_free (call); + return; + } + + priv = NM_SYSCONFIG_CONNECTION_GET_PRIVATE (self); + + priv->pk_calls = g_slist_remove (priv->pk_calls, call); + pk_result = polkit_authority_check_authorization_finish (priv->authority, result, &error); @@ -321,6 +338,7 @@ dbus_update (NMExportedConnection *exported, call->cancellable, pk_update_cb, call); + priv->pk_calls = g_slist_prepend (priv->pk_calls, call); } static void @@ -343,10 +361,25 @@ pk_delete_cb (GObject *object, GAsyncResult *result, gpointer user_data) { PolkitCall *call = user_data; NMSysconfigConnection *self = call->self; - NMSysconfigConnectionPrivate *priv = NM_SYSCONFIG_CONNECTION_GET_PRIVATE (self); + NMSysconfigConnectionPrivate *priv; PolkitAuthorizationResult *pk_result; GError *error = NULL; + /* If our NMSysconfigConnection is already gone, do nothing */ + if (call->disposed) { + error = g_error_new_literal (NM_SYSCONFIG_SETTINGS_ERROR, + NM_SYSCONFIG_SETTINGS_ERROR_GENERAL, + "Request was canceled."); + dbus_g_method_return_error (call->context, error); + g_error_free (error); + polkit_call_free (call); + return; + } + + priv = NM_SYSCONFIG_CONNECTION_GET_PRIVATE (self); + + priv->pk_calls = g_slist_remove (priv->pk_calls, call); + pk_result = polkit_authority_check_authorization_finish (priv->authority, result, &error); @@ -396,6 +429,7 @@ dbus_delete (NMExportedConnection *exported, call->cancellable, pk_delete_cb, call); + priv->pk_calls = g_slist_prepend (priv->pk_calls, call); } static void @@ -419,10 +453,25 @@ pk_secrets_cb (GObject *object, GAsyncResult *result, gpointer user_data) { PolkitCall *call = user_data; NMSysconfigConnection *self = call->self; - NMSysconfigConnectionPrivate *priv = NM_SYSCONFIG_CONNECTION_GET_PRIVATE (self); + NMSysconfigConnectionPrivate *priv; PolkitAuthorizationResult *pk_result; GError *error = NULL; + /* If our NMSysconfigConnection is already gone, do nothing */ + if (call->disposed) { + error = g_error_new_literal (NM_SYSCONFIG_SETTINGS_ERROR, + NM_SYSCONFIG_SETTINGS_ERROR_GENERAL, + "Request was canceled."); + dbus_g_method_return_error (call->context, error); + g_error_free (error); + polkit_call_free (call); + return; + } + + priv = NM_SYSCONFIG_CONNECTION_GET_PRIVATE (self); + + priv->pk_calls = g_slist_remove (priv->pk_calls, call); + pk_result = polkit_authority_check_authorization_finish (priv->authority, result, &error); @@ -478,6 +527,7 @@ dbus_get_secrets (NMExportedConnection *exported, call->cancellable, pk_secrets_cb, call); + priv->pk_calls = g_slist_prepend (priv->pk_calls, call); } /**************************************************************/ @@ -501,10 +551,19 @@ nm_sysconfig_connection_init (NMSysconfigConnection *self) static void dispose (GObject *object) { - NMSysconfigConnectionPrivate *priv = NM_SYSCONFIG_CONNECTION_GET_PRIVATE (object); + NMSysconfigConnection *self = NM_SYSCONFIG_CONNECTION (object); + NMSysconfigConnectionPrivate *priv = NM_SYSCONFIG_CONNECTION_GET_PRIVATE (self); + GSList *iter; - if (priv->authority) - g_object_unref (priv->authority); + /* Cancel PolicyKit requests */ + for (iter = priv->pk_calls; iter; iter = g_slist_next (iter)) { + PolkitCall *call = iter->data; + + call->disposed = TRUE; + g_cancellable_cancel (call->cancellable); + } + g_slist_free (priv->pk_calls); + priv->pk_calls = NULL; G_OBJECT_CLASS (nm_sysconfig_connection_parent_class)->dispose (object); } diff --git a/src/system-settings/nm-sysconfig-settings.c b/src/system-settings/nm-sysconfig-settings.c index 9bba219e10..7d422e694a 100644 --- a/src/system-settings/nm-sysconfig-settings.c +++ b/src/system-settings/nm-sysconfig-settings.c @@ -79,8 +79,12 @@ static void unmanaged_specs_changed (NMSystemConfigInterface *config, gpointer u typedef struct { PolkitAuthority *authority; + guint auth_changed_id; char *config_file; + GSList *pk_calls; + GSList *permissions_calls; + GSList *plugins; gboolean connections_loaded; GHashTable *connections; @@ -516,6 +520,8 @@ typedef struct { NMSysconfigSettings *self; DBusGMethodInvocation *context; PolkitSubject *subject; + GCancellable *cancellable; + gboolean disposed; NMConnection *connection; NMSettingsAddConnectionFunc callback; @@ -545,6 +551,7 @@ polkit_call_new (NMSysconfigSettings *self, call = g_malloc0 (sizeof (PolkitCall)); call->self = self; + call->cancellable = g_cancellable_new (); call->context = context; if (connection) call->connection = g_object_ref (connection); @@ -567,6 +574,7 @@ polkit_call_free (PolkitCall *call) { if (call->connection) g_object_unref (call->connection); + g_object_unref (call->cancellable); g_free (call->hostname); g_object_unref (call->subject); g_free (call); @@ -610,10 +618,25 @@ pk_add_cb (GObject *object, GAsyncResult *result, gpointer user_data) { PolkitCall *call = user_data; NMSysconfigSettings *self = call->self; - NMSysconfigSettingsPrivate *priv = NM_SYSCONFIG_SETTINGS_GET_PRIVATE (self); + NMSysconfigSettingsPrivate *priv; PolkitAuthorizationResult *pk_result; GError *error = NULL, *add_error = NULL; + /* If NMSysconfigSettings is already gone, do nothing */ + if (call->disposed) { + error = g_error_new_literal (NM_SYSCONFIG_SETTINGS_ERROR, + NM_SYSCONFIG_SETTINGS_ERROR_GENERAL, + "Request was canceled."); + dbus_g_method_return_error (call->context, error); + g_error_free (error); + polkit_call_free (call); + return; + } + + priv = NM_SYSCONFIG_SETTINGS_GET_PRIVATE (self); + + priv->pk_calls = g_slist_remove (priv->pk_calls, call); + pk_result = polkit_authority_check_authorization_finish (priv->authority, result, &error); @@ -680,9 +703,10 @@ add_connection (NMSettingsService *service, NM_SYSCONFIG_POLICY_ACTION_CONNECTION_MODIFY, NULL, POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION, - NULL, + call->cancellable, pk_add_cb, call); + priv->pk_calls = g_slist_append (priv->pk_calls, call); } static void @@ -690,12 +714,27 @@ pk_hostname_cb (GObject *object, GAsyncResult *result, gpointer user_data) { PolkitCall *call = user_data; NMSysconfigSettings *self = call->self; - NMSysconfigSettingsPrivate *priv = NM_SYSCONFIG_SETTINGS_GET_PRIVATE (self); + NMSysconfigSettingsPrivate *priv; PolkitAuthorizationResult *pk_result; GError *error = NULL; GSList *iter; gboolean success = FALSE; + /* If our NMSysconfigConnection is already gone, do nothing */ + if (call->disposed) { + error = g_error_new_literal (NM_SYSCONFIG_SETTINGS_ERROR, + NM_SYSCONFIG_SETTINGS_ERROR_GENERAL, + "Request was canceled."); + dbus_g_method_return_error (call->context, error); + g_error_free (error); + polkit_call_free (call); + return; + } + + priv = NM_SYSCONFIG_SETTINGS_GET_PRIVATE (self); + + priv->pk_calls = g_slist_remove (priv->pk_calls, call); + pk_result = polkit_authority_check_authorization_finish (priv->authority, result, &error); @@ -767,9 +806,10 @@ impl_settings_save_hostname (NMSysconfigSettings *self, NM_SYSCONFIG_POLICY_ACTION_HOSTNAME_MODIFY, NULL, POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION, - NULL, + call->cancellable, pk_hostname_cb, call); + priv->pk_calls = g_slist_append (priv->pk_calls, call); } static void @@ -783,7 +823,9 @@ pk_authority_changed_cb (GObject *object, gpointer user_data) typedef struct { PolkitCall *pk_call; const char *pk_action; + GCancellable *cancellable; NMSettingsSystemPermissions permission; + gboolean disposed; } PermissionsCall; static void @@ -792,10 +834,18 @@ permission_call_done (GObject *object, GAsyncResult *result, gpointer user_data) PermissionsCall *call = user_data; PolkitCall *pk_call = call->pk_call; NMSysconfigSettings *self = pk_call->self; - NMSysconfigSettingsPrivate *priv = NM_SYSCONFIG_SETTINGS_GET_PRIVATE (self); + NMSysconfigSettingsPrivate *priv; PolkitAuthorizationResult *pk_result; GError *error = NULL; + /* If NMSysconfigSettings is gone, just skip to the end */ + if (call->disposed) + goto done; + + priv = NM_SYSCONFIG_SETTINGS_GET_PRIVATE (self); + + priv->permissions_calls = g_slist_remove (priv->permissions_calls, call); + pk_result = polkit_authority_check_authorization_finish (priv->authority, result, &error); @@ -820,12 +870,21 @@ permission_call_done (GObject *object, GAsyncResult *result, gpointer user_data) g_object_unref (pk_result); +done: pk_call->permissions_calls--; if (pk_call->permissions_calls == 0) { - /* All the permissions calls are done, return the full permissions - * bitfield back to the user. - */ - dbus_g_method_return (pk_call->context, pk_call->permissions); + if (call->disposed) { + error = g_error_new_literal (NM_SYSCONFIG_SETTINGS_ERROR, + NM_SYSCONFIG_SETTINGS_ERROR_GENERAL, + "Request was canceled."); + dbus_g_method_return_error (pk_call->context, error); + g_error_free (error); + } else { + /* All the permissions calls are done, return the full permissions + * bitfield back to the user. + */ + dbus_g_method_return (pk_call->context, pk_call->permissions); + } polkit_call_free (pk_call); } @@ -850,6 +909,7 @@ start_permission_check (NMSysconfigSettings *self, call->pk_call = pk_call; call->pk_action = pk_action; call->permission = permission; + call->cancellable = g_cancellable_new (); pk_call->permissions_calls++; @@ -858,9 +918,10 @@ start_permission_check (NMSysconfigSettings *self, pk_action, NULL, 0, - NULL, + call->cancellable, permission_call_done, call); + priv->permissions_calls = g_slist_append (priv->permissions_calls, call); } static void @@ -1261,6 +1322,41 @@ nm_sysconfig_settings_new (const char *config_file, /***************************************************************/ +static void +dispose (GObject *object) +{ + NMSysconfigSettings *self = NM_SYSCONFIG_SETTINGS (object); + NMSysconfigSettingsPrivate *priv = NM_SYSCONFIG_SETTINGS_GET_PRIVATE (self); + GSList *iter; + + if (priv->auth_changed_id) { + g_signal_handler_disconnect (priv->authority, priv->auth_changed_id); + priv->auth_changed_id = 0; + } + + /* Cancel PolicyKit requests */ + for (iter = priv->pk_calls; iter; iter = g_slist_next (iter)) { + PolkitCall *call = iter->data; + + call->disposed = TRUE; + g_cancellable_cancel (call->cancellable); + } + g_slist_free (priv->pk_calls); + priv->pk_calls = NULL; + + /* Cancel PolicyKit permissions requests */ + for (iter = priv->permissions_calls; iter; iter = g_slist_next (iter)) { + PermissionsCall *call = iter->data; + + call->disposed = TRUE; + g_cancellable_cancel (call->cancellable); + } + g_slist_free (priv->permissions_calls); + priv->permissions_calls = NULL; + + G_OBJECT_CLASS (nm_sysconfig_settings_parent_class)->dispose (object); +} + static void finalize (GObject *object) { @@ -1274,9 +1370,6 @@ finalize (GObject *object) g_slist_foreach (priv->plugins, (GFunc) g_object_unref, NULL); g_slist_free (priv->plugins); - if (priv->authority) - g_object_unref (priv->authority); - g_free (priv->orig_hostname); g_free (priv->config_file); @@ -1334,6 +1427,7 @@ nm_sysconfig_settings_class_init (NMSysconfigSettingsClass *class) /* virtual methods */ object_class->notify = notify; object_class->get_property = get_property; + object_class->dispose = dispose; object_class->finalize = finalize; ss_class->list_connections = list_connections; ss_class->add_connection = add_connection; @@ -1379,9 +1473,12 @@ nm_sysconfig_settings_init (NMSysconfigSettings *self) priv->connections = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, NULL); priv->authority = polkit_authority_get (); - if (priv->authority) - g_signal_connect (priv->authority, "changed", G_CALLBACK (pk_authority_changed_cb), self); - else + if (priv->authority) { + priv->auth_changed_id = g_signal_connect (priv->authority, + "changed", + G_CALLBACK (pk_authority_changed_cb), + self); + } else g_warning ("%s: failed to create PolicyKit authority.", __func__); /* Grab hostname on startup and use that if no plugins provide one */