/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- * * Copyright (C) 2014 Bastien Nocera * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * */ #include "config.h" #include #include #include #include #include #if HAVE_NETWORK_MANAGER #include #endif /* HAVE_NETWORK_MANAGER */ #include "gnome-settings-profile.h" #include "gsd-sharing-manager.h" #include "gsd-sharing-enums.h" typedef struct { const char *name; GSettings *settings; } ServiceInfo; struct _GsdSharingManager { GObject parent; GDBusNodeInfo *introspection_data; guint name_id; GDBusConnection *connection; GCancellable *cancellable; #if HAVE_NETWORK_MANAGER NMClient *client; #endif /* HAVE_NETWORK_MANAGER */ GHashTable *services; char *current_network; char *current_network_name; char *carrier_type; GsdSharingStatus sharing_status; }; #define GSD_DBUS_NAME "org.gnome.SettingsDaemon" #define GSD_DBUS_PATH "/org/gnome/SettingsDaemon" #define GSD_DBUS_BASE_INTERFACE "org.gnome.SettingsDaemon" #define GSD_SHARING_DBUS_NAME GSD_DBUS_NAME ".Sharing" #define GSD_SHARING_DBUS_PATH GSD_DBUS_PATH "/Sharing" static const gchar introspection_xml[] = "" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " ""; static void gsd_sharing_manager_class_init (GsdSharingManagerClass *klass); static void gsd_sharing_manager_init (GsdSharingManager *manager); static void gsd_sharing_manager_finalize (GObject *object); G_DEFINE_TYPE (GsdSharingManager, gsd_sharing_manager, G_TYPE_OBJECT) static gpointer manager_object = NULL; static const char * const services[] = { "rygel", "gnome-user-share-webdav" }; static void handle_unit_cb (GObject *source_object, GAsyncResult *res, gpointer user_data) { GError *error = NULL; GVariant *ret; const char *operation = user_data; ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), res, &error); if (!ret) { g_autofree gchar *remote_error = g_dbus_error_get_remote_error (error); if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) && g_strcmp0 (remote_error, "org.freedesktop.systemd1.NoSuchUnit") != 0) g_warning ("Failed to %s service: %s", operation, error->message); g_error_free (error); return; } g_variant_unref (ret); } static void gsd_sharing_manager_handle_service (GsdSharingManager *manager, const char *method, ServiceInfo *service) { char *service_file; service_file = g_strdup_printf ("%s.service", service->name); g_dbus_connection_call (manager->connection, "org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", method, g_variant_new ("(ss)", service_file, "replace"), NULL, G_DBUS_CALL_FLAGS_NONE, -1, manager->cancellable, handle_unit_cb, (gpointer) method); g_free (service_file); } static void gsd_sharing_manager_start_service (GsdSharingManager *manager, ServiceInfo *service) { g_debug ("About to start %s", service->name); /* We use StartUnit, not StartUnitReplace, since the latter would * cancel any pending start we already have going from an * earlier _start_service() call */ gsd_sharing_manager_handle_service (manager, "StartUnit", service); } static void gsd_sharing_manager_stop_service (GsdSharingManager *manager, ServiceInfo *service) { g_debug ("About to stop %s", service->name); gsd_sharing_manager_handle_service (manager, "StopUnit", service); } #if HAVE_NETWORK_MANAGER static gboolean service_is_enabled_on_current_connection (GsdSharingManager *manager, ServiceInfo *service) { char **connections; int j; gboolean ret; connections = g_settings_get_strv (service->settings, "enabled-connections"); ret = FALSE; for (j = 0; connections[j] != NULL; j++) { if (g_strcmp0 (connections[j], manager->current_network) == 0) { ret = TRUE; break; } } g_strfreev (connections); return ret; } #else static gboolean service_is_enabled_on_current_connection (GsdSharingManager *manager, ServiceInfo *service) { return FALSE; } #endif /* HAVE_NETWORK_MANAGER */ static void gsd_sharing_manager_sync_services (GsdSharingManager *manager) { GList *services, *l; services = g_hash_table_get_values (manager->services); for (l = services; l != NULL; l = l->next) { ServiceInfo *service = l->data; gboolean should_be_started = FALSE; if (manager->sharing_status == GSD_SHARING_STATUS_AVAILABLE && service_is_enabled_on_current_connection (manager, service)) should_be_started = TRUE; if (should_be_started) gsd_sharing_manager_start_service (manager, service); else gsd_sharing_manager_stop_service (manager, service); } g_list_free (services); } #if HAVE_NETWORK_MANAGER static void properties_changed (GsdSharingManager *manager) { GVariantBuilder props_builder; GVariant *props_changed = NULL; /* not yet connected to the session bus */ if (manager->connection == NULL) return; g_variant_builder_init (&props_builder, G_VARIANT_TYPE ("a{sv}")); g_variant_builder_add (&props_builder, "{sv}", "CurrentNetwork", g_variant_new_string (manager->current_network)); g_variant_builder_add (&props_builder, "{sv}", "CurrentNetworkName", g_variant_new_string (manager->current_network_name)); g_variant_builder_add (&props_builder, "{sv}", "CarrierType", g_variant_new_string (manager->carrier_type)); g_variant_builder_add (&props_builder, "{sv}", "SharingStatus", g_variant_new_uint32 (manager->sharing_status)); props_changed = g_variant_new ("(s@a{sv}@as)", GSD_SHARING_DBUS_NAME, g_variant_builder_end (&props_builder), g_variant_new_strv (NULL, 0)); g_dbus_connection_emit_signal (manager->connection, NULL, GSD_SHARING_DBUS_PATH, "org.freedesktop.DBus.Properties", "PropertiesChanged", props_changed, NULL); } static char ** get_connections_for_service (GsdSharingManager *manager, const char *service_name) { ServiceInfo *service; service = g_hash_table_lookup (manager->services, service_name); return g_settings_get_strv (service->settings, "enabled-connections"); } #else static char ** get_connections_for_service (GsdSharingManager *manager, const char *service_name) { const char * const * connections [] = { NULL }; return g_strdupv ((char **) connections); } #endif /* HAVE_NETWORK_MANAGER */ static gboolean check_service (GsdSharingManager *manager, const char *service_name, GError **error) { if (g_hash_table_lookup (manager->services, service_name)) return TRUE; g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Invalid service name '%s'", service_name); return FALSE; } static gboolean gsd_sharing_manager_enable_service (GsdSharingManager *manager, const char *service_name, GError **error) { ServiceInfo *service; char **connections; GPtrArray *array; guint i; if (!check_service (manager, service_name, error)) return FALSE; if (manager->sharing_status != GSD_SHARING_STATUS_AVAILABLE) { g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Sharing cannot be enabled on this network, status is '%d'", manager->sharing_status); return FALSE; } service = g_hash_table_lookup (manager->services, service_name); connections = g_settings_get_strv (service->settings, "enabled-connections"); array = g_ptr_array_new (); for (i = 0; connections[i] != NULL; i++) { if (g_strcmp0 (connections[i], manager->current_network) == 0) goto bail; g_ptr_array_add (array, connections[i]); } g_ptr_array_add (array, manager->current_network); g_ptr_array_add (array, NULL); g_settings_set_strv (service->settings, "enabled-connections", (const gchar *const *) array->pdata); bail: gsd_sharing_manager_start_service (manager, service); g_ptr_array_unref (array); g_strfreev (connections); return TRUE; } static gboolean gsd_sharing_manager_disable_service (GsdSharingManager *manager, const char *service_name, const char *network_name, GError **error) { ServiceInfo *service; char **connections; GPtrArray *array; guint i; if (!check_service (manager, service_name, error)) return FALSE; service = g_hash_table_lookup (manager->services, service_name); connections = g_settings_get_strv (service->settings, "enabled-connections"); array = g_ptr_array_new (); for (i = 0; connections[i] != NULL; i++) { if (g_strcmp0 (connections[i], network_name) != 0) g_ptr_array_add (array, connections[i]); } g_ptr_array_add (array, NULL); g_settings_set_strv (service->settings, "enabled-connections", (const gchar *const *) array->pdata); g_ptr_array_unref (array); g_strfreev (connections); if (g_str_equal (network_name, manager->current_network)) gsd_sharing_manager_stop_service (manager, service); return TRUE; } #if HAVE_NETWORK_MANAGER static const char * get_type_and_name_for_connection_uuid (GsdSharingManager *manager, const char *uuid, const char **name) { NMRemoteConnection *conn; const char *type; if (!manager->client) return NULL; conn = nm_client_get_connection_by_uuid (manager->client, uuid); if (!conn) return NULL; type = nm_connection_get_connection_type (NM_CONNECTION (conn)); *name = nm_connection_get_id (NM_CONNECTION (conn)); return type; } #else static const char * get_type_and_name_for_connection_uuid (GsdSharingManager *manager, const char *id, const char **name) { return NULL; } #endif /* HAVE_NETWORK_MANAGER */ #if HAVE_NETWORK_MANAGER static gboolean connection_is_low_security (GsdSharingManager *manager, const char *uuid) { NMRemoteConnection *conn; if (!manager->client) return TRUE; conn = nm_client_get_connection_by_uuid (manager->client, uuid); if (!conn) return TRUE; /* Disable sharing on open Wi-Fi * XXX: Also do this for WEP networks? */ return (nm_connection_get_setting_wireless_security (NM_CONNECTION (conn)) == NULL); } #endif /* HAVE_NETWORK_MANAGER */ static GVariant * gsd_sharing_manager_list_networks (GsdSharingManager *manager, const char *service_name, GError **error) { char **connections; GVariantBuilder builder; guint i; if (!check_service (manager, service_name, error)) return NULL; #if HAVE_NETWORK_MANAGER if (!manager->client) { g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Not ready yet"); return NULL; } #endif /* HAVE_NETWORK_MANAGER */ connections = get_connections_for_service (manager, service_name); g_variant_builder_init (&builder, G_VARIANT_TYPE ("(a(sss))")); g_variant_builder_open (&builder, G_VARIANT_TYPE ("a(sss)")); for (i = 0; connections[i] != NULL; i++) { const char *type, *name; type = get_type_and_name_for_connection_uuid (manager, connections[i], &name); if (!type) continue; g_variant_builder_add (&builder, "(sss)", connections[i], name, type); } g_strfreev (connections); g_variant_builder_close (&builder); return g_variant_builder_end (&builder); } static GVariant * handle_get_property (GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *property_name, GError **error, gpointer user_data) { GsdSharingManager *manager = GSD_SHARING_MANAGER (user_data); /* Check session pointer as a proxy for whether the manager is in the start or stop state */ if (manager->connection == NULL) return NULL; if (g_strcmp0 (property_name, "CurrentNetwork") == 0) { return g_variant_new_string (manager->current_network); } if (g_strcmp0 (property_name, "CurrentNetworkName") == 0) { return g_variant_new_string (manager->current_network_name); } if (g_strcmp0 (property_name, "CarrierType") == 0) { return g_variant_new_string (manager->carrier_type); } if (g_strcmp0 (property_name, "SharingStatus") == 0) { return g_variant_new_uint32 (manager->sharing_status); } return NULL; } static void handle_method_call (GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *method_name, GVariant *parameters, GDBusMethodInvocation *invocation, gpointer user_data) { GsdSharingManager *manager = (GsdSharingManager *) user_data; g_debug ("Calling method '%s' for sharing", method_name); /* Check session pointer as a proxy for whether the manager is in the start or stop state */ if (manager->connection == NULL) return; if (g_strcmp0 (method_name, "EnableService") == 0) { const char *service; GError *error = NULL; g_variant_get (parameters, "(&s)", &service); if (!gsd_sharing_manager_enable_service (manager, service, &error)) g_dbus_method_invocation_take_error (invocation, error); else g_dbus_method_invocation_return_value (invocation, NULL); } else if (g_strcmp0 (method_name, "DisableService") == 0) { const char *service; const char *network_name; GError *error = NULL; g_variant_get (parameters, "(&s&s)", &service, &network_name); if (!gsd_sharing_manager_disable_service (manager, service, network_name, &error)) g_dbus_method_invocation_take_error (invocation, error); else g_dbus_method_invocation_return_value (invocation, NULL); } else if (g_strcmp0 (method_name, "ListNetworks") == 0) { const char *service; GError *error = NULL; GVariant *variant; g_variant_get (parameters, "(&s)", &service); variant = gsd_sharing_manager_list_networks (manager, service, &error); if (!variant) g_dbus_method_invocation_take_error (invocation, error); else g_dbus_method_invocation_return_value (invocation, variant); } } static const GDBusInterfaceVTable interface_vtable = { handle_method_call, handle_get_property, NULL }; static void on_bus_gotten (GObject *source_object, GAsyncResult *res, GsdSharingManager *manager) { GDBusConnection *connection; GError *error = NULL; connection = g_bus_get_finish (res, &error); if (connection == NULL) { if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) g_warning ("Could not get session bus: %s", error->message); g_error_free (error); return; } manager->connection = connection; g_dbus_connection_register_object (connection, GSD_SHARING_DBUS_PATH, manager->introspection_data->interfaces[0], &interface_vtable, manager, NULL, NULL); manager->name_id = g_bus_own_name_on_connection (connection, GSD_SHARING_DBUS_NAME, G_BUS_NAME_OWNER_FLAGS_NONE, NULL, NULL, NULL, NULL); } #if HAVE_NETWORK_MANAGER static void primary_connection_changed (GObject *gobject, GParamSpec *pspec, gpointer user_data) { GsdSharingManager *manager = user_data; NMActiveConnection *a_con; a_con = nm_client_get_primary_connection (manager->client); g_clear_pointer (&manager->current_network, g_free); g_clear_pointer (&manager->current_network_name, g_free); g_clear_pointer (&manager->carrier_type, g_free); if (a_con) { manager->current_network = g_strdup (nm_active_connection_get_uuid (a_con)); manager->current_network_name = g_strdup (nm_active_connection_get_id (a_con)); manager->carrier_type = g_strdup (nm_active_connection_get_connection_type (a_con)); if (manager->carrier_type == NULL) manager->carrier_type = g_strdup (""); } else { manager->current_network = g_strdup (""); manager->current_network_name = g_strdup (""); manager->carrier_type = g_strdup (""); } if (!a_con) { manager->sharing_status = GSD_SHARING_STATUS_OFFLINE; } else if (*(manager->carrier_type) == '\0') { /* Missing carrier type information? */ manager->sharing_status = GSD_SHARING_STATUS_OFFLINE; } else if (g_str_equal (manager->carrier_type, "bluetooth") || g_str_equal (manager->carrier_type, "gsm") || g_str_equal (manager->carrier_type, "cdma")) { manager->sharing_status = GSD_SHARING_STATUS_DISABLED_MOBILE_BROADBAND; } else if (g_str_equal (manager->carrier_type, "802-11-wireless")) { if (connection_is_low_security (manager, manager->current_network)) manager->sharing_status = GSD_SHARING_STATUS_DISABLED_LOW_SECURITY; else manager->sharing_status = GSD_SHARING_STATUS_AVAILABLE; } else { manager->sharing_status = GSD_SHARING_STATUS_AVAILABLE; } g_debug ("current network: %s", manager->current_network); g_debug ("current network name: %s", manager->current_network_name); g_debug ("conn type: %s", manager->carrier_type); g_debug ("status: %d", manager->sharing_status); properties_changed (manager); gsd_sharing_manager_sync_services (manager); } static void nm_client_ready (GObject *source_object, GAsyncResult *res, gpointer user_data) { GsdSharingManager *manager = user_data; GError *error = NULL; NMClient *client; client = nm_client_new_finish (res, &error); if (!client) { if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) g_warning ("Couldn't get NMClient: %s", error->message); g_error_free (error); return; } manager->client = client; g_signal_connect (G_OBJECT (client), "notify::primary-connection", G_CALLBACK (primary_connection_changed), manager); primary_connection_changed (NULL, NULL, manager); } #endif /* HAVE_NETWORK_MANAGER */ #define RYGEL_BUS_NAME "org.gnome.Rygel1" #define RYGEL_OBJECT_PATH "/org/gnome/Rygel1" #define RYGEL_INTERFACE_NAME "org.gnome.Rygel1" static void gsd_sharing_manager_disable_rygel (void) { GDBusConnection *connection; gchar *path; path = g_build_filename (g_get_user_config_dir (), "autostart", "rygel.desktop", NULL); if (!g_file_test (path, G_FILE_TEST_IS_SYMLINK | G_FILE_TEST_IS_REGULAR)) goto out; g_unlink (path); connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL); if (connection) { g_dbus_connection_call (connection, RYGEL_BUS_NAME, RYGEL_OBJECT_PATH, RYGEL_INTERFACE_NAME, "Shutdown", NULL, NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL); } g_object_unref (connection); out: g_free (path); } gboolean gsd_sharing_manager_start (GsdSharingManager *manager, GError **error) { g_debug ("Starting sharing manager"); gnome_settings_profile_start (NULL); manager->introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL); g_assert (manager->introspection_data != NULL); gsd_sharing_manager_disable_rygel (); manager->cancellable = g_cancellable_new (); #if HAVE_NETWORK_MANAGER nm_client_new_async (manager->cancellable, nm_client_ready, manager); #endif /* HAVE_NETWORK_MANAGER */ /* Start process of owning a D-Bus name */ g_bus_get (G_BUS_TYPE_SESSION, manager->cancellable, (GAsyncReadyCallback) on_bus_gotten, manager); gnome_settings_profile_end (NULL); return TRUE; } void gsd_sharing_manager_stop (GsdSharingManager *manager) { g_debug ("Stopping sharing manager"); if (manager->sharing_status == GSD_SHARING_STATUS_AVAILABLE && manager->connection != NULL) { manager->sharing_status = GSD_SHARING_STATUS_OFFLINE; gsd_sharing_manager_sync_services (manager); } if (manager->cancellable) { g_cancellable_cancel (manager->cancellable); g_clear_object (&manager->cancellable); } #if HAVE_NETWORK_MANAGER g_clear_object (&manager->client); #endif /* HAVE_NETWORK_MANAGER */ if (manager->name_id != 0) { g_bus_unown_name (manager->name_id); manager->name_id = 0; } g_clear_pointer (&manager->introspection_data, g_dbus_node_info_unref); g_clear_object (&manager->connection); g_clear_pointer (&manager->current_network, g_free); g_clear_pointer (&manager->current_network_name, g_free); g_clear_pointer (&manager->carrier_type, g_free); } static void gsd_sharing_manager_class_init (GsdSharingManagerClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = gsd_sharing_manager_finalize; } static void service_free (gpointer pointer) { ServiceInfo *service = pointer; g_clear_object (&service->settings); g_free (service); } static void gsd_sharing_manager_init (GsdSharingManager *manager) { guint i; manager->services = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, service_free); /* Default state */ manager->current_network = g_strdup (""); manager->current_network_name = g_strdup (""); manager->carrier_type = g_strdup (""); manager->sharing_status = GSD_SHARING_STATUS_OFFLINE; for (i = 0; i < G_N_ELEMENTS (services); i++) { ServiceInfo *service; char *path; service = g_new0 (ServiceInfo, 1); service->name = services[i]; path = g_strdup_printf ("/org/gnome/settings-daemon/plugins/sharing/%s/", services[i]); service->settings = g_settings_new_with_path ("org.gnome.settings-daemon.plugins.sharing.service", path); g_free (path); g_hash_table_insert (manager->services, (gpointer) services[i], service); } } static void gsd_sharing_manager_finalize (GObject *object) { GsdSharingManager *manager; g_return_if_fail (object != NULL); g_return_if_fail (GSD_IS_SHARING_MANAGER (object)); manager = GSD_SHARING_MANAGER (object); g_return_if_fail (manager != NULL); gsd_sharing_manager_stop (manager); g_hash_table_unref (manager->services); G_OBJECT_CLASS (gsd_sharing_manager_parent_class)->finalize (object); } GsdSharingManager * gsd_sharing_manager_new (void) { if (manager_object != NULL) { g_object_ref (manager_object); } else { manager_object = g_object_new (GSD_TYPE_SHARING_MANAGER, NULL); g_object_add_weak_pointer (manager_object, (gpointer *) &manager_object); } return GSD_SHARING_MANAGER (manager_object); }