/* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2006-2007 Nokia Corporation * Copyright (C) 2004-2009 Marcel Holtmann * * * 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, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "logging.h" #include "textfile.h" #include "hcid.h" #include "sdpd.h" #include "sdp-xml.h" #include "manager.h" #include "adapter.h" #include "device.h" #include "dbus-common.h" #include "dbus-hci.h" #include "error.h" #include "glib-helper.h" #include "agent.h" #include "storage.h" #define NUM_ELEMENTS(table) (sizeof(table)/sizeof(const char *)) #define IO_CAPABILITY_DISPLAYONLY 0x00 #define IO_CAPABILITY_DISPLAYYESNO 0x01 #define IO_CAPABILITY_KEYBOARDONLY 0x02 #define IO_CAPABILITY_NOINPUTOUTPUT 0x03 #define IO_CAPABILITY_INVALID 0xFF #define check_address(address) bachk(address) static DBusConnection *connection = NULL; static GSList *adapter_drivers = NULL; struct session_req { struct btd_adapter *adapter; DBusConnection *conn; /* Connection reference */ DBusMessage *msg; /* Unreplied message ref */ char *owner; /* Bus name of the owner */ guint id; /* Listener id */ uint8_t mode; /* Requested mode */ int refcount; /* Session refcount */ }; struct service_auth { service_auth_cb cb; void *user_data; }; struct btd_adapter { uint16_t dev_id; int up; char *path; /* adapter object path */ bdaddr_t bdaddr; /* adapter Bluetooth Address */ guint discov_timeout_id; /* discoverable timeout id */ uint32_t discov_timeout; /* discoverable time(sec) */ guint pairable_timeout_id; /* pairable timeout id */ uint32_t pairable_timeout; /* pairable time(sec) */ uint8_t scan_mode; /* scan mode: SCAN_DISABLED, SCAN_PAGE, * SCAN_INQUIRY */ uint8_t mode; /* off, connectable, discoverable, * limited */ uint8_t global_mode; /* last valid global mode */ int state; /* standard inq, periodic inq, name * resloving */ GSList *found_devices; GSList *oor_devices; /* out of range device list */ DBusMessage *discovery_cancel; /* discovery cancel message request */ GSList *passkey_agents; struct agent *agent; /* For the new API */ GSList *connections; /* Connected devices */ GSList *devices; /* Devices structure pointers */ GSList *mode_sessions; /* Request Mode sessions */ GSList *disc_sessions; /* Discovery sessions */ guint scheduler_id; /* Scheduler handle */ struct hci_dev dev; /* hci info */ gboolean pairable; /* pairable state */ gboolean initialized; gboolean already_up; /* adapter was already up on init */ gboolean off_requested; /* DEVDOWN ioctl was called */ }; static void adapter_set_pairable_timeout(struct btd_adapter *adapter, guint interval); static inline DBusMessage *invalid_args(DBusMessage *msg) { return g_dbus_create_error(msg, ERROR_INTERFACE ".InvalidArguments", "Invalid arguments in method call"); } static inline DBusMessage *not_available(DBusMessage *msg) { return g_dbus_create_error(msg, ERROR_INTERFACE ".NotAvailable", "Not Available"); } static inline DBusMessage *adapter_not_ready(DBusMessage *msg) { return g_dbus_create_error(msg, ERROR_INTERFACE ".NotReady", "Adapter is not ready"); } static inline DBusMessage *no_such_adapter(DBusMessage *msg) { return g_dbus_create_error(msg, ERROR_INTERFACE ".NoSuchAdapter", "No such adapter"); } static inline DBusMessage *failed_strerror(DBusMessage *msg, int err) { return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", strerror(err)); } static inline DBusMessage *in_progress(DBusMessage *msg, const char *str) { return g_dbus_create_error(msg, ERROR_INTERFACE ".InProgress", str); } static inline DBusMessage *not_in_progress(DBusMessage *msg, const char *str) { return g_dbus_create_error(msg, ERROR_INTERFACE ".NotInProgress", str); } static inline DBusMessage *not_authorized(DBusMessage *msg) { return g_dbus_create_error(msg, ERROR_INTERFACE ".NotAuthorized", "Not authorized"); } static inline DBusMessage *unsupported_major_class(DBusMessage *msg) { return g_dbus_create_error(msg, ERROR_INTERFACE ".UnsupportedMajorClass", "Unsupported Major Class"); } static int found_device_cmp(const struct remote_dev_info *d1, const struct remote_dev_info *d2) { int ret; if (bacmp(&d2->bdaddr, BDADDR_ANY)) { ret = bacmp(&d1->bdaddr, &d2->bdaddr); if (ret) return ret; } if (d2->name_status != NAME_ANY) { ret = (d1->name_status - d2->name_status); if (ret) return ret; } return 0; } static void dev_info_free(struct remote_dev_info *dev) { g_free(dev->alias); g_free(dev); } int pending_remote_name_cancel(struct btd_adapter *adapter) { struct remote_dev_info *dev, match; GSList *l; int dd, err = 0; /* find the pending remote name request */ memset(&match, 0, sizeof(struct remote_dev_info)); bacpy(&match.bdaddr, BDADDR_ANY); match.name_status = NAME_REQUESTED; l = g_slist_find_custom(adapter->found_devices, &match, (GCompareFunc) found_device_cmp); if (!l) /* no pending request */ return 0; dd = hci_open_dev(adapter->dev_id); if (dd < 0) return -ENODEV; dev = l->data; if (hci_read_remote_name_cancel(dd, &dev->bdaddr, HCI_REQ_TIMEOUT) < 0) { error("Remote name cancel failed: %s(%d)", strerror(errno), errno); err = -errno; } /* free discovered devices list */ g_slist_foreach(adapter->found_devices, (GFunc) dev_info_free, NULL); g_slist_free(adapter->found_devices); adapter->found_devices = NULL; hci_close_dev(dd); return err; } static int set_limited_discoverable(int dd, const uint8_t *cls, gboolean limited) { uint32_t dev_class; int num = (limited ? 2 : 1); uint8_t lap[] = { 0x33, 0x8b, 0x9e, 0x00, 0x8b, 0x9e }; /* * 1: giac * 2: giac + liac */ if (hci_write_current_iac_lap(dd, num, lap, HCI_REQ_TIMEOUT) < 0) { int err = -errno; error("Can't write current IAC LAP: %s(%d)", strerror(err), err); return err; } if (limited) { if (cls[1] & 0x20) return 0; /* Already limited */ dev_class = (cls[2] << 16) | ((cls[1] | 0x20) << 8) | cls[0]; } else { if (!(cls[1] & 0x20)) return 0; /* Already clear */ dev_class = (cls[2] << 16) | ((cls[1] & 0xdf) << 8) | cls[0]; } if (hci_write_class_of_dev(dd, dev_class, HCI_REQ_TIMEOUT) < 0) { int err = -errno; error("Can't write class of device: %s (%d)", strerror(err), err); return err; } return 0; } static const char *mode2str(uint8_t mode) { switch(mode) { case MODE_OFF: return "off"; case MODE_CONNECTABLE: return "connectable"; case MODE_DISCOVERABLE: case MODE_LIMITED: return "discoverable"; default: return "unknown"; } } static uint8_t get_mode(const bdaddr_t *bdaddr, const char *mode) { if (strcasecmp("off", mode) == 0) return MODE_OFF; else if (strcasecmp("connectable", mode) == 0) return MODE_CONNECTABLE; else if (strcasecmp("discoverable", mode) == 0) return MODE_DISCOVERABLE; else if (strcasecmp("limited", mode) == 0) return MODE_LIMITED; else if (strcasecmp("on", mode) == 0) { char onmode[14], srcaddr[18]; ba2str(bdaddr, srcaddr); if (read_on_mode(srcaddr, onmode, sizeof(onmode)) < 0) return MODE_CONNECTABLE; return get_mode(bdaddr, onmode); } else return MODE_UNKNOWN; } static void adapter_remove_discov_timeout(struct btd_adapter *adapter) { if (!adapter) return; if(adapter->discov_timeout_id == 0) return; g_source_remove(adapter->discov_timeout_id); adapter->discov_timeout_id = 0; } static gboolean discov_timeout_handler(gpointer user_data) { struct btd_adapter *adapter = user_data; int dd; uint8_t scan_enable; uint16_t dev_id = adapter->dev_id; adapter->discov_timeout_id = 0; dd = hci_open_dev(dev_id); if (dd < 0) { error("HCI device open failed: hci%d", dev_id); return FALSE; } scan_enable = adapter->scan_mode & ~SCAN_INQUIRY; hci_send_cmd(dd, OGF_HOST_CTL, OCF_WRITE_SCAN_ENABLE, 1, &scan_enable); hci_close_dev(dd); return FALSE; } static void adapter_set_discov_timeout(struct btd_adapter *adapter, guint interval) { if (adapter->discov_timeout_id) { g_source_remove(adapter->discov_timeout_id); adapter->discov_timeout_id = 0; } if (interval == 0) return; adapter->discov_timeout_id = g_timeout_add_seconds(interval, discov_timeout_handler, adapter); } static uint8_t mode2scan(uint8_t mode) { switch (mode) { case MODE_OFF: return SCAN_DISABLED; case MODE_CONNECTABLE: return SCAN_PAGE; case MODE_DISCOVERABLE: case MODE_LIMITED: return (SCAN_PAGE | SCAN_INQUIRY); default: error("Invalid mode given to mode2scan: %u", mode); return SCAN_PAGE; } } static int set_mode(struct btd_adapter *adapter, uint8_t new_mode) { uint8_t scan_enable; uint8_t current_scan = adapter->scan_mode; int err, dd; const char *modestr; scan_enable = mode2scan(new_mode); dd = hci_open_dev(adapter->dev_id); if (dd < 0) return -EIO; if (!adapter->up && scan_enable != SCAN_DISABLED) { /* Start HCI device */ if (ioctl(dd, HCIDEVUP, adapter->dev_id) == 0) goto done; /* on success */ if (errno != EALREADY) { err = -errno; error("Can't init device hci%d: %s (%d)\n", adapter->dev_id, strerror(errno), errno); hci_close_dev(dd); return err; } } if (adapter->up && scan_enable == SCAN_DISABLED) { struct hci_request rq = { .ogf = OGF_HOST_CTL, .ocf = OCF_WRITE_SCAN_ENABLE, .cparam = &scan_enable, .clen = sizeof(scan_enable), }; hci_send_req(dd, &rq, HCI_REQ_TIMEOUT); if (ioctl(dd, HCIDEVDOWN, adapter->dev_id) < 0) { err = -errno; hci_close_dev(dd); return err; } adapter->off_requested = TRUE; goto done; } if (current_scan != scan_enable) { err = hci_send_cmd(dd, OGF_HOST_CTL, OCF_WRITE_SCAN_ENABLE, 1, &scan_enable); if (err < 0) { hci_close_dev(dd); return err; } } else if ((scan_enable & SCAN_INQUIRY) && (new_mode != adapter->mode)) { adapter_remove_discov_timeout(adapter); if (adapter->discov_timeout) adapter_set_discov_timeout(adapter, adapter->discov_timeout); if (new_mode == MODE_LIMITED) set_limited_discoverable(dd, adapter->dev.class, TRUE); else if (adapter->mode == MODE_LIMITED) set_limited_discoverable(dd, adapter->dev.class, FALSE); } done: modestr = mode2str(new_mode); write_device_mode(&adapter->bdaddr, modestr); hci_close_dev(dd); adapter->mode = new_mode; return 0; } static DBusMessage *set_powered(DBusConnection *conn, DBusMessage *msg, gboolean powered, void *data) { struct btd_adapter *adapter = data; uint8_t mode; int err; mode = powered ? get_mode(&adapter->bdaddr, "on") : MODE_OFF; if (mode == adapter->mode) return dbus_message_new_method_return(msg); err = set_mode(adapter, mode); if (err < 0) return failed_strerror(msg, -err); return dbus_message_new_method_return(msg); } static DBusMessage *set_discoverable(DBusConnection *conn, DBusMessage *msg, gboolean discoverable, void *data) { struct btd_adapter *adapter = data; uint8_t mode; int err; mode = discoverable ? MODE_DISCOVERABLE : MODE_CONNECTABLE; if (mode == MODE_DISCOVERABLE && adapter->pairable) mode = MODE_LIMITED; if (mode == adapter->mode) return dbus_message_new_method_return(msg); err = set_mode(adapter, mode); if (err < 0) return failed_strerror(msg, -err); return dbus_message_new_method_return(msg); } static DBusMessage *set_pairable(DBusConnection *conn, DBusMessage *msg, gboolean pairable, void *data) { struct btd_adapter *adapter = data; uint8_t mode; int err; if (adapter->scan_mode == SCAN_DISABLED) return adapter_not_ready(msg); if (pairable == adapter->pairable) goto done; adapter->pairable = pairable; write_device_pairable(&adapter->bdaddr, pairable); emit_property_changed(connection, adapter->path, ADAPTER_INTERFACE, "Pairable", DBUS_TYPE_BOOLEAN, &pairable); if (pairable && adapter->pairable_timeout) adapter_set_pairable_timeout(adapter, adapter->pairable_timeout); if (!(adapter->scan_mode & SCAN_INQUIRY)) goto done; mode = pairable ? MODE_LIMITED : MODE_DISCOVERABLE; err = set_mode(adapter, mode); if (err < 0 && msg) return failed_strerror(msg, -err); done: return msg ? dbus_message_new_method_return(msg) : NULL; } static gboolean pairable_timeout_handler(void *data) { set_pairable(NULL, NULL, FALSE, data); return FALSE; } static void adapter_set_pairable_timeout(struct btd_adapter *adapter, guint interval) { if (adapter->pairable_timeout_id) { g_source_remove(adapter->pairable_timeout_id); adapter->pairable_timeout_id = 0; } if (interval == 0) return; adapter->pairable_timeout_id = g_timeout_add_seconds(interval, pairable_timeout_handler, adapter); } static struct session_req *find_session(GSList *list, const char *sender) { GSList *l; for (l = list; l; l = l->next) { struct session_req *req = l->data; if (g_str_equal(req->owner, sender)) return req; } return NULL; } static uint8_t get_needed_mode(struct btd_adapter *adapter, uint8_t mode) { GSList *l; if (adapter->global_mode > mode) mode = adapter->global_mode; for (l = adapter->mode_sessions; l; l = l->next) { struct session_req *req = l->data; if (req->mode > mode) mode = req->mode; } return mode; } static void session_remove(struct session_req *req) { struct btd_adapter *adapter = req->adapter; if (req->mode) { uint8_t mode; adapter->mode_sessions = g_slist_remove(adapter->mode_sessions, req); mode = get_needed_mode(adapter, adapter->global_mode); if (mode == adapter->mode) return; debug("Switching to '%s' mode", mode2str(mode)); set_mode(adapter, mode); } else { adapter->disc_sessions = g_slist_remove(adapter->disc_sessions, req); if (adapter->disc_sessions) return; debug("Stopping discovery"); g_slist_foreach(adapter->found_devices, (GFunc) dev_info_free, NULL); g_slist_free(adapter->found_devices); adapter->found_devices = NULL; if (adapter->state & STD_INQUIRY) cancel_discovery(adapter); else if (adapter->scheduler_id) g_source_remove(adapter->scheduler_id); else cancel_periodic_discovery(adapter); } } static void session_free(struct session_req *req) { debug("%s session %p with %s deactivated", req->mode ? "Mode" : "Discovery", req, req->owner); if (req->id) g_dbus_remove_watch(req->conn, req->id); session_remove(req); if (req->msg) dbus_message_unref(req->msg); if (req->conn) dbus_connection_unref(req->conn); g_free(req->owner); g_free(req); } static void session_owner_exit(DBusConnection *conn, void *user_data) { struct session_req *req = user_data; req->id = 0; session_free(req); } static struct session_req *session_ref(struct session_req *req) { req->refcount++; debug("session_ref(%p): ref=%d", req, req->refcount); return req; } static void session_unref(struct session_req *req) { req->refcount--; debug("session_unref(%p): ref=%d", req, req->refcount); if (req->refcount) return; session_free(req); } static struct session_req *create_session(struct btd_adapter *adapter, DBusConnection *conn, DBusMessage *msg, uint8_t mode, GDBusWatchFunction cb) { struct session_req *req; const char *sender = dbus_message_get_sender(msg); req = g_new0(struct session_req, 1); req->adapter = adapter; req->conn = dbus_connection_ref(conn); req->msg = dbus_message_ref(msg); req->owner = g_strdup(dbus_message_get_sender(msg)); req->mode = mode; if (cb) req->id = g_dbus_add_disconnect_watch(conn, sender, cb, req, NULL); info("%s session %p with %s activated", req->mode ? "Mode" : "Discovery", req, sender); return session_ref(req); } static void confirm_mode_cb(struct agent *agent, DBusError *derr, void *data) { struct session_req *req = data; int err; DBusMessage *reply; if (derr && dbus_error_is_set(derr)) { reply = dbus_message_new_error(req->msg, derr->name, derr->message); g_dbus_send_message(req->conn, reply); session_unref(req); return; } err = set_mode(req->adapter, req->mode); if (err < 0) reply = failed_strerror(req->msg, -err); else reply = dbus_message_new_method_return(req->msg); g_dbus_send_message(req->conn, reply); dbus_message_unref(req->msg); req->msg = NULL; if (!find_session(req->adapter->mode_sessions, req->owner)) session_unref(req); } static DBusMessage *set_discoverable_timeout(DBusConnection *conn, DBusMessage *msg, uint32_t timeout, void *data) { struct btd_adapter *adapter = data; const char *path; if (adapter->discov_timeout == timeout && timeout == 0) return dbus_message_new_method_return(msg); if (adapter->scan_mode & SCAN_INQUIRY) adapter_set_discov_timeout(adapter, timeout); adapter->discov_timeout = timeout; write_discoverable_timeout(&adapter->bdaddr, timeout); path = dbus_message_get_path(msg); emit_property_changed(conn, path, ADAPTER_INTERFACE, "DiscoverableTimeout", DBUS_TYPE_UINT32, &timeout); return dbus_message_new_method_return(msg); } static DBusMessage *set_pairable_timeout(DBusConnection *conn, DBusMessage *msg, uint32_t timeout, void *data) { struct btd_adapter *adapter = data; const char *path; if (adapter->pairable_timeout == timeout && timeout == 0) return dbus_message_new_method_return(msg); if (adapter->pairable) adapter_set_pairable_timeout(adapter, timeout); adapter->pairable_timeout = timeout; write_pairable_timeout(&adapter->bdaddr, timeout); path = dbus_message_get_path(msg); emit_property_changed(conn, path, ADAPTER_INTERFACE, "PairableTimeout", DBUS_TYPE_UINT32, &timeout); return dbus_message_new_method_return(msg); } static void update_ext_inquiry_response(int dd, struct hci_dev *dev) { uint8_t fec = 0, data[240]; if (!(dev->features[6] & LMP_EXT_INQ)) return; memset(data, 0, sizeof(data)); if (dev->ssp_mode > 0) create_ext_inquiry_response((char *) dev->name, data); if (hci_write_ext_inquiry_response(dd, fec, data, HCI_REQ_TIMEOUT) < 0) error("Can't write extended inquiry response: %s (%d)", strerror(errno), errno); } void adapter_name_changed(struct btd_adapter *adapter, const char *name) { struct hci_dev *dev = &adapter->dev; int dd; if (strncmp(name, (char *) dev->name, 248) == 0) return; write_local_name(&adapter->bdaddr, (char *) name); strncpy((char *) dev->name, name, 248); dd = hci_open_dev(adapter->dev_id); if (dd >= 0) { update_ext_inquiry_response(dd, dev); hci_close_dev(dd); } emit_property_changed(connection, adapter->path, ADAPTER_INTERFACE, "Name", DBUS_TYPE_STRING, &name); } static int adapter_set_name(struct btd_adapter *adapter, const char *name) { struct hci_dev *dev = &adapter->dev; int dd, err; write_local_name(&adapter->bdaddr, (char *) name); strncpy((char *) dev->name, name, 248); if (!adapter->up) return 0; dd = hci_open_dev(adapter->dev_id); if (dd < 0) { err = -errno; error("Can't open device hci%d: %s (%d)", adapter->dev_id, strerror(err), err); return err; } if (hci_write_local_name(dd, name, HCI_REQ_TIMEOUT) < 0) { err = -errno; error("Can't write name for hci%d: %s (%d)", adapter->dev_id, strerror(err), err); hci_close_dev(dd); return err; } update_ext_inquiry_response(dd, dev); hci_close_dev(dd); return 0; } static DBusMessage *set_name(DBusConnection *conn, DBusMessage *msg, const char *name, void *data) { struct btd_adapter *adapter = data; int ecode; if (!g_utf8_validate(name, -1, NULL)) { error("Name change failed: supplied name isn't valid UTF-8"); return invalid_args(msg); } ecode = adapter_set_name(adapter, name); if (ecode < 0) return failed_strerror(msg, -ecode); emit_property_changed(conn, adapter->path, ADAPTER_INTERFACE, "Name", DBUS_TYPE_STRING, &name); return dbus_message_new_method_return(msg); } struct btd_device *adapter_find_device(struct btd_adapter *adapter, const char *dest) { struct btd_device *device; GSList *l; if (!adapter) return NULL; l = g_slist_find_custom(adapter->devices, dest, (GCompareFunc) device_address_cmp); if (!l) return NULL; device = l->data; return device; } struct btd_device *adapter_find_connection(struct btd_adapter *adapter, uint16_t handle) { GSList *l; for (l = adapter->connections; l; l = l->next) { struct btd_device *device = l->data; if (device_has_connection(device, handle)) return device; } return NULL; } static void adapter_update_devices(struct btd_adapter *adapter) { char **devices; int i; GSList *l; /* Devices */ devices = g_new0(char *, g_slist_length(adapter->devices) + 1); for (i = 0, l = adapter->devices; l; l = l->next, i++) { struct btd_device *dev = l->data; devices[i] = (char *) device_get_path(dev); } emit_array_property_changed(connection, adapter->path, ADAPTER_INTERFACE, "Devices", DBUS_TYPE_OBJECT_PATH, &devices); g_free(devices); } struct btd_device *adapter_create_device(DBusConnection *conn, struct btd_adapter *adapter, const char *address) { struct btd_device *device; const char *path; debug("adapter_create_device(%s)", address); device = device_create(conn, adapter, address); if (!device) return NULL; device_set_temporary(device, TRUE); adapter->devices = g_slist_append(adapter->devices, device); path = device_get_path(device); g_dbus_emit_signal(conn, adapter->path, ADAPTER_INTERFACE, "DeviceCreated", DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID); adapter_update_devices(adapter); return device; } void adapter_remove_device(DBusConnection *conn, struct btd_adapter *adapter, struct btd_device *device) { const gchar *dev_path = device_get_path(device); struct agent *agent; adapter->devices = g_slist_remove(adapter->devices, device); adapter->connections = g_slist_remove(adapter->connections, device); adapter_update_devices(adapter); g_dbus_emit_signal(conn, adapter->path, ADAPTER_INTERFACE, "DeviceRemoved", DBUS_TYPE_OBJECT_PATH, &dev_path, DBUS_TYPE_INVALID); agent = device_get_agent(device); if (agent) { agent_destroy(agent, FALSE); device_set_agent(device, NULL); } device_remove(device, conn, TRUE); } struct btd_device *adapter_get_device(DBusConnection *conn, struct btd_adapter *adapter, const gchar *address) { struct btd_device *device; debug("adapter_get_device(%s)", address); if (!adapter) return NULL; device = adapter_find_device(adapter, address); if (device) return device; return adapter_create_device(conn, adapter, address); } static int start_inquiry(struct btd_adapter *adapter) { inquiry_cp cp; evt_cmd_status rp; struct hci_request rq; uint8_t lap[3] = { 0x33, 0x8b, 0x9e }; int dd, err; pending_remote_name_cancel(adapter); dd = hci_open_dev(adapter->dev_id); if (dd < 0) return dd; memset(&cp, 0, sizeof(cp)); memcpy(&cp.lap, lap, 3); cp.length = 0x08; cp.num_rsp = 0x00; memset(&rq, 0, sizeof(rq)); rq.ogf = OGF_LINK_CTL; rq.ocf = OCF_INQUIRY; rq.cparam = &cp; rq.clen = INQUIRY_CP_SIZE; rq.rparam = &rp; rq.rlen = EVT_CMD_STATUS_SIZE; rq.event = EVT_CMD_STATUS; if (hci_send_req(dd, &rq, HCI_REQ_TIMEOUT) < 0) { err = -errno; error("Unable to start inquiry: %s (%d)", strerror(err), err); hci_close_dev(dd); return err; } if (rp.status) { error("HCI_Inquiry command failed with status 0x%02x", rp.status); hci_close_dev(dd); return -bt_error(rp.status); } hci_close_dev(dd); adapter->state |= RESOLVE_NAME; return 0; } static int start_periodic_inquiry(struct btd_adapter *adapter) { periodic_inquiry_cp cp; struct hci_request rq; uint8_t lap[3] = { 0x33, 0x8b, 0x9e }; uint8_t status; int dd, err; dd = hci_open_dev(adapter->dev_id); if (dd < 0) return dd; memset(&cp, 0, sizeof(cp)); memcpy(&cp.lap, lap, 3); cp.max_period = htobs(24); cp.min_period = htobs(16); cp.length = 0x08; cp.num_rsp = 0x00; memset(&rq, 0, sizeof(rq)); rq.ogf = OGF_LINK_CTL; rq.ocf = OCF_PERIODIC_INQUIRY; rq.cparam = &cp; rq.clen = PERIODIC_INQUIRY_CP_SIZE; rq.rparam = &status; rq.rlen = sizeof(status); rq.event = EVT_CMD_COMPLETE; if (hci_send_req(dd, &rq, HCI_REQ_TIMEOUT) < 0) { err = -errno; error("Unable to start periodic inquiry: %s (%d)", strerror(err), err); hci_close_dev(dd); return err; } if (status) { error("HCI_Periodic_Inquiry_Mode failed with status 0x%02x", status); hci_close_dev(dd); return -bt_error(status); } hci_close_dev(dd); adapter->state |= RESOLVE_NAME; return 0; } static DBusMessage *adapter_start_discovery(DBusConnection *conn, DBusMessage *msg, void *data) { struct session_req *req; struct btd_adapter *adapter = data; const char *sender = dbus_message_get_sender(msg); int err; if (!adapter->up) return adapter_not_ready(msg); req = find_session(adapter->disc_sessions, sender); if (req) { session_ref(req); return dbus_message_new_method_return(msg); } if (adapter->disc_sessions) goto done; if (main_opts.inqmode) err = start_inquiry(adapter); else err = start_periodic_inquiry(adapter); if (err < 0) return failed_strerror(msg, -err); done: req = create_session(adapter, conn, msg, 0, session_owner_exit); adapter->disc_sessions = g_slist_append(adapter->disc_sessions, req); return dbus_message_new_method_return(msg); } static DBusMessage *adapter_stop_discovery(DBusConnection *conn, DBusMessage *msg, void *data) { struct btd_adapter *adapter = data; struct session_req *req; const char *sender = dbus_message_get_sender(msg); if (!adapter->up) return adapter_not_ready(msg); req = find_session(adapter->disc_sessions, sender); if (!req) return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", "Invalid discovery session"); session_unref(req); return dbus_message_new_method_return(msg); } struct remote_device_list_t { GSList *list; time_t time; }; static DBusMessage *get_properties(DBusConnection *conn, DBusMessage *msg, void *data) { struct btd_adapter *adapter = data; const char *property; DBusMessage *reply; DBusMessageIter iter; DBusMessageIter dict; char str[249], srcaddr[18]; uint32_t class; gboolean value; char **devices; int i; GSList *l; ba2str(&adapter->bdaddr, srcaddr); if (check_address(srcaddr) < 0) return adapter_not_ready(msg); reply = dbus_message_new_method_return(msg); if (!reply) return NULL; dbus_message_iter_init_append(reply, &iter); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); /* Address */ property = srcaddr; dict_append_entry(&dict, "Address", DBUS_TYPE_STRING, &property); /* Name */ memset(str, 0, sizeof(str)); strncpy(str, (char *) adapter->dev.name, 248); property = str; dict_append_entry(&dict, "Name", DBUS_TYPE_STRING, &property); /* Class */ class = adapter->dev.class[0] | adapter->dev.class[1] << 8 | adapter->dev.class[2] << 16; dict_append_entry(&dict, "Class", DBUS_TYPE_UINT32, &class); /* Powered */ value = adapter->up ? TRUE : FALSE; dict_append_entry(&dict, "Powered", DBUS_TYPE_BOOLEAN, &value); /* Discoverable */ value = adapter->scan_mode & SCAN_INQUIRY ? TRUE : FALSE; dict_append_entry(&dict, "Discoverable", DBUS_TYPE_BOOLEAN, &value); /* Pairable */ dict_append_entry(&dict, "Pairable", DBUS_TYPE_BOOLEAN, &adapter->pairable); /* DiscoverableTimeout */ dict_append_entry(&dict, "DiscoverableTimeout", DBUS_TYPE_UINT32, &adapter->discov_timeout); /* PairableTimeout */ dict_append_entry(&dict, "PairableTimeout", DBUS_TYPE_UINT32, &adapter->pairable_timeout); if (adapter->state & PERIODIC_INQUIRY || adapter->state & STD_INQUIRY) value = TRUE; else value = FALSE; /* Discovering */ dict_append_entry(&dict, "Discovering", DBUS_TYPE_BOOLEAN, &value); /* Devices */ devices = g_new0(char *, g_slist_length(adapter->devices) + 1); for (i = 0, l = adapter->devices; l; l = l->next, i++) { struct btd_device *dev = l->data; devices[i] = (char *) device_get_path(dev); } dict_append_array(&dict, "Devices", DBUS_TYPE_OBJECT_PATH, &devices, i); g_free(devices); dbus_message_iter_close_container(&iter, &dict); return reply; } static DBusMessage *set_property(DBusConnection *conn, DBusMessage *msg, void *data) { struct btd_adapter *adapter = data; DBusMessageIter iter; DBusMessageIter sub; const char *property; char srcaddr[18]; ba2str(&adapter->bdaddr, srcaddr); if (!dbus_message_iter_init(msg, &iter)) return invalid_args(msg); if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) return invalid_args(msg); dbus_message_iter_get_basic(&iter, &property); dbus_message_iter_next(&iter); if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) return invalid_args(msg); dbus_message_iter_recurse(&iter, &sub); if (g_str_equal("Name", property)) { const char *name; if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING) return invalid_args(msg); dbus_message_iter_get_basic(&sub, &name); return set_name(conn, msg, name, data); } else if (g_str_equal("Powered", property)) { gboolean powered; if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_BOOLEAN) return invalid_args(msg); dbus_message_iter_get_basic(&sub, &powered); return set_powered(conn, msg, powered, data); } else if (g_str_equal("Discoverable", property)) { gboolean discoverable; if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_BOOLEAN) return invalid_args(msg); dbus_message_iter_get_basic(&sub, &discoverable); return set_discoverable(conn, msg, discoverable, data); } else if (g_str_equal("DiscoverableTimeout", property)) { uint32_t timeout; if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT32) return invalid_args(msg); dbus_message_iter_get_basic(&sub, &timeout); return set_discoverable_timeout(conn, msg, timeout, data); } else if (g_str_equal("Pairable", property)) { gboolean pairable; if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_BOOLEAN) return invalid_args(msg); dbus_message_iter_get_basic(&sub, &pairable); return set_pairable(conn, msg, pairable, data); } else if (g_str_equal("PairableTimeout", property)) { uint32_t timeout; if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT32) return invalid_args(msg); dbus_message_iter_get_basic(&sub, &timeout); return set_pairable_timeout(conn, msg, timeout, data); } return invalid_args(msg); } static DBusMessage *request_session(DBusConnection *conn, DBusMessage *msg, void *data) { struct btd_adapter *adapter = data; struct session_req *req; const char *sender = dbus_message_get_sender(msg); uint8_t new_mode; int ret; if (!adapter->agent) return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", "No agent registered"); if (!adapter->mode_sessions) adapter->global_mode = adapter->mode; new_mode = get_mode(&adapter->bdaddr, "on"); req = find_session(adapter->mode_sessions, sender); if (req) { session_ref(req); return dbus_message_new_method_return(msg); } else { req = create_session(adapter, conn, msg, new_mode, session_owner_exit); adapter->mode_sessions = g_slist_append(adapter->mode_sessions, req); } /* No need to change mode */ if (adapter->mode >= new_mode) return dbus_message_new_method_return(msg); ret = agent_confirm_mode_change(adapter->agent, mode2str(new_mode), confirm_mode_cb, req); if (ret < 0) { session_unref(req); return failed_strerror(msg, -ret); } return NULL; } static DBusMessage *release_session(DBusConnection *conn, DBusMessage *msg, void *data) { struct btd_adapter *adapter = data; struct session_req *req; const char *sender = dbus_message_get_sender(msg); req = find_session(adapter->mode_sessions, sender); if (!req) return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", "No Mode to release"); session_unref(req); return dbus_message_new_method_return(msg); } static DBusMessage *list_devices(DBusConnection *conn, DBusMessage *msg, void *data) { struct btd_adapter *adapter = data; DBusMessage *reply; GSList *l; DBusMessageIter iter; DBusMessageIter array_iter; const gchar *dev_path; if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) return invalid_args(msg); reply = dbus_message_new_method_return(msg); if (!reply) return NULL; dbus_message_iter_init_append(reply, &iter); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH_AS_STRING, &array_iter); for (l = adapter->devices; l; l = l->next) { struct btd_device *device = l->data; dev_path = device_get_path(device); dbus_message_iter_append_basic(&array_iter, DBUS_TYPE_OBJECT_PATH, &dev_path); } dbus_message_iter_close_container(&iter, &array_iter); return reply; } static DBusMessage *cancel_device_creation(DBusConnection *conn, DBusMessage *msg, void *data) { struct btd_adapter *adapter = data; const gchar *address, *sender = dbus_message_get_sender(msg); struct btd_device *device; if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &address, DBUS_TYPE_INVALID) == FALSE) return invalid_args(msg); if (check_address(address) < 0) return invalid_args(msg); device = adapter_find_device(adapter, address); if (!device) return g_dbus_create_error(msg, ERROR_INTERFACE ".NotInProgress", "Device creation not in progress"); if (!device_is_temporary(device) || !device_is_bonding(device, sender)) return not_authorized(msg); adapter_remove_device(conn, adapter, device); return dbus_message_new_method_return(msg); } static DBusMessage *create_device(DBusConnection *conn, DBusMessage *msg, void *data) { struct btd_adapter *adapter = data; struct btd_device *device; const gchar *address; if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &address, DBUS_TYPE_INVALID) == FALSE) return invalid_args(msg); if (check_address(address) < 0) return invalid_args(msg); if (adapter_find_device(adapter, address)) return g_dbus_create_error(msg, ERROR_INTERFACE ".AlreadyExists", "Device already exists"); debug("create_device(%s)", address); device = adapter_create_device(conn, adapter, address); if (!device) return NULL; device_set_temporary(device, FALSE); device_browse(device, conn, msg, NULL, FALSE); return NULL; } static uint8_t parse_io_capability(const char *capability) { if (g_str_equal(capability, "")) return IO_CAPABILITY_DISPLAYYESNO; if (g_str_equal(capability, "DisplayOnly")) return IO_CAPABILITY_DISPLAYONLY; if (g_str_equal(capability, "DisplayYesNo")) return IO_CAPABILITY_DISPLAYYESNO; if (g_str_equal(capability, "KeyboardOnly")) return IO_CAPABILITY_KEYBOARDONLY; if (g_str_equal(capability, "NoInputOutput")) return IO_CAPABILITY_NOINPUTOUTPUT; return IO_CAPABILITY_INVALID; } static DBusMessage *create_paired_device(DBusConnection *conn, DBusMessage *msg, void *data) { struct btd_adapter *adapter = data; struct btd_device *device; const gchar *address, *agent_path, *capability, *sender; uint8_t cap; if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &address, DBUS_TYPE_OBJECT_PATH, &agent_path, DBUS_TYPE_STRING, &capability, DBUS_TYPE_INVALID) == FALSE) return invalid_args(msg); if (check_address(address) < 0) return invalid_args(msg); sender = dbus_message_get_sender(msg); if (adapter->agent && agent_matches(adapter->agent, sender, agent_path)) { error("Refusing adapter agent usage as device specific one"); return invalid_args(msg); } cap = parse_io_capability(capability); if (cap == IO_CAPABILITY_INVALID) return invalid_args(msg); device = adapter_get_device(conn, adapter, address); return device_create_bonding(device, conn, msg, agent_path, cap); } static gint device_path_cmp(struct btd_device *device, const gchar *path) { const gchar *dev_path = device_get_path(device); return strcasecmp(dev_path, path); } static DBusMessage *remove_device(DBusConnection *conn, DBusMessage *msg, void *data) { struct btd_adapter *adapter = data; struct btd_device *device; const char *path; GSList *l; if (dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID) == FALSE) return invalid_args(msg); l = g_slist_find_custom(adapter->devices, path, (GCompareFunc) device_path_cmp); if (!l) return g_dbus_create_error(msg, ERROR_INTERFACE ".DoesNotExist", "Device does not exist"); device = l->data; if (device_is_temporary(device) || device_is_busy(device)) return g_dbus_create_error(msg, ERROR_INTERFACE ".DoesNotExist", "Device creation in progress"); adapter_remove_device(conn, adapter, device); return dbus_message_new_method_return(msg); } static DBusMessage *find_device(DBusConnection *conn, DBusMessage *msg, void *data) { struct btd_adapter *adapter = data; struct btd_device *device; DBusMessage *reply; const gchar *address; GSList *l; const gchar *dev_path; if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &address, DBUS_TYPE_INVALID)) return invalid_args(msg); l = g_slist_find_custom(adapter->devices, address, (GCompareFunc) device_address_cmp); if (!l) return g_dbus_create_error(msg, ERROR_INTERFACE ".DoesNotExist", "Device does not exist"); device = l->data; reply = dbus_message_new_method_return(msg); if (!reply) return NULL; dev_path = device_get_path(device); dbus_message_append_args(reply, DBUS_TYPE_OBJECT_PATH, &dev_path, DBUS_TYPE_INVALID); return reply; } static void agent_removed(struct agent *agent, struct btd_adapter *adapter) { adapter->agent = NULL; } static DBusMessage *register_agent(DBusConnection *conn, DBusMessage *msg, void *data) { const char *path, *name, *capability; struct agent *agent; struct btd_adapter *adapter = data; uint8_t cap; if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_STRING, &capability, DBUS_TYPE_INVALID)) return NULL; if (adapter->agent) return g_dbus_create_error(msg, ERROR_INTERFACE ".AlreadyExists", "Agent already exists"); cap = parse_io_capability(capability); if (cap == IO_CAPABILITY_INVALID) return invalid_args(msg); name = dbus_message_get_sender(msg); agent = agent_create(adapter, name, path, cap, (agent_remove_cb) agent_removed, adapter); if (!agent) return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", "Failed to create a new agent"); adapter->agent = agent; debug("Agent registered for hci%d at %s:%s", adapter->dev_id, name, path); return dbus_message_new_method_return(msg); } static DBusMessage *unregister_agent(DBusConnection *conn, DBusMessage *msg, void *data) { const char *path, *name; struct btd_adapter *adapter = data; if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) return NULL; name = dbus_message_get_sender(msg); if (!adapter->agent || !agent_matches(adapter->agent, name, path)) return g_dbus_create_error(msg, ERROR_INTERFACE ".DoesNotExist", "No such agent"); agent_destroy(adapter->agent, FALSE); adapter->agent = NULL; return dbus_message_new_method_return(msg); } static GDBusMethodTable adapter_methods[] = { { "GetProperties", "", "a{sv}",get_properties }, { "SetProperty", "sv", "", set_property, G_DBUS_METHOD_FLAG_ASYNC}, { "RequestSession", "", "", request_session, G_DBUS_METHOD_FLAG_ASYNC}, { "ReleaseSession", "", "", release_session }, { "StartDiscovery", "", "", adapter_start_discovery }, { "StopDiscovery", "", "", adapter_stop_discovery, G_DBUS_METHOD_FLAG_ASYNC}, { "ListDevices", "", "ao", list_devices, G_DBUS_METHOD_FLAG_DEPRECATED}, { "CreateDevice", "s", "o", create_device, G_DBUS_METHOD_FLAG_ASYNC}, { "CreatePairedDevice", "sos", "o", create_paired_device, G_DBUS_METHOD_FLAG_ASYNC}, { "CancelDeviceCreation","s", "", cancel_device_creation }, { "RemoveDevice", "o", "", remove_device }, { "FindDevice", "s", "o", find_device }, { "RegisterAgent", "os", "", register_agent }, { "UnregisterAgent", "o", "", unregister_agent }, { } }; static GDBusSignalTable adapter_signals[] = { { "PropertyChanged", "sv" }, { "DeviceCreated", "o" }, { "DeviceRemoved", "o" }, { "DeviceFound", "sa{sv}" }, { "DeviceDisappeared", "s" }, { } }; static inline uint8_t get_inquiry_mode(struct hci_dev *dev) { if (dev->features[6] & LMP_EXT_INQ) return 2; if (dev->features[3] & LMP_RSSI_INQ) return 1; if (dev->manufacturer == 11 && dev->hci_rev == 0x00 && dev->lmp_subver == 0x0757) return 1; if (dev->manufacturer == 15) { if (dev->hci_rev == 0x03 && dev->lmp_subver == 0x6963) return 1; if (dev->hci_rev == 0x09 && dev->lmp_subver == 0x6963) return 1; if (dev->hci_rev == 0x00 && dev->lmp_subver == 0x6965) return 1; } if (dev->manufacturer == 31 && dev->hci_rev == 0x2005 && dev->lmp_subver == 0x1805) return 1; return 0; } static int adapter_read_bdaddr(uint16_t dev_id, bdaddr_t *bdaddr) { int dd, err; dd = hci_open_dev(dev_id); if (dd < 0) { err = -errno; error("Can't open device hci%d: %s (%d)", dev_id, strerror(err), err); return err; } if (hci_read_bd_addr(dd, bdaddr, HCI_REQ_TIMEOUT) < 0) { err = -errno; error("Can't read address for hci%d: %s (%d)", dev_id, strerror(err), err); hci_close_dev(dd); return err; } hci_close_dev(dd); return 0; } static int adapter_setup(struct btd_adapter *adapter, int dd) { struct hci_dev *dev = &adapter->dev; uint8_t events[8] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0x00, 0x00 }; uint8_t inqmode; int err; char name[249]; if (dev->hci_rev > 1) { if (dev->features[5] & LMP_SNIFF_SUBR) events[5] |= 0x20; if (dev->features[5] & LMP_PAUSE_ENC) events[5] |= 0x80; if (dev->features[6] & LMP_EXT_INQ) events[5] |= 0x40; if (dev->features[6] & LMP_NFLUSH_PKTS) events[7] |= 0x01; if (dev->features[7] & LMP_LSTO) events[6] |= 0x80; if (dev->features[6] & LMP_SIMPLE_PAIR) { events[6] |= 0x01; /* IO Capability Request */ events[6] |= 0x02; /* IO Capability Response */ events[6] |= 0x04; /* User Confirmation Request */ events[6] |= 0x08; /* User Passkey Request */ events[6] |= 0x10; /* Remote OOB Data Request */ events[6] |= 0x20; /* Simple Pairing Complete */ events[7] |= 0x04; /* User Passkey Notification */ events[7] |= 0x08; /* Keypress Notification */ events[7] |= 0x10; /* Remote Host Supported * Features Notification */ } hci_send_cmd(dd, OGF_HOST_CTL, OCF_SET_EVENT_MASK, sizeof(events), events); } if (read_local_name(&adapter->bdaddr, name) == 0) { memcpy(dev->name, name, 248); hci_write_local_name(dd, name, HCI_REQ_TIMEOUT); } update_ext_inquiry_response(dd, dev); inqmode = get_inquiry_mode(dev); if (inqmode < 1) return 0; if (hci_write_inquiry_mode(dd, inqmode, HCI_REQ_TIMEOUT) < 0) { err = -errno; error("Can't write inquiry mode for %s: %s (%d)", adapter->path, strerror(err), err); hci_close_dev(dd); return err; } return 0; } static void create_stored_device_from_profiles(char *key, char *value, void *user_data) { struct btd_adapter *adapter = user_data; GSList *uuids = bt_string2list(value); struct btd_device *device; bdaddr_t dst; char srcaddr[18], dstaddr[18]; ba2str(&adapter->bdaddr, srcaddr); if (g_slist_find_custom(adapter->devices, key, (GCompareFunc) device_address_cmp)) return; device = device_create(connection, adapter, key); if (!device) return; device_set_temporary(device, FALSE); adapter->devices = g_slist_append(adapter->devices, device); device_get_address(device, &dst); ba2str(&dst, dstaddr); device_probe_drivers(device, uuids); g_slist_foreach(uuids, (GFunc) g_free, NULL); g_slist_free(uuids); } static void create_stored_device_from_linkkeys(char *key, char *value, void *user_data) { struct btd_adapter *adapter = user_data; struct btd_device *device; if (g_slist_find_custom(adapter->devices, key, (GCompareFunc) device_address_cmp)) return; device = device_create(connection, adapter, key); if (device) { device_set_temporary(device, FALSE); adapter->devices = g_slist_append(adapter->devices, device); } } static void load_devices(struct btd_adapter *adapter) { char filename[PATH_MAX + 1]; char srcaddr[18]; ba2str(&adapter->bdaddr, srcaddr); create_name(filename, PATH_MAX, STORAGEDIR, srcaddr, "profiles"); textfile_foreach(filename, create_stored_device_from_profiles, adapter); create_name(filename, PATH_MAX, STORAGEDIR, srcaddr, "linkkeys"); textfile_foreach(filename, create_stored_device_from_linkkeys, adapter); } static void probe_driver(gpointer data, gpointer user_data) { struct btd_adapter *adapter = data; struct btd_adapter_driver *driver = user_data; int err; if (!adapter->up) return; err = driver->probe(adapter); if (err < 0) error("%s: %s (%d)", driver->name, strerror(-err), -err); } static void load_drivers(struct btd_adapter *adapter) { GSList *l; for (l = adapter_drivers; l; l = l->next) { struct btd_adapter_driver *driver = l->data; if (driver->probe == NULL) continue; probe_driver(adapter, driver); } } static void load_connections(struct btd_adapter *adapter, int dd) { struct hci_conn_list_req *cl = NULL; struct hci_conn_info *ci; int i; cl = g_malloc0(10 * sizeof(*ci) + sizeof(*cl)); cl->dev_id = adapter->dev_id; cl->conn_num = 10; ci = cl->conn_info; if (ioctl(dd, HCIGETCONNLIST, cl) != 0) { g_free(cl); return; } for (i = 0; i < cl->conn_num; i++, ci++) { struct btd_device *device; char address[18]; ba2str(&ci->bdaddr, address); device = adapter_get_device(connection, adapter, address); if (device) adapter_add_connection(adapter, device, ci->handle); } g_free(cl); } static int get_discoverable_timeout(const char *src) { int timeout; if (read_discoverable_timeout(src, &timeout) == 0) return timeout; return main_opts.discovto; } static int get_pairable_timeout(const char *src) { int timeout; if (read_pairable_timeout(src, &timeout) == 0) return timeout; return main_opts.pairto; } static int adapter_up(struct btd_adapter *adapter, int dd) { char mode[14], srcaddr[18]; uint8_t scan_mode; gboolean powered, dev_down = FALSE; ba2str(&adapter->bdaddr, srcaddr); adapter->off_requested = FALSE; adapter->up = 1; adapter->discov_timeout = get_discoverable_timeout(srcaddr); adapter->pairable_timeout = get_pairable_timeout(srcaddr); adapter->state = DISCOVER_TYPE_NONE; adapter->mode = MODE_CONNECTABLE; scan_mode = SCAN_PAGE; powered = TRUE; /* Set pairable mode */ if (read_device_pairable(&adapter->bdaddr, &adapter->pairable) < 0) adapter->pairable = TRUE; if (!adapter->initialized && !main_opts.remember_powered) { if (main_opts.mode == MODE_OFF) strcpy(mode, "off"); else strcpy(mode, "connectable"); } else if (read_device_mode(srcaddr, mode, sizeof(mode)) < 0) { if (!adapter->initialized && main_opts.mode == MODE_OFF) strcpy(mode, "off"); else goto proceed; } if (g_str_equal(mode, "off")) { powered = FALSE; if (!adapter->initialized) { dev_down = TRUE; goto proceed; } if (read_on_mode(srcaddr, mode, sizeof(mode)) < 0) write_device_mode(&adapter->bdaddr, "connectable"); else write_device_mode(&adapter->bdaddr, mode); return adapter_up(adapter, dd); } else if (!g_str_equal(mode, "connectable") && adapter->discov_timeout == 0) { /* Set discoverable only if timeout is 0 */ adapter->mode = adapter->pairable ? MODE_LIMITED : MODE_DISCOVERABLE; scan_mode = SCAN_PAGE | SCAN_INQUIRY; } proceed: if (dev_down == FALSE) { hci_send_cmd(dd, OGF_HOST_CTL, OCF_WRITE_SCAN_ENABLE, 1, &scan_mode); emit_property_changed(connection, adapter->path, ADAPTER_INTERFACE, "Powered", DBUS_TYPE_BOOLEAN, &powered); } if (adapter->initialized == FALSE) { load_drivers(adapter); load_devices(adapter); /* retrieve the active connections: address the scenario where * the are active connections before the daemon've started */ load_connections(adapter, dd); adapter->initialized = TRUE; } if (dev_down) { ioctl(dd, HCIDEVDOWN, adapter->dev_id); return 1; } return 0; } int adapter_start(struct btd_adapter *adapter) { struct hci_dev *dev = &adapter->dev; struct hci_dev_info di; struct hci_version ver; uint8_t features[8]; int dd, err; char name[249]; if (hci_devinfo(adapter->dev_id, &di) < 0) return -errno; if (hci_test_bit(HCI_RAW, &di.flags)) { dev->ignore = 1; return -1; } if (!bacmp(&di.bdaddr, BDADDR_ANY)) { int err; debug("Adapter %s without an address", adapter->path); err = adapter_read_bdaddr(adapter->dev_id, &di.bdaddr); if (err < 0) return err; } bacpy(&adapter->bdaddr, &di.bdaddr); memcpy(dev->features, di.features, 8); dd = hci_open_dev(adapter->dev_id); if (dd < 0) { err = -errno; error("Can't open adapter %s: %s (%d)", adapter->path, strerror(err), err); return err; } if (hci_read_local_version(dd, &ver, HCI_REQ_TIMEOUT) < 0) { err = -errno; error("Can't read version info for %s: %s (%d)", adapter->path, strerror(err), err); hci_close_dev(dd); return err; } dev->hci_rev = ver.hci_rev; dev->lmp_ver = ver.lmp_ver; dev->lmp_subver = ver.lmp_subver; dev->manufacturer = ver.manufacturer; if (hci_read_local_features(dd, features, HCI_REQ_TIMEOUT) < 0) { err = -errno; error("Can't read features for %s: %s (%d)", adapter->path, strerror(err), err); hci_close_dev(dd); return err; } memcpy(dev->features, features, 8); if (hci_read_class_of_dev(dd, dev->class, HCI_REQ_TIMEOUT) < 0) { err = -errno; error("Can't read class of adapter on %s: %s (%d)", adapter->path, strerror(err), err); hci_close_dev(dd); return err; } if (hci_read_local_name(dd, sizeof(name), name, HCI_REQ_TIMEOUT) < 0) { err = -errno; error("Can't read local name on %s: %s (%d)", adapter->path, strerror(err), err); hci_close_dev(dd); return err; } memcpy(dev->name, name, 248); if (!(features[6] & LMP_SIMPLE_PAIR)) goto setup; if (ioctl(dd, HCIGETAUTHINFO, NULL) < 0 && errno != EINVAL) hci_write_simple_pairing_mode(dd, 0x01, HCI_REQ_TIMEOUT); if (hci_read_simple_pairing_mode(dd, &dev->ssp_mode, HCI_REQ_TIMEOUT) < 0) { err = -errno; error("Can't read simple pairing mode on %s: %s (%d)", adapter->path, strerror(err), err); /* Fall through since some chips have broken * read_simple_pairing_mode behavior */ } setup: hci_send_cmd(dd, OGF_LINK_POLICY, OCF_READ_DEFAULT_LINK_POLICY, 0, NULL); if (hci_test_bit(HCI_INQUIRY, &di.flags)) adapter->state |= STD_INQUIRY; else adapter->state &= ~STD_INQUIRY; adapter_setup(adapter, dd); err = adapter_up(adapter, dd); hci_close_dev(dd); info("Adapter %s has been enabled", adapter->path); return err; } static void reply_pending_requests(struct btd_adapter *adapter) { GSList *l; if (!adapter) return; /* pending bonding */ for (l = adapter->devices; l; l = l->next) { struct btd_device *device = l->data; if (device_is_bonding(device, NULL)) device_cancel_bonding(device, HCI_OE_USER_ENDED_CONNECTION); } if (adapter->state & STD_INQUIRY) { /* Cancel inquiry initiated by D-Bus client */ if (adapter->disc_sessions) cancel_discovery(adapter); } if (adapter->state & PERIODIC_INQUIRY) { /* Stop periodic inquiry initiated by D-Bus client */ if (adapter->disc_sessions) cancel_periodic_discovery(adapter); } } static void unload_drivers(struct btd_adapter *adapter) { GSList *l; for (l = adapter_drivers; l; l = l->next) { struct btd_adapter_driver *driver = l->data; if (driver->remove) driver->remove(adapter); } } int adapter_stop(struct btd_adapter *adapter) { gboolean powered, discoverable, pairable; /* cancel pending timeout */ if (adapter->discov_timeout_id) { g_source_remove(adapter->discov_timeout_id); adapter->discov_timeout_id = 0; } /* check pending requests */ reply_pending_requests(adapter); if (adapter->disc_sessions) { g_slist_foreach(adapter->disc_sessions, (GFunc) session_free, NULL); g_slist_free(adapter->disc_sessions); adapter->disc_sessions = NULL; } if (adapter->found_devices) { g_slist_foreach(adapter->found_devices, (GFunc) dev_info_free, NULL); g_slist_free(adapter->found_devices); adapter->found_devices = NULL; } if (adapter->oor_devices) { g_slist_foreach(adapter->oor_devices, (GFunc) free, NULL); g_slist_free(adapter->oor_devices); adapter->oor_devices = NULL; } if (adapter->connections) { g_slist_free(adapter->connections); adapter->connections = NULL; } if (adapter->scan_mode == (SCAN_PAGE | SCAN_INQUIRY)) { discoverable = FALSE; emit_property_changed(connection, adapter->path, ADAPTER_INTERFACE, "Discoverable", DBUS_TYPE_BOOLEAN, &discoverable); } if ((adapter->scan_mode & SCAN_PAGE) && adapter->pairable == TRUE) { pairable = FALSE; emit_property_changed(connection, adapter->path, ADAPTER_INTERFACE, "Pairable", DBUS_TYPE_BOOLEAN, &pairable); } powered = FALSE; emit_property_changed(connection, adapter->path, ADAPTER_INTERFACE, "Powered", DBUS_TYPE_BOOLEAN, &powered); adapter->up = 0; adapter->scan_mode = SCAN_DISABLED; adapter->mode = MODE_OFF; adapter->state = DISCOVER_TYPE_NONE; info("Adapter %s has been disabled", adapter->path); return 0; } int adapter_update(struct btd_adapter *adapter) { struct hci_dev *dev = &adapter->dev; int dd; if (dev->ignore) return 0; dd = hci_open_dev(adapter->dev_id); if (dd < 0) { int err = -errno; error("Can't open adapter %s: %s (%d)", adapter->path, strerror(err), err); return err; } update_ext_inquiry_response(dd, dev); hci_close_dev(dd); return 0; } int adapter_get_class(struct btd_adapter *adapter, uint8_t *cls) { struct hci_dev *dev = &adapter->dev; memcpy(cls, dev->class, 3); return 0; } int adapter_set_class(struct btd_adapter *adapter, uint8_t *cls) { struct hci_dev *dev = &adapter->dev; uint32_t class; if (memcmp(dev->class, cls, 3) == 0) return 0; memcpy(dev->class, cls, 3); write_local_class(&adapter->bdaddr, cls); class = cls[0] | (cls[1] << 8) | (cls[2] << 16); emit_property_changed(connection, adapter->path, ADAPTER_INTERFACE, "Class", DBUS_TYPE_UINT32, &class); return 0; } int adapter_update_ssp_mode(struct btd_adapter *adapter, int dd, uint8_t mode) { struct hci_dev *dev = &adapter->dev; dev->ssp_mode = mode; update_ext_inquiry_response(dd, dev); hci_close_dev(dd); return 0; } static void adapter_free(gpointer user_data) { struct btd_adapter *adapter = user_data; agent_destroy(adapter->agent, FALSE); adapter->agent = NULL; g_free(adapter->path); g_free(adapter); return; } struct btd_adapter *adapter_create(DBusConnection *conn, int id, gboolean devup) { char path[MAX_PATH_LENGTH]; struct btd_adapter *adapter; const char *base_path = manager_get_base_path(); if (!connection) connection = conn; snprintf(path, sizeof(path), "%s/hci%d", base_path, id); adapter = g_try_new0(struct btd_adapter, 1); if (!adapter) { error("adapter_create: failed to alloc memory for %s", path); return NULL; } adapter->dev_id = id; adapter->state |= RESOLVE_NAME; adapter->path = g_strdup(path); adapter->already_up = devup; if (!g_dbus_register_interface(conn, path, ADAPTER_INTERFACE, adapter_methods, adapter_signals, NULL, adapter, adapter_free)) { error("Adapter interface init failed on path %s", path); adapter_free(adapter); return NULL; } return adapter; } void adapter_remove(struct btd_adapter *adapter) { GSList *l; char *path = g_strdup(adapter->path); debug("Removing adapter %s", path); unload_drivers(adapter); for (l = adapter->devices; l; l = l->next) device_remove(l->data, connection, FALSE); g_slist_free(adapter->devices); /* Return adapter to down state if it was not up on init */ if (adapter->up && !adapter->already_up) { int dd = hci_open_dev(adapter->dev_id); if (dd < 0) goto done; ioctl(dd, HCIDEVDOWN, adapter->dev_id); hci_close_dev(dd); } done: g_dbus_unregister_interface(connection, path, ADAPTER_INTERFACE); g_free(path); } uint16_t adapter_get_dev_id(struct btd_adapter *adapter) { return adapter->dev_id; } const gchar *adapter_get_path(struct btd_adapter *adapter) { if (!adapter) return NULL; return adapter->path; } void adapter_get_address(struct btd_adapter *adapter, bdaddr_t *bdaddr) { bacpy(bdaddr, &adapter->bdaddr); } void adapter_set_state(struct btd_adapter *adapter, int state) { gboolean discov_active = FALSE; const char *path = adapter->path; if (adapter->state == state) return; if (state & PERIODIC_INQUIRY || state & STD_INQUIRY) discov_active = TRUE; else if (adapter->disc_sessions && main_opts.inqmode) adapter->scheduler_id = g_timeout_add_seconds(main_opts.inqmode, (GSourceFunc) start_inquiry, adapter); /* Send out of range */ if (!discov_active) adapter_update_oor_devices(adapter); emit_property_changed(connection, path, ADAPTER_INTERFACE, "Discovering", DBUS_TYPE_BOOLEAN, &discov_active); adapter->state = state; } int adapter_get_state(struct btd_adapter *adapter) { return adapter->state; } struct remote_dev_info *adapter_search_found_devices(struct btd_adapter *adapter, struct remote_dev_info *match) { GSList *l; l = g_slist_find_custom(adapter->found_devices, match, (GCompareFunc) found_device_cmp); if (l) return l->data; return NULL; } static int dev_rssi_cmp(struct remote_dev_info *d1, struct remote_dev_info *d2) { int rssi1, rssi2; rssi1 = d1->rssi < 0 ? -d1->rssi : d1->rssi; rssi2 = d2->rssi < 0 ? -d2->rssi : d2->rssi; return rssi1 - rssi2; } int adapter_add_found_device(struct btd_adapter *adapter, bdaddr_t *bdaddr, int8_t rssi, uint32_t class, const char *alias, name_status_t name_status) { struct remote_dev_info *dev, match; memset(&match, 0, sizeof(struct remote_dev_info)); bacpy(&match.bdaddr, bdaddr); match.name_status = NAME_ANY; /* ignore repeated entries */ dev = adapter_search_found_devices(adapter, &match); if (dev) { /* device found, update the attributes */ if (rssi != 0) dev->rssi = rssi; dev->class = class; if (alias) { g_free(dev->alias); dev->alias = g_strdup(alias); } /* Get remote name can be received while inquiring. Keep in * mind that multiple inquiry result events can be received * from the same remote device. */ if (name_status != NAME_NOT_REQUIRED) dev->name_status = name_status; adapter->found_devices = g_slist_sort(adapter->found_devices, (GCompareFunc) dev_rssi_cmp); return -EALREADY; } dev = g_new0(struct remote_dev_info, 1); bacpy(&dev->bdaddr, bdaddr); dev->rssi = rssi; dev->class = class; if (alias) dev->alias = g_strdup(alias); dev->name_status = name_status; adapter->found_devices = g_slist_insert_sorted(adapter->found_devices, dev, (GCompareFunc) dev_rssi_cmp); return 0; } int adapter_remove_found_device(struct btd_adapter *adapter, bdaddr_t *bdaddr) { struct remote_dev_info *dev, match; memset(&match, 0, sizeof(struct remote_dev_info)); bacpy(&match.bdaddr, bdaddr); dev = adapter_search_found_devices(adapter, &match); if (!dev) return -1; dev->name_status = NAME_NOT_REQUIRED; return 0; } void adapter_update_oor_devices(struct btd_adapter *adapter) { GSList *l; struct remote_dev_info *dev; bdaddr_t tmp; for (l = adapter->oor_devices; l; l = l->next) { char *address = l->data; struct remote_dev_info match; memset(&match, 0, sizeof(struct remote_dev_info)); str2ba(address, &match.bdaddr); g_dbus_emit_signal(connection, adapter->path, ADAPTER_INTERFACE, "DeviceDisappeared", DBUS_TYPE_STRING, &address, DBUS_TYPE_INVALID); dev = adapter_search_found_devices(adapter, &match); if (!dev) continue; adapter->found_devices = g_slist_remove(adapter->found_devices, dev); dev_info_free(dev); } g_slist_foreach(adapter->oor_devices, (GFunc) free, NULL); g_slist_free(adapter->oor_devices); adapter->oor_devices = NULL; for (l = adapter->found_devices; l; l = l->next) { dev = l->data; baswap(&tmp, &dev->bdaddr); adapter->oor_devices = g_slist_append(adapter->oor_devices, batostr(&tmp)); } } void adapter_remove_oor_device(struct btd_adapter *adapter, char *peer_addr) { GSList *l; l = g_slist_find_custom(adapter->oor_devices, peer_addr, (GCompareFunc) strcmp); if (l) { char *dev = l->data; adapter->oor_devices = g_slist_remove(adapter->oor_devices, dev); g_free(dev); } } void adapter_mode_changed(struct btd_adapter *adapter, uint8_t scan_mode) { const gchar *path = adapter_get_path(adapter); gboolean powered, discoverable, pairable; int dd; if (adapter->scan_mode == scan_mode) return; adapter_remove_discov_timeout(adapter); switch (scan_mode) { case SCAN_DISABLED: adapter->mode = MODE_OFF; powered = FALSE; discoverable = FALSE; pairable = FALSE; break; case SCAN_PAGE: adapter->mode = MODE_CONNECTABLE; powered = TRUE; discoverable = FALSE; pairable = adapter->pairable; break; case (SCAN_PAGE | SCAN_INQUIRY): adapter->mode = MODE_DISCOVERABLE; powered = TRUE; discoverable = TRUE; pairable = adapter->pairable; if (adapter->discov_timeout != 0) adapter_set_discov_timeout(adapter, adapter->discov_timeout); break; case SCAN_INQUIRY: /* Address the scenario where a low-level application like * hciconfig changed the scan mode */ if (adapter->discov_timeout != 0) adapter_set_discov_timeout(adapter, adapter->discov_timeout); /* ignore, this event should not be sent */ default: /* ignore, reserved */ return; } if (powered == FALSE) emit_property_changed(connection, path, ADAPTER_INTERFACE, "Powered", DBUS_TYPE_BOOLEAN, &powered); /* If page scanning gets toggled emit the Pairable property */ if ((adapter->scan_mode & SCAN_PAGE) != (scan_mode & SCAN_PAGE)) emit_property_changed(connection, adapter->path, ADAPTER_INTERFACE, "Pairable", DBUS_TYPE_BOOLEAN, &pairable); dd = hci_open_dev(adapter->dev_id); if (dd < 0) { error("HCI device open failed: hci%d", adapter->dev_id); goto done; } if (discoverable && adapter->pairable) set_limited_discoverable(dd, adapter->dev.class, TRUE); else if (!discoverable) set_limited_discoverable(dd, adapter->dev.class, FALSE); hci_close_dev(dd); done: emit_property_changed(connection, path, ADAPTER_INTERFACE, "Discoverable", DBUS_TYPE_BOOLEAN, &discoverable); adapter->scan_mode = scan_mode; } struct agent *adapter_get_agent(struct btd_adapter *adapter) { if (!adapter || !adapter->agent) return NULL; return adapter->agent; } void adapter_add_connection(struct btd_adapter *adapter, struct btd_device *device, uint16_t handle) { if (g_slist_find(adapter->connections, device)) { error("Unable to add connection %d", handle); return; } device_add_connection(device, connection, handle); adapter->connections = g_slist_append(adapter->connections, device); } void adapter_remove_connection(struct btd_adapter *adapter, struct btd_device *device, uint16_t handle) { bdaddr_t bdaddr; if (!g_slist_find(adapter->connections, device)) { error("No matching connection for handle %u", handle); return; } device_remove_connection(device, connection, handle); adapter->connections = g_slist_remove(adapter->connections, device); /* clean pending HCI cmds */ device_get_address(device, &bdaddr); hci_req_queue_remove(adapter->dev_id, &bdaddr); if (device_is_authenticating(device)) device_cancel_authentication(device, TRUE); if (device_is_temporary(device)) { const char *path = device_get_path(device); debug("Removing temporary device %s", path); adapter_remove_device(connection, adapter, device); } } gboolean adapter_has_discov_sessions(struct btd_adapter *adapter) { if (!adapter || !adapter->disc_sessions) return FALSE; return TRUE; } int btd_register_adapter_driver(struct btd_adapter_driver *driver) { GSList *adapters; adapter_drivers = g_slist_append(adapter_drivers, driver); if (driver->probe == NULL) return 0; adapters = manager_get_adapters(); g_slist_foreach(adapters, probe_driver, driver); return 0; } void btd_unregister_adapter_driver(struct btd_adapter_driver *driver) { adapter_drivers = g_slist_remove(adapter_drivers, driver); } static void agent_auth_cb(struct agent *agent, DBusError *derr, void *user_data) { struct service_auth *auth = user_data; auth->cb(derr, auth->user_data); g_free(auth); } static int btd_adapter_authorize(struct btd_adapter *adapter, const bdaddr_t *dst, const char *uuid, service_auth_cb cb, void *user_data) { struct service_auth *auth; struct btd_device *device; struct agent *agent; char address[18]; gboolean trusted; const gchar *dev_path; ba2str(dst, address); device = adapter_find_device(adapter, address); if (!device) return -EPERM; /* Device connected? */ if (!g_slist_find(adapter->connections, device)) return -ENOTCONN; trusted = read_trust(&adapter->bdaddr, address, GLOBAL_TRUST); if (trusted) { cb(NULL, user_data); return 0; } device = adapter_find_device(adapter, address); if (!device) return -EPERM; agent = device_get_agent(device); if (!agent) agent = adapter->agent; if (!agent) return -EPERM; auth = g_try_new0(struct service_auth, 1); if (!auth) return -ENOMEM; auth->cb = cb; auth->user_data = user_data; dev_path = device_get_path(device); return agent_authorize(agent, dev_path, uuid, agent_auth_cb, auth); } int btd_request_authorization(const bdaddr_t *src, const bdaddr_t *dst, const char *uuid, service_auth_cb cb, void *user_data) { struct btd_adapter *adapter; GSList *adapters; if (src == NULL || dst == NULL) return -EINVAL; if (bacmp(src, BDADDR_ANY) != 0) goto proceed; /* Handle request authorization for ANY adapter */ adapters = manager_get_adapters(); for (; adapters; adapters = adapters->next) { int err; adapter = adapters->data; err = btd_adapter_authorize(adapter, dst, uuid, cb, user_data); if (err == 0) return 0; } return -EPERM; proceed: adapter = manager_find_adapter(src); if (!adapter) return -EPERM; return btd_adapter_authorize(adapter, dst, uuid, cb, user_data); } int btd_cancel_authorization(const bdaddr_t *src, const bdaddr_t *dst) { struct btd_adapter *adapter = manager_find_adapter(src); struct btd_device *device; struct agent *agent; char address[18]; if (!adapter) return -EPERM; ba2str(dst, address); device = adapter_find_device(adapter, address); if (!device) return -EPERM; /* * FIXME: Cancel fails if authorization is requested to adapter's * agent and in the meanwhile CreatePairedDevice is called. */ agent = device_get_agent(device); if (!agent) agent = adapter->agent; if (!agent) return -EPERM; return agent_cancel(agent); } static gchar *adapter_any_path = NULL; static int adapter_any_refcount = 0; const char *adapter_any_get_path(void) { return adapter_any_path; } const char *btd_adapter_any_request_path(void) { if (adapter_any_refcount > 0) return adapter_any_path; adapter_any_path = g_strdup_printf("%s/any", manager_get_base_path()); adapter_any_refcount++; return adapter_any_path; } void btd_adapter_any_release_path(void) { adapter_any_refcount--; if (adapter_any_refcount > 0) return; g_free(adapter_any_path); adapter_any_path = NULL; } gboolean adapter_is_pairable(struct btd_adapter *adapter) { return adapter->pairable; } gboolean adapter_powering_down(struct btd_adapter *adapter) { return adapter->off_requested; }