| /* |
| * |
| * Connection Manager |
| * |
| * Copyright (C) 2007-2013 Intel Corporation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| * |
| * 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 St, Fifth Floor, Boston, MA 02110-1301 USA |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <stdio.h> |
| #include <errno.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <netinet/ether.h> |
| |
| #include <gdbus.h> |
| |
| #define CONNMAN_API_SUBJECT_TO_CHANGE |
| #include <connman/plugin.h> |
| #include <connman/technology.h> |
| #include <connman/device.h> |
| #include <connman/inet.h> |
| #include <connman/dbus.h> |
| #include <connman/log.h> |
| |
| #define BLUEZ_SERVICE "org.bluez" |
| #define BLUEZ_MANAGER_INTERFACE BLUEZ_SERVICE ".Manager" |
| #define BLUEZ_ADAPTER_INTERFACE BLUEZ_SERVICE ".Adapter" |
| #define BLUEZ_DEVICE_INTERFACE BLUEZ_SERVICE ".Device" |
| #define BLUEZ_NETWORK_INTERFACE BLUEZ_SERVICE ".Network" |
| #define BLUEZ_NETWORK_SERVER BLUEZ_SERVICE ".NetworkServer" |
| |
| #define LIST_ADAPTERS "ListAdapters" |
| #define ADAPTER_ADDED "AdapterAdded" |
| #define ADAPTER_REMOVED "AdapterRemoved" |
| #define DEVICE_REMOVED "DeviceRemoved" |
| |
| #define PROPERTY_CHANGED "PropertyChanged" |
| #define GET_PROPERTIES "GetProperties" |
| #define SET_PROPERTY "SetProperty" |
| |
| #define CONNECT "Connect" |
| #define DISCONNECT "Disconnect" |
| |
| #define REGISTER "Register" |
| #define UNREGISTER "Unregister" |
| |
| #define UUID_NAP "00001116-0000-1000-8000-00805f9b34fb" |
| |
| #define TIMEOUT 60000 |
| |
| static DBusConnection *connection; |
| |
| static GHashTable *bluetooth_devices = NULL; |
| static GHashTable *bluetooth_networks = NULL; |
| static GHashTable *pending_networks = NULL; |
| |
| static int pan_probe(struct connman_network *network) |
| { |
| GHashTableIter iter; |
| gpointer key, val; |
| |
| g_hash_table_iter_init(&iter, bluetooth_networks); |
| while (g_hash_table_iter_next(&iter, &key, &val)) { |
| struct connman_network *known = val; |
| |
| if (network != known) |
| continue; |
| |
| DBG("network %p", network); |
| |
| return 0; |
| } |
| |
| return -EOPNOTSUPP; |
| } |
| |
| static void pan_remove(struct connman_network *network) |
| { |
| DBG("network %p", network); |
| } |
| |
| static void connect_reply(DBusPendingCall *call, void *user_data) |
| { |
| char *path = user_data; |
| struct connman_network *network; |
| DBusMessage *reply; |
| DBusError error; |
| const char *interface = NULL; |
| int index; |
| |
| network = g_hash_table_lookup(bluetooth_networks, path); |
| if (!network) |
| return; |
| |
| DBG("network %p", network); |
| |
| reply = dbus_pending_call_steal_reply(call); |
| |
| dbus_error_init(&error); |
| |
| if (dbus_set_error_from_message(&error, reply)) { |
| connman_error("%s", error.message); |
| dbus_error_free(&error); |
| |
| goto err; |
| } |
| |
| if (!dbus_message_get_args(reply, &error, DBUS_TYPE_STRING, |
| &interface, DBUS_TYPE_INVALID)) { |
| if (dbus_error_is_set(&error)) { |
| connman_error("%s", error.message); |
| dbus_error_free(&error); |
| } else |
| connman_error("Wrong arguments for connect"); |
| goto err; |
| } |
| |
| if (!interface) |
| goto err; |
| |
| DBG("interface %s", interface); |
| |
| index = connman_inet_ifindex(interface); |
| |
| connman_network_set_index(network, index); |
| |
| connman_network_set_connected(network, true); |
| |
| dbus_message_unref(reply); |
| |
| dbus_pending_call_unref(call); |
| |
| return; |
| err: |
| |
| connman_network_set_connected(network, false); |
| |
| dbus_message_unref(reply); |
| |
| dbus_pending_call_unref(call); |
| } |
| |
| static int pan_connect(struct connman_network *network) |
| { |
| const char *path = connman_network_get_string(network, "Path"); |
| const char *uuid = "nap"; |
| DBusMessage *message; |
| DBusPendingCall *call; |
| |
| DBG("network %p", network); |
| |
| if (!path) |
| return -EINVAL; |
| |
| message = dbus_message_new_method_call(BLUEZ_SERVICE, path, |
| BLUEZ_NETWORK_INTERFACE, CONNECT); |
| if (!message) |
| return -ENOMEM; |
| |
| dbus_message_set_auto_start(message, FALSE); |
| |
| dbus_message_append_args(message, DBUS_TYPE_STRING, &uuid, |
| DBUS_TYPE_INVALID); |
| |
| if (!dbus_connection_send_with_reply(connection, message, |
| &call, TIMEOUT * 10)) { |
| connman_error("Failed to connect service"); |
| dbus_message_unref(message); |
| return -EINVAL; |
| } |
| |
| if (!call) { |
| connman_error("D-Bus connection not available"); |
| dbus_message_unref(message); |
| return -EINVAL; |
| } |
| |
| dbus_pending_call_set_notify(call, connect_reply, g_strdup(path), |
| g_free); |
| |
| dbus_message_unref(message); |
| |
| return -EINPROGRESS; |
| } |
| |
| static void disconnect_reply(DBusPendingCall *call, void *user_data) |
| { |
| char *path = user_data; |
| struct connman_network *network; |
| DBusMessage *reply; |
| DBusError error; |
| |
| network = g_hash_table_lookup(bluetooth_networks, path); |
| if (!network) |
| return; |
| |
| DBG("network %p", network); |
| |
| reply = dbus_pending_call_steal_reply(call); |
| |
| dbus_error_init(&error); |
| |
| if (dbus_set_error_from_message(&error, reply)) { |
| connman_error("%s", error.message); |
| dbus_error_free(&error); |
| goto done; |
| } |
| |
| if (!dbus_message_get_args(reply, &error, DBUS_TYPE_INVALID)) { |
| if (dbus_error_is_set(&error)) { |
| connman_error("%s", error.message); |
| dbus_error_free(&error); |
| } else |
| connman_error("Wrong arguments for disconnect"); |
| goto done; |
| } |
| |
| connman_network_set_connected(network, false); |
| |
| done: |
| dbus_message_unref(reply); |
| |
| dbus_pending_call_unref(call); |
| |
| connman_network_unref(network); |
| } |
| |
| static int pan_disconnect(struct connman_network *network, bool user_initiated) |
| { |
| const char *path = connman_network_get_string(network, "Path"); |
| DBusMessage *message; |
| DBusPendingCall *call; |
| |
| DBG("network %p", network); |
| |
| if (!path) |
| return -EINVAL; |
| |
| message = dbus_message_new_method_call(BLUEZ_SERVICE, path, |
| BLUEZ_NETWORK_INTERFACE, DISCONNECT); |
| if (!message) |
| return -ENOMEM; |
| |
| dbus_message_set_auto_start(message, FALSE); |
| |
| dbus_message_append_args(message, DBUS_TYPE_INVALID); |
| |
| if (!dbus_connection_send_with_reply(connection, message, |
| &call, TIMEOUT)) { |
| connman_error("Failed to disconnect service"); |
| dbus_message_unref(message); |
| return -EINVAL; |
| } |
| |
| if (!call) { |
| connman_error("D-Bus connection not available"); |
| dbus_message_unref(message); |
| return -EINVAL; |
| } |
| |
| connman_network_ref(network); |
| |
| connman_network_set_associating(network, false); |
| |
| dbus_pending_call_set_notify(call, disconnect_reply, g_strdup(path), |
| g_free); |
| |
| dbus_message_unref(message); |
| |
| return 0; |
| } |
| |
| static struct connman_network_driver pan_driver = { |
| .name = "bluetooth_legacy-pan", |
| .type = CONNMAN_NETWORK_TYPE_BLUETOOTH_PAN, |
| .priority = CONNMAN_NETWORK_PRIORITY_LOW, |
| .probe = pan_probe, |
| .remove = pan_remove, |
| .connect = pan_connect, |
| .disconnect = pan_disconnect, |
| }; |
| |
| static gboolean network_changed(DBusConnection *conn, |
| DBusMessage *message, void *user_data) |
| { |
| const char *path = dbus_message_get_path(message); |
| struct connman_network *network; |
| DBusMessageIter iter, value; |
| const char *key; |
| |
| DBG("path %s", path); |
| |
| network = g_hash_table_lookup(bluetooth_networks, path); |
| if (!network) |
| return TRUE; |
| |
| if (!dbus_message_iter_init(message, &iter)) |
| return TRUE; |
| |
| dbus_message_iter_get_basic(&iter, &key); |
| |
| dbus_message_iter_next(&iter); |
| dbus_message_iter_recurse(&iter, &value); |
| |
| if (g_str_equal(key, "Connected")) { |
| dbus_bool_t connected; |
| |
| dbus_message_iter_get_basic(&value, &connected); |
| |
| if (connected) |
| return TRUE; |
| |
| connman_network_set_associating(network, false); |
| connman_network_set_connected(network, false); |
| } |
| |
| return TRUE; |
| } |
| |
| static void extract_properties(DBusMessage *reply, const char **parent, |
| const char **address, |
| const char **name, |
| const char **alias, |
| dbus_bool_t *powered, |
| dbus_bool_t *scanning, |
| DBusMessageIter *uuids, |
| DBusMessageIter *networks) |
| { |
| DBusMessageIter array, dict; |
| |
| if (!dbus_message_iter_init(reply, &array)) |
| return; |
| |
| if (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_ARRAY) |
| return; |
| |
| dbus_message_iter_recurse(&array, &dict); |
| |
| while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) { |
| DBusMessageIter entry, value; |
| const char *key; |
| |
| dbus_message_iter_recurse(&dict, &entry); |
| dbus_message_iter_get_basic(&entry, &key); |
| |
| dbus_message_iter_next(&entry); |
| dbus_message_iter_recurse(&entry, &value); |
| |
| if (g_str_equal(key, "Adapter")) { |
| if (parent) |
| dbus_message_iter_get_basic(&value, parent); |
| } else if (g_str_equal(key, "Address")) { |
| if (address) |
| dbus_message_iter_get_basic(&value, address); |
| } else if (g_str_equal(key, "Name")) { |
| if (name) |
| dbus_message_iter_get_basic(&value, name); |
| } else if (g_str_equal(key, "Alias")) { |
| if (alias) |
| dbus_message_iter_get_basic(&value, alias); |
| } else if (g_str_equal(key, "Powered")) { |
| if (powered) |
| dbus_message_iter_get_basic(&value, powered); |
| } else if (g_str_equal(key, "Discovering")) { |
| if (scanning) |
| dbus_message_iter_get_basic(&value, scanning); |
| } else if (g_str_equal(key, "Devices")) { |
| if (networks) |
| memcpy(networks, &value, sizeof(value)); |
| } else if (g_str_equal(key, "UUIDs")) { |
| if (uuids) |
| memcpy(uuids, &value, sizeof(value)); |
| } |
| |
| dbus_message_iter_next(&dict); |
| } |
| } |
| |
| static dbus_bool_t has_pan(DBusMessageIter *array) |
| { |
| DBusMessageIter value; |
| |
| if (dbus_message_iter_get_arg_type(array) != DBUS_TYPE_ARRAY) |
| return FALSE; |
| |
| dbus_message_iter_recurse(array, &value); |
| |
| while (dbus_message_iter_get_arg_type(&value) == DBUS_TYPE_STRING) { |
| const char *uuid; |
| |
| dbus_message_iter_get_basic(&value, &uuid); |
| |
| if (g_strcmp0(uuid, UUID_NAP) == 0) |
| return TRUE; |
| |
| dbus_message_iter_next(&value); |
| } |
| |
| return FALSE; |
| } |
| |
| static void network_properties_reply(DBusPendingCall *call, void *user_data) |
| { |
| char *path = user_data; |
| struct connman_device *device; |
| struct connman_network *network; |
| DBusMessage *reply; |
| DBusMessageIter uuids; |
| const char *parent = NULL, *address = NULL, *name = NULL; |
| struct ether_addr addr; |
| char ident[13]; |
| |
| reply = dbus_pending_call_steal_reply(call); |
| |
| extract_properties(reply, &parent, &address, NULL, &name, |
| NULL, NULL, &uuids, NULL); |
| |
| if (!parent) |
| goto done; |
| |
| device = g_hash_table_lookup(bluetooth_devices, parent); |
| if (!device) |
| goto done; |
| |
| if (!address) |
| goto done; |
| |
| ether_aton_r(address, &addr); |
| |
| snprintf(ident, 13, "%02x%02x%02x%02x%02x%02x", |
| addr.ether_addr_octet[0], |
| addr.ether_addr_octet[1], |
| addr.ether_addr_octet[2], |
| addr.ether_addr_octet[3], |
| addr.ether_addr_octet[4], |
| addr.ether_addr_octet[5]); |
| |
| if (!has_pan(&uuids)) |
| goto done; |
| |
| network = connman_device_get_network(device, ident); |
| if (network) |
| goto done; |
| |
| network = connman_network_create(ident, |
| CONNMAN_NETWORK_TYPE_BLUETOOTH_PAN); |
| if (!network) |
| goto done; |
| |
| connman_network_set_string(network, "Path", path); |
| |
| connman_network_set_name(network, name); |
| |
| g_hash_table_replace(bluetooth_networks, g_strdup(path), network); |
| |
| connman_device_add_network(device, network); |
| |
| connman_network_set_group(network, ident); |
| |
| done: |
| dbus_message_unref(reply); |
| |
| dbus_pending_call_unref(call); |
| } |
| |
| static void add_network(const char *path) |
| { |
| DBusMessage *message; |
| DBusPendingCall *call; |
| |
| DBG("path %s", path); |
| |
| message = dbus_message_new_method_call(BLUEZ_SERVICE, path, |
| BLUEZ_DEVICE_INTERFACE, GET_PROPERTIES); |
| if (!message) |
| return; |
| |
| dbus_message_set_auto_start(message, FALSE); |
| |
| if (!dbus_connection_send_with_reply(connection, message, |
| &call, TIMEOUT)) { |
| connman_error("Failed to get network properties for %s", path); |
| goto done; |
| } |
| |
| if (!call) { |
| connman_error("D-Bus connection not available"); |
| goto done; |
| } |
| |
| dbus_pending_call_set_notify(call, network_properties_reply, |
| g_strdup(path), g_free); |
| |
| done: |
| dbus_message_unref(message); |
| } |
| |
| static void check_networks(DBusMessageIter *array) |
| { |
| DBusMessageIter value; |
| |
| if (dbus_message_iter_get_arg_type(array) != DBUS_TYPE_ARRAY) |
| return; |
| |
| dbus_message_iter_recurse(array, &value); |
| |
| while (dbus_message_iter_get_arg_type(&value) == DBUS_TYPE_OBJECT_PATH) { |
| const char *path; |
| |
| dbus_message_iter_get_basic(&value, &path); |
| |
| add_network(path); |
| |
| dbus_message_iter_next(&value); |
| } |
| } |
| |
| static void check_pending_networks(const char *adapter) |
| { |
| GSList *networks, *list; |
| |
| networks = g_hash_table_lookup(pending_networks, adapter); |
| if (!networks) |
| return; |
| |
| for (list = networks; list; list = list->next) { |
| char *path = list->data; |
| |
| add_network(path); |
| } |
| |
| g_hash_table_remove(pending_networks, adapter); |
| } |
| |
| static gboolean adapter_changed(DBusConnection *conn, |
| DBusMessage *message, void *user_data) |
| { |
| const char *path = dbus_message_get_path(message); |
| struct connman_device *device; |
| DBusMessageIter iter, value; |
| const char *key; |
| |
| DBG("path %s", path); |
| |
| device = g_hash_table_lookup(bluetooth_devices, path); |
| if (!device) |
| return TRUE; |
| |
| if (!dbus_message_iter_init(message, &iter)) |
| return TRUE; |
| |
| dbus_message_iter_get_basic(&iter, &key); |
| |
| dbus_message_iter_next(&iter); |
| dbus_message_iter_recurse(&iter, &value); |
| |
| if (g_str_equal(key, "Powered")) { |
| dbus_bool_t val; |
| |
| dbus_message_iter_get_basic(&value, &val); |
| connman_device_set_powered(device, val); |
| if (val) |
| check_pending_networks(path); |
| } else if (g_str_equal(key, "Discovering")) { |
| dbus_bool_t val; |
| |
| dbus_message_iter_get_basic(&value, &val); |
| connman_device_set_scanning(device, |
| CONNMAN_SERVICE_TYPE_BLUETOOTH, val); |
| } else if (g_str_equal(key, "Devices")) { |
| check_networks(&value); |
| } |
| |
| return TRUE; |
| } |
| |
| static gboolean device_removed(DBusConnection *conn, |
| DBusMessage *message, void *user_data) |
| { |
| const char *network_path; |
| struct connman_network *network; |
| struct connman_device *device; |
| DBusMessageIter iter; |
| |
| DBG(""); |
| |
| if (!dbus_message_iter_init(message, &iter)) |
| return TRUE; |
| |
| dbus_message_iter_get_basic(&iter, &network_path); |
| |
| network = g_hash_table_lookup(bluetooth_networks, network_path); |
| if (!network) |
| return TRUE; |
| |
| device = connman_network_get_device(network); |
| if (!device) |
| return TRUE; |
| |
| g_hash_table_remove(bluetooth_networks, network_path); |
| |
| return TRUE; |
| } |
| |
| static gboolean device_changed(DBusConnection *conn, |
| DBusMessage *message, void *user_data) |
| { |
| const char *path = dbus_message_get_path(message); |
| DBusMessageIter iter, value; |
| const char *key; |
| |
| DBG("path %s", path); |
| |
| if (!dbus_message_iter_init(message, &iter)) |
| return TRUE; |
| |
| dbus_message_iter_get_basic(&iter, &key); |
| |
| dbus_message_iter_next(&iter); |
| dbus_message_iter_recurse(&iter, &value); |
| |
| DBG("key %s", key); |
| |
| if (g_str_equal(key, "UUIDs")) |
| add_network(path); |
| |
| return TRUE; |
| } |
| |
| static void remove_device_networks(struct connman_device *device) |
| { |
| GHashTableIter iter; |
| gpointer key, value; |
| GSList *key_list = NULL; |
| GSList *list; |
| |
| if (!bluetooth_networks) |
| return; |
| |
| g_hash_table_iter_init(&iter, bluetooth_networks); |
| |
| while (g_hash_table_iter_next(&iter, &key, &value)) { |
| struct connman_network *network = value; |
| |
| if (connman_network_get_device(network) != device) |
| continue; |
| |
| key_list = g_slist_prepend(key_list, key); |
| } |
| |
| for (list = key_list; list; list = list->next) { |
| const char *network_path = list->data; |
| |
| g_hash_table_remove(bluetooth_networks, network_path); |
| } |
| |
| g_slist_free(key_list); |
| } |
| |
| static void add_pending_networks(const char *adapter, DBusMessageIter *array) |
| { |
| DBusMessageIter value; |
| GSList *list = NULL; |
| |
| if (dbus_message_iter_get_arg_type(array) != DBUS_TYPE_ARRAY) |
| return; |
| |
| dbus_message_iter_recurse(array, &value); |
| |
| while (dbus_message_iter_get_arg_type(&value) == DBUS_TYPE_OBJECT_PATH) { |
| const char *path; |
| |
| dbus_message_iter_get_basic(&value, &path); |
| |
| list = g_slist_prepend(list, g_strdup(path)); |
| |
| dbus_message_iter_next(&value); |
| } |
| |
| if (!list) |
| return; |
| |
| g_hash_table_replace(pending_networks, g_strdup(adapter), list); |
| } |
| |
| static void adapter_properties_reply(DBusPendingCall *call, void *user_data) |
| { |
| char *path = user_data; |
| struct connman_device *device; |
| DBusMessage *reply; |
| DBusMessageIter networks; |
| const char *address = NULL, *name = NULL; |
| dbus_bool_t powered = FALSE, scanning = FALSE; |
| struct ether_addr addr; |
| char ident[13]; |
| |
| DBG("path %s", path); |
| |
| reply = dbus_pending_call_steal_reply(call); |
| |
| if (!path) |
| goto done; |
| |
| extract_properties(reply, NULL, &address, &name, NULL, |
| &powered, &scanning, NULL, &networks); |
| |
| if (!address) |
| goto done; |
| |
| if (g_strcmp0(address, "00:00:00:00:00:00") == 0) |
| goto done; |
| |
| device = g_hash_table_lookup(bluetooth_devices, path); |
| if (device) |
| goto update; |
| |
| ether_aton_r(address, &addr); |
| |
| snprintf(ident, 13, "%02x%02x%02x%02x%02x%02x", |
| addr.ether_addr_octet[0], |
| addr.ether_addr_octet[1], |
| addr.ether_addr_octet[2], |
| addr.ether_addr_octet[3], |
| addr.ether_addr_octet[4], |
| addr.ether_addr_octet[5]); |
| |
| device = connman_device_create("bluetooth_legacy", |
| CONNMAN_DEVICE_TYPE_BLUETOOTH); |
| if (!device) |
| goto done; |
| |
| g_hash_table_insert(bluetooth_devices, g_strdup(path), device); |
| |
| connman_device_set_ident(device, ident); |
| |
| connman_device_set_string(device, "Path", path); |
| |
| if (connman_device_register(device) < 0) { |
| connman_device_unref(device); |
| g_hash_table_remove(bluetooth_devices, path); |
| goto done; |
| } |
| |
| update: |
| connman_device_set_string(device, "Address", address); |
| connman_device_set_string(device, "Name", name); |
| connman_device_set_string(device, "Path", path); |
| |
| connman_device_set_powered(device, powered); |
| connman_device_set_scanning(device, |
| CONNMAN_SERVICE_TYPE_BLUETOOTH, scanning); |
| |
| if (!powered) { |
| remove_device_networks(device); |
| add_pending_networks(path, &networks); |
| } else |
| check_networks(&networks); |
| |
| done: |
| dbus_message_unref(reply); |
| |
| dbus_pending_call_unref(call); |
| } |
| |
| static void add_adapter(DBusConnection *conn, const char *path) |
| { |
| DBusMessage *message; |
| DBusPendingCall *call; |
| |
| DBG("path %s", path); |
| |
| message = dbus_message_new_method_call(BLUEZ_SERVICE, path, |
| BLUEZ_ADAPTER_INTERFACE, GET_PROPERTIES); |
| if (!message) |
| return; |
| |
| dbus_message_set_auto_start(message, FALSE); |
| |
| if (!dbus_connection_send_with_reply(conn, message, &call, TIMEOUT)) { |
| connman_error("Failed to get adapter properties for %s", path); |
| goto done; |
| } |
| |
| if (!call) { |
| connman_error("D-Bus connection not available"); |
| goto done; |
| } |
| |
| dbus_pending_call_set_notify(call, adapter_properties_reply, |
| g_strdup(path), g_free); |
| |
| done: |
| dbus_message_unref(message); |
| } |
| |
| static gboolean adapter_added(DBusConnection *conn, DBusMessage *message, |
| void *user_data) |
| { |
| const char *path; |
| |
| dbus_message_get_args(message, NULL, DBUS_TYPE_OBJECT_PATH, &path, |
| DBUS_TYPE_INVALID); |
| add_adapter(conn, path); |
| return TRUE; |
| } |
| |
| static void remove_adapter(DBusConnection *conn, const char *path) |
| { |
| DBG("path %s", path); |
| |
| g_hash_table_remove(bluetooth_devices, path); |
| g_hash_table_remove(pending_networks, path); |
| } |
| |
| static gboolean adapter_removed(DBusConnection *conn, DBusMessage *message, |
| void *user_data) |
| { |
| const char *path; |
| |
| dbus_message_get_args(message, NULL, DBUS_TYPE_OBJECT_PATH, &path, |
| DBUS_TYPE_INVALID); |
| remove_adapter(conn, path); |
| return TRUE; |
| } |
| |
| static void list_adapters_reply(DBusPendingCall *call, void *user_data) |
| { |
| DBusMessage *reply; |
| DBusError error; |
| char **adapters; |
| int i, num_adapters; |
| |
| DBG(""); |
| |
| reply = dbus_pending_call_steal_reply(call); |
| |
| dbus_error_init(&error); |
| |
| if (dbus_set_error_from_message(&error, reply)) { |
| connman_error("%s", error.message); |
| dbus_error_free(&error); |
| goto done; |
| } |
| |
| if (!dbus_message_get_args(reply, &error, DBUS_TYPE_ARRAY, |
| DBUS_TYPE_OBJECT_PATH, &adapters, |
| &num_adapters, DBUS_TYPE_INVALID)) { |
| if (dbus_error_is_set(&error)) { |
| connman_error("%s", error.message); |
| dbus_error_free(&error); |
| } else |
| connman_error("Wrong arguments for adapter list"); |
| goto done; |
| } |
| |
| for (i = 0; i < num_adapters; i++) |
| add_adapter(connection, adapters[i]); |
| |
| g_strfreev(adapters); |
| |
| done: |
| dbus_message_unref(reply); |
| |
| dbus_pending_call_unref(call); |
| } |
| |
| static void unregister_device(gpointer data) |
| { |
| struct connman_device *device = data; |
| |
| DBG(""); |
| |
| remove_device_networks(device); |
| |
| connman_device_unregister(device); |
| connman_device_unref(device); |
| } |
| |
| static void remove_network(gpointer data) |
| { |
| struct connman_network *network = data; |
| struct connman_device *device; |
| |
| DBG("network %p", network); |
| |
| device = connman_network_get_device(network); |
| if (device) |
| connman_device_remove_network(device, network); |
| |
| connman_network_unref(network); |
| } |
| |
| static void remove_pending_networks(gpointer data) |
| { |
| GSList *list = data; |
| |
| g_slist_free_full(list, g_free); |
| } |
| |
| static void bluetooth_connect(DBusConnection *conn, void *user_data) |
| { |
| DBusMessage *message; |
| DBusPendingCall *call; |
| |
| DBG("connection %p", conn); |
| |
| bluetooth_devices = g_hash_table_new_full(g_str_hash, g_str_equal, |
| g_free, unregister_device); |
| |
| bluetooth_networks = g_hash_table_new_full(g_str_hash, g_str_equal, |
| g_free, remove_network); |
| |
| pending_networks = g_hash_table_new_full(g_str_hash, g_str_equal, |
| g_free, remove_pending_networks); |
| |
| message = dbus_message_new_method_call(BLUEZ_SERVICE, "/", |
| BLUEZ_MANAGER_INTERFACE, LIST_ADAPTERS); |
| if (!message) |
| return; |
| |
| dbus_message_set_auto_start(message, FALSE); |
| |
| if (!dbus_connection_send_with_reply(conn, message, &call, TIMEOUT)) { |
| connman_error("Failed to get Bluetooth adapters"); |
| goto done; |
| } |
| |
| if (!call) { |
| connman_error("D-Bus connection not available"); |
| goto done; |
| } |
| |
| dbus_pending_call_set_notify(call, list_adapters_reply, NULL, NULL); |
| |
| done: |
| dbus_message_unref(message); |
| } |
| |
| static void bluetooth_disconnect(DBusConnection *conn, void *user_data) |
| { |
| DBG("connection %p", conn); |
| |
| if (!bluetooth_devices) |
| return; |
| |
| g_hash_table_destroy(bluetooth_networks); |
| bluetooth_networks = NULL; |
| g_hash_table_destroy(bluetooth_devices); |
| bluetooth_devices = NULL; |
| g_hash_table_destroy(pending_networks); |
| pending_networks = NULL; |
| } |
| |
| static int bluetooth_probe(struct connman_device *device) |
| { |
| GHashTableIter iter; |
| gpointer key, value; |
| |
| DBG("device %p", device); |
| |
| if (!bluetooth_devices) |
| return -ENOTSUP; |
| |
| g_hash_table_iter_init(&iter, bluetooth_devices); |
| |
| while (g_hash_table_iter_next(&iter, &key, &value)) { |
| struct connman_device *device_pan = value; |
| |
| if (device == device_pan) |
| return 0; |
| } |
| |
| return -ENOTSUP; |
| } |
| |
| static void bluetooth_remove(struct connman_device *device) |
| { |
| DBG("device %p", device); |
| } |
| |
| static void powered_reply(DBusPendingCall *call, void *user_data) |
| { |
| DBusError error; |
| DBusMessage *reply; |
| |
| DBG(""); |
| |
| reply = dbus_pending_call_steal_reply(call); |
| |
| dbus_error_init(&error); |
| |
| if (dbus_set_error_from_message(&error, reply)) { |
| connman_error("%s", error.message); |
| dbus_error_free(&error); |
| dbus_message_unref(reply); |
| dbus_pending_call_unref(call); |
| return; |
| } |
| |
| dbus_message_unref(reply); |
| dbus_pending_call_unref(call); |
| |
| add_adapter(connection, user_data); |
| } |
| |
| static int change_powered(DBusConnection *conn, const char *path, |
| dbus_bool_t powered) |
| { |
| DBusMessage *message; |
| DBusMessageIter iter; |
| DBusPendingCall *call; |
| |
| DBG(""); |
| |
| if (!path) |
| return -EINVAL; |
| |
| message = dbus_message_new_method_call(BLUEZ_SERVICE, path, |
| BLUEZ_ADAPTER_INTERFACE, SET_PROPERTY); |
| if (!message) |
| return -ENOMEM; |
| |
| dbus_message_set_auto_start(message, FALSE); |
| |
| dbus_message_iter_init_append(message, &iter); |
| connman_dbus_property_append_basic(&iter, "Powered", |
| DBUS_TYPE_BOOLEAN, &powered); |
| |
| if (!dbus_connection_send_with_reply(conn, message, &call, TIMEOUT)) { |
| connman_error("Failed to change Powered property"); |
| dbus_message_unref(message); |
| return -EINVAL; |
| } |
| |
| if (!call) { |
| connman_error("D-Bus connection not available"); |
| dbus_message_unref(message); |
| return -EINVAL; |
| } |
| |
| dbus_pending_call_set_notify(call, powered_reply, |
| g_strdup(path), g_free); |
| |
| dbus_message_unref(message); |
| |
| return -EINPROGRESS; |
| } |
| |
| static int bluetooth_enable(struct connman_device *device) |
| { |
| const char *path = connman_device_get_string(device, "Path"); |
| |
| DBG("device %p", device); |
| |
| return change_powered(connection, path, TRUE); |
| } |
| |
| static int bluetooth_disable(struct connman_device *device) |
| { |
| const char *path = connman_device_get_string(device, "Path"); |
| |
| DBG("device %p", device); |
| |
| return change_powered(connection, path, FALSE); |
| } |
| |
| static struct connman_device_driver bluetooth_driver = { |
| .name = "bluetooth_legacy", |
| .type = CONNMAN_DEVICE_TYPE_BLUETOOTH, |
| .probe = bluetooth_probe, |
| .remove = bluetooth_remove, |
| .enable = bluetooth_enable, |
| .disable = bluetooth_disable, |
| }; |
| |
| static int tech_probe(struct connman_technology *technology) |
| { |
| return 0; |
| } |
| |
| static void tech_remove(struct connman_technology *technology) |
| { |
| } |
| |
| static void server_register_reply(DBusPendingCall *call, void *user_data) |
| { |
| struct connman_technology *technology = user_data; |
| DBusError error; |
| DBusMessage *reply; |
| |
| DBG(""); |
| |
| reply = dbus_pending_call_steal_reply(call); |
| |
| dbus_error_init(&error); |
| |
| if (dbus_set_error_from_message(&error, reply)) { |
| connman_error("%s", error.message); |
| dbus_error_free(&error); |
| dbus_message_unref(reply); |
| dbus_pending_call_unref(call); |
| return; |
| } |
| |
| dbus_message_unref(reply); |
| dbus_pending_call_unref(call); |
| |
| connman_technology_tethering_notify(technology, true); |
| } |
| |
| static void server_unregister_reply(DBusPendingCall *call, void *user_data) |
| { |
| struct connman_technology *technology = user_data; |
| DBusError error; |
| DBusMessage *reply; |
| |
| DBG(""); |
| |
| reply = dbus_pending_call_steal_reply(call); |
| |
| dbus_error_init(&error); |
| |
| if (dbus_set_error_from_message(&error, reply)) { |
| connman_error("%s", error.message); |
| dbus_error_free(&error); |
| dbus_message_unref(reply); |
| dbus_pending_call_unref(call); |
| return; |
| } |
| |
| dbus_message_unref(reply); |
| dbus_pending_call_unref(call); |
| |
| connman_technology_tethering_notify(technology, false); |
| } |
| |
| |
| static void server_register(const char *path, const char *uuid, |
| struct connman_technology *technology, |
| const char *bridge, bool enabled) |
| { |
| DBusMessage *message; |
| DBusPendingCall *call; |
| char *command; |
| |
| DBG("path %s enabled %d", path, enabled); |
| |
| command = enabled ? REGISTER : UNREGISTER; |
| |
| message = dbus_message_new_method_call(BLUEZ_SERVICE, path, |
| BLUEZ_NETWORK_SERVER, command); |
| if (!message) |
| return; |
| |
| dbus_message_set_auto_start(message, FALSE); |
| |
| dbus_message_append_args(message, DBUS_TYPE_STRING, &uuid, |
| DBUS_TYPE_INVALID); |
| |
| if (enabled) |
| dbus_message_append_args(message, DBUS_TYPE_STRING, &bridge, |
| DBUS_TYPE_INVALID); |
| |
| if (!dbus_connection_send_with_reply(connection, message, |
| &call, TIMEOUT)) { |
| connman_error("Failed to enable PAN server"); |
| dbus_message_unref(message); |
| return; |
| } |
| |
| if (!call) { |
| connman_error("D-Bus connection not available"); |
| dbus_message_unref(message); |
| return; |
| } |
| |
| if (enabled) |
| dbus_pending_call_set_notify(call, server_register_reply, |
| technology, NULL); |
| else |
| dbus_pending_call_set_notify(call, server_unregister_reply, |
| technology, NULL); |
| |
| dbus_message_unref(message); |
| } |
| |
| struct tethering_info { |
| struct connman_technology *technology; |
| const char *bridge; |
| }; |
| |
| static void enable_nap(gpointer key, gpointer value, gpointer user_data) |
| { |
| struct tethering_info *info = user_data; |
| struct connman_device *device = value; |
| const char *path; |
| |
| DBG(""); |
| |
| path = connman_device_get_string(device, "Path"); |
| |
| server_register(path, "nap", info->technology, info->bridge, true); |
| } |
| |
| static void disable_nap(gpointer key, gpointer value, gpointer user_data) |
| { |
| struct tethering_info *info = user_data; |
| struct connman_device *device = value; |
| const char *path; |
| |
| DBG(""); |
| |
| path = connman_device_get_string(device, "Path"); |
| |
| server_register(path, "nap", info->technology, info->bridge, false); |
| } |
| |
| static int tech_set_tethering(struct connman_technology *technology, |
| const char *identifier, const char *passphrase, |
| const char *bridge, bool enabled) |
| { |
| struct tethering_info info = { |
| .technology = technology, |
| .bridge = bridge, |
| }; |
| |
| DBG("bridge %s", bridge); |
| |
| if (!bluetooth_devices) |
| return -ENOTCONN; |
| |
| if (enabled) |
| g_hash_table_foreach(bluetooth_devices, enable_nap, &info); |
| else |
| g_hash_table_foreach(bluetooth_devices, disable_nap, &info); |
| |
| return 0; |
| } |
| |
| static struct connman_technology_driver tech_driver = { |
| .name = "bluetooth_legacy", |
| .type = CONNMAN_SERVICE_TYPE_BLUETOOTH, |
| .priority = -10, |
| .probe = tech_probe, |
| .remove = tech_remove, |
| .set_tethering = tech_set_tethering, |
| }; |
| |
| static guint watch; |
| static guint added_watch; |
| static guint removed_watch; |
| static guint adapter_watch; |
| static guint device_watch; |
| static guint device_removed_watch; |
| static guint network_watch; |
| |
| static int bluetooth_init(void) |
| { |
| int err; |
| |
| connection = connman_dbus_get_connection(); |
| if (!connection) |
| return -EIO; |
| |
| watch = g_dbus_add_service_watch(connection, BLUEZ_SERVICE, |
| bluetooth_connect, bluetooth_disconnect, NULL, NULL); |
| |
| added_watch = g_dbus_add_signal_watch(connection, BLUEZ_SERVICE, NULL, |
| BLUEZ_MANAGER_INTERFACE, |
| ADAPTER_ADDED, adapter_added, |
| NULL, NULL); |
| |
| removed_watch = g_dbus_add_signal_watch(connection, BLUEZ_SERVICE, NULL, |
| BLUEZ_MANAGER_INTERFACE, |
| ADAPTER_REMOVED, adapter_removed, |
| NULL, NULL); |
| |
| adapter_watch = g_dbus_add_signal_watch(connection, BLUEZ_SERVICE, |
| NULL, BLUEZ_ADAPTER_INTERFACE, |
| PROPERTY_CHANGED, adapter_changed, |
| NULL, NULL); |
| |
| device_removed_watch = g_dbus_add_signal_watch(connection, |
| BLUEZ_SERVICE, NULL, |
| BLUEZ_ADAPTER_INTERFACE, |
| DEVICE_REMOVED, device_removed, |
| NULL, NULL); |
| |
| device_watch = g_dbus_add_signal_watch(connection, BLUEZ_SERVICE, NULL, |
| BLUEZ_DEVICE_INTERFACE, |
| PROPERTY_CHANGED, device_changed, |
| NULL, NULL); |
| |
| network_watch = g_dbus_add_signal_watch(connection, BLUEZ_SERVICE, |
| NULL, BLUEZ_NETWORK_INTERFACE, |
| PROPERTY_CHANGED, network_changed, |
| NULL, NULL); |
| |
| if (watch == 0 || added_watch == 0 || removed_watch == 0 |
| || adapter_watch == 0 || network_watch == 0 |
| || device_watch == 0 |
| || device_removed_watch == 0) { |
| err = -EIO; |
| goto remove; |
| } |
| |
| err = connman_network_driver_register(&pan_driver); |
| if (err < 0) |
| goto remove; |
| |
| err = connman_device_driver_register(&bluetooth_driver); |
| if (err < 0) { |
| connman_network_driver_unregister(&pan_driver); |
| goto remove; |
| } |
| |
| err = connman_technology_driver_register(&tech_driver); |
| if (err < 0) { |
| connman_device_driver_unregister(&bluetooth_driver); |
| connman_network_driver_unregister(&pan_driver); |
| goto remove; |
| } |
| |
| return 0; |
| |
| remove: |
| g_dbus_remove_watch(connection, watch); |
| g_dbus_remove_watch(connection, added_watch); |
| g_dbus_remove_watch(connection, removed_watch); |
| g_dbus_remove_watch(connection, adapter_watch); |
| g_dbus_remove_watch(connection, device_removed_watch); |
| g_dbus_remove_watch(connection, device_watch); |
| g_dbus_remove_watch(connection, network_watch); |
| |
| dbus_connection_unref(connection); |
| |
| return err; |
| } |
| |
| static void bluetooth_exit(void) |
| { |
| g_dbus_remove_watch(connection, watch); |
| g_dbus_remove_watch(connection, added_watch); |
| g_dbus_remove_watch(connection, removed_watch); |
| g_dbus_remove_watch(connection, adapter_watch); |
| g_dbus_remove_watch(connection, device_removed_watch); |
| g_dbus_remove_watch(connection, device_watch); |
| g_dbus_remove_watch(connection, network_watch); |
| |
| /* |
| * We unset the disabling of the Bluetooth device when shutting down |
| * so that non-PAN BT connections are not affected. |
| */ |
| bluetooth_driver.disable = NULL; |
| |
| bluetooth_disconnect(connection, NULL); |
| |
| connman_technology_driver_unregister(&tech_driver); |
| |
| connman_device_driver_unregister(&bluetooth_driver); |
| connman_network_driver_unregister(&pan_driver); |
| |
| dbus_connection_unref(connection); |
| } |
| |
| CONNMAN_PLUGIN_DEFINE(bluetooth_legacy, "Bluetooth technology plugin (legacy)", |
| VERSION, CONNMAN_PLUGIN_PRIORITY_LOW, |
| bluetooth_init, bluetooth_exit) |