diff options
Diffstat (limited to 'hcid')
54 files changed, 21456 insertions, 0 deletions
diff --git a/hcid/Makefile.am b/hcid/Makefile.am new file mode 100644 index 00000000..763374f1 --- /dev/null +++ b/hcid/Makefile.am @@ -0,0 +1,64 @@ + +if CONFIGFILES +dbusdir = $(sysconfdir)/dbus-1/system.d + +dbus_DATA = bluetooth.conf + +confdir = $(sysconfdir)/bluetooth + +conf_DATA = hcid.conf + +statedir = $(localstatedir)/lib/bluetooth + +state_DATA = +endif + +noinst_LIBRARIES = libhciserver.a + +libhciserver_a_SOURCES = hcid.h security.c storage.c \ + parser.h parser.y lexer.l kword.c kword.h \ + server.h server.c manager.h manager.c \ + adapter.h adapter.c device.h device.c plugin.h plugin.c \ + dbus-common.c dbus-common.h dbus-error.c dbus-error.h \ + dbus-database.c dbus-database.h dbus-security.c dbus-security.h \ + dbus-service.c dbus-service.h \ + dbus-hci.h dbus-hci.c dbus-sdp.c dbus-sdp.h \ + telephony.h telephony.c agent.h agent.c + +sbin_PROGRAMS = hcid + +hcid_SOURCES = main.c + +hcid_LDADD = libhciserver.a \ + $(top_builddir)/sdpd/libsdpserver.a \ + $(top_builddir)/common/libhelper.a \ + @GDBUS_LIBS@ @GMODULE_LIBS@ @GLIB_LIBS@ @DBUS_LIBS@ @BLUEZ_LIBS@ + +if MAINTAINER_MODE +plugindir = $(abs_top_srcdir)/plugins +else +plugindir = $(libdir)/bluetooth/plugins +endif + +AM_CFLAGS = @BLUEZ_CFLAGS@ @DBUS_CFLAGS@ \ + @GLIB_CFLAGS@ @GMODULE_CFLAGS@ @GDBUS_CFLAGS@ \ + -DPLUGINDIR=\""$(plugindir)"\" + +INCLUDES = -I$(top_srcdir)/common -I$(top_srcdir)/sdpd + +BUILT_SOURCES = parser.h + +if MANPAGES +man_MANS = hcid.8 hcid.conf.5 +endif + +AM_YFLAGS = -d + +CLEANFILES = lexer.c parser.c parser.h + +EXTRA_DIST = bluetooth.conf hcid.8 hcid.conf.5 hcid.conf dbus-api.txt \ + list-devices test-discovery test-manager test-adapter test-device \ + simple-service simple-agent service-record.dtd \ + service-did.xml service-spp.xml service-opp.xml service-ftp.xml + +MAINTAINERCLEANFILES = Makefile.in diff --git a/hcid/adapter.c b/hcid/adapter.c new file mode 100644 index 00000000..29bf51ca --- /dev/null +++ b/hcid/adapter.c @@ -0,0 +1,4577 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 <config.h> +#endif + +#define _GNU_SOURCE +#include <stdio.h> +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> +#include <time.h> +#include <sys/param.h> +#include <sys/ioctl.h> +#include <sys/socket.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/hci.h> +#include <bluetooth/hci_lib.h> +#include <bluetooth/l2cap.h> +#include <bluetooth/sdp.h> +#include <bluetooth/sdp_lib.h> + +#include <glib.h> +#include <dbus/dbus.h> +#include <gdbus.h> + +#include "hcid.h" + +#include "adapter.h" +#include "device.h" + +#include "textfile.h" +#include "oui.h" +#include "dbus-common.h" +#include "dbus-hci.h" +#include "dbus-sdp.h" +#include "dbus-database.h" +#include "dbus-service.h" +#include "dbus-security.h" +#include "dbus-error.h" +#include "error.h" +#include "glib-helper.h" +#include "logging.h" +#include "agent.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 + +struct mode_req { + struct adapter *adapter; + DBusConnection *conn; /* Connection reference */ + DBusMessage *msg; /* Message reference */ + uint8_t mode; /* Requested mode */ + guint id; /* Listener id */ +}; + +static const char *service_cls[] = { + "positioning", + "networking", + "rendering", + "capturing", + "object transfer", + "audio", + "telephony", + "information" +}; + +static const char *major_cls[] = { + "miscellaneous", + "computer", + "phone", + "access point", + "audio/video", + "peripheral", + "imaging", + "wearable", + "toy", + "uncategorized" +}; + +static const char *computer_minor_cls[] = { + "uncategorized", + "desktop", + "server", + "laptop", + "handheld", + "palm", + "wearable" +}; + +static const char *phone_minor_cls[] = { + "uncategorized", + "cellular", + "cordless", + "smart phone", + "modem", + "isdn" +}; + +static const char *access_point_minor_cls[] = { + "fully", + "1-17 percent", + "17-33 percent", + "33-50 percent", + "50-67 percent", + "67-83 percent", + "83-99 percent", + "not available" +}; + +static const char *audio_video_minor_cls[] = { + "uncategorized", + "headset", + "handsfree", + "unknown", + "microphone", + "loudspeaker", + "headphones", + "portable audio", + "car audio", + "set-top box", + "hifi audio", + "vcr", + "video camera", + "camcorder", + "video monitor", + "video display and loudspeaker", + "video conferencing", + "unknown", + "gaming/toy" +}; + +static const char *peripheral_minor_cls[] = { + "uncategorized", + "keyboard", + "pointing", + "combo" +}; + +#if 0 +static const char *peripheral_2_minor_cls[] = { + "uncategorized", + "joystick", + "gamepad", + "remote control", + "sensing", + "digitizer tablet", + "card reader" +}; +#endif + +static const char *imaging_minor_cls[] = { + "display", + "camera", + "scanner", + "printer" +}; + +static const char *wearable_minor_cls[] = { + "wrist watch", + "pager", + "jacket", + "helmet", + "glasses" +}; + +static const char *toy_minor_cls[] = { + "robot", + "vehicle", + "doll", + "controller", + "game" +}; + +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 auth_req_cmp(const void *p1, const void *p2) +{ + const struct pending_auth_info *pb1 = p1; + const bdaddr_t *bda = p2; + + return bda ? bacmp(&pb1->bdaddr, bda) : -1; +} + +void adapter_auth_request_replied(struct adapter *adapter, bdaddr_t *dba) +{ + GSList *l; + struct pending_auth_info *auth; + + l = g_slist_find_custom(adapter->auth_reqs, dba, auth_req_cmp); + if (!l) + return; + + auth = l->data; + + auth->replied = TRUE; +} + +struct pending_auth_info *adapter_find_auth_request(struct adapter *adapter, + bdaddr_t *dba) +{ + GSList *l; + + l = g_slist_find_custom(adapter->auth_reqs, dba, auth_req_cmp); + if (l) + return l->data; + + return NULL; +} + +void adapter_remove_auth_request(struct adapter *adapter, bdaddr_t *dba) +{ + GSList *l; + struct pending_auth_info *auth; + + l = g_slist_find_custom(adapter->auth_reqs, dba, auth_req_cmp); + if (!l) + return; + + auth = l->data; + + adapter->auth_reqs = g_slist_remove(adapter->auth_reqs, auth); + + g_free(auth); +} + +struct pending_auth_info *adapter_new_auth_request(struct adapter *adapter, + bdaddr_t *dba, + auth_type_t type) +{ + struct pending_auth_info *info; + + debug("hcid_dbus_new_auth_request"); + + info = g_new0(struct pending_auth_info, 1); + + bacpy(&info->bdaddr, dba); + info->type = type; + adapter->auth_reqs = g_slist_append(adapter->auth_reqs, info); + + if (adapter->bonding && !bacmp(dba, &adapter->bonding->bdaddr)) + adapter->bonding->auth_active = 1; + + return info; +} + +int pending_remote_name_cancel(struct 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, 1000) < 0) { + error("Remote name cancel failed: %s(%d)", strerror(errno), errno); + err = -errno; + } + + /* free discovered devices list */ + g_slist_foreach(adapter->found_devices, (GFunc) g_free, NULL); + g_slist_free(adapter->found_devices); + adapter->found_devices = NULL; + + hci_close_dev(dd); + return err; +} + +static int auth_info_agent_cmp(const void *a, const void *b) +{ + const struct pending_auth_info *auth = a; + const struct agent *agent = b; + + if (auth->agent == agent) + return 0; + + return -1; +} + +static void device_agent_removed(struct agent *agent, void *user_data) +{ + struct device *device = user_data; + struct pending_auth_info *auth; + GSList *l; + + device->agent = NULL; + + l = g_slist_find_custom(device->adapter->auth_reqs, agent, + auth_info_agent_cmp); + if (!l) + return; + + auth = l->data; + auth->agent = NULL; +} + +static struct bonding_request_info *bonding_request_new(DBusConnection *conn, + DBusMessage *msg, + struct adapter *adapter, + const char *address, + const char *agent_path, + uint8_t capability) +{ + struct bonding_request_info *bonding; + struct device *device; + + debug("bonding_request_new(%s)", address); + + if (hcid_dbus_use_experimental() && agent_path) { + const char *name = dbus_message_get_sender(msg); + + device = adapter_get_device(conn, adapter, address); + if (!device) + return NULL; + + device->agent = agent_create(adapter, name, agent_path, + capability, + device_agent_removed, + device); + debug("Temporary agent registered for hci%d/%s at %s:%s", + adapter->dev_id, device->address, name, + agent_path); + } + + bonding = g_new0(struct bonding_request_info, 1); + + bonding->conn = dbus_connection_ref(conn); + bonding->msg = dbus_message_ref(msg); + bonding->adapter = adapter; + + str2ba(address, &bonding->bdaddr); + + return bonding; +} + +const char *mode2str(uint8_t mode) +{ + switch(mode) { + case MODE_OFF: + return "off"; + case MODE_CONNECTABLE: + return "connectable"; + case MODE_DISCOVERABLE: + return "discoverable"; + case MODE_LIMITED: + return "limited"; + default: + return "unknown"; + } +} + +static uint8_t on_mode(const char *addr) +{ + char mode[14]; + bdaddr_t sba; + + str2ba(addr, &sba); + + if (read_on_mode(&sba, mode, sizeof(mode)) < 0) + return MODE_CONNECTABLE; + + return str2mode(addr, mode); +} + +uint8_t str2mode(const char *addr, 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) + return on_mode(addr); + else + return MODE_UNKNOWN; +} + +static DBusMessage *adapter_get_info(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct adapter *adapter = data; + const char *property; + DBusMessage *reply; + DBusMessageIter iter; + DBusMessageIter dict; + bdaddr_t ba; + char str[249]; + uint8_t cls[3]; + + if (check_address(adapter->address) < 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); + + property = adapter->address; + dbus_message_iter_append_dict_entry(&dict, "address", + DBUS_TYPE_STRING, &property); + + memset(str, 0, sizeof(str)); + property = str; + str2ba(adapter->address, &ba); + + if (!read_local_name(&ba, str)) + dbus_message_iter_append_dict_entry(&dict, "name", + DBUS_TYPE_STRING, &property); + + get_device_version(adapter->dev_id, str, sizeof(str)); + dbus_message_iter_append_dict_entry(&dict, "version", + DBUS_TYPE_STRING, &property); + + get_device_revision(adapter->dev_id, str, sizeof(str)); + dbus_message_iter_append_dict_entry(&dict, "revision", + DBUS_TYPE_STRING, &property); + + get_device_manufacturer(adapter->dev_id, str, sizeof(str)); + dbus_message_iter_append_dict_entry(&dict, "manufacturer", + DBUS_TYPE_STRING, &property); + + get_device_company(adapter->dev_id, str, sizeof(str)); + dbus_message_iter_append_dict_entry(&dict, "company", + DBUS_TYPE_STRING, &property); + + property = mode2str(adapter->mode); + + dbus_message_iter_append_dict_entry(&dict, "mode", + DBUS_TYPE_STRING, &property); + + dbus_message_iter_append_dict_entry(&dict, "discoverable_timeout", + DBUS_TYPE_UINT32, &adapter->discov_timeout); + + if (!read_local_class(&ba, cls)) { + uint32_t class; + + memcpy(&class, cls, 3); + dbus_message_iter_append_dict_entry(&dict, "class", + DBUS_TYPE_UINT32, &class); + + property = major_class_str(class); + dbus_message_iter_append_dict_entry(&dict, "major_class", + DBUS_TYPE_STRING, &property); + + property = minor_class_str(class); + dbus_message_iter_append_dict_entry(&dict, "minor_class", + DBUS_TYPE_STRING, &property); + } + + dbus_message_iter_close_container(&iter, &dict); + + return reply; +} + +static DBusMessage *adapter_get_address(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct adapter *adapter = data; + const char *paddr = adapter->address; + DBusMessage *reply; + + if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) + return invalid_args(msg); + + if (check_address(paddr) < 0) + return adapter_not_ready(msg); + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_append_args(reply, DBUS_TYPE_STRING, &paddr, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *adapter_get_version(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct adapter *adapter = data; + DBusMessage *reply; + char str[20], *str_ptr = str; + int err; + + if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) + return invalid_args(msg); + + err = get_device_version(adapter->dev_id, str, sizeof(str)); + if (err < 0) + return failed_strerror(msg, -err); + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_append_args(reply, DBUS_TYPE_STRING, &str_ptr, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *adapter_get_revision(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct adapter *adapter = data; + DBusMessage *reply; + char str[64], *str_ptr = str; + int err; + + if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) + return invalid_args(msg); + + err = get_device_revision(adapter->dev_id, str, sizeof(str)); + if (err < 0) + return failed_strerror(msg, -err); + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_append_args(reply, DBUS_TYPE_STRING, &str_ptr, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *adapter_get_manufacturer(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct adapter *adapter = data; + DBusMessage *reply; + char str[64], *str_ptr = str; + int err; + + if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) + return invalid_args(msg); + + err = get_device_manufacturer(adapter->dev_id, str, sizeof(str)); + if (err < 0) + return failed_strerror(msg, -err); + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_append_args(reply, DBUS_TYPE_STRING, &str_ptr, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *adapter_get_company(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct adapter *adapter = data; + DBusMessage *reply; + char str[64], *str_ptr = str; + int err; + + if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) + return invalid_args(msg); + + err = get_device_company(adapter->dev_id, str, sizeof(str)); + if (err < 0) + return failed_strerror(msg, -err); + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_append_args(reply, DBUS_TYPE_STRING, &str_ptr, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *adapter_list_modes(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + DBusMessage *reply; + DBusMessageIter iter; + DBusMessageIter array_iter; + const char *mode_ptr[] = { "off", "connectable", "discoverable", "limited" }; + int i; + + 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_STRING_AS_STRING, &array_iter); + for (i = 0; i < 4; i++) + dbus_message_iter_append_basic(&array_iter, DBUS_TYPE_STRING, + &mode_ptr[i]); + + dbus_message_iter_close_container(&iter, &array_iter); + + return reply; +} + +static DBusMessage *adapter_get_mode(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + const struct adapter *adapter = data; + DBusMessage *reply = NULL; + const char *mode; + + 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; + + mode = mode2str(adapter->mode); + + dbus_message_append_args(reply, DBUS_TYPE_STRING, &mode, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *set_mode(DBusConnection *conn, DBusMessage *msg, + uint8_t new_mode, void *data) +{ + struct adapter *adapter = data; + uint8_t scan_enable; + uint8_t current_scan = adapter->scan_enable; + bdaddr_t local; + gboolean limited; + int err, dd; + const char *mode; + + switch(new_mode) { + case MODE_OFF: + scan_enable = SCAN_DISABLED; + break; + case MODE_CONNECTABLE: + scan_enable = SCAN_PAGE; + break; + case MODE_DISCOVERABLE: + case MODE_LIMITED: + scan_enable = (SCAN_PAGE | SCAN_INQUIRY); + break; + default: + return invalid_args(msg); + } + + /* Do reverse resolution in case of "on" mode */ + mode = mode2str(new_mode); + + dd = hci_open_dev(adapter->dev_id); + if (dd < 0) + return no_such_adapter(msg); + + if (!adapter->up && + (hcid.offmode == HCID_OFFMODE_NOSCAN || + (hcid.offmode == HCID_OFFMODE_DEVDOWN && + 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 failed_strerror(msg, err); + } + } + + if (adapter->up && scan_enable == SCAN_DISABLED && + hcid.offmode == HCID_OFFMODE_DEVDOWN) { + if (ioctl(dd, HCIDEVDOWN, adapter->dev_id) < 0) { + hci_close_dev(dd); + return failed_strerror(msg, errno); + } + + goto done; + } + + limited = (new_mode == MODE_LIMITED ? TRUE : FALSE); + err = set_limited_discoverable(dd, adapter->class, limited); + if (err < 0) { + hci_close_dev(dd); + return failed_strerror(msg, -err); + } + + if (current_scan != scan_enable) { + struct hci_request rq; + uint8_t status = 0; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_HOST_CTL; + rq.ocf = OCF_WRITE_SCAN_ENABLE; + rq.cparam = &scan_enable; + rq.clen = sizeof(scan_enable); + rq.rparam = &status; + rq.rlen = sizeof(status); + rq.event = EVT_CMD_COMPLETE; + + if (hci_send_req(dd, &rq, 1000) < 0) { + err = errno; + error("Sending write scan enable command failed: %s (%d)", + strerror(errno), errno); + hci_close_dev(dd); + return failed_strerror(msg, err); + } + + if (status) { + error("Setting scan enable failed with status 0x%02x", + status); + hci_close_dev(dd); + return failed_strerror(msg, bt_error(status)); + } + } else { + /* discoverable or limited */ + if ((scan_enable & SCAN_INQUIRY) && (new_mode != adapter->mode)) { + g_dbus_emit_signal(conn, + dbus_message_get_path(msg), + ADAPTER_INTERFACE, + "ModeChanged", + DBUS_TYPE_STRING, &mode, + DBUS_TYPE_INVALID); + + if (adapter->timeout_id) + g_source_remove(adapter->timeout_id); + + if (!adapter->sessions && !adapter->discov_timeout) + adapter->timeout_id = g_timeout_add(adapter->discov_timeout * 1000, + discov_timeout_handler, adapter); + } + } +done: + str2ba(adapter->address, &local); + write_device_mode(&local, mode); + + hci_close_dev(dd); + + adapter->mode = new_mode; + + return dbus_message_new_method_return(msg); +} + +gint find_session(struct mode_req *req, DBusMessage *msg) +{ + const char *name = dbus_message_get_sender(req->msg); + const char *sender = dbus_message_get_sender(msg); + + return strcmp(name, sender); +} + +static void confirm_mode_cb(struct agent *agent, DBusError *err, void *data) +{ + struct mode_req *req = data; + DBusMessage *reply; + + if (err && dbus_error_is_set(err)) { + reply = dbus_message_new_error(req->msg, err->name, err->message); + dbus_connection_send(req->conn, reply, NULL); + dbus_message_unref(reply); + goto cleanup; + } + + reply = set_mode(req->conn, req->msg, req->mode, req->adapter); + dbus_connection_send(req->conn, reply, NULL); + dbus_message_unref(reply); + + if (!g_slist_find_custom(req->adapter->sessions, req->msg, + (GCompareFunc) find_session)) + goto cleanup; + + return; + +cleanup: + dbus_message_unref(req->msg); + if (req->id) + g_dbus_remove_watch(req->conn, req->id); + dbus_connection_unref(req->conn); + g_free(req); +} + +static DBusMessage *confirm_mode(DBusConnection *conn, DBusMessage *msg, + const char *mode, void *data) +{ + struct adapter *adapter = data; + struct mode_req *req; + int ret; + + if (!adapter->agent) + return dbus_message_new_method_return(msg); + + req = g_new0(struct mode_req, 1); + req->adapter = adapter; + req->conn = dbus_connection_ref(conn); + req->msg = dbus_message_ref(msg); + req->mode = str2mode(adapter->address, mode); + + ret = agent_confirm_mode_change(adapter->agent, mode, confirm_mode_cb, + req); + if (ret < 0) { + dbus_connection_unref(req->conn); + dbus_message_unref(req->msg); + g_free(req); + return invalid_args(msg); + } + + return NULL; +} + +static DBusMessage *adapter_set_mode(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct adapter *adapter = data; + const char *mode; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &mode, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + if (!mode) + return invalid_args(msg); + + adapter->global_mode = str2mode(adapter->address, mode); + + if (adapter->global_mode == adapter->mode) + return dbus_message_new_method_return(msg); + + if (adapter->sessions && adapter->global_mode < adapter->mode) + return confirm_mode(conn, msg, mode, data); + + return set_mode(conn, msg, str2mode(adapter->address, mode), data); +} + +static DBusMessage *adapter_get_discoverable_to(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + const struct adapter *adapter = data; + DBusMessage *reply; + + 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_append_args(reply, DBUS_TYPE_UINT32, &adapter->discov_timeout, + DBUS_TYPE_INVALID); + + return reply; +} + +static void resolve_paths(DBusMessage *msg, char **old_path, char **new_path) +{ + const char *path = dbus_message_get_path(msg); + + if (!path) + return; + + if (old_path) + *old_path = NULL; + + if (new_path) + *new_path = NULL; + + /* old path calls */ + if (g_str_has_prefix(path, BASE_PATH)) { + if (old_path) + *old_path = g_strdup(path); + + if (hcid_dbus_use_experimental() && new_path) + *new_path = g_strdup(path + ADAPTER_PATH_INDEX); + + return; + } + + if (old_path) + *old_path = g_strconcat(BASE_PATH, path, NULL); + + if (new_path) + *new_path = g_strdup(path); +} + +static DBusMessage *set_discoverable_timeout(DBusConnection *conn, + DBusMessage *msg, + uint32_t timeout, + void *data) +{ + struct adapter *adapter = data; + bdaddr_t bdaddr; + char *old_path, *new_path; + + if (adapter->timeout_id) { + g_source_remove(adapter->timeout_id); + adapter->timeout_id = 0; + } + + if ((timeout != 0) && (adapter->scan_enable & SCAN_INQUIRY)) + adapter->timeout_id = g_timeout_add(timeout * 1000, + discov_timeout_handler, + adapter); + + adapter->discov_timeout = timeout; + + str2ba(adapter->address, &bdaddr); + write_discoverable_timeout(&bdaddr, timeout); + + resolve_paths(msg, &old_path, &new_path); + + g_dbus_emit_signal(conn, old_path, + ADAPTER_INTERFACE, + "DiscoverableTimeoutChanged", + DBUS_TYPE_UINT32, &timeout, + DBUS_TYPE_INVALID); + if (new_path) { + dbus_connection_emit_property_changed(conn, new_path, + ADAPTER_INTERFACE, + "DiscoverableTimeout", + DBUS_TYPE_UINT32, &timeout); + } + + g_free(old_path); + g_free(new_path); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *adapter_set_discoverable_to(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct adapter *adapter = data; + uint32_t timeout; + + if (!adapter->up) + return adapter_not_ready(msg); + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_UINT32, &timeout, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + return set_discoverable_timeout(conn, msg, timeout, data); +} + +static DBusMessage *adapter_is_connectable(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + const struct adapter *adapter = data; + DBusMessage *reply; + const uint8_t scan_enable = adapter->scan_enable; + dbus_bool_t connectable = FALSE; + + if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) + return invalid_args(msg); + + if (scan_enable & SCAN_PAGE) + connectable = TRUE; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &connectable, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *adapter_is_discoverable(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + const struct adapter *adapter = data; + DBusMessage *reply; + const uint8_t scan_enable = adapter->scan_enable; + dbus_bool_t discoverable = FALSE; + + if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) + return invalid_args(msg); + + if (scan_enable & SCAN_INQUIRY) + discoverable = TRUE; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &discoverable, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *adapter_is_connected(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + DBusMessage *reply; + dbus_bool_t connected = FALSE; + + struct adapter *adapter = data; + GSList *l = adapter->active_conn; + + const char *peer_addr; + bdaddr_t peer_bdaddr; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &peer_addr, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + if (check_address(peer_addr) < 0) + return invalid_args(msg); + + str2ba(peer_addr, &peer_bdaddr); + + l = g_slist_find_custom(l, &peer_bdaddr, active_conn_find_by_bdaddr); + if (l) + connected = TRUE; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &connected, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *adapter_list_connections(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + DBusMessage *reply; + DBusMessageIter iter; + DBusMessageIter array_iter; + struct adapter *adapter = data; + GSList *l = adapter->active_conn; + + 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_STRING_AS_STRING, &array_iter); + + while (l) { + char peer_addr[18]; + const char *paddr = peer_addr; + struct active_conn_info *dev = l->data; + + ba2str(&dev->bdaddr, peer_addr); + + dbus_message_iter_append_basic(&array_iter, DBUS_TYPE_STRING, + &paddr); + + l = l->next; + } + + dbus_message_iter_close_container(&iter, &array_iter); + + return reply; +} + +static DBusMessage *adapter_get_major_class(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + const struct adapter *adapter = data; + DBusMessage *reply; + const char *str_ptr = "computer"; + + if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) + return invalid_args(msg); + + /* FIXME: Currently, only computer major class is supported */ + if ((adapter->class[1] & 0x1f) != 1) + return unsupported_major_class(msg); + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_append_args(reply, DBUS_TYPE_STRING, &str_ptr, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *adapter_list_minor_classes(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + const struct adapter *adapter = data; + DBusMessage *reply; + DBusMessageIter iter; + DBusMessageIter array_iter; + const char **minor_ptr; + uint8_t major_class; + int size, i; + + if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) + return invalid_args(msg); + + major_class = adapter->class[1] & 0x1F; + + switch (major_class) { + case 1: /* computer */ + minor_ptr = computer_minor_cls; + size = sizeof(computer_minor_cls) / sizeof(*computer_minor_cls); + break; + case 2: /* phone */ + minor_ptr = phone_minor_cls; + size = sizeof(phone_minor_cls) / sizeof(*phone_minor_cls); + break; + default: + return unsupported_major_class(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_STRING_AS_STRING, &array_iter); + for (i = 0; i < size; i++) + dbus_message_iter_append_basic(&array_iter, DBUS_TYPE_STRING, + &minor_ptr[i]); + + dbus_message_iter_close_container(&iter, &array_iter); + + return reply; +} + +static DBusMessage *adapter_get_minor_class(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct adapter *adapter = data; + DBusMessage *reply; + const char *str_ptr = ""; + uint8_t minor_class; + + if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) + return invalid_args(msg); + + /* FIXME: Currently, only computer major class is supported */ + if ((adapter->class[1] & 0x1f) != 1) + return unsupported_major_class(msg); + + minor_class = adapter->class[0] >> 2; + + /* Validate computer minor class */ + if (minor_class > (sizeof(computer_minor_cls) / sizeof(*computer_minor_cls))) + goto failed; + + str_ptr = computer_minor_cls[minor_class]; + +failed: + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_append_args(reply, DBUS_TYPE_STRING, &str_ptr, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *adapter_set_minor_class(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct adapter *adapter = data; + const char *minor; + uint32_t dev_class = 0xFFFFFFFF; + int i, dd; + + if (!adapter->up) + return adapter_not_ready(msg); + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &minor, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + if (!minor) + return invalid_args(msg); + + dd = hci_open_dev(adapter->dev_id); + if (dd < 0) + return no_such_adapter(msg); + + /* Currently, only computer major class is supported */ + if ((adapter->class[1] & 0x1f) != 1) { + hci_close_dev(dd); + return unsupported_major_class(msg); + } + for (i = 0; i < sizeof(computer_minor_cls) / sizeof(*computer_minor_cls); i++) + if (!strcasecmp(minor, computer_minor_cls[i])) { + /* Remove the format type */ + dev_class = i << 2; + break; + } + + /* Check if it's a valid minor class */ + if (dev_class == 0xFFFFFFFF) { + hci_close_dev(dd); + return invalid_args(msg); + } + + /* set the service class and major class */ + dev_class |= (adapter->class[2] << 16) | (adapter->class[1] << 8); + + if (hci_write_class_of_dev(dd, dev_class, 2000) < 0) { + int err = errno; + error("Can't write class of device on hci%d: %s(%d)", + adapter->dev_id, strerror(errno), errno); + hci_close_dev(dd); + return failed_strerror(msg, err); + } + + g_dbus_emit_signal(conn, dbus_message_get_path(msg), + ADAPTER_INTERFACE, "MinorClassChanged", + DBUS_TYPE_STRING, &minor, + DBUS_TYPE_INVALID); + + hci_close_dev(dd); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *adapter_get_service_classes(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct adapter *adapter = data; + DBusMessage *reply; + DBusMessageIter iter; + DBusMessageIter array_iter; + const char *str_ptr; + int i; + + if (!adapter->up) + return adapter_not_ready(msg); + + 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_STRING_AS_STRING, &array_iter); + + for (i = 0; i < (sizeof(service_cls) / sizeof(*service_cls)); i++) { + if (adapter->class[2] & (1 << i)) { + str_ptr = service_cls[i]; + dbus_message_iter_append_basic(&array_iter, + DBUS_TYPE_STRING, &str_ptr); + } + } + + dbus_message_iter_close_container(&iter, &array_iter); + + return reply; +} + +static DBusMessage *adapter_get_name(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct adapter *adapter = data; + DBusMessage *reply; + char str[249], *str_ptr = str; + int err; + bdaddr_t ba; + + if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) + return invalid_args(msg); + + str2ba(adapter->address, &ba); + + err = read_local_name(&ba, str); + if (err < 0) { + if (!adapter->up) + return adapter_not_ready(msg); + + err = get_device_name(adapter->dev_id, str, sizeof(str)); + if (err < 0) + return failed_strerror(msg, -err); + } + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_append_args(reply, DBUS_TYPE_STRING, &str_ptr, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *set_name(DBusConnection *conn, DBusMessage *msg, + const char *name, void *data) +{ + struct adapter *adapter = data; + bdaddr_t bdaddr; + int ecode; + char *new_path; + + if (!g_utf8_validate(name, -1, NULL)) { + error("Name change failed: the supplied name isn't valid UTF-8"); + return invalid_args(msg); + } + + str2ba(adapter->address, &bdaddr); + + write_local_name(&bdaddr, (char *) name); + + if (!adapter->up) + goto done; + + ecode = set_device_name(adapter->dev_id, name); + if (ecode < 0) + return failed_strerror(msg, -ecode); +done: + resolve_paths(msg, NULL, &new_path); + + if (new_path) { + dbus_connection_emit_property_changed(conn, new_path, + ADAPTER_INTERFACE, + "Name", DBUS_TYPE_STRING, + &name); + } + + g_free(new_path); + return dbus_message_new_method_return(msg); +} + +static DBusMessage *adapter_set_name(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + char *str_ptr; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &str_ptr, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + return set_name(conn, msg, str_ptr, data); +} + +static DBusMessage *adapter_get_remote_info(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct adapter *adapter = data; + DBusMessage *reply; + DBusMessageIter iter; + DBusMessageIter dict; + bdaddr_t src, dst; + const char *addr_ptr; + char filename[PATH_MAX + 1]; + char buf[64]; + const char *ptr; + char *str; + dbus_bool_t boolean; + uint32_t class; + int compid, ver, subver; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &addr_ptr, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + if (check_address(addr_ptr) < 0) + 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_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); + + /* Name */ + create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, "names"); + str = textfile_caseget(filename, addr_ptr); + if (str) { + dbus_message_iter_append_dict_entry(&dict, "name", + DBUS_TYPE_STRING, &str); + free(str); + } + + str2ba(adapter->address, &src); + str2ba(addr_ptr, &dst); + + /* Remote device class */ + if (read_remote_class(&src, &dst, &class) == 0) { + + dbus_message_iter_append_dict_entry(&dict, "class", + DBUS_TYPE_UINT32, &class); + + ptr = major_class_str(class); + dbus_message_iter_append_dict_entry(&dict, "major_class", + DBUS_TYPE_STRING, &ptr); + + ptr = minor_class_str(class); + dbus_message_iter_append_dict_entry(&dict, "minor_class", + DBUS_TYPE_STRING, &ptr); + } + + /* Alias */ + if (get_device_alias(adapter->dev_id, &dst, buf, sizeof(buf)) > 0) { + ptr = buf; + dbus_message_iter_append_dict_entry(&dict, "alias", + DBUS_TYPE_STRING, &ptr); + } + + /* Bonded */ + create_name(filename, PATH_MAX, STORAGEDIR, + adapter->address, "linkkeys"); + str = textfile_caseget(filename, addr_ptr); + if (str) { + boolean = TRUE; + free(str); + } else { + boolean = FALSE; + } + + dbus_message_iter_append_dict_entry(&dict, "bonded", + DBUS_TYPE_BOOLEAN, &boolean); + + /* Trusted */ + boolean = read_trust(&src, addr_ptr, GLOBAL_TRUST); + dbus_message_iter_append_dict_entry(&dict, "trusted", + DBUS_TYPE_BOOLEAN, &boolean); + + /* Connected */ + if (g_slist_find_custom(adapter->active_conn, &dst, + active_conn_find_by_bdaddr)) + boolean = TRUE; + else + boolean = FALSE; + + dbus_message_iter_append_dict_entry(&dict, "connected", + DBUS_TYPE_BOOLEAN, &boolean); + + /* HCI Revision/Manufacturer/Version */ + create_name(filename, PATH_MAX, STORAGEDIR, + adapter->address, "manufacturers"); + + str = textfile_caseget(filename, addr_ptr); + if (!str) + goto done; + + if (sscanf(str, "%d %d %d", &compid, &ver, &subver) != 3) { + /* corrupted file data */ + free(str); + goto done; + } + + free(str); + + ptr = buf; + snprintf(buf, 16, "HCI 0x%X", subver); + dbus_message_iter_append_dict_entry(&dict, "revision", + DBUS_TYPE_STRING, &ptr); + + ptr = bt_compidtostr(compid); + dbus_message_iter_append_dict_entry(&dict, "manufacturer", + DBUS_TYPE_STRING, &ptr); + + str = lmp_vertostr(ver); + snprintf(buf, 64, "Bluetooth %s", str); + bt_free(str); + + create_name(filename, PATH_MAX, STORAGEDIR, + adapter->address, "features"); + + str = textfile_caseget(filename, addr_ptr); + if (str) { + if (strlen(str) == 16) { + uint8_t features; + /* Getting the third byte */ + features = ((str[6] - 48) << 4) | (str[7] - 48); + if (features & (LMP_EDR_ACL_2M | LMP_EDR_ACL_3M)) + snprintf(buf, 64, "Bluetooth %s + EDR", ptr); + + } + free(str); + } + ptr = buf; + dbus_message_iter_append_dict_entry(&dict, "version", + DBUS_TYPE_STRING, &ptr); + +done: + dbus_message_iter_close_container(&iter, &dict); + + return reply; +} + +static DBusMessage *adapter_get_remote_svc(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + return get_remote_svc_rec(conn, msg, data, SDP_FORMAT_BINARY); +} + +static DBusMessage *adapter_get_remote_svc_xml(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + return get_remote_svc_rec(conn, msg, data, SDP_FORMAT_XML); +} + +static DBusMessage *adapter_get_remote_svc_handles(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + return get_remote_svc_handles(conn, msg, data); +} + +static DBusMessage *adapter_get_remote_svc_identifiers(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + return get_remote_svc_identifiers(conn, msg, data); +} + +static DBusMessage *adapter_finish_sdp_transact(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + return finish_remote_svc_transact(conn, msg, data); +} + +static DBusMessage *adapter_get_remote_version(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct adapter *adapter = data; + DBusMessage *reply; + char filename[PATH_MAX + 1]; + char *addr_ptr, *str; + char *str_ver = NULL; + char info_array[64], *info = info_array; + int compid, ver, subver; + + memset(info_array, 0, 64); + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &addr_ptr, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + if (check_address(addr_ptr) < 0) + return invalid_args(msg); + + create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, + "manufacturers"); + + str = textfile_caseget(filename, addr_ptr); + if (!str) + return not_available(msg); + + if (sscanf(str, "%d %d %d", &compid, &ver, &subver) != 3) { + /* corrupted file data */ + free(str); + goto failed; + } + + free(str); + + str_ver = lmp_vertostr(ver); + + /* Default value */ + snprintf(info, 64, "Bluetooth %s", str_ver); + + create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, + "features"); + + str = textfile_caseget(filename, addr_ptr); + if (!str) + goto failed; + + /* Check if the data is not corrupted */ + if (strlen(str) == 16) { + uint8_t features; + /* Getting the third byte */ + features = ((str[6] - 48) << 4) | (str[7] - 48); + if (features & (LMP_EDR_ACL_2M | LMP_EDR_ACL_3M)) + snprintf(info, 64, "Bluetooth %s + EDR", str_ver); + } + + free(str); + +failed: + if (str_ver) + bt_free(str_ver); + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_append_args(reply, DBUS_TYPE_STRING, &info, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *adapter_get_remote_revision(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct adapter *adapter = data; + DBusMessage *reply; + char filename[PATH_MAX + 1]; + char *addr_ptr, *str; + char info_array[16], *info = info_array; + int compid, ver, subver; + + memset(info_array, 0, 16); + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &addr_ptr, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + if (check_address(addr_ptr) < 0) + return invalid_args(msg); + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, + "manufacturers"); + + str = textfile_caseget(filename, addr_ptr); + if (!str) + return not_available(msg); + + if (sscanf(str, "%d %d %d", &compid, &ver, &subver) == 3) + snprintf(info, 16, "HCI 0x%X", subver); + + free(str); + + dbus_message_append_args(reply, DBUS_TYPE_STRING, &info, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *adapter_get_remote_manufacturer(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct adapter *adapter = data; + DBusMessage *reply; + char filename[PATH_MAX + 1]; + char *addr_ptr, *str; + char info_array[64], *info = info_array; + int compid, ver, subver; + + memset(info_array, 0, 64); + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &addr_ptr, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + if (check_address(addr_ptr) < 0) + return invalid_args(msg); + + create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, + "manufacturers"); + + str = textfile_caseget(filename, addr_ptr); + if (!str) + return not_available(msg); + + if (sscanf(str, "%d %d %d", &compid, &ver, &subver) == 3) + info = bt_compidtostr(compid); + + free(str); + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_append_args(reply, DBUS_TYPE_STRING, &info, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *adapter_get_remote_company(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + DBusMessage *reply; + bdaddr_t bdaddr; + char oui[9], *str_bdaddr, *tmp; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &str_bdaddr, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + str2ba(str_bdaddr, &bdaddr); + ba2oui(&bdaddr, oui); + + tmp = ouitocomp(oui); + if (!tmp) + return not_available(msg); + + reply = dbus_message_new_method_return(msg); + if (!reply) { + free(tmp); + return NULL; + } + + dbus_message_append_args(reply, DBUS_TYPE_STRING, &tmp, + DBUS_TYPE_INVALID); + + free(tmp); + + return reply; +} + +static DBusMessage *get_remote_class(DBusConnection *conn, DBusMessage *msg, + void *data, uint32_t *class) +{ + struct adapter *adapter = data; + char *addr_peer; + bdaddr_t local, peer; + int ecode; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &addr_peer, + DBUS_TYPE_INVALID)) { + return invalid_args(msg); + } + + if (check_address(addr_peer) < 0) + return invalid_args(msg); + + str2ba(addr_peer, &peer); + str2ba(adapter->address, &local); + + ecode = read_remote_class(&local, &peer, class); + if (ecode < 0) + return not_available(msg); + + return NULL; +} + +static DBusMessage *adapter_get_remote_major_class(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + DBusMessage *reply; + const char *major_class; + uint32_t class; + + reply = get_remote_class(conn, msg, data, &class); + if (reply) + return reply; + + major_class = major_class_str(class); + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_append_args(reply, DBUS_TYPE_STRING, &major_class, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *adapter_get_remote_minor_class(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + DBusMessage *reply; + const char *minor_class; + uint32_t class; + + reply = get_remote_class(conn, msg, data, &class); + if (reply) + return reply; + + minor_class = minor_class_str(class); + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_append_args(reply, DBUS_TYPE_STRING, &minor_class, + DBUS_TYPE_INVALID); + + return reply; +} + +static void append_class_string(const char *class, DBusMessageIter *iter) +{ + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &class); +} + +static DBusMessage *adapter_get_remote_service_cls(DBusConnection *conn, + DBusMessage *msg, + void *data) +{ + DBusMessage *reply; + DBusMessageIter iter, array_iter; + GSList *service_classes; + uint32_t class; + + reply = get_remote_class(conn, msg, data, &class); + if (reply) + return reply; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + service_classes = service_classes_str(class); + + dbus_message_iter_init_append(reply, &iter); + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_TYPE_STRING_AS_STRING, &array_iter); + + g_slist_foreach(service_classes, (GFunc) append_class_string, + &array_iter); + + dbus_message_iter_close_container(&iter, &array_iter); + + g_slist_free(service_classes); + + return reply; +} + +static DBusMessage *adapter_get_remote_class(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + DBusMessage *reply; + uint32_t class; + + reply = get_remote_class(conn, msg, data, &class); + if (reply) + return reply; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_append_args(reply, DBUS_TYPE_UINT32, &class, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *adapter_get_remote_features(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + char filename[PATH_MAX + 1]; + struct adapter *adapter = data; + DBusMessage *reply = NULL; + DBusMessageIter iter, array_iter; + uint8_t features[8], *ptr = features; + const char *addr; + char *str; + int i; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &addr, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + if (check_address(addr) < 0) + return invalid_args(msg); + + create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, "features"); + + str = textfile_caseget(filename, addr); + if (!str) + return not_available(msg); + + memset(features, 0, sizeof(features)); + for (i = 0; i < sizeof(features); i++) { + char tmp[3]; + + memcpy(tmp, str + (i * 2), 2); + tmp[2] = '\0'; + + features[i] = (uint8_t) strtol(tmp, NULL, 16); + } + + reply = dbus_message_new_method_return(msg); + if (!reply) { + free(str); + return NULL; + } + + dbus_message_iter_init_append(reply, &iter); + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_TYPE_BYTE_AS_STRING, &array_iter); + + dbus_message_iter_append_fixed_array(&array_iter, + DBUS_TYPE_BYTE, &ptr, sizeof(features)); + + dbus_message_iter_close_container(&iter, &array_iter); + + free(str); + + return reply; +} + +static DBusMessage *adapter_get_remote_name(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + char filename[PATH_MAX + 1]; + struct adapter *adapter = data; + DBusMessage *reply = NULL; + const char *peer_addr; + bdaddr_t peer_bdaddr; + char *str; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &peer_addr, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + if (check_address(peer_addr) < 0) + return invalid_args(msg); + + /* check if it is in the cache */ + create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, "names"); + + str = textfile_caseget(filename, peer_addr); + + if (str) { + reply = dbus_message_new_method_return(msg); + if (!reply) { + free(str); + return NULL; + } + + /* send the cached name */ + dbus_message_append_args(reply, DBUS_TYPE_STRING, &str, + DBUS_TYPE_INVALID); + + free(str); + return reply; + } + + if (!adapter->up) + return adapter_not_ready(msg); + + /* If the discover process is not running, return an error */ + if (!adapter->discov_active && !adapter->pdiscov_active) + return not_available(msg); + + /* Queue the request when there is a discovery running */ + str2ba(peer_addr, &peer_bdaddr); + found_device_add(&adapter->found_devices, &peer_bdaddr, 0, NAME_REQUIRED); + + return g_dbus_create_error(msg, + ERROR_INTERFACE ".RequestDeferred", + "Request Deferred"); +} + +static DBusMessage *adapter_get_remote_alias(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct adapter *adapter = data; + DBusMessage *reply; + char str[249], *str_ptr = str, *addr_ptr; + bdaddr_t bdaddr; + int ecode; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &addr_ptr, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + if (check_address(addr_ptr) < 0) + return invalid_args(msg); + + str2ba(addr_ptr, &bdaddr); + + ecode = get_device_alias(adapter->dev_id, &bdaddr, str, sizeof(str)); + if (ecode < 0) + return not_available(msg); + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_append_args(reply, DBUS_TYPE_STRING, &str_ptr, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *adapter_set_remote_alias(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct adapter *adapter = data; + char *alias, *addr, *old_path, *new_path; + bdaddr_t bdaddr; + int ecode; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &addr, + DBUS_TYPE_STRING, &alias, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + if ((strlen(alias) == 0) || (check_address(addr) < 0)) { + error("Alias change failed: Invalid parameter"); + return invalid_args(msg); + } + + str2ba(addr, &bdaddr); + + ecode = set_device_alias(adapter->dev_id, &bdaddr, alias); + if (ecode < 0) + return failed_strerror(msg, -ecode); + + resolve_paths(msg, &old_path, &new_path); + + g_dbus_emit_signal(conn, old_path, + ADAPTER_INTERFACE, "RemoteAliasChanged", + DBUS_TYPE_STRING, &addr, + DBUS_TYPE_STRING, &alias, + DBUS_TYPE_INVALID); + + if (new_path) { + struct device *device; + + device = adapter_find_device(adapter, addr); + if (device) { + dbus_connection_emit_property_changed(conn, + device->path, DEVICE_INTERFACE, + "Alias", DBUS_TYPE_STRING, &alias); + } + } + + g_free(old_path); + g_free(new_path); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *adapter_clear_remote_alias(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct adapter *adapter = data; + char *addr_ptr; + bdaddr_t bdaddr; + int ecode, had_alias = 1; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &addr_ptr, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + if (check_address(addr_ptr) < 0) { + error("Alias clear failed: Invalid parameter"); + return invalid_args(msg); + } + + str2ba(addr_ptr, &bdaddr); + + ecode = get_device_alias(adapter->dev_id, &bdaddr, NULL, 0); + if (ecode == -ENXIO) + had_alias = 0; + + ecode = set_device_alias(adapter->dev_id, &bdaddr, NULL); + if (ecode < 0) + return failed_strerror(msg, -ecode); + + if (had_alias) + g_dbus_emit_signal(conn, dbus_message_get_path(msg), + ADAPTER_INTERFACE, + "RemoteAliasCleared", + DBUS_TYPE_STRING, &addr_ptr, + DBUS_TYPE_INVALID); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *adapter_last_seen(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct adapter *adapter = data; + DBusMessage *reply; + char filename[PATH_MAX + 1]; + char *addr_ptr, *str; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &addr_ptr, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + if (check_address(addr_ptr) < 0) + return invalid_args(msg); + + create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, + "lastseen"); + + str = textfile_caseget(filename, addr_ptr); + if (!str) + return not_available(msg); + + reply = dbus_message_new_method_return(msg); + if (!reply) { + free(str); + return NULL; + } + + dbus_message_append_args(reply, DBUS_TYPE_STRING, &str, + DBUS_TYPE_INVALID); + + free(str); + + return reply; +} + +static DBusMessage *adapter_last_used(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct adapter *adapter = data; + DBusMessage *reply; + char filename[PATH_MAX + 1]; + char *addr_ptr, *str; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &addr_ptr, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + if (check_address(addr_ptr) < 0) + return invalid_args(msg); + + create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, + "lastused"); + + str = textfile_caseget(filename, addr_ptr); + if (!str) + return not_available(msg); + + reply = dbus_message_new_method_return(msg); + if (!reply) { + free(str); + return NULL; + } + + dbus_message_append_args(reply, DBUS_TYPE_STRING, &str, + DBUS_TYPE_INVALID); + + free(str); + + return reply; +} + +gboolean dc_pending_timeout_handler(void *data) +{ + int dd; + struct adapter *adapter = data; + struct pending_dc_info *pending_dc = adapter->pending_dc; + DBusMessage *reply; + + dd = hci_open_dev(adapter->dev_id); + + if (dd < 0) { + error_no_such_adapter(pending_dc->conn, + pending_dc->msg); + dc_pending_timeout_cleanup(adapter); + return FALSE; + } + + /* Send the HCI disconnect command */ + if (hci_disconnect(dd, htobs(pending_dc->conn_handle), + HCI_OE_USER_ENDED_CONNECTION, + 500) < 0) { + int err = errno; + error("Disconnect failed"); + error_failed_errno(pending_dc->conn, pending_dc->msg, err); + } else { + reply = dbus_message_new_method_return(pending_dc->msg); + if (reply) { + dbus_connection_send(pending_dc->conn, reply, NULL); + dbus_message_unref(reply); + } else + error("Failed to allocate disconnect reply"); + } + + hci_close_dev(dd); + dc_pending_timeout_cleanup(adapter); + + return FALSE; +} + +void dc_pending_timeout_cleanup(struct adapter *adapter) +{ + dbus_connection_unref(adapter->pending_dc->conn); + dbus_message_unref(adapter->pending_dc->msg); + g_free(adapter->pending_dc); + adapter->pending_dc = NULL; +} + +static DBusMessage *adapter_dc_remote_device(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct adapter *adapter = data; + GSList *l = adapter->active_conn; + const char *peer_addr; + bdaddr_t peer_bdaddr; + + if (!adapter->up) + return adapter_not_ready(msg); + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &peer_addr, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + if (check_address(peer_addr) < 0) + return invalid_args(msg); + + str2ba(peer_addr, &peer_bdaddr); + + l = g_slist_find_custom(l, &peer_bdaddr, active_conn_find_by_bdaddr); + if (!l) + return g_dbus_create_error(msg, + ERROR_INTERFACE ".NotConnected", + "Device not connected"); + + if (adapter->pending_dc) + return in_progress(msg, "Disconnection in progress"); + + adapter->pending_dc = g_new0(struct pending_dc_info, 1); + + /* Start waiting... */ + adapter->pending_dc->timeout_id = + g_timeout_add(DC_PENDING_TIMEOUT, + dc_pending_timeout_handler, + adapter); + + if (!adapter->pending_dc->timeout_id) { + g_free(adapter->pending_dc); + adapter->pending_dc = NULL; + return NULL; + } + + adapter->pending_dc->conn = dbus_connection_ref(conn); + adapter->pending_dc->msg = dbus_message_ref(msg); + adapter->pending_dc->conn_handle = + ((struct active_conn_info *) l->data)->handle; + + g_dbus_emit_signal(conn, dbus_message_get_path(msg), + ADAPTER_INTERFACE, + "RemoteDeviceDisconnectRequested", + DBUS_TYPE_STRING, &peer_addr, + DBUS_TYPE_INVALID); + + return NULL; +} + +static void reply_authentication_failure(struct bonding_request_info *bonding) +{ + DBusMessage *reply; + int status; + + status = bonding->hci_status ? + bonding->hci_status : HCI_AUTHENTICATION_FAILURE; + + reply = new_authentication_return(bonding->msg, status); + if (reply) { + dbus_connection_send(bonding->conn, reply, NULL); + dbus_message_unref(reply); + } +} + +struct device *adapter_find_device(struct adapter *adapter, const char *dest) +{ + struct 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 device *adapter_create_device(DBusConnection *conn, + struct adapter *adapter, const char *address) +{ + struct device *device; + + debug("adapter_create_device(%s)", address); + + device = device_create(conn, adapter, address); + if (!device) + return NULL; + + device->temporary = TRUE; + + adapter->devices = g_slist_append(adapter->devices, device); + + return device; +} + +static DBusMessage *remove_bonding(DBusConnection *conn, DBusMessage *msg, + const char *address, void *data) +{ + struct adapter *adapter = data; + struct device *device; + char path[MAX_PATH_LENGTH], filename[PATH_MAX + 1]; + char *str; + bdaddr_t src, dst; + GSList *l; + int dev, err; + gboolean paired; + + str2ba(adapter->address, &src); + str2ba(address, &dst); + + dev = hci_open_dev(adapter->dev_id); + if (dev < 0 && msg) + return no_such_adapter(msg); + + create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, + "linkkeys"); + + /* textfile_del doesn't return an error when the key is not found */ + str = textfile_caseget(filename, address); + paired = str ? TRUE : FALSE; + g_free(str); + + if (!paired && msg) { + hci_close_dev(dev); + return g_dbus_create_error(msg, + ERROR_INTERFACE ".DoesNotExist", + "Bonding does not exist"); + } + + /* Delete the link key from storage */ + if (textfile_casedel(filename, address) < 0 && msg) { + hci_close_dev(dev); + err = errno; + return failed_strerror(msg, err); + } + + /* Delete the link key from the Bluetooth chip */ + hci_delete_stored_link_key(dev, &dst, 0, 1000); + + /* find the connection */ + l = g_slist_find_custom(adapter->active_conn, &dst, + active_conn_find_by_bdaddr); + if (l) { + struct active_conn_info *con = l->data; + /* Send the HCI disconnect command */ + if ((hci_disconnect(dev, htobs(con->handle), + HCI_OE_USER_ENDED_CONNECTION, 500) < 0) + && msg){ + int err = errno; + error("Disconnect failed"); + hci_close_dev(dev); + return failed_strerror(msg, err); + } + } + + hci_close_dev(dev); + + if (paired) { + snprintf(path, MAX_PATH_LENGTH, BASE_PATH "/hci%d", + adapter->dev_id); + g_dbus_emit_signal(conn, path, + ADAPTER_INTERFACE, "BondingRemoved", + DBUS_TYPE_STRING, &address, + DBUS_TYPE_INVALID); + } + + device = adapter_find_device(adapter, address); + if (!device) + goto proceed; + + if (paired) { + gboolean paired = FALSE; + dbus_connection_emit_property_changed(conn, device->path, + DEVICE_INTERFACE, "Paired", + DBUS_TYPE_BOOLEAN, &paired); + } + +proceed: + if(!msg) + goto done; + + return dbus_message_new_method_return(msg); + +done: + return NULL; +} + + +void adapter_remove_device(DBusConnection *conn, struct adapter *adapter, + struct device *device) +{ + bdaddr_t src; + char path[MAX_PATH_LENGTH]; + + str2ba(adapter->address, &src); + delete_entry(&src, "profiles", device->address); + + remove_bonding(conn, NULL, device->address, adapter); + + if (!device->temporary) { + snprintf(path, MAX_PATH_LENGTH, "/hci%d", adapter->dev_id); + g_dbus_emit_signal(conn, path, + ADAPTER_INTERFACE, + "DeviceRemoved", + DBUS_TYPE_OBJECT_PATH, &device->path, + DBUS_TYPE_INVALID); + } + + if (device->agent) { + agent_destroy(device->agent, FALSE); + device->agent = NULL; + } + + adapter->devices = g_slist_remove(adapter->devices, device); + + device_remove(conn, device); +} + +struct device *adapter_get_device(DBusConnection *conn, + struct adapter *adapter, const gchar *address) +{ + struct 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); +} + +void remove_pending_device(struct adapter *adapter) +{ + struct device *device; + char address[18]; + + ba2str(&adapter->bonding->bdaddr, address); + device = adapter_find_device(adapter, address); + if (!device) + return; + + if (device->temporary) + adapter_remove_device(adapter->bonding->conn, adapter, device); +} + +static gboolean create_bonding_conn_complete(GIOChannel *io, GIOCondition cond, + struct adapter *adapter) +{ + struct hci_request rq; + auth_requested_cp cp; + evt_cmd_status rp; + struct l2cap_conninfo cinfo; + socklen_t len; + int sk, dd, ret; + + if (!adapter->bonding) { + /* If we come here it implies a bug somewhere */ + debug("create_bonding_conn_complete: no pending bonding!"); + g_io_channel_close(io); + g_io_channel_unref(io); + return FALSE; + } + + if (cond & G_IO_NVAL) { + error_authentication_canceled(adapter->bonding->conn, + adapter->bonding->msg); + goto cleanup; + } + + if (cond & (G_IO_HUP | G_IO_ERR)) { + debug("Hangup or error on bonding IO channel"); + + if (!adapter->bonding->auth_active) + error_connection_attempt_failed(adapter->bonding->conn, + adapter->bonding->msg, + ENETDOWN); + else + reply_authentication_failure(adapter->bonding); + + goto failed; + } + + sk = g_io_channel_unix_get_fd(io); + + len = sizeof(ret); + if (getsockopt(sk, SOL_SOCKET, SO_ERROR, &ret, &len) < 0) { + error("Can't get socket error: %s (%d)", + strerror(errno), errno); + error_failed_errno(adapter->bonding->conn, adapter->bonding->msg, + errno); + goto failed; + } + + if (ret != 0) { + if (adapter->bonding->auth_active) + reply_authentication_failure(adapter->bonding); + else + error_connection_attempt_failed(adapter->bonding->conn, + adapter->bonding->msg, + ret); + goto failed; + } + + len = sizeof(cinfo); + if (getsockopt(sk, SOL_L2CAP, L2CAP_CONNINFO, &cinfo, &len) < 0) { + error("Can't get connection info: %s (%d)", + strerror(errno), errno); + error_failed_errno(adapter->bonding->conn, adapter->bonding->msg, + errno); + goto failed; + } + + dd = hci_open_dev(adapter->dev_id); + if (dd < 0) { + error_no_such_adapter(adapter->bonding->conn, + adapter->bonding->msg); + goto failed; + } + + memset(&rp, 0, sizeof(rp)); + + memset(&cp, 0, sizeof(cp)); + cp.handle = htobs(cinfo.hci_handle); + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_LINK_CTL; + rq.ocf = OCF_AUTH_REQUESTED; + rq.cparam = &cp; + rq.clen = AUTH_REQUESTED_CP_SIZE; + rq.rparam = &rp; + rq.rlen = EVT_CMD_STATUS_SIZE; + rq.event = EVT_CMD_STATUS; + + if (hci_send_req(dd, &rq, 500) < 0) { + error("Unable to send HCI request: %s (%d)", + strerror(errno), errno); + error_failed_errno(adapter->bonding->conn, adapter->bonding->msg, + errno); + hci_close_dev(dd); + goto failed; + } + + if (rp.status) { + error("HCI_Authentication_Requested failed with status 0x%02x", + rp.status); + error_failed_errno(adapter->bonding->conn, adapter->bonding->msg, + bt_error(rp.status)); + hci_close_dev(dd); + goto failed; + } + + hci_close_dev(dd); + + adapter->bonding->auth_active = 1; + + adapter->bonding->io_id = g_io_add_watch(io, + G_IO_NVAL | G_IO_HUP | G_IO_ERR, + (GIOFunc) create_bonding_conn_complete, + adapter); + + return FALSE; + +failed: + g_io_channel_close(io); + remove_pending_device(adapter); + +cleanup: + g_dbus_remove_watch(adapter->bonding->conn, + adapter->bonding->listener_id); + bonding_request_free(adapter->bonding); + adapter->bonding = NULL; + + return FALSE; +} + +static void cancel_auth_request(struct pending_auth_info *auth, int dev_id) +{ + int dd; + + if (auth->replied) + return; + + dd = hci_open_dev(dev_id); + if (dd < 0) { + error("hci_open_dev: %s (%d)", strerror(errno), errno); + return; + } + + switch (auth->type) { + case AUTH_TYPE_PINCODE: + hci_send_cmd(dd, OGF_LINK_CTL, OCF_PIN_CODE_NEG_REPLY, + 6, &auth->bdaddr); + break; + case AUTH_TYPE_CONFIRM: + hci_send_cmd(dd, OGF_LINK_CTL, OCF_USER_CONFIRM_NEG_REPLY, + 6, &auth->bdaddr); + break; + case AUTH_TYPE_PASSKEY: + hci_send_cmd(dd, OGF_LINK_CTL, OCF_USER_PASSKEY_NEG_REPLY, + 6, &auth->bdaddr); + break; + case AUTH_TYPE_NOTIFY: + /* User Notify doesn't require any reply */ + break; + } + + auth->replied = TRUE; + + hci_close_dev(dd); +} + +static void create_bond_req_exit(void *user_data) +{ + struct adapter *adapter = user_data; + struct pending_auth_info *auth; + char path[MAX_PATH_LENGTH]; + + snprintf(path, sizeof(path), "%s/hci%d", BASE_PATH, adapter->dev_id); + + debug("CreateConnection requestor exited before bonding was completed"); + + cancel_passkey_agent_requests(adapter->passkey_agents, path, + &adapter->bonding->bdaddr); + release_passkey_agents(adapter, &adapter->bonding->bdaddr); + + auth = adapter_find_auth_request(adapter, &adapter->bonding->bdaddr); + if (auth) { + cancel_auth_request(auth, adapter->dev_id); + if (auth->agent) + agent_cancel(auth->agent); + adapter_remove_auth_request(adapter, &adapter->bonding->bdaddr); + } + + remove_pending_device(adapter); + + g_io_channel_close(adapter->bonding->io); + if (adapter->bonding->io_id) + g_source_remove(adapter->bonding->io_id); + bonding_request_free(adapter->bonding); + adapter->bonding = NULL; +} + +static DBusMessage *create_bonding(DBusConnection *conn, DBusMessage *msg, + const char *address, const char *agent_path, + uint8_t capability, void *data) +{ + char filename[PATH_MAX + 1]; + char *str; + struct adapter *adapter = data; + struct bonding_request_info *bonding; + bdaddr_t bdaddr; + int sk; + + str2ba(address, &bdaddr); + + /* check if there is a pending discover: requested by D-Bus/non clients */ + if (adapter->discov_active) + return in_progress(msg, "Discover in progress"); + + pending_remote_name_cancel(adapter); + + if (adapter->bonding) + return in_progress(msg, "Bonding in progress"); + + if (adapter_find_auth_request(adapter, &bdaddr)) + return in_progress(msg, "Bonding in progress"); + + /* check if a link key already exists */ + create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, + "linkkeys"); + + str = textfile_caseget(filename, address); + if (str) { + free(str); + return g_dbus_create_error(msg, + ERROR_INTERFACE ".AlreadyExists", + "Bonding already exists"); + } + + sk = l2raw_connect(adapter->address, &bdaddr); + if (sk < 0) + return g_dbus_create_error(msg, + ERROR_INTERFACE ".ConnectionAttemptFailed", + "Connection attempt failed"); + + bonding = bonding_request_new(conn, msg, adapter, address, agent_path, + capability); + if (!bonding) { + close(sk); + return NULL; + } + + bonding->io = g_io_channel_unix_new(sk); + bonding->io_id = g_io_add_watch(bonding->io, + G_IO_OUT | G_IO_NVAL | G_IO_HUP | G_IO_ERR, + (GIOFunc) create_bonding_conn_complete, + adapter); + + bonding->listener_id = g_dbus_add_disconnect_watch(conn, + dbus_message_get_sender(msg), + create_bond_req_exit, adapter, + NULL); + + adapter->bonding = bonding; + + return NULL; +} + +static DBusMessage *adapter_create_bonding(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct adapter *adapter = data; + char *address; + + if (!adapter->up) + return adapter_not_ready(msg); + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &address, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + if (check_address(address) < 0) + return invalid_args(msg); + + return create_bonding(conn, msg, address, NULL, + IO_CAPABILITY_INVALID, data); +} +static DBusMessage *adapter_cancel_bonding(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct adapter *adapter = data; + const char *address; + bdaddr_t bdaddr; + struct bonding_request_info *bonding = adapter->bonding; + struct pending_auth_info *auth_req; + + if (!adapter->up) + return adapter_not_ready(msg); + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &address, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + if (check_address(address) < 0) + return invalid_args(msg); + + str2ba(address, &bdaddr); + if (!bonding || bacmp(&bonding->bdaddr, &bdaddr)) + return not_in_progress(msg, "Bonding is not in progress"); + + if (strcmp(dbus_message_get_sender(adapter->bonding->msg), + dbus_message_get_sender(msg))) + return not_authorized(msg); + + adapter->bonding->cancel = 1; + + auth_req = adapter_find_auth_request(adapter, &bdaddr); + if (auth_req) { + if (auth_req->replied) { + /* + * If disconnect can't be applied and the PIN code + * request was already replied it doesn't make sense + * cancel the remote passkey: return not authorized. + */ + g_io_channel_close(adapter->bonding->io); + return not_authorized(msg); + } + + cancel_auth_request(auth_req, adapter->dev_id); + if (auth_req->agent) + agent_cancel(auth_req->agent); + adapter_remove_auth_request(adapter, &bdaddr); + } + + g_io_channel_close(adapter->bonding->io); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *adapter_remove_bonding(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct adapter *adapter = data; + char *address; + + if (!adapter->up) + return adapter_not_ready(msg); + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &address, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + if (check_address(address) < 0) + return invalid_args(msg); + + return remove_bonding(conn, msg, address, data); +} + +static DBusMessage *adapter_has_bonding(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct adapter *adapter = data; + DBusMessage *reply; + char filename[PATH_MAX + 1]; + char *addr_ptr, *str; + dbus_bool_t result; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &addr_ptr, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + if (check_address(addr_ptr) < 0) + return invalid_args(msg); + + create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, + "linkkeys"); + + str = textfile_caseget(filename, addr_ptr); + if (str) { + result = TRUE; + free(str); + } else + result = FALSE; + + reply = dbus_message_new_method_return(msg); + + dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &result, + DBUS_TYPE_INVALID); + + return reply; +} + +static void list_bondings_do_append(char *key, char *value, void *data) +{ + DBusMessageIter *iter = data; + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &key); +} + +static DBusMessage *adapter_list_bondings(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct adapter *adapter = data; + DBusMessageIter iter; + DBusMessageIter array_iter; + DBusMessage *reply; + char filename[PATH_MAX + 1]; + + if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) + return invalid_args(msg); + + create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, + "linkkeys"); + + reply = dbus_message_new_method_return(msg); + + dbus_message_iter_init_append(reply, &iter); + + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_TYPE_STRING_AS_STRING, &array_iter); + + textfile_foreach(filename, list_bondings_do_append, &array_iter); + + dbus_message_iter_close_container(&iter, &array_iter); + + return reply; +} + +static DBusMessage *adapter_get_pin_code_length(DBusConnection *conn, + DBusMessage *msg, + void *data) +{ + struct adapter *adapter = data; + DBusMessage *reply; + bdaddr_t local, peer; + char *addr_ptr; + uint8_t length; + int len; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &addr_ptr, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + if (check_address(addr_ptr) < 0) + return invalid_args(msg); + + str2ba(adapter->address, &local); + + str2ba(addr_ptr, &peer); + + len = read_pin_length(&local, &peer); + if (len < 0) + return g_dbus_create_error(msg, + ERROR_INTERFACE ".DoesNotExist", + "Record does not exist"); + + reply = dbus_message_new_method_return(msg); + + length = len; + + dbus_message_append_args(reply, DBUS_TYPE_BYTE, &length, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *adapter_get_encryption_key_size(DBusConnection *conn, + DBusMessage *msg, + void *data) +{ + struct adapter *adapter = data; + DBusMessage *reply; + bdaddr_t bdaddr; + char *addr_ptr; + uint8_t size; + int val; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &addr_ptr, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + if (check_address(addr_ptr) < 0) + return invalid_args(msg); + + str2ba(addr_ptr, &bdaddr); + + val = get_encryption_key_size(adapter->dev_id, &bdaddr); + if (val < 0) + return failed_strerror(msg, -val); + + reply = dbus_message_new_method_return(msg); + + size = val; + + dbus_message_append_args(reply, DBUS_TYPE_BYTE, &size, + DBUS_TYPE_INVALID); + + return reply; +} + +static void periodic_discover_req_exit(void *user_data) +{ + struct adapter *adapter = user_data; + + debug("PeriodicDiscovery requestor exited"); + + /* Cleanup the discovered devices list and send the cmd to exit from + * periodic inquiry or cancel remote name request. The return value can + * be ignored. */ + + cancel_periodic_discovery(adapter); +} + +static DBusMessage *adapter_start_periodic(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + periodic_inquiry_cp cp; + struct hci_request rq; + struct adapter *adapter = data; + uint8_t lap[3] = { 0x33, 0x8b, 0x9e }; + uint8_t status; + int dd; + + if (!adapter->up) + return adapter_not_ready(msg); + + if (dbus_message_is_method_call(msg, ADAPTER_INTERFACE, + "StartPeriodicDiscovery")) { + if (!dbus_message_has_signature(msg, + DBUS_TYPE_INVALID_AS_STRING)) + return invalid_args(msg); + } + + if (adapter->discov_active || adapter->pdiscov_active) + return in_progress(msg, "Discover in progress"); + + pending_remote_name_cancel(adapter); + + dd = hci_open_dev(adapter->dev_id); + if (dd < 0) + return no_such_adapter(msg); + + 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, 1000) < 0) { + int err = errno; + error("Unable to start periodic inquiry: %s (%d)", + strerror(errno), errno); + hci_close_dev(dd); + return failed_strerror(msg, err); + } + + if (status) { + error("HCI_Periodic_Inquiry_Mode failed with status 0x%02x", + status); + hci_close_dev(dd); + return failed_strerror(msg, bt_error(status)); + } + + adapter->pdiscov_requestor = g_strdup(dbus_message_get_sender(msg)); + + if (adapter->pdiscov_resolve_names) + adapter->discov_type = PERIODIC_INQUIRY | RESOLVE_NAME; + else + adapter->discov_type = PERIODIC_INQUIRY; + + hci_close_dev(dd); + + /* track the request owner to cancel it automatically if the owner + * exits */ + adapter->pdiscov_listener = g_dbus_add_disconnect_watch(conn, + dbus_message_get_sender(msg), + periodic_discover_req_exit, + adapter, NULL); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *adapter_stop_periodic(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct adapter *adapter = data; + int err; + + if (!adapter->up) + return adapter_not_ready(msg); + + if (dbus_message_is_method_call(msg, ADAPTER_INTERFACE, + "StopPeriodicDiscovery")) { + if (!dbus_message_has_signature(msg, + DBUS_TYPE_INVALID_AS_STRING)) + return invalid_args(msg); + } + + if (!adapter->pdiscov_active) + return g_dbus_create_error(msg, + ERROR_INTERFACE ".NotAuthorized", + "Not authorized"); + /* + * Cleanup the discovered devices list and send the cmd to exit + * from periodic inquiry mode or cancel remote name request. + */ + err = cancel_periodic_discovery(adapter); + if (err < 0) { + if (err == -ENODEV) + return no_such_adapter(msg); + + else + return failed_strerror(msg, -err); + } + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *adapter_is_periodic(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + DBusMessage *reply; + struct adapter *adapter = data; + dbus_bool_t active = adapter->pdiscov_active; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &active, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *adapter_set_pdiscov_resolve(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct adapter *adapter = data; + dbus_bool_t resolve; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_BOOLEAN, &resolve, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + debug("SetPeriodicDiscoveryNameResolving(%s)", + resolve ? "TRUE" : "FALSE"); + + adapter->pdiscov_resolve_names = resolve; + + if (adapter->pdiscov_active) { + if (resolve) + adapter->discov_type |= RESOLVE_NAME; + else + adapter->discov_type &= ~RESOLVE_NAME; + } + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *adapter_get_pdiscov_resolve(DBusConnection *conn, + DBusMessage *msg, + void *data) +{ + DBusMessage *reply; + struct adapter *adapter = data; + dbus_bool_t resolve = adapter->pdiscov_resolve_names; + + 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_append_args(reply, DBUS_TYPE_BOOLEAN, &resolve, + DBUS_TYPE_INVALID); + + return reply; +} + +static void discover_devices_req_exit(void *user_data) +{ + struct adapter *adapter = user_data; + + debug("DiscoverDevices requestor exited"); + + /* Cleanup the discovered devices list and send the command to cancel + * inquiry or cancel remote name request. The return can be ignored. */ + cancel_discovery(adapter); +} + +static DBusMessage *adapter_discover_devices(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + const char *method; + inquiry_cp cp; + evt_cmd_status rp; + struct hci_request rq; + struct adapter *adapter = data; + uint8_t lap[3] = { 0x33, 0x8b, 0x9e }; + int dd; + + if (!adapter->up) + return adapter_not_ready(msg); + + if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) + return invalid_args(msg); + + if (adapter->discov_active) + return in_progress(msg, "Discover in progress"); + + pending_remote_name_cancel(adapter); + + if (adapter->bonding) + return in_progress(msg, "Bonding in progress"); + + dd = hci_open_dev(adapter->dev_id); + if (dd < 0) + return no_such_adapter(msg); + + 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, 500) < 0) { + int err = errno; + error("Unable to start inquiry: %s (%d)", + strerror(errno), errno); + hci_close_dev(dd); + return failed_strerror(msg, err); + } + + if (rp.status) { + error("HCI_Inquiry command failed with status 0x%02x", + rp.status); + hci_close_dev(dd); + return failed_strerror(msg, bt_error(rp.status)); + } + + method = dbus_message_get_member(msg); + if (strcmp("DiscoverDevicesWithoutNameResolving", method) == 0) + adapter->discov_type |= STD_INQUIRY; + else + adapter->discov_type |= (STD_INQUIRY | RESOLVE_NAME); + + adapter->discov_requestor = g_strdup(dbus_message_get_sender(msg)); + + + hci_close_dev(dd); + + /* track the request owner to cancel it automatically if the owner + * exits */ + adapter->discov_listener = g_dbus_add_disconnect_watch(conn, + dbus_message_get_sender(msg), + discover_devices_req_exit, + adapter, NULL); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *adapter_cancel_discovery(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct adapter *adapter = data; + int err; + + if (!adapter->up) + return adapter_not_ready(msg); + + if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) + return invalid_args(msg); + + /* is there discover pending? or discovery cancel was requested + * previously */ + if (!adapter->discov_active || adapter->discovery_cancel) + return g_dbus_create_error(msg, + ERROR_INTERFACE ".NotAuthorized", + "Not Authorized"); + + /* only the discover requestor can cancel the inquiry process */ + if (!adapter->discov_requestor || + strcmp(adapter->discov_requestor, dbus_message_get_sender(msg))) + return g_dbus_create_error(msg, + ERROR_INTERFACE ".NotAuthorized", + "Not Authorized"); + + /* Cleanup the discovered devices list and send the cmd to cancel + * inquiry or cancel remote name request */ + err = cancel_discovery(adapter); + if (err < 0) { + if (err == -ENODEV) + return no_such_adapter(msg); + else + return failed_strerror(msg, -err); + } + + /* Reply before send DiscoveryCompleted */ + adapter->discovery_cancel = dbus_message_ref(msg); + + return NULL; +} + +struct remote_device_list_t { + GSList *list; + time_t time; +}; + +static void list_remote_devices_do_append(char *key, char *value, void *data) +{ + struct remote_device_list_t *param = data; + char *address; + struct tm date; + + if (g_slist_find_custom(param->list, key, (GCompareFunc) strcasecmp)) + return; + + if (param->time){ + strptime(value, "%Y-%m-%d %H:%M:%S %Z", &date); + if (difftime(mktime(&date), param->time) < 0) + return; + } + + address = g_strdup(key); + + param->list = g_slist_append(param->list, address); +} + +static void remote_devices_do_append(void *data, void *user_data) +{ + DBusMessageIter *iter = user_data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &data); +} + +static DBusMessage *adapter_list_remote_devices(DBusConnection *conn, + DBusMessage *msg, + void *data) +{ + struct adapter *adapter = data; + DBusMessageIter iter; + DBusMessageIter array_iter; + DBusMessage *reply; + char filename[PATH_MAX + 1]; + struct remote_device_list_t param = { NULL, 0 }; + + if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) + return invalid_args(msg); + + /* Add Bonded devices to the list */ + create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, "linkkeys"); + textfile_foreach(filename, list_remote_devices_do_append, ¶m); + + /* Add Trusted devices to the list */ + create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, "trusts"); + textfile_foreach(filename, list_remote_devices_do_append, ¶m); + + /* Add Last Used devices to the list */ + create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, "lastused"); + textfile_foreach(filename, list_remote_devices_do_append, ¶m); + + reply = dbus_message_new_method_return(msg); + + dbus_message_iter_init_append(reply, &iter); + + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_TYPE_STRING_AS_STRING, &array_iter); + + g_slist_foreach(param.list, remote_devices_do_append, &array_iter); + + g_slist_foreach(param.list, (GFunc) free, NULL); + g_slist_free(param.list); + + dbus_message_iter_close_container(&iter, &array_iter); + + return reply; +} + +static void append_connected(struct active_conn_info *dev, GSList *list) +{ + char address[18]; + + ba2str(&dev->bdaddr, address); + if (g_slist_find_custom(list, address, (GCompareFunc) strcasecmp)) + return; + + list = g_slist_append(list, g_strdup(address)); +} + +static DBusMessage *adapter_list_recent_remote_devices(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct adapter *adapter = data; + struct tm date; + const char *string; + DBusMessageIter iter; + DBusMessageIter array_iter; + DBusMessage *reply; + char filename[PATH_MAX + 1]; + struct remote_device_list_t param = { NULL, 0 }; + int len; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &string, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + /* Date format is "YYYY-MM-DD HH:MM:SS GMT" */ + len = strlen(string); + if (len && (strptime(string, "%Y-%m-%d %H:%M:%S", &date) == NULL)) + return invalid_args(msg); + + /* Bonded and trusted: mandatory entries(no matter the date/time) */ + create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, "linkkeys"); + textfile_foreach(filename, list_remote_devices_do_append, ¶m); + + create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, "trusts"); + textfile_foreach(filename, list_remote_devices_do_append, ¶m); + + /* Last seen/used: append devices since the date informed */ + if (len) + param.time = mktime(&date); + + create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, "lastseen"); + textfile_foreach(filename, list_remote_devices_do_append, ¶m); + + create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, "lastused"); + textfile_foreach(filename, list_remote_devices_do_append, ¶m); + + /* connected: force appending connected devices, lastused might not match */ + g_slist_foreach(adapter->active_conn, (GFunc) append_connected, param.list); + + reply = dbus_message_new_method_return(msg); + + dbus_message_iter_init_append(reply, &iter); + + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_TYPE_STRING_AS_STRING, &array_iter); + + g_slist_foreach(param.list, remote_devices_do_append, &array_iter); + + g_slist_foreach(param.list, (GFunc) free, NULL); + g_slist_free(param.list); + + dbus_message_iter_close_container(&iter, &array_iter); + + return reply; +} + + +static DBusMessage *adapter_set_trusted(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct adapter *adapter = data; + bdaddr_t local; + const char *address; + char *old_path, *new_path; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &address, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + if (check_address(address) < 0) + return invalid_args(msg); + + str2ba(adapter->address, &local); + + write_trust(&local, address, GLOBAL_TRUST, TRUE); + + resolve_paths(msg, &old_path, &new_path); + + g_dbus_emit_signal(conn, old_path, + ADAPTER_INTERFACE, "TrustAdded", + DBUS_TYPE_STRING, &address, + DBUS_TYPE_INVALID); + + if (new_path) { + struct device *device; + gboolean trust = TRUE; + + device = adapter_find_device(adapter, address); + if (device) { + dbus_connection_emit_property_changed(conn, + device->path, DEVICE_INTERFACE, + "Trusted", DBUS_TYPE_BOOLEAN, &trust); + } + } + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *adapter_is_trusted(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct adapter *adapter = data; + DBusMessage *reply; + const char *address; + dbus_bool_t trusted; + bdaddr_t local; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &address, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + if (check_address(address) < 0) + return invalid_args(msg); + + str2ba(adapter->address, &local); + + trusted = read_trust(&local, address, GLOBAL_TRUST); + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_append_args(reply, + DBUS_TYPE_BOOLEAN, &trusted, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *adapter_remove_trust(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct adapter *adapter = data; + const char *address; + bdaddr_t local; + char *old_path, *new_path; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &address, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + if (check_address(address) < 0) + return invalid_args(msg); + + str2ba(adapter->address, &local); + + write_trust(&local, address, GLOBAL_TRUST, FALSE); + + resolve_paths(msg, &old_path, &new_path); + + g_dbus_emit_signal(conn, old_path, + ADAPTER_INTERFACE, "TrustRemoved", + DBUS_TYPE_STRING, &address, + DBUS_TYPE_INVALID); + + if (new_path) { + struct device *device; + gboolean trust = FALSE; + + device = adapter_find_device(adapter, address); + if (device) { + dbus_connection_emit_property_changed(conn, + device->path, DEVICE_INTERFACE, + "Trusted", DBUS_TYPE_BOOLEAN, &trust); + } + } + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *adapter_list_trusts(DBusConnection *conn, + DBusMessage *msg, + void *data) +{ + struct adapter *adapter = data; + DBusMessage *reply; + GSList *trusts, *l; + char **addrs; + bdaddr_t local; + int len; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + str2ba(adapter->address, &local); + + trusts = list_trusts(&local, GLOBAL_TRUST); + + addrs = g_new(char *, g_slist_length(trusts)); + + for (l = trusts, len = 0; l; l = l->next, len++) + addrs[len] = l->data; + + dbus_message_append_args(reply, + DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, + &addrs, len, + DBUS_TYPE_INVALID); + + g_free(addrs); + g_slist_foreach(trusts, (GFunc) g_free, NULL); + g_slist_free(trusts); + + return reply; +} + +static DBusMessage *get_properties(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct adapter *adapter = data; + const char *property; + DBusMessage *reply; + DBusMessageIter iter; + DBusMessageIter dict; + bdaddr_t ba; + char str[249]; + + if (check_address(adapter->address) < 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 = adapter->address; + dbus_message_iter_append_dict_entry(&dict, "Address", + DBUS_TYPE_STRING, &property); + + /* Name */ + memset(str, 0, sizeof(str)); + property = str; + str2ba(adapter->address, &ba); + + if (!read_local_name(&ba, str)) + dbus_message_iter_append_dict_entry(&dict, "Name", + DBUS_TYPE_STRING, &property); + + /* Mode */ + property = mode2str(adapter->mode); + + dbus_message_iter_append_dict_entry(&dict, "Mode", + DBUS_TYPE_STRING, &property); + + /* DiscoverableTimeout */ + dbus_message_iter_append_dict_entry(&dict, "DiscoverableTimeout", + DBUS_TYPE_UINT32, &adapter->discov_timeout); + + /* PeriodicDiscovery */ + dbus_message_iter_append_dict_entry(&dict, "PeriodicDiscovery", + DBUS_TYPE_BOOLEAN, &adapter->pdiscov_active); + + dbus_message_iter_close_container(&iter, &dict); + + return reply; +} + +static DBusMessage *set_property(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct adapter *adapter = data; + DBusMessageIter iter; + DBusMessageIter sub; + const char *property; + + 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("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("PeriodicDiscovery", property)) { + dbus_bool_t value; + + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_BOOLEAN) + return invalid_args(msg); + dbus_message_iter_get_basic(&sub, &value); + + if (value) + return adapter_start_periodic(conn, msg, data); + else + return adapter_stop_periodic(conn, msg, data); + } else if (g_str_equal("Mode", property)) { + const char *mode; + + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING) + return invalid_args(msg); + + dbus_message_iter_get_basic(&sub, &mode); + + adapter->global_mode = str2mode(adapter->address, mode); + + if (adapter->global_mode == adapter->mode) + return dbus_message_new_method_return(msg); + + if (adapter->sessions && adapter->global_mode < adapter->mode) + return confirm_mode(conn, msg, mode, data); + + return set_mode(conn, msg, str2mode(adapter->address, mode), + data); + } + + return invalid_args(msg); +} + +static void session_exit(void *data) +{ + struct mode_req *req = data; + struct adapter *adapter = req->adapter; + + adapter->sessions = g_slist_remove(adapter->sessions, req); + + if (!adapter->sessions) { + debug("Falling back to '%s' mode", mode2str(adapter->global_mode)); + /* FIXME: fallback to previous mode + set_mode(req->conn, req->msg, adapter->global_mode, adapter); + */ + } + dbus_connection_unref(req->conn); + dbus_message_unref(req->msg); + g_free(req); +} + +static DBusMessage *request_mode(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + const char *mode; + struct adapter *adapter = data; + struct mode_req *req; + uint8_t new_mode; + int ret; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &mode, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + new_mode = str2mode(adapter->address, mode); + if (new_mode != MODE_CONNECTABLE && new_mode != MODE_DISCOVERABLE) + return invalid_args(msg); + + if (!adapter->agent) + return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", + "No agent registered"); + + if (g_slist_find_custom(adapter->sessions, msg, + (GCompareFunc) find_session)) + return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", + "Mode already requested"); + + req = g_new0(struct mode_req, 1); + req->adapter = adapter; + req->conn = dbus_connection_ref(conn); + req->msg = dbus_message_ref(msg); + req->mode = new_mode; + req->id = g_dbus_add_disconnect_watch(conn, + dbus_message_get_sender(msg), + session_exit, req, NULL); + + if (!adapter->sessions) + adapter->global_mode = adapter->mode; + adapter->sessions = g_slist_append(adapter->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, mode, confirm_mode_cb, + req); + if (ret < 0) { + dbus_message_unref(req->msg); + g_dbus_remove_watch(req->conn, req->id); + dbus_connection_unref(req->conn); + g_free(req); + return invalid_args(msg); + } + + return NULL; +} + +static DBusMessage *release_mode(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct adapter *adapter = data; + GSList *l; + + l = g_slist_find_custom(adapter->sessions, msg, + (GCompareFunc) find_session); + if (!l) + return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", + "No Mode to release"); + + session_exit(l->data); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *list_devices(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct adapter *adapter = data; + DBusMessage *reply; + GSList *l; + DBusMessageIter iter; + DBusMessageIter array_iter; + + 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 device *device = l->data; + + if (device->temporary) + continue; + + dbus_message_iter_append_basic(&array_iter, + DBUS_TYPE_OBJECT_PATH, &device->path); + } + + dbus_message_iter_close_container(&iter, &array_iter); + + return reply; +} + +static DBusMessage *create_device(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct adapter *adapter = data; + struct 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 = device_create(conn, adapter, address); + if (!device) + return NULL; + + device->temporary = FALSE; + + device_browse(device, conn, msg, NULL); + + adapter->devices = g_slist_append(adapter->devices, device); + + 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) +{ + const gchar *address, *agent_path, *capability; + 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); + + cap = parse_io_capability(capability); + if (cap == IO_CAPABILITY_INVALID) + return invalid_args(msg); + + return create_bonding(conn, msg, address, agent_path, cap, data); +} + +static gint device_path_cmp(struct device *device, const gchar *path) +{ + return strcasecmp(device->path, path); +} + +static DBusMessage *remove_device(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct adapter *adapter = data; + struct 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->temporary || device->discov_active) + 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 adapter *adapter = data; + struct device *device; + DBusMessage *reply; + const gchar *address; + GSList *l; + + 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; + + if (device->temporary) + return g_dbus_create_error(msg, + ERROR_INTERFACE ".DoesNotExist", + "Device creation in progress"); + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_append_args(reply, + DBUS_TYPE_OBJECT_PATH, &device->path, + DBUS_TYPE_INVALID); + + return reply; +} + +static void agent_removed(struct agent *agent, struct adapter *adapter) +{ + struct pending_auth_info *auth; + GSList *l; + + adapter->agent = NULL; + + l = g_slist_find_custom(adapter->auth_reqs, agent, + auth_info_agent_cmp); + if (!l) + return; + + auth = l->data; + auth->agent = NULL; +} + +static DBusMessage *register_agent(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + const char *path, *name, *capability; + struct agent *agent; + struct 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 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 DBusMessage *add_service_record(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct adapter *adapter = data; + DBusMessage *reply; + const char *sender, *record; + dbus_uint32_t handle; + bdaddr_t src; + int err; + + if (dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &record, DBUS_TYPE_INVALID) == FALSE) + return NULL; + + sender = dbus_message_get_sender(msg); + str2ba(adapter->address, &src); + err = add_xml_record(conn, sender, &src, record, &handle); + if (err < 0) + return failed_strerror(msg, err); + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_append_args(reply, DBUS_TYPE_UINT32, &handle, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *update_service_record(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct adapter *adapter = data; + bdaddr_t src; + + str2ba(adapter->address, &src); + + return update_xml_record(conn, msg, &src); +} + +static DBusMessage *remove_service_record(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + dbus_uint32_t handle; + const char *sender; + + if (dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT32, &handle, + DBUS_TYPE_INVALID) == FALSE) + return NULL; + + sender = dbus_message_get_sender(msg); + + if (remove_record(conn, sender, handle) < 0) + return not_available(msg); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *request_authorization(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + /* FIXME implement the request */ + + return NULL; +} + +static DBusMessage *cancel_authorization(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + /* FIXME implement cancel request */ + + return dbus_message_new_method_return(msg); +} + +/* BlueZ 4.0 API */ +static GDBusMethodTable adapter_methods[] = { + { "GetProperties", "", "a{sv}",get_properties }, + { "SetProperty", "sv", "", set_property, + G_DBUS_METHOD_FLAG_ASYNC}, + { "RequestMode", "s", "", request_mode, + G_DBUS_METHOD_FLAG_ASYNC}, + { "ReleaseMode", "", "", release_mode }, + { "DiscoverDevices", "", "", adapter_discover_devices}, + { "CancelDiscovery", "", "", adapter_cancel_discovery, + G_DBUS_METHOD_FLAG_ASYNC}, + { "ListDevices", "", "ao", list_devices }, + { "CreateDevice", "s", "o", create_device, + G_DBUS_METHOD_FLAG_ASYNC}, + { "CreatePairedDevice", "sos", "o", create_paired_device, + G_DBUS_METHOD_FLAG_ASYNC}, + { "RemoveDevice", "o", "", remove_device }, + { "FindDevice", "s", "o", find_device }, + { "RegisterAgent", "os", "", register_agent }, + { "UnregisterAgent", "o", "", unregister_agent }, + { "AddServiceRecord", "s", "u", add_service_record }, + { "UpdateServiceRecord","us", "", update_service_record }, + { "RemoveServiceRecord","u", "", remove_service_record }, + { "RequestAuthorization","su", "", request_authorization, + G_DBUS_METHOD_FLAG_ASYNC}, + { "CancelAuthorization","", "", cancel_authorization }, + { } +}; + +/* Deprecated */ +static GDBusMethodTable old_adapter_methods[] = { + { "GetInfo", "", "a{sv}", + adapter_get_info }, + { "GetAddress", "", "s", + adapter_get_address }, + { "GetVersion", "", "s", + adapter_get_version }, + { "GetRevision", "", "s", + adapter_get_revision }, + { "GetManufacturer", "", "s", + adapter_get_manufacturer }, + { "GetCompany", "", "s", + adapter_get_company }, + { "ListAvailableModes", "", "as", + adapter_list_modes }, + { "GetMode", "", "s", + adapter_get_mode }, + { "SetMode", "s", "", + adapter_set_mode }, + { "GetDiscoverableTimeout", "", "u", + adapter_get_discoverable_to }, + { "SetDiscoverableTimeout", "u", "", + adapter_set_discoverable_to }, + { "IsConnectable", "", "b", + adapter_is_connectable }, + { "IsDiscoverable", "", "b", + adapter_is_discoverable }, + { "IsConnected", "s", "b", + adapter_is_connected }, + { "ListConnections", "", "as", + adapter_list_connections }, + { "GetMajorClass", "", "s", + adapter_get_major_class }, + { "ListAvailableMinorClasses", "", "as", + adapter_list_minor_classes }, + { "GetMinorClass", "", "s", + adapter_get_minor_class }, + { "SetMinorClass", "s", "", + adapter_set_minor_class }, + { "GetServiceClasses", "", "as", + adapter_get_service_classes }, + { "GetName", "", "s", + adapter_get_name }, + { "SetName", "s", "", + adapter_set_name }, + + { "GetRemoteInfo", "s", "a{sv}", + adapter_get_remote_info }, + { "GetRemoteServiceRecord", "su", "ay", + adapter_get_remote_svc, G_DBUS_METHOD_FLAG_ASYNC }, + { "GetRemoteServiceRecordAsXML", "su", "s", + adapter_get_remote_svc_xml, G_DBUS_METHOD_FLAG_ASYNC }, + { "GetRemoteServiceHandles", "ss", "au", + adapter_get_remote_svc_handles, G_DBUS_METHOD_FLAG_ASYNC }, + { "GetRemoteServiceIdentifiers", "s", "as", + adapter_get_remote_svc_identifiers, G_DBUS_METHOD_FLAG_ASYNC }, + { "FinishRemoteServiceTransaction", "s", "", + adapter_finish_sdp_transact }, + { "GetRemoteVersion", "s", "s", + adapter_get_remote_version }, + { "GetRemoteRevision", "s", "s", + adapter_get_remote_revision }, + { "GetRemoteManufacturer", "s", "s", + adapter_get_remote_manufacturer }, + { "GetRemoteCompany", "s", "s", + adapter_get_remote_company }, + { "GetRemoteMajorClass", "s", "s", + adapter_get_remote_major_class }, + { "GetRemoteMinorClass", "s", "s", + adapter_get_remote_minor_class }, + { "GetRemoteServiceClasses", "s", "as", + adapter_get_remote_service_cls }, + { "GetRemoteClass", "s", "u", + adapter_get_remote_class }, + { "GetRemoteFeatures", "s", "ay", + adapter_get_remote_features }, + { "GetRemoteName", "s", "s", + adapter_get_remote_name }, + { "GetRemoteAlias", "s", "s", + adapter_get_remote_alias }, + { "SetRemoteAlias", "ss", "", + adapter_set_remote_alias }, + { "ClearRemoteAlias", "s", "", + adapter_clear_remote_alias }, + + { "LastSeen", "s", "s", + adapter_last_seen }, + { "LastUsed", "s", "s", + adapter_last_used }, + + { "DisconnectRemoteDevice", "s", "", + adapter_dc_remote_device, G_DBUS_METHOD_FLAG_ASYNC}, + + { "CreateBonding", "s", "", + adapter_create_bonding, G_DBUS_METHOD_FLAG_ASYNC}, + { "CancelBondingProcess", "s", "", + adapter_cancel_bonding }, + { "RemoveBonding", "s", "", + adapter_remove_bonding }, + { "HasBonding", "s", "b", + adapter_has_bonding }, + { "ListBondings", "", "as", + adapter_list_bondings }, + { "GetPinCodeLength", "s", "y", + adapter_get_pin_code_length }, + { "GetEncryptionKeySize", "s", "y", + adapter_get_encryption_key_size }, + + { "StartPeriodicDiscovery", "", "", + adapter_start_periodic }, + { "StopPeriodicDiscovery", "", "", + adapter_stop_periodic }, + { "IsPeriodicDiscovery", "", "b", + adapter_is_periodic }, + { "SetPeriodicDiscoveryNameResolving", "b", "", + adapter_set_pdiscov_resolve }, + { "GetPeriodicDiscoveryNameResolving", "", "b", + adapter_get_pdiscov_resolve }, + { "DiscoverDevices", "", "", + adapter_discover_devices }, + { "CancelDiscovery", "", "", + adapter_cancel_discovery, G_DBUS_METHOD_FLAG_ASYNC }, + { "DiscoverDevicesWithoutNameResolving","", "", + adapter_discover_devices }, + { "ListRemoteDevices", "", "as", + adapter_list_remote_devices }, + { "ListRecentRemoteDevices", "s", "as", + adapter_list_recent_remote_devices}, + + { "SetTrusted", "s", "", + adapter_set_trusted }, + { "IsTrusted", "s", "b", + adapter_is_trusted }, + { "RemoveTrust", "s", "", + adapter_remove_trust }, + { "ListTrusts", "", "as", + adapter_list_trusts }, + + { } +}; + +/* BlueZ 4.X */ +static GDBusSignalTable adapter_signals[] = { + { "DiscoveryStarted", "" }, + { "DiscoveryCompleted", "" }, + { "DeviceCreated", "o" }, + { "DeviceRemoved", "o" }, + { "DeviceFound", "sa{sv}" }, + { "PropertyChanged", "sv" }, + { "DeviceDisappeared", "s" }, + { } +}; + +/* Deprecated */ +static GDBusSignalTable old_adapter_signals[] = { + { "DiscoveryStarted", "" }, + { "DiscoveryCompleted", "" }, + { "ModeChanged", "s" }, + { "DiscoverableTimeoutChanged", "u" }, + { "MinorClassChanged", "s" }, + { "NameChanged", "s" }, + { "PeriodicDiscoveryStarted", "" }, + { "PeriodicDiscoveryStopped", "" }, + { "RemoteDeviceFound", "sun" }, + { "RemoteDeviceDisappeared", "s" }, + { "RemoteClassUpdated", "su" }, + { "RemoteNameUpdated", "ss" }, + { "RemoteNameFailed", "s" }, + { "RemoteNameRequested", "s" }, + { "RemoteAliasChanged", "ss" }, + { "RemoteAliasCleared", "s" }, + { "RemoteDeviceConnected", "s" }, + { "RemoteDeviceDisconnectRequested", "s" }, + { "RemoteDeviceDisconnected", "s" }, + { "RemoteIdentifiersUpdated", "sas" }, + { "BondingCreated", "s" }, + { "BondingRemoved", "s" }, + { "TrustAdded", "s" }, + { "TrustRemoved", "s" }, + { } +}; + +dbus_bool_t adapter_init(DBusConnection *conn, + const char *path, struct adapter *adapter) +{ + if (hcid_dbus_use_experimental()) + g_dbus_register_interface(conn, path + ADAPTER_PATH_INDEX, + ADAPTER_INTERFACE, adapter_methods, + adapter_signals, NULL, adapter, NULL); + + return g_dbus_register_interface(conn, + path, ADAPTER_INTERFACE, + old_adapter_methods, old_adapter_signals, + NULL, adapter, NULL); +} + +dbus_bool_t adapter_cleanup(DBusConnection *conn, const char *path) +{ + return g_dbus_unregister_interface(conn, path, ADAPTER_INTERFACE); +} + +const char *major_class_str(uint32_t class) +{ + uint8_t index = (class >> 8) & 0x1F; + + if (index > 8) + return major_cls[9]; /* uncategorized */ + + return major_cls[index]; +} + +const char *minor_class_str(uint32_t class) +{ + uint8_t major_index = (class >> 8) & 0x1F; + uint8_t minor_index; + + switch (major_index) { + case 1: /* computer */ + minor_index = (class >> 2) & 0x3F; + if (minor_index < NUM_ELEMENTS(computer_minor_cls)) + return computer_minor_cls[minor_index]; + else + return ""; + case 2: /* phone */ + minor_index = (class >> 2) & 0x3F; + if (minor_index < NUM_ELEMENTS(phone_minor_cls)) + return phone_minor_cls[minor_index]; + return ""; + case 3: /* access point */ + minor_index = (class >> 5) & 0x07; + if (minor_index < NUM_ELEMENTS(access_point_minor_cls)) + return access_point_minor_cls[minor_index]; + else + return ""; + case 4: /* audio/video */ + minor_index = (class >> 2) & 0x3F; + if (minor_index < NUM_ELEMENTS(audio_video_minor_cls)) + return audio_video_minor_cls[minor_index]; + else + return ""; + case 5: /* peripheral */ + minor_index = (class >> 6) & 0x03; + if (minor_index < NUM_ELEMENTS(peripheral_minor_cls)) + return peripheral_minor_cls[minor_index]; + else + return ""; + case 6: /* imaging */ + { + uint8_t shift_minor = 0; + + minor_index = (class >> 4) & 0x0F; + while (shift_minor < (sizeof(imaging_minor_cls) / sizeof(*imaging_minor_cls))) { + if (((minor_index >> shift_minor) & 0x01) == 0x01) + return imaging_minor_cls[shift_minor]; + shift_minor++; + } + } + break; + case 7: /* wearable */ + minor_index = (class >> 2) & 0x3F; + if (minor_index < NUM_ELEMENTS(wearable_minor_cls)) + return wearable_minor_cls[minor_index]; + else + return ""; + case 8: /* toy */ + minor_index = (class >> 2) & 0x3F; + if (minor_index < NUM_ELEMENTS(toy_minor_cls)) + return toy_minor_cls[minor_index]; + else + return ""; + } + + return ""; +} + +GSList *service_classes_str(uint32_t class) +{ + uint8_t services = class >> 16; + GSList *l = NULL; + int i; + + for (i = 0; i < (sizeof(service_cls) / sizeof(*service_cls)); i++) { + if (!(services & (1 << i))) + continue; + + l = g_slist_append(l, (void *) service_cls[i]); + } + + return l; +} diff --git a/hcid/adapter.h b/hcid/adapter.h new file mode 100644 index 00000000..f6ed7a22 --- /dev/null +++ b/hcid/adapter.h @@ -0,0 +1,165 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 + * + */ + +#define ADAPTER_INTERFACE "org.bluez.Adapter" + +#define INVALID_DEV_ID 0xFFFF + +#define BONDING_TIMEOUT 45000 /* 45 sec */ + +#define DC_PENDING_TIMEOUT 2000 /* 2 secs */ + +/* Discover types */ +#define DISCOVER_TYPE_NONE 0x00 +#define STD_INQUIRY 0x01 +#define PERIODIC_INQUIRY 0x02 + +/* Actions executed after inquiry complete */ +#define RESOLVE_NAME 0x10 + +typedef enum { + NAME_ANY, + NAME_NOT_REQUIRED, /* used by get remote name without name resolving */ + NAME_REQUIRED, /* remote name needs be resolved */ + NAME_REQUESTED, /* HCI remote name request was sent */ + NAME_SENT /* D-Bus signal RemoteNameUpdated sent */ +} name_status_t; + +typedef enum { + AUTH_TYPE_PINCODE, + AUTH_TYPE_PASSKEY, + AUTH_TYPE_CONFIRM, + AUTH_TYPE_NOTIFY, +} auth_type_t; + +struct remote_dev_info { + bdaddr_t bdaddr; + int8_t rssi; + name_status_t name_status; +}; + +struct bonding_request_info { + DBusConnection *conn; + DBusMessage *msg; + struct adapter *adapter; + bdaddr_t bdaddr; + GIOChannel *io; + guint io_id; + guint listener_id; + int hci_status; + int cancel; + int auth_active; +}; + +struct pending_auth_info { + auth_type_t type; + bdaddr_t bdaddr; + gboolean replied; /* If we've already replied to the request */ + struct agent *agent; /* Agent associated with the request */ +}; + +struct active_conn_info { + bdaddr_t bdaddr; + uint16_t handle; +}; + +struct pending_dc_info { + DBusConnection *conn; + DBusMessage *msg; + uint16_t conn_handle; + guint timeout_id; +}; + +struct adapter { + uint16_t dev_id; + int up; + char *path; /* adapter object path */ + char address[18]; /* adapter Bluetooth Address */ + guint timeout_id; /* discoverable timeout id */ + uint32_t discov_timeout; /* discoverable time(msec) */ + uint8_t scan_enable; /* scan mode: SCAN_DISABLED, SCAN_PAGE, SCAN_INQUIRY */ + uint8_t mode; /* off, connectable, discoverable, limited */ + uint8_t global_mode; /* last valid global mode */ + uint8_t class[3]; /* device class */ + int discov_active; /* standard discovery active: includes name resolution step */ + int pdiscov_active; /* periodic discovery active */ + int pinq_idle; /* tracks the idle time for periodic inquiry */ + int discov_type; /* type requested */ + int pdiscov_resolve_names; /* Resolve names when doing periodic discovery */ + GSList *found_devices; + GSList *oor_devices; /* out of range device list */ + char *pdiscov_requestor; /* periodic discovery requestor unique name */ + guint pdiscov_listener; + char *discov_requestor; /* discovery requestor unique name */ + guint discov_listener; + DBusMessage *discovery_cancel; /* discovery cancel message request */ + GSList *passkey_agents; + struct agent *agent; /* For the new API */ + GSList *active_conn; + struct bonding_request_info *bonding; + GSList *auth_reqs; /* Received and replied HCI + authentication requests */ + struct pending_dc_info *pending_dc; + GSList *devices; /* Devices structure pointers */ + GSList *sessions; /* Request Mode sessions */ +}; + +dbus_bool_t adapter_init(DBusConnection *conn, + const char *path, struct adapter *adapter); + +dbus_bool_t adapter_cleanup(DBusConnection *conn, const char *path); + +struct device *adapter_get_device(DBusConnection *conn, + struct adapter *adapter, const gchar *address); + +struct device *adapter_find_device(struct adapter *adapter, const char *dest); + +void adapter_remove_device(DBusConnection *conn, struct adapter *adapter, + struct device *device); +struct device *adapter_create_device(DBusConnection *conn, + struct adapter *adapter, const char *address); + +const char *major_class_str(uint32_t class); + +const char *minor_class_str(uint32_t class); + +const char *mode2str(uint8_t mode); + +uint8_t str2mode(const char *addr, const char *mode); + +GSList *service_classes_str(uint32_t class); + +int pending_remote_name_cancel(struct adapter *adapter); + +void dc_pending_timeout_cleanup(struct adapter *adapter); + +void remove_pending_device(struct adapter *adapter); + +void adapter_auth_request_replied(struct adapter *adapter, bdaddr_t *dba); +struct pending_auth_info *adapter_find_auth_request(struct adapter *adapter, + bdaddr_t *dba); +void adapter_remove_auth_request(struct adapter *adapter, bdaddr_t *dba); +struct pending_auth_info *adapter_new_auth_request(struct adapter *adapter, + bdaddr_t *dba, + auth_type_t type); diff --git a/hcid/agent.c b/hcid/agent.c new file mode 100644 index 00000000..91dbd2d9 --- /dev/null +++ b/hcid/agent.c @@ -0,0 +1,730 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2008 Nokia Corporation + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 <config.h> +#endif + +#include <stdio.h> +#include <errno.h> +#include <stdlib.h> +#include <sys/socket.h> +#include <sys/ioctl.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/hci.h> +#include <bluetooth/hci_lib.h> +#include <bluetooth/sdp.h> + +#include <glib.h> +#include <dbus/dbus.h> +#include <gdbus.h> + +#include "hcid.h" +#include "dbus-common.h" +#include "dbus-service.h" +#include "dbus-error.h" +#include "error.h" +#include "adapter.h" +#include "dbus-hci.h" +#include "device.h" +#include "agent.h" + +#define REQUEST_TIMEOUT (60 * 1000) /* 60 seconds */ +#define AGENT_TIMEOUT (10 * 60 * 1000) /* 10 minutes */ + +typedef enum { + AGENT_REQUEST_PASSKEY, + AGENT_REQUEST_CONFIRMATION, + AGENT_REQUEST_PINCODE, + AGENT_REQUEST_AUTHORIZE, + AGENT_REQUEST_CONFIRM_MODE +} agent_request_type_t; + +struct agent { + struct adapter *adapter; + char *name; + char *path; + uint8_t capability; + struct agent_request *request; + int exited; + agent_remove_cb remove_cb; + void *remove_cb_data; + guint listener_id; +}; + +struct agent_request { + agent_request_type_t type; + struct agent *agent; + DBusPendingCall *call; + void *cb; + void *user_data; +}; + +static DBusConnection *connection = NULL; + +static void agent_release(struct agent *agent) +{ + DBusMessage *message; + + debug("Releasing agent %s, %s", agent->name, agent->path); + + if (agent->request) + agent_cancel(agent); + + message = dbus_message_new_method_call(agent->name, agent->path, + "org.bluez.Agent", "Release"); + if (message == NULL) { + error("Couldn't allocate D-Bus message"); + return; + } + + dbus_message_set_no_reply(message, TRUE); + + dbus_connection_send(connection, message, NULL); + + dbus_message_unref(message); +} + +static int send_cancel_request(struct agent_request *req) +{ + DBusMessage *message; + + message = dbus_message_new_method_call(req->agent->name, req->agent->path, + "org.bluez.Agent", "Cancel"); + if (message == NULL) { + error("Couldn't allocate D-Bus message"); + return -ENOMEM; + } + + dbus_message_set_no_reply(message, TRUE); + + dbus_connection_send(connection, message, NULL); + + dbus_message_unref(message); + + return 0; +} + +static void agent_request_free(struct agent_request *req) +{ + if (req->call) + dbus_pending_call_unref(req->call); + if (req->agent && req->agent->request) + req->agent->request = NULL; + g_free(req); +} + +static void agent_exited(void *user_data) +{ + struct agent *agent = user_data; + + debug("Agent exited without calling Unregister"); + + agent_destroy(agent, TRUE); +} + +static void agent_free(struct agent *agent) +{ + if (!agent) + return; + + if (agent->remove_cb) + agent->remove_cb(agent, agent->remove_cb_data); + + if (agent->request) { + DBusError err; + agent_pincode_cb pincode_cb; + agent_cb cb; + + dbus_error_init(&err); + dbus_set_error_const(&err, "org.bluez.Error.Failed", "Canceled"); + + switch (agent->request->type) { + case AGENT_REQUEST_PINCODE: + pincode_cb = agent->request->cb; + pincode_cb(agent, &err, NULL, agent->request->user_data); + break; + default: + cb = agent->request->cb; + cb(agent, &err, agent->request->user_data); + } + + dbus_error_free(&err); + + agent_cancel(agent); + } + + if (!agent->exited) { + g_dbus_remove_watch(connection, agent->listener_id); + agent_release(agent); + } + + g_free(agent->name); + g_free(agent->path); + + g_free(agent); +} + +struct agent *agent_create(struct adapter *adapter, const char *name, + const char *path, uint8_t capability, + agent_remove_cb cb, void *remove_cb_data) +{ + struct agent *agent; + + if (adapter->agent && g_str_equal(adapter->agent->name, name)) + return NULL; + + agent = g_new0(struct agent, 1); + + agent->adapter = adapter; + agent->name = g_strdup(name); + agent->path = g_strdup(path); + agent->capability = capability; + agent->remove_cb = cb; + agent->remove_cb_data = remove_cb_data; + + agent->listener_id = g_dbus_add_disconnect_watch(connection, name, + agent_exited, agent, + NULL); + + return agent; +} + +int agent_destroy(struct agent *agent, gboolean exited) +{ + agent->exited = exited; + agent_free(agent); + return 0; +} + +static struct agent_request *agent_request_new(struct agent *agent, + agent_request_type_t type, + void *cb, + void *user_data) +{ + struct agent_request *req; + + req = g_new0(struct agent_request, 1); + + req->agent = agent; + req->type = type; + req->cb = cb; + req->user_data = user_data; + + return req; +} + +int agent_cancel(struct agent *agent) +{ + if (!agent->request) + return -EINVAL; + + if (agent->request->call) + dbus_pending_call_cancel(agent->request->call); + + if (!agent->exited) + send_cancel_request(agent->request); + + agent_request_free(agent->request); + agent->request = NULL; + + return 0; +} + +static DBusPendingCall *agent_call_authorize(struct agent *agent, + const char *device_path, + const char *uuid) +{ + DBusMessage *message; + DBusPendingCall *call; + + message = dbus_message_new_method_call(agent->name, agent->path, + "org.bluez.Agent", "Authorize"); + if (!message) { + error("Couldn't allocate D-Bus message"); + return NULL; + } + + dbus_message_append_args(message, + DBUS_TYPE_OBJECT_PATH, &device_path, + DBUS_TYPE_STRING, &uuid, + DBUS_TYPE_INVALID); + + if (dbus_connection_send_with_reply(connection, message, + &call, REQUEST_TIMEOUT) == FALSE) { + error("D-Bus send failed"); + dbus_message_unref(message); + return NULL; + } + + dbus_message_unref(message); + return call; +} + +static void simple_agent_reply(DBusPendingCall *call, void *user_data) +{ + struct agent_request *req = user_data; + struct agent *agent = req->agent; + DBusMessage *message; + DBusError err; + agent_cb cb = req->cb; + + /* steal_reply will always return non-NULL since the callback + * is only called after a reply has been received */ + message = dbus_pending_call_steal_reply(call); + + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, message)) { + + error("Agent replied with an error: %s, %s", + err.name, err.message); + + cb(agent, &err, req->user_data); + dbus_error_free(&err); + goto done; + } + + dbus_error_init(&err); + if (!dbus_message_get_args(message, &err, DBUS_TYPE_INVALID)) { + error("Wrong reply signature: %s", err.message); + cb(agent, &err, req->user_data); + dbus_error_free(&err); + goto done; + } + + cb(agent, NULL, req->user_data); +done: + dbus_message_unref(message); + + agent->request = NULL; + agent_request_free(req); +} + +int agent_authorize(struct agent *agent, + const char *path, + const char *uuid, + agent_cb cb, + void *user_data) +{ + struct agent_request *req; + + if (agent->request) + return -EBUSY; + + req = agent_request_new(agent, AGENT_REQUEST_AUTHORIZE, cb, user_data); + + req->call = agent_call_authorize(agent, path, uuid); + if (!req->call) { + agent_request_free(req); + return DBUS_HANDLER_RESULT_NEED_MEMORY; + } + + dbus_pending_call_set_notify(req->call, simple_agent_reply, req, NULL); + agent->request = req; + + debug("authorize request was sent for %s", path); + + return 0; +} + +static DBusPendingCall *pincode_request_new(struct agent *agent, + const char *device_path, + dbus_bool_t numeric) +{ + DBusMessage *message; + DBusPendingCall *call; + + message = dbus_message_new_method_call(agent->name, agent->path, + "org.bluez.Agent", "RequestPinCode"); + if (message == NULL) { + error("Couldn't allocate D-Bus message"); + return NULL; + } + + dbus_message_append_args(message, DBUS_TYPE_OBJECT_PATH, &device_path, + DBUS_TYPE_INVALID); + + if (dbus_connection_send_with_reply(connection, message, + &call, REQUEST_TIMEOUT) == FALSE) { + error("D-Bus send failed"); + dbus_message_unref(message); + return NULL; + } + + dbus_message_unref(message); + return call; +} + +static void pincode_reply(DBusPendingCall *call, void *user_data) +{ + struct agent_request *req = user_data; + struct agent *agent = req->agent; + struct adapter *adapter = agent->adapter; + agent_pincode_cb cb = req->cb; + DBusMessage *message; + DBusError err; + bdaddr_t sba; + size_t len; + char *pin; + + /* steal_reply will always return non-NULL since the callback + * is only called after a reply has been received */ + message = dbus_pending_call_steal_reply(call); + + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, message)) { + error("Agent replied with an error: %s, %s", + err.name, err.message); + + cb(agent, &err, NULL, req->user_data); + dbus_error_free(&err); + goto done; + } + + dbus_error_init(&err); + if (!dbus_message_get_args(message, &err, + DBUS_TYPE_STRING, &pin, + DBUS_TYPE_INVALID)) { + error("Wrong passkey reply signature: %s", err.message); + cb(agent, &err, NULL, req->user_data); + dbus_error_free(&err); + goto done; + } + + len = strlen(pin); + + dbus_error_init(&err); + if (len > 16 || len < 1) { + error("Invalid passkey length from handler"); + dbus_set_error_const(&err, "org.bluez.Error.InvalidArgs", + "Invalid passkey length"); + cb(agent, &err, NULL, req->user_data); + dbus_error_free(&err); + goto done; + } + + str2ba(adapter->address, &sba); + + set_pin_length(&sba, len); + + cb(agent, NULL, pin, req->user_data); + +done: + if (message) + dbus_message_unref(message); + + dbus_pending_call_cancel(req->call); + agent_request_free(req); +} + +int agent_request_pincode(struct agent *agent, struct device *device, + agent_pincode_cb cb, void *user_data) +{ + struct agent_request *req; + + if (agent->request) + return -EBUSY; + + req = agent_request_new(agent, AGENT_REQUEST_PINCODE, cb, user_data); + + req->call = pincode_request_new(agent, device->path, FALSE); + if (!req->call) + goto failed; + + dbus_pending_call_set_notify(req->call, pincode_reply, req, NULL); + + agent->request = req; + + return 0; + +failed: + g_free(req); + return -1; +} + +static DBusPendingCall *confirm_mode_change_request_new(struct agent *agent, + const char *mode) +{ + DBusMessage *message; + DBusPendingCall *call; + + message = dbus_message_new_method_call(agent->name, agent->path, + "org.bluez.Agent", "ConfirmModeChange"); + if (message == NULL) { + error("Couldn't allocate D-Bus message"); + return NULL; + } + + dbus_message_append_args(message, + DBUS_TYPE_STRING, &mode, + DBUS_TYPE_INVALID); + + if (dbus_connection_send_with_reply(connection, message, + &call, REQUEST_TIMEOUT) == FALSE) { + error("D-Bus send failed"); + dbus_message_unref(message); + return NULL; + } + + dbus_message_unref(message); + + return call; +} + +int agent_confirm_mode_change(struct agent *agent, const char *new_mode, + agent_cb cb, void *user_data) +{ + struct agent_request *req; + + if (agent->request) + return -EBUSY; + + debug("Calling Agent.ConfirmModeChange: name=%s, path=%s, mode=%s", + agent->name, agent->path, new_mode); + + req = agent_request_new(agent, AGENT_REQUEST_CONFIRM_MODE, + cb, user_data); + + req->call = confirm_mode_change_request_new(agent, new_mode); + if (!req->call) + goto failed; + + dbus_pending_call_set_notify(req->call, simple_agent_reply, req, NULL); + + agent->request = req; + + return 0; + +failed: + agent_request_free(req); + return -1; +} + +static DBusPendingCall *passkey_request_new(struct agent *agent, + const char *device_path) +{ + DBusMessage *message; + DBusPendingCall *call; + + message = dbus_message_new_method_call(agent->name, agent->path, + "org.bluez.Agent", "RequestPasskey"); + if (message == NULL) { + error("Couldn't allocate D-Bus message"); + return NULL; + } + + dbus_message_append_args(message, DBUS_TYPE_OBJECT_PATH, &device_path, + DBUS_TYPE_INVALID); + + if (dbus_connection_send_with_reply(connection, message, + &call, REQUEST_TIMEOUT) == FALSE) { + error("D-Bus send failed"); + dbus_message_unref(message); + return NULL; + } + + dbus_message_unref(message); + return call; +} + +static void passkey_reply(DBusPendingCall *call, void *user_data) +{ + struct agent_request *req = user_data; + struct agent *agent = req->agent; + agent_passkey_cb cb = req->cb; + DBusMessage *message; + DBusError err; + uint32_t passkey; + + /* steal_reply will always return non-NULL since the callback + * is only called after a reply has been received */ + message = dbus_pending_call_steal_reply(call); + + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, message)) { + error("Agent replied with an error: %s, %s", + err.name, err.message); + cb(agent, &err, 0, req->user_data); + dbus_error_free(&err); + goto done; + } + + dbus_error_init(&err); + if (!dbus_message_get_args(message, &err, + DBUS_TYPE_UINT32, &passkey, + DBUS_TYPE_INVALID)) { + error("Wrong passkey reply signature: %s", err.message); + cb(agent, &err, 0, req->user_data); + dbus_error_free(&err); + goto done; + } + + cb(agent, NULL, passkey, req->user_data); + +done: + if (message) + dbus_message_unref(message); + + dbus_pending_call_cancel(req->call); + agent_request_free(req); +} + +int agent_request_passkey(struct agent *agent, struct device *device, + agent_passkey_cb cb, void *user_data) +{ + struct agent_request *req; + + if (agent->request) + return -EBUSY; + + debug("Calling Agent.RequestPasskey: name=%s, path=%s", + agent->name, agent->path); + + req = agent_request_new(agent, AGENT_REQUEST_PASSKEY, cb, user_data); + + req->call = passkey_request_new(agent, device->path); + if (!req->call) + goto failed; + + dbus_pending_call_set_notify(req->call, passkey_reply, req, NULL); + + agent->request = req; + + return 0; + +failed: + agent_request_free(req); + return -1; +} + +static DBusPendingCall *confirmation_request_new(struct agent *agent, + const char *device_path, + uint32_t passkey) +{ + DBusMessage *message; + DBusPendingCall *call; + + message = dbus_message_new_method_call(agent->name, agent->path, + "org.bluez.Agent", "RequestConfirmation"); + if (message == NULL) { + error("Couldn't allocate D-Bus message"); + return NULL; + } + + dbus_message_append_args(message, + DBUS_TYPE_OBJECT_PATH, &device_path, + DBUS_TYPE_UINT32, &passkey, + DBUS_TYPE_INVALID); + + if (dbus_connection_send_with_reply(connection, message, + &call, REQUEST_TIMEOUT) == FALSE) { + error("D-Bus send failed"); + dbus_message_unref(message); + return NULL; + } + + dbus_message_unref(message); + + return call; +} + +int agent_request_confirmation(struct agent *agent, struct device *device, + uint32_t passkey, agent_cb cb, + void *user_data) +{ + struct agent_request *req; + + if (agent->request) + return -EBUSY; + + debug("Calling Agent.RequestConfirmation: name=%s, path=%s, passkey=%06u", + agent->name, agent->path, passkey); + + req = agent_request_new(agent, AGENT_REQUEST_CONFIRMATION, cb, + user_data); + + req->call = confirmation_request_new(agent, device->path, passkey); + if (!req->call) + goto failed; + + dbus_pending_call_set_notify(req->call, simple_agent_reply, req, NULL); + + agent->request = req; + + return 0; + +failed: + agent_request_free(req); + return -1; +} + +int agent_display_passkey(struct agent *agent, struct device *device, + uint32_t passkey) +{ + DBusMessage *message; + + message = dbus_message_new_method_call(agent->name, agent->path, + "org.bluez.Agent", "DisplayPasskey"); + if (!message) { + error("Couldn't allocate D-Bus message"); + return -1; + } + + dbus_message_append_args(message, + DBUS_TYPE_OBJECT_PATH, &device->path, + DBUS_TYPE_UINT32, &passkey, + DBUS_TYPE_INVALID); + + if (!g_dbus_send_message(connection, message)) { + error("D-Bus send failed"); + dbus_message_unref(message); + return -1; + } + + return 0; +} + +uint8_t agent_get_io_capability(struct agent *agent) +{ + return agent->capability; +} + +gboolean agent_matches(struct agent *agent, const char *name, const char *path) +{ + if (g_str_equal(agent->name, name) && g_str_equal(agent->path, path)) + return TRUE; + + return FALSE; +} + +void agent_exit(void) +{ + dbus_connection_unref(connection); +} + +void agent_init(void) +{ + connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL); +} diff --git a/hcid/agent.h b/hcid/agent.h new file mode 100644 index 00000000..37ee5edd --- /dev/null +++ b/hcid/agent.h @@ -0,0 +1,71 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2008 Nokia Corporation + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 + * + */ + +struct agent; + +typedef void (*agent_cb) (struct agent *agent, DBusError *err, + void *user_data); + +typedef void (*agent_pincode_cb) (struct agent *agent, DBusError *err, + const char *pincode, void *user_data); + +typedef void (*agent_passkey_cb) (struct agent *agent, DBusError *err, + uint32_t passkey, void *user_data); + +typedef void (*agent_remove_cb) (struct agent *agent, void *user_data); + +struct agent *agent_create(struct adapter *adapter, const char *name, + const char *path, uint8_t capability, + agent_remove_cb cb, void *remove_cb_data); + +int agent_destroy(struct agent *agent, gboolean exited); + +int agent_authorize(struct agent *agent, const char *path, + const char *uuid, agent_cb cb, void *user_data); + +int agent_request_pincode(struct agent *agent, struct device *device, + agent_pincode_cb cb, void *user_data); + +int agent_confirm_mode_change(struct agent *agent, const char *new_mode, + agent_cb cb, void *user_data); + +int agent_request_passkey(struct agent *agent, struct device *device, + agent_passkey_cb cb, void *user_data); + +int agent_request_confirmation(struct agent *agent, struct device *device, + uint32_t passkey, agent_cb cb, + void *user_data); + +int agent_display_passkey(struct agent *agent, struct device *device, + uint32_t passkey); + +int agent_cancel(struct agent *agent); + +uint8_t agent_get_io_capability(struct agent *agent); + +gboolean agent_matches(struct agent *agent, const char *name, const char *path); + +void agent_init(void); +void agent_exit(void); + diff --git a/hcid/bluetooth.conf b/hcid/bluetooth.conf new file mode 100644 index 00000000..88545fac --- /dev/null +++ b/hcid/bluetooth.conf @@ -0,0 +1,37 @@ +<!-- This configuration file specifies the required security policies + for Bluetooth core daemon to work. --> + +<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" + "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> +<busconfig> + + <!-- ../system.conf have denied everything, so we just punch some holes --> + + <policy user="root"> + <allow own="org.bluez"/> + </policy> + + <policy at_console="true"> + <allow send_path="/"/> + <allow send_path="/org/bluez"/> + + <allow send_destination="org.bluez.Manager"/> + <allow receive_sender="org.bluez.Manager"/> + + <allow send_destination="org.bluez.Adapter"/> + <allow receive_sender="org.bluez.Adapter"/> + + <allow send_destination="org.bluez.Device"/> + <allow receive_sender="org.bluez.Device"/> + + <allow send_destination="org.bluez.Service"/> + <allow receive_sender="org.bluez.Service"/> + + <allow send_destination="org.bluez.Database"/> + <allow receive_sender="org.bluez.Database"/> + + <allow send_destination="org.bluez.Security"/> + <allow receive_sender="org.bluez.Security"/> + </policy> + +</busconfig> diff --git a/hcid/dbus-api.txt b/hcid/dbus-api.txt new file mode 100644 index 00000000..622477be --- /dev/null +++ b/hcid/dbus-api.txt @@ -0,0 +1,1401 @@ +D-Bus API description for BlueZ +******************************* + +Copyright (C) 2004-2007 Marcel Holtmann <marcel@holtmann.org> +Copyright (C) 2005-2006 Johan Hedberg <johan.hedberg@nokia.com> +Copyright (C) 2005-2006 Claudio Takahasi <claudio.takahasi@indt.org.br> +Copyright (C) 2005-2006 Eduardo Rocha <eduardo.rocha@indt.org.br> + + +Constant definitions +==================== + +The class of device definition from the Bluetooth specification divides into +three different parts. It the major class, the minor class and the service +classes. The D-Bus interface will always use string constants to identify +any of these classes. + +Service classes positioning, networking, rendering, capturing, + object transfer, audio, telephony, information + +Major classes miscellaneous, computer, phone, access point, + audio/video, peripheral, imaging, wearable, toy, + uncategorized + +Minor classes computer uncategorized, desktop, server, laptop, handheld, + palm, wearable + +Minor classes phone uncategorized, cellular, cordless, smart phone, + modem, isdn + +Minor classes access point fully, 1-17 percent, 17-33 percent, + 33-50 percent, 50-67 percent, 67-83 percent, + 83-99 percent, not available + +Minor classes audio video uncategorized, headset, handsfree,microphone, + loudspeaker, headphones, portable audio, car audio, + set-top box, hifi audio, vcr, video camera, camcorder, + video monitor, video display and loudspeaker, + video conferencing, gaming/toy, unknown + +Minor classes peripheral uncategorized, keyboard, pointing, combo + +Minor classes imaging display, camera, scanner, printer + +Minor classes wearable wrist watch, pager, jacket, helmet, glasses + +Minor classes toy robot, vehicle, doll, controller, game + +Error hierarchy +=============== + +Interface org.bluez.Error + +Shared Errors (Can be thrown by hcid or any bluetooth service) + + DeviceUnreachable + + The remote device is either powered down or out of range. + + AlreadyConnected + A connection request has been received on an already + connected device. + + ConnectionAttemptFailed + + An unexpected error (other than DeviceUnreachable) error + has occured while attempting a connection to a device. + + NotConnected + The remote device is not connected, while the method call + would expect it to be, or is not in the expected state to + perform the action. + + InProgress + + Error returned if an operation is in progress. Since + this is a generic error that can be used in various + situations, the error message should be more clear + about what is in progress. For example "Bonding in + progress". + + InvalidArguments + + The DBUS request does not contain the right number of + arguments with the right type, or the arguments are there + but their value is wrong, or does not makes sense in the + current context. + + OutOfMemory + + Error returned when a memory allocation via malloc() + fails. This error is similar to ENOMEM. + + NotAvailable + + Error returned when a specified record is not + available. + + NotSupported + + The remote device does not support the expected + feature. + + AlreadyExists + One of the requested elements already exists + + DoesNotExist + One of the requested elements does not exist + + Canceled + The operation was canceled. + + Failed + + This is a the most generic error. + It is thrown when something unexpected happens. + + +Hcid specific Errors (Can be thrown by hcid only) + + NotReady + + Error returned when the adapter is DOWN. + + UnknwownMethod + + This is an experimental method. + + NotAuthorized + + Error returned when the caller of a method is not + authorized. This might happen if a caller tries to + terminate a connection that it hasn't created. + + Rejected + + NoSuchAdapter + + Error returned when the requested adapter doesn't + exists. This error is similar to ENODEV. + + NoSuchService + + RequestDeferred + + NotInProgress + + UnsupportedMajorClass + + AuthenticationCanceled + + AuthenticationFailed + + AuthenticationTimeout + + AuthenticationRejected + + RepeatedAttempts + +Manager hierarchy +================= + +Service org.bluez +Interface org.bluez.Manager +Object path /org/bluez + +Methods uint32 InterfaceVersion() + + Returns the current interface version. At the moment + only version 0 is supported. + + Possible errors: org.bluez.Error.InvalidArguments + + string DefaultAdapter() + + Returns object path for the default adapter. + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.NoSuchAdapter + + string FindAdapter(string pattern) + + Returns object path for the specified adapter. Valid + patterns are "hci0" or "00:11:22:33:44:55". + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.NoSuchAdapter + + array{string} ListAdapters() + + Returns list of adapter object paths under /org/bluez + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.Failed + org.bluez.Error.OutOfMemory + + string FindService(string pattern) + + Returns object path for the specified service. Valid + patterns are the unqiue identifier or a bus name. + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.NoSuchService + + array{string} ListServices() + + Returns list of object paths of current services. + + Possible errors: org.bluez.Error.InvalidArguments + + string ActivateService(string pattern) + + Returns the unqiue bus id of the specified service. + Valid patterns are the same as for FindService(). If + the service is not running it will be started. + +Signals void AdapterAdded(string path) + + Parameter is object path of added adapter. + + void AdapterRemoved(string path) + + Parameter is object path of removed adapter. + + void DefaultAdapterChanged(string path) + + Parameter is object path of the new default adapter, + or an empty string if there is no available adapters. + + void ServiceAdded(string path) + + Parameter is object path of registered service agent. + + void ServiceRemoved(string path) + + Parameter is object path of unregistered service agent. + + +Database hierarchy +================== + +Service org.bluez +Interface org.bluez.Database +Object path /org/bluez + +Methods uint32 AddServiceRecord(array{byte}) + + Adds a new service record and returns the assigned + record handle. + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.Failed + + uint32 AddServiceRecordFromXML(string record) + + Adds a new service record and returns the assigned + record handle. + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.Failed + + void UpdateServiceRecord(uint32 handle, array{byte}) + + Updates a given service record. + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.NotAvailable + org.bluez.Error.Failed + + void UpdateServiceRecordFromXML(uint32 handle, string record) + + Updates a given service record provided in the + XML format. + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.NotAvailable + org.bluez.Error.Failed + + void RemoveServiceRecord(uint32 handle) + + Remove a service record identified by its handle. + + It is only possible to remove service records that + where added by the current connection. + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.NotAuthorized + org.bluez.Error.DoesNotExist + org.bluez.Error.Failed + + +Adapter hierarchy +================= + +Service org.bluez +Interface org.bluez.Adapter +Object path /org/bluez/{hci0,hci1,...} + +Methods dict GetInfo() + + Returns the properties of the local adapter. + + Possible errors: org.bluez.Error.NotReady + + string GetAddress() + + Returns the device address for a given path. + + Example: "00:11:22:33:44:55" + + Possible errors: org.bluez.Error.NotReady + + string GetVersion() + + Returns the version of the Bluetooth chip. This version + is compiled from the LMP version. In case of EDR the + features attribute must be checked. + + Example: "Bluetooth 2.0 + EDR" + + Possible errors: none + + string GetRevision() + + Returns the revision of the Bluetooth chip. This is a + vendor specific value and in most cases it represents + the firmware version. This might derive from the HCI + revision and LMP subversion values or via extra vendor + specific commands. + + In case the revision of a chip is not available. This + method should return the LMP subversion value as a + string. + + Example: "HCI 19.2" + + Possible errors: org.bluez.Error.Failed + + string GetManufacturer() + + Returns the manufacturer of the Bluetooth chip. If + the company id is not know the sting "Company ID %d" + where %d should be replaced with the numeric value + from the manufacturer field. + + Example: "Cambridge Silicon Radio" + + Possible errors: org.bluez.Error.Failed + + string GetCompany() + + Returns the company name from the OUI database of the + Bluetooth device address. This function will need a + valid and up-to-date oui.txt from the IEEE. This value + will be different from the manufacturer string in the + most cases. + + If the oui.txt file is not present or the OUI part of + the BD_ADDR is not listed, it should return the + string "OUI %s" where %s is the actual OUI. + + Example: "Apple Computer" + + Possible errors: org.bluez.Error.Failed + + array{string} ListAvailableModes() + + Returns a list of available modes the adapter can + be switched into. + + string GetMode() + + Returns the current mode of a adapter. + + Valid modes: "off", "connectable", "discoverable", + "limited". + + Possible errors: none + + void SetMode(string mode) + + Sets mode of the adapter. See GetMode for valid strings + for the mode parameter. In addition to the valid strings + for GetMode, this method also supports a special + parameter value "on" which will change the mode to the + previous non-off mode (or do nothing if the current + mode isn't "off"). + + Possible errors: org.bluez.Error.NoSuchAdapter + org.bluez.Error.Failed + + uint32 GetDiscoverableTimeout() + + Returns the discoverable timeout in seconds. A value + of zero means that the timeout is disabled and it will + stay in discoverable/limited mode forever. + + The default value for the discoverable timeout should + be 180 seconds (3 minutes). + + Possible errors: none + + void SetDiscoverableTimeout(uint32 timeout) + + Sets the discoverable timeout in seconds. A value of + zero disables the timeout and the adapter would be + always discoverable/limited. + + Changing this value doesn't set the adapter into + discoverable/limited mode. The SetMode method must be used. + + Possible errors: org.bluez.Error.NotReady + org.bluez.Error.InvalidArguments + + boolean IsConnectable() + + Returns true if the local adapter is connectable and + false if it is switched off. + + It is also possible to use GetMode to retrieve this + information. + + Possible errors: none + + boolean IsDiscoverable() + + Returns true if the local adapter is discoverable/limited + and false if it is only connectable or switched off. + + It is also possible to use GetMode to retrieve this + information. + + Possible errors: none + + boolean IsConnected(string address) + + Return true if the local adapter is connected to + the remote device. + + Possible errors: org.bluez.Error.InvalidArguments + + array{string} ListConnections() + + Returns a list with addresses of currently connected + remote devices. + + Possible errors: none + + string GetMajorClass() + + Returns the current major class value for this + system. Currently, only "computer" is supported. + For the other values, unsupported major class + error is returned. + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.UnsupportedMajorClass + + array{string} ListAvailableMinorClasses() + + Returns a list of available minor classes for the + currently used major class. At the moment this should + only return a list of minor classes if the major + class is set to "computer". + + If the major class is not "computer" an error should + be returned. + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.UnsupportedMajorClass + + string GetMinorClass() + + Returns the current minor class value for this + system where the default major class is "computer". + + If the major class is not "computer" an error should + be returned. + + Valid values: "uncategorized", "desktop", "server", + "laptop", "handheld", "palm", "wearable" + + The default value is "uncategorized". + + Possible errors:org.bluez.Error.InvalidArguments + org.bluez.Error.UnsupportedMajorClass + + void SetMinorClass(string minor) + + Sets the local minor class and on success it sends + a MinorClassChanged signal. + + If the major class is not "computer" an error should + be returned. + + Possible errors: org.bluez.Error.NotReady + org.bluez.Error.InvalidArguments + org.bluez.Error.NoSuchAdapter + org.bluez.Error.Failed + org.bluez.Error.UnsupportedMajorClass + + array{string} GetServiceClasses() + + Returns the current set of service classes. + + In the case no service classes are set (when no + service has been registered) an empty list should + be returned. + + Valid values: "positioning", "networking", "rendering", + "capturing", "object transfer", "audio", + "telephony", "information" + + Possible errors: org.bluez.Error.NotReady + org.bluez.Error.NoSuchAdapter + org.bluez.Error.Failed + + string GetName() + + Returns the local adapter name (friendly name) in UTF-8. + + Possible errors: org.bluez.Error.Failed + + void SetName(string name) + + Sets the local adapter name. If EIR is supported by + the local hardware this modifies also the extended + response data value. + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.Failed + + Questions: What to do (in case of EIR) if one + low-level API call fails. + + dict GetRemoteInfo(string address) + + Returns the properties for a remote device. + + string GetRemoteVersion(string address) + + Get the version info for a remote device. This request + returns always this information based on its cached + data. The base for this string is the LMP version + value and the features for EDR support. + + Not available can be received if the remote device was + not contacted(connected) previously. Remote data is + automatically retrieved in the first connection. + + Example: "Bluetooth 2.0 + EDR" + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.NotAvailable + + string GetRemoteRevision(string address) + + Get the revision of the Bluetooth chip. This is a + vendor specific value and in most cases it represents + the firmware version. This derives only from the LMP + subversion value. + + Example: "HCI 19.2" + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.NotAvailable + + string GetRemoteManufacturer(string address) + + Get the manufacturer of the chip for a remote device. + + Example: "Nokia Mobile Phones" + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.NotAvailable + + string GetRemoteCompany(string address) + + Get the company name from the OUI database of the + Bluetooth device address. This function will need a + valid and up-to-date oui.txt from the IEEE. This value + will be different from the manufacturer string in the + most cases. + + Example: "Microsoft Corporation" + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.NotAvailable + + string GetRemoteMajorClass(string address) + + Get the major device class of the specified device. + + Example: "computer" + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.NotAvailable + + string GetRemoteMinorClass(string address) + + Get the minor device class of the specified device. + + Example: "laptop" + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.NotAvailable + + array{string} GetRemoteServiceClasses(string address) + + Get the service classes of the specified device. + + Example: ["networking", "object transfer"] + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.NotAvailable + + uint32 GetRemoteClass(string address) + + Get the remote major, minor, and service classes + encoded as 32 bit integer. + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.NotAvailable + + array{byte} GetRemoteFeatures(string address) + + Get the remote features encoded as bit mask. + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.NotAvailable + + string GetRemoteName(string address) + + Get the remote device's name. This request returns always + a cached name. The service daemon is responsible for + updating the cache. + + NotAvailable error is returned if the name is not in + the cache. But if there is a discovery running, then + this function will return RequestDeferred. In this + case the service daemon will queue the request and + it will try to resolve the name at the next possible + opportunity. On success a RemoteNameUpdated signal will + be send and if a failure happens it will be indicated by + a RemoteNameFailed signal. + + If this is an empty string, the UI might want to + display the BD_ADDR instead. + + Example: "00:11:22:33:44:55", "Nokia 770" + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.NotAvailable + org.bluez.Error.NotReady + org.bluez.Error.RequestDeferred + + string GetRemoteAlias(string address) + + Returns alias name for remote device. If this is + an empty string value, the UI should show the + remote name instead. + + An alias should supersede the remote name. + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.NotAvailable + + void SetRemoteAlias(string address, string alias) + + Sets alias name for remote device. If alias name is + empty, then no alias is set. + + On success the SetRemoteAlias method will produce a + RemoteAliasChanged signal which applications can use + to update their current display of the remote device + name. + + Possible errors: org.bluez.Error.Failed + org.bluez.Error.InvalidArguments + + void ClearRemoteAlias(string address) + + Resets alias name for remote device. If there is no + alias set for the device this method will silently + succeed, but no RemoteAliasCleared signal has to be + sent in this case. + + On success the ClearRemoteAlias method will produce + a RemoteAliasCleared signal. + + Possible errors: org.bluez.Error.Failed + org.bluez.Error.InvalidArguments + + string LastSeen(string address) + + Returns the date and time when the adapter has been + seen by a discover procedure. + + Example: "2006-02-08 12:00:00 GMT" + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.NotAvailable + + Question: Can we find a better name? + + string LastUsed(string address) + + Returns the date and time of the last time when the + adapter has been connected. + + Example: "2006-02-08 12:00:00 GMT" + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.NotAvailable + + Question: Can we find a better name? + + void DisconnectRemoteDevice(string address) + + This method disconnects a specific remote device by + terminating the low-level ACL connection. The use of + this method should be restricted to administrator + use. + + A RemoteDeviceDisconnectRequested signal will be + sent and the actual disconnection will only happen 2 + seconds later. This enables upper-level applications + to terminate their connections gracefully before the + ACL connection is terminated. + + Possible errors: org.bluez.Error.NotReady + org.bluez.Error.Failed + org.bluez.Error.NoSuchAdapter + org.bluez.Error.InvalidArguments + org.bluez.Error.NotConnected + org.bluez.Error.InProgress + + void CreateBonding(string address) + + This method creates a bonding with a remote device. + + If a link key for this adapter already exists, this + procedure should fail instead of trying to create a + new pairing. + + If no connection to the remote device exists, a + low-level ACL connection must be created. + + This function will block and the calling application + should take care of setting are higher timeout. This + might be needed in case of a page timeout from the + low-level HCI commands. + + In case of success it will send a BondingCreated + signal. + + Possible errors: org.bluez.Error.NotReady + org.bluez.Error.Failed + org.bluez.Error.InvalidArguments + org.bluez.Error.AlreadyExists + org.bluez.Error.InProgress + org.bluez.Error.NoSuchAdapter + org.bluez.Error.ConnectionAttemptFailed + org.bluez.Error.AuthenticationFailed + org.bluez.Error.AuthenticationTimeout + org.bluez.Error.AuthenticationRejected + org.bluez.Error.AuthenticationCanceled + + void CancelBondingProcess(string address) + + This method will cancel the CreateBonding process. + + The CreateBonding method will return + AuthenticationCanceled to signal that an attempt to + create a bonding has been canceled. + + Possible errors: org.bluez.Error.NotReady + org.bluez.Error.Failed + org.bluez.Error.InvalidArguments + org.bluez.Error.NotInProgress + org.bluez.Error.NotAuthorized + + void RemoveBonding(string address) + + This method removes the bonding with a remote device. + + For security reasons this includes removing the actual + link key and also disconnecting any open connections + for the remote device. + + If the link key was stored on the Bluetooth chip, it + must be removed from there, too. + + After deleting the link key this method will send a + BondingRemoved signal. + + Possible errors: org.bluez.Error.NotReady + org.bluez.Error.Failed + org.bluez.Error.InvalidArguments + org.bluez.Error.NoSuchAdapter + org.bluez.Error.DoesNotExist + + boolean HasBonding(string address) + + Returns true if the remote device is bonded and false + if no link key is available. + + Possible errors: org.bluez.Error.InvalidArguments + + array{string} ListBondings() + + List device addresses of currently bonded adapter. + + Possible errors: none + + uint8 GetPinCodeLength(string address) + + Returns the PIN code length that was used in the + pairing process. + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.DoesNotExist + + uint8 GetEncryptionKeySize(string address) + + Returns the currently used encryption key size. + + This method will fail if no connection to the address + has been established. + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.Failed + + void SetTrusted(string address) + + Marks the remote device as trusted. Authorization + request will automatically succeed. + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.AlreadyExists + + boolean IsTrusted(string address) + + Returns true if the user is trusted or false otherwise. + The address parameter must match one of the remote + devices of the service. + + Possible errors: org.bluez.Error.InvalidArguments + + void RemoveTrust(string address) + + Marks the remote device as not trusted. + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.DoesNotExist + + array{string} ListTrusts() + + Returns a list of remote devices that are trusted. + + void DiscoverDevices() + + This method starts the device discovery procedure. This + includes an inquiry procedure and remote device name + resolving. + + On start up this process will generate a DiscoveryStarted + signal and then return RemoteDeviceFound and also + RemoteNameUpdated signals. If the procedure has been + finished an DiscoveryCompleted signal will be sent. + + Possible errors: org.bluez.Error.NotReady + org.bluez.Error.Failed + org.bluez.Error.InProgress + org.bluez.Error.NoSuchAdapter + + void DiscoverDevicesWithoutNameResolving() + + This method starts the device discovery procedure. This + includes an inquiry and an optional remote device name + resolving. The remote names can be retrieved with + GetRemoteName and in the case a name doesn't exist it + will be queued for later resolving and GetRemoteName + will return an error. + + While this procedure is running every found device + will be returned with RemoteDeviceFound. While + DiscoverDevices() automatically resolves unknown + devices names and sends RemoteNameUpdated in this + case it will only happen if GetRemoteName has been + called and no previously stored name is available. + + Possible errors: org.bluez.Error.NotReady + org.bluez.Error.Failed + org.bluez.Error.InProgress + org.bluez.Error.NoSuchAdapter + + void CancelDiscovery() + + This method will cancel any previous DiscoverDevices + or DiscoverDevicesWithoutNameResolving actions. + + Possible errors: org.bluez.Error.NotReady + org.bluez.Error.Failed + org.bluez.Error.NotAuthorized + org.bluez.Error.NoSuchAdapter + + void StartPeriodicDiscovery() + + This method starts a periodic discovery. + + Possible errors: org.bluez.error.NotReady + org.bluez.Error.Failed + org.bluez.Error.InProgress + org.bluez.Error.NoSuchAdapter + + void StopPeriodicDiscovery() + + This method stops a periodic discovery. If the + adapter is not in the periodic inquiry mode an + error(not authorized) is returned. Everyone can + request exit from this mode, it is not restricted + to start requestor. + + Possible errors: org.bluez.Error.NotReady + org.bluez.Error.Failed + org.bluez.Error.NotAuthorized + org.bluez.Error.NoSuchAdapter + + boolean IsPeriodicDiscovery() + + Returns true if the periodic inquiry is active and + false if it is switched off. + + Possible errors: none + + void SetPeriodicDiscoveryNameResolving(boolean resolve_names) + + Enable or disable automatic remote name resolving for + periodic discovery. + + Possible errors: org.bluez.Error.InvalidArguments + + boolean GetPeriodicDiscoveryNameResolving() + + Check if automatic remote name resolving is enabled or not + for periodic discovery. + + Possible error: org.bluez.Error.InvalidArguments + + array{uint32} GetRemoteServiceHandles(string address, string match) + + This method will request the SDP database of a remote + device and retrieve the service record handles. To + request service browse send an empty match string. + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.InProgress + org.bluez.Error.ConnectionAttemptFailed + org.bluez.Error.Failed + + array{byte} GetRemoteServiceRecord(string address, uint32 handle) + + This method will request the SDP database of a remote + device for a service record and return the binary + stream of it. + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.InProgress + org.bluez.Error.Failed + + string GetRemoteServiceRecordAsXML(string address, uint32 handle) + + This method will request the SDP database of a remote + device for a service record and return its data in XML + format. + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.InProgress + org.bluez.Error.Failed + + array{string} GetRemoteServiceIdentifiers(string address) + + This method will request the SDP database of a remote + device for all supported services. The identifiers are + returned in UUID 128 string format. + + Possible errors: org.bluez.Error.InProgress + org.bluez.Error.Failed + + void FinishRemoteServiceTransaction(string address) + + This method will finish all SDP transaction for that + given address. In general this call is not needed, + but in cases of resources restricted devices it + is useful to call this to finish the SDP transaction + before proceeded with profile specific connections. + + array{string} ListRemoteDevices() + + List addresses of all known remote devices (bonded, + trusted and used). + + Possible errors: none + + array{string} ListRecentRemoteDevices(string date) + + List addresses of all bonded, trusted, seen or used remote + devices since date. Bonded and trusted devices are always + included(the date informed is not applied). + + date format is "YYYY-MM-DD HH:MM:SS GMT" + + Possible errors: none + +Signals void ModeChanged(string mode) + + If the current mode is changed with SetMode this signal + will inform about the new mode. + + This signal can also be triggered by low-level HCI + commands. + + void DiscoverableTimeoutChanged(uint32 timeout) + + After changing the discoverable timeout this signal + provide the new timeout value. + + void MinorClassChanged(string minor) + + After changing the minor class with SetMinorClass this + signal will provide the new class value. + + void NameChanged(string name) + + After changing the local adapter name with SetName this + signal will provide the new name. + + This signal can also be triggered by low-level HCI + commands. + + void DiscoveryStarted() + + This signal indicates that a device discovery + procedure has been started. + + void DiscoveryCompleted() + + This signal indicates that a device discovery + procedure has been completed. + + void PeriodicDiscoveryStarted() + + This signal indicates that a periodic discovery + procedure has been started. + + void PeriodicDiscoveryStopped() + + This signal indicates that a periodic discovery + procedure has been completed. + + void RemoteDeviceFound(string address, uint32 class, int16 rssi) + + This signal will be send every time an inquiry result + has been found by the service daemon. In general they + only appear during a device discovery. + + void RemoteDeviceDisappeared(string address) + + This signal will be send when an inquiry session for + a periodic discovery finishes and previously found + devices are no longer in range or visible. + + void RemoteClassUpdated(string address, uint32 class) + + This signal will be send every time the remote class + of device has been changed. This happens for example + after a remote connection attempt. This signal will + not be send if the class of device hasn't changed + compared to cached one. + + void RemoteNameUpdated(string address, string name) + + This signal will be send every time the service daemon + detect a new name for a remote device. + + void RemoteIdentifiersUpdated(string address, array{string identifiers}) + + This signal is sent to indicate the provided services of a given + remote device. It will be sent after GetRemoteServiceIdentifiers + calls. This signal has at least one identifier and it does not + contain repeated entries. + + void RemoteNameFailed(string address) + + This signal will be sent every time the service daemon + tries to resolve a remote and this fails. + + void RemoteNameRequested(string address) + + This signal will be sent every time the service daemon + tries to resolve a remote name during discovery. + + void RemoteAliasChanged(string address, string alias) + + After changing an alias with SetRemoteAlias this + signal will indicate the new alias. + + void RemoteAliasCleared(string address) + + After removing an alias with ClearRemoteAlias this + signal will indicate that the alias is no longer + valid. + + void RemoteDeviceConnected(string address) + + This signal will be send if a low level connection + between two devices has been created. + + void RemoteDeviceDisconnectRequested(string address) + + This signal will be sent when a low level + disconnection to a remote device has been requested. + The actual disconnection will happen 2 seconds later. + + void RemoteDeviceDisconnected(string address) + + This signal will be send if a low level connection + between two devices has been terminated. + + void BondingCreated(string address) + + Signals that a successful bonding has been created. + + void BondingRemoved(string address) + + Signals that a bonding was removed. + + void TrustAdded(string address) + + Sent when SetTrusted() is called. + + void TrustRemoved(string address) + + Sent when RemoveTrust() is called. + +Service hierarchy +================= + +Service org.bluez +Interface org.bluez.Service +Object path path from org.bluez.Manager.ListServices() + +Methods dict GetInfo() + + Returns the service properties. + + string GetIdentifier() + + This method returns the service identifier. + + string GetName() + + This method returns the service name. + + string GetDescription() + + This method returns the service description. + + string GetBusName() [experimental] + + Returns the unique bus name of the service if it has + been started. + + Possible errors: org.bluez.Error.NotAvailable + + void Start() + + This method tells the system to start the + service. + + void Stop() + + This method tells the system to stop the + service. + + boolean IsRunning() + + Returns true if the service has been started and + is currently active. Otherwise, it returns false. + + boolean IsExternal() + + Returns true if the service was registered using the + Database.RegisterService method instead of a .service + file. The Start and Stop methods are not applicable to + external services and will return an error. + + array{string} ListTrusts() [experimental] + + Returns a list of remote devices that are trusted + for the service. + + void SetTrusted(string address) [experimental] + + Marks the user as trusted. + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.AlreadyExists + + boolean IsTrusted(string address) [experimental] + + Returns true if the user is trusted or false otherwise. + The address parameter must match one of the + current users of the service. + + Possible errors: org.bluez.Error.InvalidArguments + + void RemoveTrust(string address) [experimental] + + Marks the user as not trusted. + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.DoesNotExist + +Signals void Started() + + The object path of this signal contains which service + was started. + + void Stopped() + + The object path of this signal contains which service + was stopped. + + void TrustAdded(string address) + + Sent when SetTrusted() is called. + + void TrustRemoved(string address) + + Sent when RemoveTrust() is called. + + +Security hierarchy +================== + +Service org.bluez +Interface org.bluez.Security +Object path /org/bluez or /org/bluez/{hci0,hci1,...} + +Methods void RegisterDefaultPasskeyAgent(string path) + + This registers the default passkey agent. It can + register a passkey for all adapters or for a + specific device depending on with object path has + been used. + + The path parameter defines the object path of the + passkey agent that will be called when a passkey + needs to be entered. + + If an application disconnects from the bus all + registered passkey agent will be removed. + + Possible errors: org.bluez.Error.AlreadyExists + + void UnregisterDefaultPasskeyAgent(string path) + + This unregisters a default passkey agent that has + been previously registered. The object path and + the path parameter must match the same values that + has been used on registration. + + Possible errors: org.bluez.Error.DoesNotExist + + void RegisterPasskeyAgent(string path, string address) + + This registers the application passkey agent that + will be used for any application specific passkey + tasks. + + The path parameter defines the object path of the + passkey agent that will be called when a passkey + needs to be entered. The address defines the remote + device that it will answer passkey requests for. + + If an application disconnects from the bus all + registered passkey agent will be removed. It will + also be unregistered after a timeout and if the + pairing succeeds or fails. The application has to + take care of that it reregisters the passkey agent. + + Possible errors: org.bluez.Error.AlreadyExists + + void UnregisterPasskeyAgent(string path, string address) + + This unregisters a passkey agent that has been + previously registered. The object path and the path + and address parameter must match the same values + that has been used on registration. + + The method is actually only needed if an application + wants to removed the passkey agent and don't wanna + wait for the automatic timeout. + + Possible errors: org.bluez.Error.DoesNotExist + + void RegisterDefaultAuthorizationAgent(string path) + + This registers the default authorization agent. It can + register an authorization agent for all adapters or + for a specific one depending on which object path has + been used. + + The path parameter defines the object path of the + authorization agent that will be called when an + authorization request needs to be answered. + + void UnregisterDefaultAuthorizationAgent(string path) + + This unregisters a default authorization agent that has + been previously registered. The path parameter must + match the same value that has been used on + registration. + + +PasskeyAgent hierarchy +====================== + +Service unique name +Interface org.bluez.PasskeyAgent +Object path freely definable + +Methods string Request(string path, string address) + + This method gets called when the service daemon + needs to get the passkey for an authentication. The + return value is actual passkey. It is a 1 to 16 + byte PIN code in UTF-8 format. + + The first argument contains the path of the local + adapter and the second one the remote address. + + Possible errors: org.bluez.Error.Rejected + org.bluez.Error.Canceled + + void Cancel(string path, string address) + + This method gets called to indicate that the + authentication request failed before a reply was + returned by the Request method. + + void Release() + + This method gets called when the service daemon + unregisters a passkey agent. An agent can use + it to do cleanup tasks. There is no need to + unregister the agent, because when this method + gets called it has already been unregistered. + + +AuthorizationAgent hierarchy (experimental) +=========================================== + +Service unique name +Interface org.bluez.AuthorizationAgent +Object path freely definable + +Methods void Authorize(string adapter_path, string address, + string service_path, string uuid) + + This method gets called when the service daemon wants + to get an authorization for accessing a service. This + method should return if the remote user is granted + access or an error otherwise. + + The adapter_path parameter is the object path of the + local adapter. The address, service_path and action + parameters correspond to the remote device address, + the object path of the service and the uuid of the + profile. + + Possible errors: org.bluez.Error.Rejected + org.bluez.Error.Canceled + + void Cancel(string adapter_path, string address, + string service_path, string uuid) + + This method cancels a previous authorization request. + The adapter_path, address, service_path and uuid + parameters must match the same values that have been + used when the Authorize() method was called. + + void Release() + + This method gets called when the service daemon + unregisters an authorization agent. An agent can + use it to do cleanup tasks. There is no need to + unregister the agent, because when this method + gets called it has already been unregistered. diff --git a/hcid/dbus-common.c b/hcid/dbus-common.c new file mode 100644 index 00000000..99585d0a --- /dev/null +++ b/hcid/dbus-common.c @@ -0,0 +1,364 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * Copyright (C) 2005-2007 Johan Hedberg <johan.hedberg@nokia.com> + * + * + * 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 <config.h> +#endif + +#include <stdio.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/ioctl.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/l2cap.h> +#include <bluetooth/sdp.h> +#include <bluetooth/sdp_lib.h> + +#include <glib.h> +#include <dbus/dbus.h> +#include <gdbus.h> + +#include "hcid.h" +#include "manager.h" +#include "adapter.h" +#include "dbus-hci.h" +#include "dbus-service.h" +#include "dbus-database.h" +#include "dbus-security.h" +#include "dbus-sdp.h" +#include "dbus-common.h" + +#define BLUEZ_NAME "org.bluez" + +#define RECONNECT_RETRY_TIMEOUT 5000 + +static int experimental = 0; + +int l2raw_connect(const char *local, const bdaddr_t *remote) +{ + struct sockaddr_l2 addr; + long arg; + int sk; + + sk = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_L2CAP); + if (sk < 0) { + error("Can't create socket: %s (%d)", strerror(errno), errno); + return sk; + } + + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + str2ba(local, &addr.l2_bdaddr); + + if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + error("Can't bind socket: %s (%d)", strerror(errno), errno); + goto failed; + } + + arg = fcntl(sk, F_GETFL); + if (arg < 0) { + error("Can't get file flags: %s (%d)", strerror(errno), errno); + goto failed; + } + + arg |= O_NONBLOCK; + if (fcntl(sk, F_SETFL, arg) < 0) { + error("Can't set file flags: %s (%d)", strerror(errno), errno); + goto failed; + } + + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + bacpy(&addr.l2_bdaddr, remote); + + if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + if (errno == EAGAIN || errno == EINPROGRESS) + return sk; + error("Can't connect socket: %s (%d)", strerror(errno), errno); + goto failed; + } + + return sk; + +failed: + close(sk); + return -1; +} + +void hcid_dbus_set_experimental(void) +{ + experimental = 1; +} + +int hcid_dbus_use_experimental(void) +{ + return experimental; +} + +static gboolean system_bus_reconnect(void *data) +{ + DBusConnection *conn = get_dbus_connection(); + struct hci_dev_list_req *dl = NULL; + struct hci_dev_req *dr; + int sk, i; + gboolean ret_val = TRUE; + + if (conn) { + if (dbus_connection_get_is_connected(conn)) + return FALSE; + } + + if (hcid_dbus_init() < 0) + return TRUE; + + /* Create and bind HCI socket */ + sk = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI); + if (sk < 0) { + error("Can't open HCI socket: %s (%d)", + strerror(errno), errno); + return TRUE; + } + + dl = g_malloc0(HCI_MAX_DEV * sizeof(*dr) + sizeof(*dl)); + + dl->dev_num = HCI_MAX_DEV; + dr = dl->dev_req; + + if (ioctl(sk, HCIGETDEVLIST, (void *) dl) < 0) { + info("Can't get device list: %s (%d)", + strerror(errno), errno); + goto failed; + } + + /* reset the default device */ + manager_set_default_adapter(-1); + + for (i = 0; i < dl->dev_num; i++, dr++) + hcid_dbus_register_device(dr->dev_id); + + ret_val = FALSE; + +failed: + if (sk >= 0) + close(sk); + + g_free(dl); + + return ret_val; +} + +static void disconnect_callback(void *user_data) +{ + set_dbus_connection(NULL); + + release_services(NULL); + + g_timeout_add(RECONNECT_RETRY_TIMEOUT, + system_bus_reconnect, NULL); +} + +void hcid_dbus_unregister(void) +{ + DBusConnection *conn = get_dbus_connection(); + char **children; + int i; + + if (!conn || !dbus_connection_get_is_connected(conn)) + return; + + /* Unregister all paths in Adapter path hierarchy */ + if (!dbus_connection_list_registered(conn, BASE_PATH, &children)) + return; + + for (i = 0; children[i]; i++) { + char dev_path[MAX_PATH_LENGTH]; + + if (children[i][0] != 'h') + continue; + + snprintf(dev_path, sizeof(dev_path), "%s/%s", BASE_PATH, + children[i]); + + unregister_adapter_path(dev_path); + } + + dbus_free_string_array(children); +} + +void hcid_dbus_exit(void) +{ + DBusConnection *conn = get_dbus_connection(); + + if (!conn || !dbus_connection_get_is_connected(conn)) + return; + + release_default_agent_old(); + release_default_auth_agent(); + release_services(conn); + + database_cleanup(conn, BASE_PATH); + + manager_cleanup(conn, BASE_PATH); + + set_dbus_connection(NULL); + + dbus_connection_unref(conn); +} + +int hcid_dbus_init(void) +{ + DBusConnection *conn; + + conn = g_dbus_setup_bus(DBUS_BUS_SYSTEM, BLUEZ_NAME, NULL); + if (!conn) + return -1; + + if (g_dbus_set_disconnect_function(conn, disconnect_callback, + NULL, NULL) == FALSE) { + dbus_connection_unref(conn); + return -1; + } + + if (!manager_init(conn, BASE_PATH)) + return -1; + + if (!database_init(conn, BASE_PATH)) + return -1; + + if (!security_init(conn, BASE_PATH)) + return -1; + + set_dbus_connection(conn); + + return 0; +} + +static void dbus_message_iter_append_variant(DBusMessageIter *iter, + int type, void *val) +{ + DBusMessageIter value; + DBusMessageIter array; + char *sig; + + switch (type) { + case DBUS_TYPE_STRING: + sig = DBUS_TYPE_STRING_AS_STRING; + break; + case DBUS_TYPE_BYTE: + sig = DBUS_TYPE_BYTE_AS_STRING; + break; + case DBUS_TYPE_INT16: + sig = DBUS_TYPE_INT16_AS_STRING; + break; + case DBUS_TYPE_UINT16: + sig = DBUS_TYPE_UINT16_AS_STRING; + break; + case DBUS_TYPE_INT32: + sig = DBUS_TYPE_INT32_AS_STRING; + break; + case DBUS_TYPE_UINT32: + sig = DBUS_TYPE_UINT32_AS_STRING; + break; + case DBUS_TYPE_BOOLEAN: + sig = DBUS_TYPE_BOOLEAN_AS_STRING; + break; + case DBUS_TYPE_ARRAY: + sig = DBUS_TYPE_ARRAY_AS_STRING DBUS_TYPE_STRING_AS_STRING; + break; + case DBUS_TYPE_OBJECT_PATH: + sig = DBUS_TYPE_OBJECT_PATH_AS_STRING; + break; + default: + error("Could not append variant with type %d", type); + return; + } + + dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, sig, &value); + + if (type == DBUS_TYPE_ARRAY) { + int i; + const char ***str_array = val; + + dbus_message_iter_open_container(&value, DBUS_TYPE_ARRAY, + DBUS_TYPE_STRING_AS_STRING, &array); + + for (i = 0; (*str_array)[i]; i++) + dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, + &((*str_array)[i])); + + dbus_message_iter_close_container(&value, &array); + } else + dbus_message_iter_append_basic(&value, type, val); + + dbus_message_iter_close_container(iter, &value); +} + +void dbus_message_iter_append_dict_entry(DBusMessageIter *dict, + const char *key, int type, void *val) +{ + DBusMessageIter entry; + + dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY, + NULL, &entry); + + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key); + + dbus_message_iter_append_variant(&entry, type, val); + + dbus_message_iter_close_container(dict, &entry); +} + +dbus_bool_t dbus_connection_emit_property_changed(DBusConnection *conn, + const char *path, + const char *interface, + const char *name, + int type, void *value) +{ + DBusMessage *signal; + DBusMessageIter iter; + gboolean ret; + + signal = dbus_message_new_signal(path, interface, "PropertyChanged"); + + if (!signal) { + error("Unable to allocate new %s.PropertyChanged signal", + interface); + return FALSE; + } + + dbus_message_iter_init_append(signal, &iter); + + dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &name); + dbus_message_iter_append_variant(&iter, type, value); + + ret = dbus_connection_send(conn, signal, NULL); + + dbus_message_unref(signal); + return ret; +} diff --git a/hcid/dbus-common.h b/hcid/dbus-common.h new file mode 100644 index 00000000..afdf7569 --- /dev/null +++ b/hcid/dbus-common.h @@ -0,0 +1,47 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 + * + */ + +#define BASE_PATH "/org/bluez" +#define ADAPTER_PATH_INDEX 10 + +#define MAX_PATH_LENGTH 64 + +int str2uuid(uuid_t *uuid, const char *string); + +int l2raw_connect(const char *local, const bdaddr_t *remote); + +#define check_address(address) bachk(address) + +void hcid_dbus_exit(void); +int hcid_dbus_init(void); +void hcid_dbus_unregister(void); + +void dbus_message_iter_append_dict_entry(DBusMessageIter *dict, + const char *key, int type, void *val); + +dbus_bool_t dbus_connection_emit_property_changed(DBusConnection *conn, + const char *path, + const char *interface, + const char *name, + int type, void *value); diff --git a/hcid/dbus-database.c b/hcid/dbus-database.c new file mode 100644 index 00000000..ab88c8f5 --- /dev/null +++ b/hcid/dbus-database.c @@ -0,0 +1,380 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 <config.h> +#endif + +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/hci.h> +#include <bluetooth/hci_lib.h> +#include <bluetooth/sdp.h> +#include <bluetooth/sdp_lib.h> + +#include <gdbus.h> + +#include "hcid.h" +#include "sdpd.h" +#include "sdp-xml.h" +#include "manager.h" +#include "adapter.h" +#include "dbus-hci.h" +#include "dbus-common.h" +#include "error.h" +#include "dbus-service.h" +#include "dbus-security.h" +#include "dbus-database.h" + +static GSList *records = NULL; + +struct record_data { + uint32_t handle; + char *sender; + guint listener_id; +}; + +static struct record_data *find_record(uint32_t handle, const char *sender) +{ + GSList *list; + + for (list = records; list; list = list->next) { + struct record_data *data = list->data; + if (handle == data->handle && !strcmp(sender, data->sender)) + return data; + } + + return NULL; +} + +static void exit_callback(void *user_data) +{ + struct record_data *user_record = user_data; + + debug("remove record"); + + records = g_slist_remove(records, user_record); + + remove_record_from_server(user_record->handle); + + g_free(user_record->sender); + g_free(user_record); +} + +static inline DBusMessage *invalid_arguments(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 *failed(DBusMessage *msg) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", "Failed"); +} + +static DBusMessage *add_service_record(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + DBusMessageIter iter, array; + const char *sender; + struct record_data *user_record; + sdp_record_t *sdp_record; + const uint8_t *record; + int scanned, len = -1; + + dbus_message_iter_init(msg, &iter); + dbus_message_iter_recurse(&iter, &array); + + dbus_message_iter_get_fixed_array(&array, &record, &len); + if (len <= 0) + return invalid_arguments(msg); + + sdp_record = sdp_extract_pdu_safe(record, len, &scanned); + if (!sdp_record) { + error("Parsing of service record failed"); + return failed(msg); + } + + if (scanned != len) { + error("Size mismatch of service record"); + sdp_record_free(sdp_record); + return failed(msg); + } + + if (add_record_to_server(BDADDR_ANY, sdp_record) < 0) { + error("Failed to register service record"); + sdp_record_free(sdp_record); + return failed(msg); + } + + user_record = g_new0(struct record_data, 1); + + user_record->handle = sdp_record->handle; + + sender = dbus_message_get_sender(msg); + + user_record->sender = g_strdup(sender); + + records = g_slist_append(records, user_record); + + user_record->listener_id = g_dbus_add_disconnect_watch(conn, sender, + exit_callback, + user_record, + NULL); + + debug("listener_id %d", user_record->listener_id); + + return g_dbus_create_reply(msg, DBUS_TYPE_UINT32, &user_record->handle, + DBUS_TYPE_INVALID); +} + +int add_xml_record(DBusConnection *conn, const char *sender, bdaddr_t *src, + const char *record, dbus_uint32_t *handle) +{ + struct record_data *user_record; + sdp_record_t *sdp_record; + + sdp_record = sdp_xml_parse_record(record, strlen(record)); + if (!sdp_record) { + error("Parsing of XML service record failed"); + return -EIO; + } + + if (add_record_to_server(src, sdp_record) < 0) { + error("Failed to register service record"); + sdp_record_free(sdp_record); + return -EIO; + } + + user_record = g_new0(struct record_data, 1); + + user_record->handle = sdp_record->handle; + + user_record->sender = g_strdup(sender); + + records = g_slist_append(records, user_record); + + user_record->listener_id = g_dbus_add_disconnect_watch(conn, sender, + exit_callback, user_record, NULL); + + debug("listener_id %d", user_record->listener_id); + + *handle = user_record->handle; + + return 0; +} + +static DBusMessage *add_service_record_from_xml(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + const char *sender, *record; + dbus_uint32_t handle; + int err; + + if (dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &record, DBUS_TYPE_INVALID) == FALSE) + return NULL; + + sender = dbus_message_get_sender(msg); + + err = add_xml_record(conn, sender, BDADDR_ANY, record, &handle); + if (err < 0) + return failed(msg); + + return g_dbus_create_reply(msg, DBUS_TYPE_UINT32, &handle, + DBUS_TYPE_INVALID); +} + +static DBusMessage *update_record(DBusConnection *conn, DBusMessage *msg, + bdaddr_t *src, dbus_uint32_t handle, sdp_record_t *sdp_record) +{ + int err; + + if (remove_record_from_server(handle) < 0) { + sdp_record_free(sdp_record); + return g_dbus_create_error(msg, + ERROR_INTERFACE ".NotAvailable", + "Not Available"); + } + + sdp_record->handle = handle; + err = add_record_to_server(src, sdp_record); + if (err < 0) { + sdp_record_free(sdp_record); + error("Failed to update the service record"); + return g_dbus_create_error(msg, + ERROR_INTERFACE ".Failed", + strerror(EIO)); + } + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *update_service_record(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct record_data *user_record; + DBusMessageIter iter, array; + sdp_record_t *sdp_record; + dbus_uint32_t handle; + const uint8_t *bin_record; + int scanned, size = -1; + + dbus_message_iter_init(msg, &iter); + dbus_message_iter_get_basic(&iter, &handle); + dbus_message_iter_next(&iter); + dbus_message_iter_recurse(&iter, &array); + + dbus_message_iter_get_fixed_array(&array, &bin_record, &size); + if (size <= 0) + return invalid_arguments(msg); + + user_record = find_record(handle, dbus_message_get_sender(msg)); + if (!user_record) + return not_available(msg); + + sdp_record = sdp_extract_pdu_safe(bin_record, size, &scanned); + if (!sdp_record) { + error("Parsing of service record failed"); + return invalid_arguments(msg); + } + + if (scanned != size) { + error("Size mismatch of service record"); + sdp_record_free(sdp_record); + return invalid_arguments(msg); + } + + return update_record(conn, msg, BDADDR_ANY, handle, sdp_record); +} + +DBusMessage *update_xml_record(DBusConnection *conn, + DBusMessage *msg, bdaddr_t *src) +{ + struct record_data *user_record; + sdp_record_t *sdp_record; + const char *record; + dbus_uint32_t handle; + int len; + + if (dbus_message_get_args(msg, NULL, + DBUS_TYPE_UINT32, &handle, + DBUS_TYPE_STRING, &record, + DBUS_TYPE_INVALID) == FALSE) + return NULL; + + len = (record ? strlen(record) : 0); + if (len == 0) + return invalid_arguments(msg); + + user_record = find_record(handle, dbus_message_get_sender(msg)); + if (!user_record) + return g_dbus_create_error(msg, + ERROR_INTERFACE ".NotAvailable", + "Not Available"); + + sdp_record = sdp_xml_parse_record(record, len); + if (!sdp_record) { + error("Parsing of XML service record failed"); + sdp_record_free(sdp_record); + return g_dbus_create_error(msg, + ERROR_INTERFACE ".Failed", + strerror(EIO)); + } + + return update_record(conn, msg, src, handle, sdp_record); +} + +static DBusMessage *update_service_record_from_xml(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + return update_xml_record(conn, msg, BDADDR_ANY); +} + +int remove_record(DBusConnection *conn, const char *sender, + dbus_uint32_t handle) +{ + struct record_data *user_record; + + debug("remove record 0x%x", handle); + + user_record = find_record(handle, sender); + if (!user_record) + return -1; + + debug("listner_id %d", user_record->listener_id); + + g_dbus_remove_watch(conn, user_record->listener_id); + + exit_callback(user_record); + + return 0; +} + +static DBusMessage *remove_service_record(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + dbus_uint32_t handle; + const char *sender; + + if (dbus_message_get_args(msg, NULL, + DBUS_TYPE_UINT32, &handle, DBUS_TYPE_INVALID) == FALSE) + return NULL; + + sender = dbus_message_get_sender(msg); + + if (remove_record(conn, sender, handle) < 0) + return not_available(msg); + + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static GDBusMethodTable database_methods[] = { + { "AddServiceRecord", "ay", "u", add_service_record }, + { "AddServiceRecordFromXML", "s", "u", add_service_record_from_xml }, + { "UpdateServiceRecord", "uay", "", update_service_record }, + { "UpdateServiceRecordFromXML", "us", "", update_service_record_from_xml }, + { "RemoveServiceRecord", "u", "", remove_service_record }, + { } +}; + +dbus_bool_t database_init(DBusConnection *conn, const char *path) +{ + return g_dbus_register_interface(conn, path, DATABASE_INTERFACE, + database_methods, NULL, NULL, NULL, NULL); +} + +void database_cleanup(DBusConnection *conn, const char *path) +{ + g_dbus_unregister_interface(conn, path, DATABASE_INTERFACE); +} diff --git a/hcid/dbus-database.h b/hcid/dbus-database.h new file mode 100644 index 00000000..c173afaf --- /dev/null +++ b/hcid/dbus-database.h @@ -0,0 +1,35 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 + * + */ + +#define DATABASE_INTERFACE "org.bluez.Database" + +dbus_bool_t database_init(DBusConnection *conn, const char *path); +void database_cleanup(DBusConnection *conn, const char *path); + +int add_xml_record(DBusConnection *conn, const char *sender, bdaddr_t *src, + const char *record, dbus_uint32_t *handle); +DBusMessage *update_xml_record(DBusConnection *conn, + DBusMessage *msg, bdaddr_t *src); +int remove_record(DBusConnection *conn, const char *sender, + dbus_uint32_t handle); diff --git a/hcid/dbus-error.c b/hcid/dbus-error.c new file mode 100644 index 00000000..76d9f095 --- /dev/null +++ b/hcid/dbus-error.c @@ -0,0 +1,91 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 <config.h> +#endif + +#include <stdio.h> +#include <errno.h> + +#include <bluetooth/sdp.h> + +#include <dbus/dbus.h> +#include <gdbus.h> + +#include "hcid.h" +#include "dbus-common.h" +#include "dbus-error.h" +#include "error.h" + +static inline DBusHandlerResult send_message_and_unref(DBusConnection *conn, + DBusMessage *msg) +{ + if (msg) { + dbus_connection_send(conn, msg, NULL); + dbus_message_unref(msg); + } + + return DBUS_HANDLER_RESULT_HANDLED; +} + +DBusHandlerResult error_no_such_adapter(DBusConnection *conn, DBusMessage *msg) +{ + return send_message_and_unref(conn, + dbus_message_new_error(msg, ERROR_INTERFACE ".NoSuchAdapter", + "No such adapter")); +} + +DBusHandlerResult error_authentication_canceled(DBusConnection *conn, DBusMessage *msg) +{ + return send_message_and_unref(conn, + dbus_message_new_error(msg, ERROR_INTERFACE ".AuthenticationCanceled", + "Authentication Canceled")); +} + +static const char *strsdperror(int err) +{ + switch (err) { + case SDP_INVALID_VERSION: + return "Invalid/unsupported SDP version"; + case SDP_INVALID_RECORD_HANDLE: + return "Invalid Service Record Handle"; + case SDP_INVALID_SYNTAX: + return "Invalid request syntax"; + case SDP_INVALID_PDU_SIZE: + return "Invalid PDU size"; + case SDP_INVALID_CSTATE: + return "Invalid Continuation State"; + default: + return "Undefined error"; + } +} + +DBusHandlerResult error_sdp_failed(DBusConnection *conn, DBusMessage *msg, int err) +{ + const char *str = strsdperror(err); + + return send_message_and_unref(conn, + dbus_message_new_error(msg, ERROR_INTERFACE ".Failed", str)); +} diff --git a/hcid/dbus-error.h b/hcid/dbus-error.h new file mode 100644 index 00000000..6b3a22c4 --- /dev/null +++ b/hcid/dbus-error.h @@ -0,0 +1,33 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 + * + */ + +/* + Please update dbus-api.txt in hcid folder when changes are made to this file. + */ + +DBusHandlerResult error_no_such_adapter(DBusConnection *conn, DBusMessage *msg); +/* Used only for hcid device audit feature */ +DBusHandlerResult error_authentication_canceled(DBusConnection *conn, DBusMessage *msg); +DBusHandlerResult error_auth_agent_does_not_exist(DBusConnection *conn, DBusMessage *msg); +DBusHandlerResult error_sdp_failed(DBusConnection *conn, DBusMessage *msg, int err); diff --git a/hcid/dbus-hci.c b/hcid/dbus-hci.c new file mode 100644 index 00000000..617ded21 --- /dev/null +++ b/hcid/dbus-hci.c @@ -0,0 +1,2713 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 <config.h> +#endif + +#define _GNU_SOURCE +#include <stdio.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <sys/param.h> +#include <sys/ioctl.h> +#include <sys/socket.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/hci.h> +#include <bluetooth/hci_lib.h> +#include <bluetooth/sdp.h> + +#include <glib.h> +#include <dbus/dbus.h> +#include <gdbus.h> + +#include "hcid.h" +#include "textfile.h" +#include "manager.h" +#include "adapter.h" +#include "device.h" +#include "error.h" +#include "glib-helper.h" +#include "dbus-common.h" +#include "dbus-error.h" +#include "dbus-service.h" +#include "dbus-security.h" +#include "agent.h" +#include "dbus-hci.h" + +static DBusConnection *connection = NULL; + +void bonding_request_free(struct bonding_request_info *bonding) +{ + struct device *device; + char address[18]; + + if (!bonding) + return; + + if (bonding->msg) + dbus_message_unref(bonding->msg); + + if (bonding->conn) + dbus_connection_unref(bonding->conn); + + if (bonding->io) + g_io_channel_unref(bonding->io); + + ba2str(&bonding->bdaddr, address); + + device = adapter_find_device(bonding->adapter, address); + if (device && device->agent) { + agent_destroy(device->agent, FALSE); + device->agent = NULL; + } + + g_free(bonding); +} + +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; +} + +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 found_device_add(GSList **list, bdaddr_t *bdaddr, int8_t rssi, + name_status_t name_status) +{ + struct remote_dev_info *dev, match; + GSList *l; + + memset(&match, 0, sizeof(struct remote_dev_info)); + bacpy(&match.bdaddr, bdaddr); + match.name_status = NAME_ANY; + + /* ignore repeated entries */ + l = g_slist_find_custom(*list, &match, (GCompareFunc) found_device_cmp); + if (l) { + /* device found, update the attributes */ + dev = l->data; + + if (rssi != 0) + dev->rssi = rssi; + + /* 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; + + *list = g_slist_sort(*list, (GCompareFunc) dev_rssi_cmp); + + return -EALREADY; + } + + dev = g_new0(struct remote_dev_info, 1); + + bacpy(&dev->bdaddr, bdaddr); + dev->rssi = rssi; + dev->name_status = name_status; + + *list = g_slist_insert_sorted(*list, dev, (GCompareFunc) dev_rssi_cmp); + + return 0; +} + +static int found_device_remove(GSList **list, bdaddr_t *bdaddr) +{ + struct remote_dev_info *dev, match; + GSList *l; + + memset(&match, 0, sizeof(struct remote_dev_info)); + bacpy(&match.bdaddr, bdaddr); + + l = g_slist_find_custom(*list, &match, (GCompareFunc) found_device_cmp); + if (!l) + return -1; + + dev = l->data; + *list = g_slist_remove(*list, dev); + g_free(dev); + + return 0; +} + +int active_conn_find_by_bdaddr(const void *data, const void *user_data) +{ + const struct active_conn_info *con = data; + const bdaddr_t *bdaddr = user_data; + + return bacmp(&con->bdaddr, bdaddr); +} + +static int active_conn_find_by_handle(const void *data, const void *user_data) +{ + const struct active_conn_info *dev = data; + const uint16_t *handle = user_data; + + if (dev->handle == *handle) + return 0; + + return -1; +} + +static int active_conn_append(GSList **list, bdaddr_t *bdaddr, + uint16_t handle) +{ + struct active_conn_info *dev; + + dev = g_new0(struct active_conn_info, 1); + + bacpy(&dev->bdaddr, bdaddr); + dev->handle = handle; + + *list = g_slist_append(*list, dev); + return 0; +} + +DBusMessage *new_authentication_return(DBusMessage *msg, uint8_t status) +{ + switch (status) { + case 0x00: /* success */ + return dbus_message_new_method_return(msg); + + case 0x04: /* page timeout */ + case 0x08: /* connection timeout */ + case 0x10: /* connection accept timeout */ + case 0x22: /* LMP response timeout */ + case 0x28: /* instant passed - is this a timeout? */ + return dbus_message_new_error(msg, + ERROR_INTERFACE ".AuthenticationTimeout", + "Authentication Timeout"); + case 0x17: /* too frequent pairing attempts */ + return dbus_message_new_error(msg, + ERROR_INTERFACE ".RepeatedAttempts", + "Repeated Attempts"); + + case 0x06: + case 0x18: /* pairing not allowed (e.g. gw rejected attempt) */ + return dbus_message_new_error(msg, + ERROR_INTERFACE ".AuthenticationRejected", + "Authentication Rejected"); + + case 0x07: /* memory capacity */ + case 0x09: /* connection limit */ + case 0x0a: /* synchronous connection limit */ + case 0x0d: /* limited resources */ + case 0x14: /* terminated due to low resources */ + return dbus_message_new_error(msg, + ERROR_INTERFACE ".AuthenticationCanceled", + "Authentication Canceled"); + + case 0x05: /* authentication failure */ + case 0x0E: /* rejected due to security reasons - is this auth failure? */ + case 0x25: /* encryption mode not acceptable - is this auth failure? */ + case 0x26: /* link key cannot be changed - is this auth failure? */ + case 0x29: /* pairing with unit key unsupported - is this auth failure? */ + case 0x2f: /* insufficient security - is this auth failure? */ + default: + return dbus_message_new_error(msg, + ERROR_INTERFACE ".AuthenticationFailed", + "Authentication Failed"); + } +} + +static dbus_bool_t send_adapter_signal(DBusConnection *conn, int devid, + const char *name, int first, ...) +{ + va_list var_args; + dbus_bool_t ret; + char path[MAX_PATH_LENGTH]; + + snprintf(path, sizeof(path)-1, "%s/hci%d", BASE_PATH, devid); + + va_start(var_args, first); + ret = g_dbus_emit_signal_valist(conn, path, ADAPTER_INTERFACE, + name, first, var_args); + va_end(var_args); + + return ret; +} + +static void adapter_mode_changed(struct adapter *adapter, uint8_t scan_enable) +{ + const char *mode; + + adapter->scan_enable = scan_enable; + + switch (scan_enable) { + case SCAN_DISABLED: + mode = "off"; + adapter->mode = MODE_OFF; + break; + case SCAN_PAGE: + mode = "connectable"; + adapter->mode = MODE_CONNECTABLE; + break; + case (SCAN_PAGE | SCAN_INQUIRY): + + if (adapter->discov_timeout != 0) + adapter->timeout_id = g_timeout_add(adapter->discov_timeout * 1000, + discov_timeout_handler, adapter); + + if (adapter->mode == MODE_LIMITED) { + mode = "limited"; + } else { + adapter->mode = MODE_DISCOVERABLE; + mode = "discoverable"; + } + break; + case SCAN_INQUIRY: + /* Address the scenario where another app changed the scan mode */ + if (adapter->discov_timeout != 0) + adapter->timeout_id = g_timeout_add(adapter->discov_timeout * 1000, + discov_timeout_handler, adapter); + /* ignore, this event should not be sent*/ + default: + /* ignore, reserved */ + return; + } + + g_dbus_emit_signal(connection, adapter->path, ADAPTER_INTERFACE, + "ModeChanged", + DBUS_TYPE_STRING, &mode, + DBUS_TYPE_INVALID); + + if (hcid_dbus_use_experimental()) { + const char *ptr = adapter->path + ADAPTER_PATH_INDEX; + dbus_connection_emit_property_changed(connection, ptr, + ADAPTER_INTERFACE, "Mode", + DBUS_TYPE_STRING, &mode); + } +} + +/* + * HCI D-Bus services + */ +static void reply_pending_requests(const char *path, struct adapter *adapter) +{ + if (!path || !adapter) + return; + + /* pending bonding */ + if (adapter->bonding) { + error_authentication_canceled(connection, adapter->bonding->msg); + + remove_pending_device(adapter); + + g_dbus_remove_watch(adapter->bonding->conn, + adapter->bonding->listener_id); + + if (adapter->bonding->io_id) + g_source_remove(adapter->bonding->io_id); + g_io_channel_close(adapter->bonding->io); + bonding_request_free(adapter->bonding); + adapter->bonding = NULL; + } + + /* If there is a pending reply for discovery cancel */ + if (adapter->discovery_cancel) { + DBusMessage *reply; + reply = dbus_message_new_method_return(adapter->discovery_cancel); + dbus_connection_send(connection, reply, NULL); + dbus_message_unref(reply); + dbus_message_unref(adapter->discovery_cancel); + adapter->discovery_cancel = NULL; + } + + if (adapter->discov_active) { + /* Send discovery completed signal if there isn't name + * to resolve */ + if (hcid_dbus_use_experimental()) { + const char *ptr = path + ADAPTER_PATH_INDEX; + + g_dbus_emit_signal(connection, ptr, + ADAPTER_INTERFACE, + "DiscoveryCompleted", + DBUS_TYPE_INVALID); + + } + + g_dbus_emit_signal(connection, path, + ADAPTER_INTERFACE, + "DiscoveryCompleted", + DBUS_TYPE_INVALID); + + /* Cancel inquiry initiated by D-Bus client */ + if (adapter->discov_requestor) + cancel_discovery(adapter); + } + + if (adapter->pdiscov_active) { + /* Send periodic discovery stopped signal exit or stop + * the device */ + g_dbus_emit_signal(connection, path, + ADAPTER_INTERFACE, + "PeriodicDiscoveryStopped", + DBUS_TYPE_INVALID); + + /* Stop periodic inquiry initiated by D-Bus client */ + if (adapter->pdiscov_requestor) + cancel_periodic_discovery(adapter); + } +} + +static void do_unregister(gpointer data, gpointer user_data) +{ + DBusConnection *conn = user_data; + struct device *device = data; + + device_remove(conn, device); +} + +int unregister_adapter_path(const char *path) +{ + struct adapter *adapter; + + info("Unregister path: %s", path); + + __remove_servers(path); + + adapter = manager_find_adapter_by_path(path); + if (!adapter) + goto unreg; + + /* check pending requests */ + reply_pending_requests(path, adapter); + + cancel_passkey_agent_requests(adapter->passkey_agents, path, NULL); + + release_passkey_agents(adapter, NULL); + + if (adapter->agent) { + agent_destroy(adapter->agent, FALSE); + adapter->agent = NULL; + } + + if (adapter->discov_requestor) { + g_dbus_remove_watch(connection, adapter->discov_listener); + adapter->discov_listener = 0; + g_free(adapter->discov_requestor); + adapter->discov_requestor = NULL; + } + + if (adapter->pdiscov_requestor) { + g_dbus_remove_watch(connection, adapter->pdiscov_listener); + adapter->pdiscov_listener = 0; + g_free(adapter->pdiscov_requestor); + adapter->pdiscov_requestor = NULL; + } + + if (adapter->found_devices) { + g_slist_foreach(adapter->found_devices, + (GFunc) g_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->auth_reqs) { + g_slist_foreach(adapter->auth_reqs, + (GFunc) g_free, NULL); + g_slist_free(adapter->auth_reqs); + adapter->auth_reqs = NULL; + } + + if (adapter->active_conn) { + g_slist_foreach(adapter->active_conn, + (GFunc) free, NULL); + g_slist_free(adapter->active_conn); + adapter->active_conn = NULL; + } + + /* Check if there is a pending RemoteDeviceDisconnect request */ + if (adapter->pending_dc) { + error_no_such_adapter(adapter->pending_dc->conn, + adapter->pending_dc->msg); + g_source_remove(adapter->pending_dc->timeout_id); + dc_pending_timeout_cleanup(adapter); + } + + if (adapter->devices) { + g_slist_foreach(adapter->devices, do_unregister, + connection); + g_slist_free(adapter->devices); + } + + manager_remove_adapter(adapter); + + g_free(adapter->path); + g_free(adapter); + +unreg: + if (!adapter_cleanup(connection, path)) { + error("Failed to unregister adapter interface on %s object", + path); + return -1; + } + + if (!security_cleanup(connection, path)) { + error("Failed to unregister security interface on %s object", + path); + return -1; + } + + if (hcid_dbus_use_experimental()) { + const char *ptr = path + ADAPTER_PATH_INDEX; + + adapter_cleanup(connection, ptr); + } + + return 0; +} + +/***************************************************************** + * + * Section reserved to HCI commands confirmation handling and low + * level events(eg: device attached/dettached. + * + *****************************************************************/ + +int hcid_dbus_register_device(uint16_t id) +{ + char path[MAX_PATH_LENGTH]; + char *ptr = path + ADAPTER_PATH_INDEX; + struct adapter *adapter; + + snprintf(path, sizeof(path), "%s/hci%d", BASE_PATH, id); + + adapter = g_try_new0(struct adapter, 1); + if (!adapter) { + error("Failed to alloc memory to D-Bus path register data (%s)", + path); + return -1; + } + + adapter->dev_id = id; + adapter->pdiscov_resolve_names = 1; + + if (!adapter_init(connection, path, adapter)) { + error("Adapter interface init failed on path %s", path); + g_free(adapter); + return -1; + } + + adapter->path = g_strdup(path); + + if (!security_init(connection, path)) { + error("Security interface init failed on path %s", path); + goto failed; + } + + __probe_servers(path); + + manager_add_adapter(adapter); + + return 0; + +failed: + if (hcid_dbus_use_experimental()) + g_dbus_unregister_interface(connection, ptr, ADAPTER_INTERFACE); + + g_dbus_unregister_interface(connection, path, ADAPTER_INTERFACE); + + g_free(adapter->path); + g_free(adapter); + + return -1; +} + +int hcid_dbus_unregister_device(uint16_t id) +{ + char path[MAX_PATH_LENGTH]; + + snprintf(path, sizeof(path), "%s/hci%d", BASE_PATH, id); + + return unregister_adapter_path(path); +} + +static void create_stored_device_from_profiles(char *key, char *value, + void *user_data) +{ + struct adapter *adapter = user_data; + GSList *uuids = bt_string2list(value); + struct device *device; + + device = device_create(connection, adapter, key); + if (device) { + device->temporary = FALSE; + adapter->devices = g_slist_append(adapter->devices, device); + device_probe_drivers(device, uuids); + g_slist_free(uuids); + } +} + +static void create_stored_device_from_linkkeys(char *key, char *value, + void *user_data) +{ + struct adapter *adapter = user_data; + struct device *device; + + if (g_slist_find_custom(adapter->devices, + key, (GCompareFunc) device_address_cmp)) + return; + + device = device_create(connection, adapter, key); + if (device) { + device->temporary = FALSE; + adapter->devices = g_slist_append(adapter->devices, device); + } +} + +static void register_devices(bdaddr_t *src, struct adapter *adapter) +{ + char filename[PATH_MAX + 1]; + char addr[18]; + + ba2str(src, addr); + + create_name(filename, PATH_MAX, STORAGEDIR, addr, "profiles"); + textfile_foreach(filename, create_stored_device_from_profiles, adapter); + + create_name(filename, PATH_MAX, STORAGEDIR, addr, "linkkeys"); + textfile_foreach(filename, create_stored_device_from_linkkeys, adapter); +} + +int hcid_dbus_start_device(uint16_t id) +{ + char path[MAX_PATH_LENGTH]; + char *ptr = path + ADAPTER_PATH_INDEX; + struct hci_dev_info di; + struct adapter* adapter; + struct hci_conn_list_req *cl = NULL; + struct hci_conn_info *ci; + const char *mode; + int i, err, dd = -1, ret = -1; + + snprintf(path, sizeof(path), "%s/hci%d", BASE_PATH, id); + + if (hci_devinfo(id, &di) < 0) { + error("Getting device info failed: hci%d", id); + return -1; + } + + if (hci_test_bit(HCI_RAW, &di.flags)) + return -1; + + adapter = manager_find_adapter_by_path(path); + if (!adapter) { + error("Getting %s path data failed!", path); + return -1; + } + + if (hci_test_bit(HCI_INQUIRY, &di.flags)) + adapter->discov_active = 1; + else + adapter->discov_active = 0; + + adapter->up = 1; + adapter->discov_timeout = get_discoverable_timeout(id); + adapter->discov_type = DISCOVER_TYPE_NONE; + + dd = hci_open_dev(id); + if (dd < 0) + goto failed; + + adapter->scan_enable = get_startup_scan(id); + hci_send_cmd(dd, OGF_HOST_CTL, OCF_WRITE_SCAN_ENABLE, + 1, &adapter->scan_enable); + /* + * Get the adapter Bluetooth address + */ + err = get_device_address(adapter->dev_id, adapter->address, + sizeof(adapter->address)); + if (err < 0) + goto failed; + + err = get_device_class(adapter->dev_id, adapter->class); + if (err < 0) + goto failed; + + adapter->mode = get_startup_mode(id); + if (adapter->mode == MODE_LIMITED) + set_limited_discoverable(dd, adapter->class, TRUE); + + /* + * retrieve the active connections: address the scenario where + * the are active connections before the daemon've started + */ + + cl = g_malloc0(10 * sizeof(*ci) + sizeof(*cl)); + + cl->dev_id = id; + cl->conn_num = 10; + ci = cl->conn_info; + + if (ioctl(dd, HCIGETCONNLIST, cl) < 0) + goto failed; + + for (i = 0; i < cl->conn_num; i++, ci++) + active_conn_append(&adapter->active_conn, + &ci->bdaddr, ci->handle); + + ret = 0; + + mode = mode2str(adapter->mode); + g_dbus_emit_signal(connection, path, ADAPTER_INTERFACE, + "ModeChanged", + DBUS_TYPE_STRING, &mode, + DBUS_TYPE_INVALID); + + if (hcid_dbus_use_experimental()) { + dbus_connection_emit_property_changed(connection, ptr, + ADAPTER_INTERFACE, "Mode", + DBUS_TYPE_STRING, &mode); + } + + if (manager_get_default_adapter() < 0) + manager_set_default_adapter(id); + + if (hcid_dbus_use_experimental()) + register_devices(&di.bdaddr, adapter); + +failed: + if (dd >= 0) + hci_close_dev(dd); + + g_free(cl); + + return ret; +} + +static void send_dc_signal(struct active_conn_info *dev, const char *path) +{ + char addr[18]; + const char *paddr = addr; + + ba2str(&dev->bdaddr, addr); + + g_dbus_emit_signal(connection, path, ADAPTER_INTERFACE, + "RemoteDeviceDisconnected", + DBUS_TYPE_STRING, &paddr, + DBUS_TYPE_INVALID); +} + +int hcid_dbus_stop_device(uint16_t id) +{ + char path[MAX_PATH_LENGTH]; + struct adapter *adapter; + const char *mode = "off"; + + snprintf(path, sizeof(path), "%s/hci%d", BASE_PATH, id); + + adapter = manager_find_adapter_by_path(path); + if (!adapter) { + error("Getting %s path data failed!", path); + return -1; + } + + /* cancel pending timeout */ + if (adapter->timeout_id) { + g_source_remove(adapter->timeout_id); + adapter->timeout_id = 0; + } + + /* check pending requests */ + reply_pending_requests(path, adapter); + + cancel_passkey_agent_requests(adapter->passkey_agents, path, NULL); + + release_passkey_agents(adapter, NULL); + + if (adapter->discov_requestor) { + g_dbus_remove_watch(connection, adapter->discov_listener); + adapter->discov_listener = 0; + g_free(adapter->discov_requestor); + adapter->discov_requestor = NULL; + } + + if (adapter->pdiscov_requestor) { + g_dbus_remove_watch(connection, adapter->pdiscov_listener); + adapter->pdiscov_listener = 0; + g_free(adapter->pdiscov_requestor); + adapter->pdiscov_requestor = NULL; + } + + if (adapter->found_devices) { + g_slist_foreach(adapter->found_devices, (GFunc) g_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->auth_reqs) { + g_slist_foreach(adapter->auth_reqs, (GFunc) g_free, NULL); + g_slist_free(adapter->auth_reqs); + adapter->auth_reqs = NULL; + } + + if (adapter->active_conn) { + g_slist_foreach(adapter->active_conn, (GFunc) send_dc_signal, path); + g_slist_foreach(adapter->active_conn, (GFunc) g_free, NULL); + g_slist_free(adapter->active_conn); + adapter->active_conn = NULL; + } + + send_adapter_signal(connection, adapter->dev_id, "ModeChanged", + DBUS_TYPE_STRING, &mode, + DBUS_TYPE_INVALID); + + if (hcid_dbus_use_experimental()) { + const char *ptr = path + ADAPTER_PATH_INDEX; + dbus_connection_emit_property_changed(connection, ptr, + ADAPTER_INTERFACE, "Mode", + DBUS_TYPE_STRING, &mode); + } + + adapter->up = 0; + adapter->scan_enable = SCAN_DISABLED; + adapter->mode = MODE_OFF; + adapter->discov_active = 0; + adapter->pdiscov_active = 0; + adapter->pinq_idle = 0; + adapter->discov_type = DISCOVER_TYPE_NONE; + + return 0; +} + +static void pincode_cb(struct agent *agent, DBusError *err, const char *pincode, + struct device *device) +{ + struct adapter *adapter = device->adapter; + pin_code_reply_cp pr; + bdaddr_t sba, dba; + size_t len; + int dev; + struct pending_auth_info *auth; + + /* No need to reply anything if the authentication already failed */ + if (adapter->bonding && adapter->bonding->hci_status) + return; + + dev = hci_open_dev(adapter->dev_id); + if (dev < 0) { + error("hci_open_dev(%d): %s (%d)", adapter->dev_id, + strerror(errno), errno); + return; + } + + str2ba(adapter->address, &sba); + str2ba(device->address, &dba); + + auth = adapter_find_auth_request(adapter, &dba); + + if (err) { + hci_send_cmd(dev, OGF_LINK_CTL, + OCF_PIN_CODE_NEG_REPLY, 6, &dba); + goto done; + } + + len = strlen(pincode); + + set_pin_length(&sba, len); + + memset(&pr, 0, sizeof(pr)); + bacpy(&pr.bdaddr, &dba); + memcpy(pr.pin_code, pincode, len); + pr.pin_len = len; + hci_send_cmd(dev, OGF_LINK_CTL, OCF_PIN_CODE_REPLY, PIN_CODE_REPLY_CP_SIZE, &pr); + +done: + if (auth) { + auth->replied = TRUE; + auth->agent = NULL; + } + hci_close_dev(dev); +} + +int hcid_dbus_request_pin(int dev, bdaddr_t *sba, struct hci_conn_info *ci) +{ + char addr[18]; + struct adapter *adapter; + struct device *device; + struct agent *agent; + int ret; + + adapter = manager_find_adapter(sba); + if (!adapter) { + error("No matching adapter found"); + return -1; + } + + if (!hcid_dbus_use_experimental()) + goto old_fallback; + + ba2str(&ci->bdaddr, addr); + + device = adapter_find_device(adapter, addr); + agent = device && device->agent ? device->agent : adapter->agent; + if (!agent) + goto old_fallback; + + if (!device) { + device = adapter_create_device(connection, adapter, addr); + if (!device) + return -ENODEV; + } + + ret = agent_request_pincode(agent, device, + (agent_pincode_cb) pincode_cb, + device); + if (ret == 0) { + struct pending_auth_info *auth; + auth = adapter_new_auth_request(adapter, &ci->bdaddr, + AUTH_TYPE_PINCODE); + auth->agent = agent; + } + + + return ret; + +old_fallback: + ret = handle_passkey_request_old(connection, dev, adapter, sba, + &ci->bdaddr); + if (ret == 0) + adapter_new_auth_request(adapter, &ci->bdaddr, + AUTH_TYPE_PINCODE); + return ret; +} + +static void confirm_cb(struct agent *agent, DBusError *err, void *user_data) +{ + struct device *device = user_data; + struct adapter *adapter = device->adapter; + user_confirm_reply_cp cp; + int dd; + struct pending_auth_info *auth; + + /* No need to reply anything if the authentication already failed */ + if (adapter->bonding && adapter->bonding->hci_status) + return; + + dd = hci_open_dev(adapter->dev_id); + if (dd < 0) { + error("Unable to open hci%d", adapter->dev_id); + return; + } + + memset(&cp, 0, sizeof(cp)); + str2ba(device->address, &cp.bdaddr); + + auth = adapter_find_auth_request(adapter, &cp.bdaddr); + + if (err) + hci_send_cmd(dd, OGF_LINK_CTL, OCF_USER_CONFIRM_NEG_REPLY, + USER_CONFIRM_REPLY_CP_SIZE, &cp); + else + hci_send_cmd(dd, OGF_LINK_CTL, OCF_USER_CONFIRM_REPLY, + USER_CONFIRM_REPLY_CP_SIZE, &cp); + + if (auth) { + auth->replied = TRUE; + auth->agent = FALSE; + } + + hci_close_dev(dd); +} + +static void passkey_cb(struct agent *agent, DBusError *err, uint32_t passkey, + void *user_data) +{ + struct device *device = user_data; + struct adapter *adapter = device->adapter; + user_passkey_reply_cp cp; + bdaddr_t dba; + int dd; + struct pending_auth_info *auth; + + /* No need to reply anything if the authentication already failed */ + if (adapter->bonding && adapter->bonding->hci_status) + return; + + dd = hci_open_dev(adapter->dev_id); + if (dd < 0) { + error("Unable to open hci%d", adapter->dev_id); + return; + } + + str2ba(device->address, &dba); + + memset(&cp, 0, sizeof(cp)); + bacpy(&cp.bdaddr, &dba); + cp.passkey = passkey; + + auth = adapter_find_auth_request(adapter, &dba); + + if (err) + hci_send_cmd(dd, OGF_LINK_CTL, + OCF_USER_PASSKEY_NEG_REPLY, 6, &dba); + else + hci_send_cmd(dd, OGF_LINK_CTL, OCF_USER_PASSKEY_REPLY, + USER_PASSKEY_REPLY_CP_SIZE, &cp); + + if (auth) { + auth->replied = TRUE; + auth->agent = NULL; + } + + hci_close_dev(dd); +} + +static int get_auth_requirements(bdaddr_t *local, bdaddr_t *remote, + uint8_t *auth) +{ + struct hci_auth_info_req req; + char addr[18]; + int err, dd, dev_id; + + ba2str(local, addr); + + dev_id = hci_devid(addr); + if (dev_id < 0) + return dev_id; + + dd = hci_open_dev(dev_id); + if (dd < 0) + return dd; + + memset(&req, 0, sizeof(req)); + bacpy(&req.bdaddr, remote); + + err = ioctl(dd, HCIGETAUTHINFO, (unsigned long) &req); + if (err < 0) { + debug("HCIGETAUTHINFO failed: %s (%d)", + strerror(errno), errno); + hci_close_dev(dd); + return err; + } + + hci_close_dev(dd); + + if (auth) + *auth = req.type; + + return 0; +} + +int hcid_dbus_user_confirm(bdaddr_t *sba, bdaddr_t *dba, uint32_t passkey) +{ + struct adapter *adapter; + struct device *device; + struct agent *agent; + char addr[18]; + uint8_t type; + struct pending_auth_info *auth; + + adapter = manager_find_adapter(sba); + if (!adapter) { + error("No matching adapter found"); + return -1; + } + + if (get_auth_requirements(sba, dba, &type) < 0) { + int dd; + + dd = hci_open_dev(adapter->dev_id); + if (dd < 0) { + error("Unable to open hci%d", adapter->dev_id); + return -1; + } + + hci_send_cmd(dd, OGF_LINK_CTL, + OCF_USER_CONFIRM_NEG_REPLY, 6, dba); + + hci_close_dev(dd); + + return 0; + } + + ba2str(dba, addr); + + device = adapter_get_device(connection, adapter, addr); + if (!device) { + error("Device creation failed"); + return -1; + } + + /* If no MITM protection required, auto-accept */ + if (!(device->auth & 0x01) && !(type & 0x01)) { + int dd; + + dd = hci_open_dev(adapter->dev_id); + if (dd < 0) { + error("Unable to open hci%d", adapter->dev_id); + return -1; + } + + hci_send_cmd(dd, OGF_LINK_CTL, + OCF_USER_CONFIRM_REPLY, 6, dba); + + hci_close_dev(dd); + + return 0; + } + + if (device->agent) + agent = device->agent; + else + agent = adapter->agent; + + if (!agent) { + error("No agent available for user confirm request"); + return -1; + } + + if (agent_request_confirmation(agent, device, passkey, + confirm_cb, device) < 0) { + error("Requesting passkey failed"); + return -1; + } + + auth = adapter_new_auth_request(adapter, dba, AUTH_TYPE_CONFIRM); + auth->agent = agent; + + return 0; +} + +int hcid_dbus_user_passkey(bdaddr_t *sba, bdaddr_t *dba) +{ + struct adapter *adapter; + struct device *device; + struct agent *agent; + char addr[18]; + struct pending_auth_info *auth; + + adapter = manager_find_adapter(sba); + if (!adapter) { + error("No matching adapter found"); + return -1; + } + + ba2str(dba, addr); + + device = adapter_get_device(connection, adapter, addr); + if (device && device->agent) + agent = device->agent; + else + agent = adapter->agent; + + if (!agent) { + error("No agent available for user confirm request"); + return -1; + } + + if (agent_request_passkey(agent, device, passkey_cb, device) < 0) { + error("Requesting passkey failed"); + return -1; + } + + auth = adapter_new_auth_request(adapter, dba, AUTH_TYPE_PASSKEY); + auth->agent = agent; + + return 0; +} + +int hcid_dbus_user_notify(bdaddr_t *sba, bdaddr_t *dba, uint32_t passkey) +{ + struct adapter *adapter; + struct device *device; + struct agent *agent; + char addr[18]; + struct pending_auth_info *auth; + + adapter = manager_find_adapter(sba); + if (!adapter) { + error("No matching adapter found"); + return -1; + } + + ba2str(dba, addr); + + device = adapter_get_device(connection, adapter, addr); + if (device && device->agent) + agent = device->agent; + else + agent = adapter->agent; + + if (!agent) { + error("No agent available for user confirm request"); + return -1; + } + + if (agent_display_passkey(agent, device, passkey) < 0) { + error("Displaying passkey failed"); + return -1; + } + + auth = adapter_new_auth_request(adapter, dba, AUTH_TYPE_NOTIFY); + auth->agent = agent; + + return 0; +} + +void hcid_dbus_bonding_process_complete(bdaddr_t *local, bdaddr_t *peer, + uint8_t status) +{ + struct adapter *adapter; + char peer_addr[18]; + const char *paddr = peer_addr; + DBusMessage *reply; + struct device *device; + struct bonding_request_info *bonding; + gboolean paired = TRUE; + struct pending_auth_info *auth; + + debug("hcid_dbus_bonding_process_complete: status=%02x", status); + + ba2str(peer, peer_addr); + + adapter = manager_find_adapter(local); + if (!adapter) { + error("Unable to find matching adapter"); + return; + } + + if (status) { + if (adapter->bonding) + adapter->bonding->hci_status = status; + cancel_passkey_agent_requests(adapter->passkey_agents, + adapter->path, peer); + } + + auth = adapter_find_auth_request(adapter, peer); + if (!auth) { + debug("hcid_dbus_bonding_process_complete: no pending auth request"); + goto proceed; + } + + if (auth->agent) + agent_cancel(auth->agent); + + adapter_remove_auth_request(adapter, peer); + + if (status) + goto proceed; + + send_adapter_signal(connection, adapter->dev_id, "BondingCreated", + DBUS_TYPE_STRING, &paddr, DBUS_TYPE_INVALID); + + device = adapter_get_device(connection, adapter, paddr); + if (device) { + char *ptr = adapter->path + ADAPTER_PATH_INDEX; + + debug("hcid_dbus_bonding_process_complete: removing temporary flag"); + + device->temporary = FALSE; + + g_dbus_emit_signal(connection, ptr, + ADAPTER_INTERFACE, + "DeviceCreated", + DBUS_TYPE_OBJECT_PATH, + &device->path, + DBUS_TYPE_INVALID); + + dbus_connection_emit_property_changed(connection, device->path, + DEVICE_INTERFACE, "Paired", + DBUS_TYPE_BOOLEAN, &paired); + } + +proceed: + + release_passkey_agents(adapter, peer); + + bonding = adapter->bonding; + if (!bonding || bacmp(&bonding->bdaddr, peer)) + return; /* skip: no bonding req pending */ + + if (bonding->cancel) { + /* reply authentication canceled */ + error_authentication_canceled(connection, bonding->msg); + goto cleanup; + } + + /* reply authentication success or an error */ + if (dbus_message_is_method_call(bonding->msg, ADAPTER_INTERFACE, + "CreateBonding")) { + reply = new_authentication_return(bonding->msg, status); + dbus_connection_send(connection, reply, NULL); + dbus_message_unref(reply); + } else if ((device = adapter_find_device(adapter, paddr))) { + if (status) { + reply = new_authentication_return(bonding->msg, status); + dbus_connection_send(connection, reply, NULL); + dbus_message_unref(reply); + } else { + device->temporary = FALSE; + device_browse(device, bonding->conn, + bonding->msg, NULL); + } + } + +cleanup: + g_dbus_remove_watch(connection, adapter->bonding->listener_id); + + if (adapter->bonding->io_id) + g_source_remove(adapter->bonding->io_id); + g_io_channel_close(adapter->bonding->io); + bonding_request_free(adapter->bonding); + adapter->bonding = NULL; +} + +void hcid_dbus_inquiry_start(bdaddr_t *local) +{ + struct adapter *adapter; + + adapter = manager_find_adapter(local); + if (!adapter) { + error("Unable to find matching adapter"); + return; + } + + adapter->discov_active = 1; + /* + * Cancel pending remote name request and clean the device list + * when inquiry is supported in periodic inquiry idle state. + */ + if (adapter->pdiscov_active) + pending_remote_name_cancel(adapter); + + /* Disable name resolution for non D-Bus clients */ + if (!adapter->discov_requestor) + adapter->discov_type &= ~RESOLVE_NAME; + + if (hcid_dbus_use_experimental()) + dbus_connection_emit_property_changed(connection, + adapter->path + ADAPTER_PATH_INDEX, + ADAPTER_INTERFACE, "PeriodicDiscovery", + DBUS_TYPE_BOOLEAN, &adapter->discov_active); + + send_adapter_signal(connection, adapter->dev_id, "DiscoveryStarted", + DBUS_TYPE_INVALID); + + if (hcid_dbus_use_experimental()) + g_dbus_emit_signal(connection, + adapter->path + ADAPTER_PATH_INDEX, + ADAPTER_INTERFACE, + "DiscoveryStarted", + DBUS_TYPE_INVALID); +} + +int found_device_req_name(struct adapter *adapter) +{ + struct hci_request rq; + evt_cmd_status rp; + remote_name_req_cp cp; + struct remote_dev_info match; + GSList *l; + int dd, req_sent = 0; + + /* get the next remote address */ + if (!adapter->found_devices) + return -ENODATA; + + memset(&match, 0, sizeof(struct remote_dev_info)); + bacpy(&match.bdaddr, BDADDR_ANY); + match.name_status = NAME_REQUIRED; + + l = g_slist_find_custom(adapter->found_devices, &match, + (GCompareFunc) found_device_cmp); + if (!l) + return -ENODATA; + + dd = hci_open_dev(adapter->dev_id); + if (dd < 0) + return -errno; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_LINK_CTL; + rq.ocf = OCF_REMOTE_NAME_REQ; + rq.cparam = &cp; + rq.clen = REMOTE_NAME_REQ_CP_SIZE; + rq.rparam = &rp; + rq.rlen = EVT_CMD_STATUS_SIZE; + rq.event = EVT_CMD_STATUS; + + /* send at least one request or return failed if the list is empty */ + do { + struct remote_dev_info *dev = l->data; + char peer_addr[18]; + const char *signal = NULL, *paddr = peer_addr; + + /* flag to indicate the current remote name requested */ + dev->name_status = NAME_REQUESTED; + + memset(&rp, 0, sizeof(rp)); + memset(&cp, 0, sizeof(cp)); + bacpy(&cp.bdaddr, &dev->bdaddr); + cp.pscan_rep_mode = 0x02; + + ba2str(&dev->bdaddr, peer_addr); + + if (hci_send_req(dd, &rq, 500) < 0) { + error("Unable to send the HCI remote name request: %s (%d)", + strerror(errno), errno); + signal = "RemoteNameFailed"; + } + + if (rp.status) { + error("Remote name request failed with status 0x%02x", + rp.status); + signal = "RemoteNameFailed"; + } + + if (!signal) { + req_sent = 1; + /* if we are in discovery, inform application of getting name */ + if (adapter->discov_type & (STD_INQUIRY | PERIODIC_INQUIRY)) + signal = "RemoteNameRequested"; + } + + if (signal) + send_adapter_signal(connection, adapter->dev_id, signal, + DBUS_TYPE_STRING, &paddr, + DBUS_TYPE_INVALID); + + if (req_sent) + break; + + /* if failed, request the next element */ + /* remove the element from the list */ + adapter->found_devices = g_slist_remove(adapter->found_devices, dev); + g_free(dev); + + /* get the next element */ + l = g_slist_find_custom(adapter->found_devices, &match, + (GCompareFunc) found_device_cmp); + + } while (l); + + hci_close_dev(dd); + + if (!req_sent) + return -ENODATA; + + return 0; +} + +static void send_out_of_range(const char *path, GSList *l) +{ + while (l) { + const char *peer_addr = l->data; + + g_dbus_emit_signal(connection, path, + ADAPTER_INTERFACE, + "RemoteDeviceDisappeared", + DBUS_TYPE_STRING, &peer_addr, + DBUS_TYPE_INVALID); + + if (hcid_dbus_use_experimental()) { + const char *ptr = path + ADAPTER_PATH_INDEX; + g_dbus_emit_signal(connection, ptr, + ADAPTER_INTERFACE, + "DeviceDisappeared", + DBUS_TYPE_STRING, + &peer_addr, + DBUS_TYPE_INVALID); + } + + l = l->next; + } +} + +void hcid_dbus_inquiry_complete(bdaddr_t *local) +{ + struct adapter *adapter; + struct remote_dev_info *dev; + bdaddr_t tmp; + + adapter = manager_find_adapter(local); + if (!adapter) { + error("Unable to find matching adapter"); + return; + } + + /* Out of range verification */ + if (adapter->pdiscov_active && !adapter->discov_active) { + GSList *l; + + send_out_of_range(adapter->path, adapter->oor_devices); + + g_slist_foreach(adapter->oor_devices, (GFunc) free, NULL); + g_slist_free(adapter->oor_devices); + adapter->oor_devices = NULL; + + l = adapter->found_devices; + while (l) { + dev = l->data; + baswap(&tmp, &dev->bdaddr); + adapter->oor_devices = g_slist_append(adapter->oor_devices, + batostr(&tmp)); + l = l->next; + } + } + + adapter->pinq_idle = 1; + + /* + * Enable resolution again: standard inquiry can be + * received in the periodic inquiry idle state. + */ + if (adapter->pdiscov_requestor && adapter->pdiscov_resolve_names) + adapter->discov_type |= RESOLVE_NAME; + + /* + * The following scenarios can happen: + * 1. standard inquiry: always send discovery completed signal + * 2. standard inquiry + name resolving: send discovery completed + * after name resolving + * 3. periodic inquiry: skip discovery completed signal + * 4. periodic inquiry + standard inquiry: always send discovery + * completed signal + * + * Keep in mind that non D-Bus requests can arrive. + */ + + if (!found_device_req_name(adapter)) + return; /* skip - there is name to resolve */ + + if (adapter->discov_active) { + if (hcid_dbus_use_experimental()) { + const char *ptr = adapter->path + ADAPTER_PATH_INDEX; + g_dbus_emit_signal(connection, ptr, + ADAPTER_INTERFACE, + "DiscoveryCompleted", + DBUS_TYPE_INVALID); + + } + + g_dbus_emit_signal(connection, adapter->path, + ADAPTER_INTERFACE, + "DiscoveryCompleted", + DBUS_TYPE_INVALID); + adapter->discov_active = 0; + } + + /* free discovered devices list */ + g_slist_foreach(adapter->found_devices, (GFunc) g_free, NULL); + g_slist_free(adapter->found_devices); + adapter->found_devices = NULL; + + if (adapter->discov_requestor) { + g_dbus_remove_watch(connection, adapter->discov_listener); + adapter->discov_listener = 0; + g_free(adapter->discov_requestor); + adapter->discov_requestor = NULL; + + /* If there is a pending reply for discovery cancel */ + if (adapter->discovery_cancel) { + DBusMessage *reply; + reply = dbus_message_new_method_return(adapter->discovery_cancel); + dbus_connection_send(connection, reply, NULL); + dbus_message_unref(reply); + dbus_message_unref(adapter->discovery_cancel); + adapter->discovery_cancel = NULL; + } + + /* reset the discover type for standard inquiry only */ + adapter->discov_type &= ~STD_INQUIRY; + } +} + +void hcid_dbus_periodic_inquiry_start(bdaddr_t *local, uint8_t status) +{ + struct adapter *adapter; + + /* Don't send the signal if the cmd failed */ + if (status) + return; + + adapter = manager_find_adapter(local); + if (!adapter) { + error("No matching adapter found"); + return; + } + + adapter->pdiscov_active = 1; + + /* Disable name resolution for non D-Bus clients */ + if (!adapter->pdiscov_requestor) + adapter->discov_type &= ~RESOLVE_NAME; + + if (hcid_dbus_use_experimental()) + dbus_connection_emit_property_changed(connection, + adapter->path + ADAPTER_PATH_INDEX, + ADAPTER_INTERFACE, + "PeriodicDiscovery", + DBUS_TYPE_BOOLEAN, + &adapter->pdiscov_active); + + g_dbus_emit_signal(connection, adapter->path, ADAPTER_INTERFACE, + "PeriodicDiscoveryStarted", + DBUS_TYPE_INVALID); +} + +void hcid_dbus_periodic_inquiry_exit(bdaddr_t *local, uint8_t status) +{ + struct adapter *adapter; + char *ptr; + + /* Don't send the signal if the cmd failed */ + if (status) + return; + + adapter = manager_find_adapter(local); + if (!adapter) { + error("No matching adapter found"); + return; + } + + ptr = adapter->path + ADAPTER_PATH_INDEX; + + /* reset the discover type to be able to handle D-Bus and non D-Bus + * requests */ + adapter->pdiscov_active = 0; + adapter->discov_type &= ~(PERIODIC_INQUIRY | RESOLVE_NAME); + + /* free discovered devices list */ + g_slist_foreach(adapter->found_devices, (GFunc) g_free, NULL); + g_slist_free(adapter->found_devices); + adapter->found_devices = NULL; + + /* free out of range devices list */ + g_slist_foreach(adapter->oor_devices, (GFunc) free, NULL); + g_slist_free(adapter->oor_devices); + adapter->oor_devices = NULL; + + if (adapter->pdiscov_requestor) { + g_dbus_remove_watch(connection, adapter->pdiscov_listener); + adapter->pdiscov_listener = 0; + g_free(adapter->pdiscov_requestor); + adapter->pdiscov_requestor = NULL; + } + + /* workaround: inquiry completed is not sent when exiting from + * periodic inquiry */ + if (adapter->discov_active) { + if (hcid_dbus_use_experimental()) + g_dbus_emit_signal(connection, ptr, + ADAPTER_INTERFACE, + "DiscoveryCompleted", + DBUS_TYPE_INVALID); + + g_dbus_emit_signal(connection, adapter->path, + ADAPTER_INTERFACE, + "DiscoveryCompleted", + DBUS_TYPE_INVALID); + adapter->discov_active = 0; + } + + /* Send discovery completed signal if there isn't name to resolve */ + g_dbus_emit_signal(connection, adapter->path, + ADAPTER_INTERFACE, + "PeriodicDiscoveryStopped", + DBUS_TYPE_INVALID); + + if (hcid_dbus_use_experimental()) + dbus_connection_emit_property_changed(connection, ptr, + ADAPTER_INTERFACE, + "PeriodicDiscovery", + DBUS_TYPE_BOOLEAN, + &adapter->discov_active); +} + +static char *extract_eir_name(uint8_t *data, uint8_t *type) +{ + if (!data || !type) + return NULL; + + if (data[0] == 0) + return NULL; + + *type = data[1]; + + switch (*type) { + case 0x08: + case 0x09: + return strndup((char *) (data + 2), data[0] - 1); + } + + return NULL; +} + +static void append_dict_valist(DBusMessageIter *iter, + const char *first_key, + va_list var_args) +{ + DBusMessageIter dict; + const char *key; + int type; + void *val; + + 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); + + key = first_key; + while (key) { + type = va_arg(var_args, int); + val = va_arg(var_args, void *); + dbus_message_iter_append_dict_entry(&dict, key, type, val); + key = va_arg(var_args, char *); + } + + dbus_message_iter_close_container(iter, &dict); +} + +static void emit_device_found(const char *path, const char *address, + const char *first_key, ...) +{ + DBusMessage *signal; + DBusMessageIter iter; + va_list var_args; + + signal = dbus_message_new_signal(path, ADAPTER_INTERFACE, + "DeviceFound"); + if (!signal) { + error("Unable to allocate new %s.DeviceFound signal", + ADAPTER_INTERFACE); + return; + } + dbus_message_iter_init_append(signal, &iter); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &address); + + va_start(var_args, first_key); + append_dict_valist(&iter, first_key, var_args); + va_end(var_args); + + dbus_connection_send(connection, signal, NULL); + + dbus_message_unref(signal); +} + +void hcid_dbus_inquiry_result(bdaddr_t *local, bdaddr_t *peer, uint32_t class, + int8_t rssi, uint8_t *data) +{ + char filename[PATH_MAX + 1]; + struct adapter *adapter; + GSList *l; + char local_addr[18], peer_addr[18], *name, *tmp_name; + const char *paddr = peer_addr; + struct remote_dev_info match; + dbus_int16_t tmp_rssi = rssi; + uint8_t name_type = 0x00; + name_status_t name_status; + + ba2str(local, local_addr); + ba2str(peer, peer_addr); + + adapter = manager_find_adapter(local); + if (!adapter) { + error("No matching adapter found"); + return; + } + + write_remote_class(local, peer, class); + + if (data) + write_remote_eir(local, peer, data); + + /* + * workaround to identify situation when the daemon started and + * a standard inquiry or periodic inquiry was already running + */ + if (!adapter->discov_active && !adapter->pdiscov_active) + adapter->pdiscov_active = 1; + + /* reset the idle flag when the inquiry complete event arrives */ + if (adapter->pdiscov_active) { + adapter->pinq_idle = 0; + + /* Out of range list update */ + 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); + } + } + + /* send the device found signal */ + g_dbus_emit_signal(connection, adapter->path, + ADAPTER_INTERFACE, + "RemoteDeviceFound", + DBUS_TYPE_STRING, &paddr, + DBUS_TYPE_UINT32, &class, + DBUS_TYPE_INT16, &tmp_rssi, + DBUS_TYPE_INVALID); + + memset(&match, 0, sizeof(struct remote_dev_info)); + bacpy(&match.bdaddr, peer); + match.name_status = NAME_SENT; + /* if found: don't send the name again */ + l = g_slist_find_custom(adapter->found_devices, &match, + (GCompareFunc) found_device_cmp); + if (l) + return; + + /* the inquiry result can be triggered by NON D-Bus client */ + if (adapter->discov_type & RESOLVE_NAME) + name_status = NAME_REQUIRED; + else + name_status = NAME_NOT_REQUIRED; + + create_name(filename, PATH_MAX, STORAGEDIR, local_addr, "names"); + name = textfile_get(filename, peer_addr); + + tmp_name = extract_eir_name(data, &name_type); + if (tmp_name) { + if (name_type == 0x09) { + write_device_name(local, peer, tmp_name); + name_status = NAME_NOT_REQUIRED; + + if (name) + g_free(name); + + name = tmp_name; + } else { + if (name) + free(tmp_name); + else + name = tmp_name; + } + } + + if (name) { + g_dbus_emit_signal(connection, adapter->path, + ADAPTER_INTERFACE, + "RemoteNameUpdated", + DBUS_TYPE_STRING, &paddr, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID); + + if (name_type != 0x08) + name_status = NAME_SENT; + + if (hcid_dbus_use_experimental()) { + emit_device_found(adapter->path + ADAPTER_PATH_INDEX, + paddr, + "Address", DBUS_TYPE_STRING, &paddr, + "Class", DBUS_TYPE_UINT32, &class, + "RSSI", DBUS_TYPE_INT16, &tmp_rssi, + "Name", DBUS_TYPE_STRING, &name, + NULL); + } + + g_free(name); + } else if (hcid_dbus_use_experimental()) { + emit_device_found(adapter->path + ADAPTER_PATH_INDEX, + paddr, + "Address", DBUS_TYPE_STRING, &paddr, + "Class", DBUS_TYPE_UINT32, &class, + "RSSI", DBUS_TYPE_INT16, &tmp_rssi, + NULL); + } + + /* add in the list to track name sent/pending */ + found_device_add(&adapter->found_devices, peer, rssi, name_status); +} + +void hcid_dbus_remote_class(bdaddr_t *local, bdaddr_t *peer, uint32_t class) +{ + char peer_addr[18]; + const char *paddr = peer_addr; + uint32_t old_class = 0; + struct adapter *adapter; + + read_remote_class(local, peer, &old_class); + + if (old_class == class) + return; + + adapter = manager_find_adapter(local); + if (!adapter) { + error("No matching adapter found"); + return; + } + + ba2str(peer, peer_addr); + + send_adapter_signal(connection, adapter->dev_id, + "RemoteClassUpdated", + DBUS_TYPE_STRING, &paddr, + DBUS_TYPE_UINT32, &class, + DBUS_TYPE_INVALID); + + if (hcid_dbus_use_experimental()) { + GSList *l; + struct device *device; + + l = g_slist_find_custom(adapter->devices, paddr, + (GCompareFunc) device_address_cmp); + if (!l) + return; + + device = l->data; + dbus_connection_emit_property_changed(connection, + device->path, DEVICE_INTERFACE, + "Class", DBUS_TYPE_UINT32, &class); + } +} + +void hcid_dbus_remote_name(bdaddr_t *local, bdaddr_t *peer, uint8_t status, + char *name) +{ + struct adapter *adapter; + char peer_addr[18]; + const char *paddr = peer_addr; + + adapter = manager_find_adapter(local); + if (!adapter) { + error("No matching adapter found"); + return; + } + + ba2str(peer, peer_addr); + + if (status) + g_dbus_emit_signal(connection, adapter->path, + ADAPTER_INTERFACE, + "RemoteNameFailed", + DBUS_TYPE_STRING, &paddr, + DBUS_TYPE_INVALID); + else { + g_dbus_emit_signal(connection, adapter->path, + ADAPTER_INTERFACE, + "RemoteNameUpdated", + DBUS_TYPE_STRING, &paddr, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID); + + if (hcid_dbus_use_experimental()) { + struct device *device; + + device = adapter_find_device(adapter, paddr); + if (device) { + dbus_connection_emit_property_changed(connection, + device->path, DEVICE_INTERFACE, + "Name", DBUS_TYPE_STRING, &name); + } + } + } + + /* remove from remote name request list */ + found_device_remove(&adapter->found_devices, peer); + + /* check if there is more devices to request names */ + if (!found_device_req_name(adapter)) + return; /* skip if a new request has been sent */ + + /* free discovered devices list */ + g_slist_foreach(adapter->found_devices, (GFunc) g_free, NULL); + g_slist_free(adapter->found_devices); + adapter->found_devices = NULL; + + /* The discovery completed signal must be sent only for discover + * devices request WITH name resolving */ + if (adapter->discov_requestor) { + g_dbus_remove_watch(connection, adapter->discov_listener); + adapter->discov_listener = 0; + g_free(adapter->discov_requestor); + adapter->discov_requestor = NULL; + + /* If there is a pending reply for discovery cancel */ + if (adapter->discovery_cancel) { + DBusMessage *reply; + reply = dbus_message_new_method_return(adapter->discovery_cancel); + dbus_connection_send(connection, reply, NULL); + dbus_message_unref(reply); + dbus_message_unref(adapter->discovery_cancel); + adapter->discovery_cancel = NULL; + } + + /* Disable name resolution for non D-Bus clients */ + if (!adapter->pdiscov_requestor) + adapter->discov_type &= ~RESOLVE_NAME; + } + + if (adapter->discov_active) { + if (hcid_dbus_use_experimental()) + g_dbus_emit_signal(connection, + adapter->path + ADAPTER_PATH_INDEX, + ADAPTER_INTERFACE, + "DiscoveryCompleted", + DBUS_TYPE_INVALID); + + g_dbus_emit_signal(connection, adapter->path, + ADAPTER_INTERFACE, + "DiscoveryCompleted", + DBUS_TYPE_INVALID); + adapter->discov_active = 0; + } +} + +void hcid_dbus_conn_complete(bdaddr_t *local, uint8_t status, uint16_t handle, + bdaddr_t *peer) +{ + char peer_addr[18]; + const char *paddr = peer_addr; + struct adapter *adapter; + + adapter = manager_find_adapter(local); + if (!adapter) { + error("No matching adapter found"); + return; + } + + ba2str(peer, peer_addr); + + if (status) { + struct pending_auth_info *auth; + + cancel_passkey_agent_requests(adapter->passkey_agents, + adapter->path, peer); + release_passkey_agents(adapter, peer); + + auth = adapter_find_auth_request(adapter, peer); + if (auth && auth->agent) + agent_cancel(auth->agent); + + adapter_remove_auth_request(adapter, peer); + + if (adapter->bonding) + adapter->bonding->hci_status = status; + } else { + /* Send the remote device connected signal */ + g_dbus_emit_signal(connection, adapter->path, + ADAPTER_INTERFACE, + "RemoteDeviceConnected", + DBUS_TYPE_STRING, &paddr, + DBUS_TYPE_INVALID); + + if (hcid_dbus_use_experimental()) { + struct device *device; + gboolean connected = TRUE; + + device = adapter_find_device(adapter, paddr); + if (device) { + dbus_connection_emit_property_changed(connection, + device->path, DEVICE_INTERFACE, + "Connected", DBUS_TYPE_BOOLEAN, + &connected); + } + } + + /* add in the active connetions list */ + active_conn_append(&adapter->active_conn, peer, handle); + } +} + +void hcid_dbus_disconn_complete(bdaddr_t *local, uint8_t status, + uint16_t handle, uint8_t reason) +{ + DBusMessage *reply; + char peer_addr[18]; + const char *paddr = peer_addr; + struct adapter *adapter; + struct device *device; + struct active_conn_info *dev; + GSList *l; + gboolean connected = FALSE; + struct pending_auth_info *auth; + + if (status) { + error("Disconnection failed: 0x%02x", status); + return; + } + + adapter = manager_find_adapter(local); + if (!adapter) { + error("No matching adapter found"); + return; + } + + l = g_slist_find_custom(adapter->active_conn, &handle, + active_conn_find_by_handle); + + if (!l) + return; + + dev = l->data; + + ba2str(&dev->bdaddr, peer_addr); + + /* clean pending HCI cmds */ + hci_req_queue_remove(adapter->dev_id, &dev->bdaddr); + + /* Cancel D-Bus/non D-Bus requests */ + cancel_passkey_agent_requests(adapter->passkey_agents, adapter->path, + &dev->bdaddr); + release_passkey_agents(adapter, &dev->bdaddr); + + auth = adapter_find_auth_request(adapter, &dev->bdaddr); + if (auth && auth->agent) + agent_cancel(auth->agent); + + adapter_remove_auth_request(adapter, &dev->bdaddr); + + /* Check if there is a pending CreateBonding request */ + if (adapter->bonding && (bacmp(&adapter->bonding->bdaddr, &dev->bdaddr) == 0)) { + if (adapter->bonding->cancel) { + /* reply authentication canceled */ + error_authentication_canceled(connection, + adapter->bonding->msg); + } else { + reply = new_authentication_return(adapter->bonding->msg, + HCI_AUTHENTICATION_FAILURE); + dbus_connection_send(connection, reply, NULL); + dbus_message_unref(reply); + } + + g_dbus_remove_watch(adapter->bonding->conn, + adapter->bonding->listener_id); + + if (adapter->bonding->io_id) + g_source_remove(adapter->bonding->io_id); + g_io_channel_close(adapter->bonding->io); + bonding_request_free(adapter->bonding); + adapter->bonding = NULL; + } + + /* Check if there is a pending RemoteDeviceDisconnect request */ + if (adapter->pending_dc) { + reply = dbus_message_new_method_return(adapter->pending_dc->msg); + if (reply) { + dbus_connection_send(connection, reply, NULL); + dbus_message_unref(reply); + } else + error("Failed to allocate disconnect reply"); + + g_source_remove(adapter->pending_dc->timeout_id); + dc_pending_timeout_cleanup(adapter); + } + + /* Send the remote device disconnected signal */ + g_dbus_emit_signal(connection, adapter->path, + ADAPTER_INTERFACE, + "RemoteDeviceDisconnected", + DBUS_TYPE_STRING, &paddr, + DBUS_TYPE_INVALID); + + adapter->active_conn = g_slist_remove(adapter->active_conn, dev); + g_free(dev); + + device = adapter_find_device(adapter, paddr); + if (device) { + dbus_connection_emit_property_changed(connection, + device->path, DEVICE_INTERFACE, + "Connected", DBUS_TYPE_BOOLEAN, + &connected); + if (device->temporary) { + debug("Removing temporary device %s", device->address); + adapter_remove_device(connection, adapter, device); + } + } +} + +int set_limited_discoverable(int dd, const uint8_t *cls, gboolean limited) +{ + uint32_t dev_class; + int err; + 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, 1000) < 0) { + 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, 1000) < 0) { + err = errno; + error("Can't write class of device: %s (%d)", + strerror(err), err); + return -err; + } + + return 0; +} + +int set_service_classes(int dd, const uint8_t *cls, uint8_t value) +{ + uint32_t dev_class; + int err; + + if (cls[2] == value) + return 0; /* Already set */ + + dev_class = (value << 16) | (cls[1] << 8) | cls[0]; + + if (hci_write_class_of_dev(dd, dev_class, 1000) < 0) { + err = errno; + error("Can't write class of device: %s (%d)", + strerror(err), err); + return -err; + } + + return 0; +} + +gboolean discov_timeout_handler(void *data) +{ + struct adapter *adapter = data; + struct hci_request rq; + int dd; + uint8_t scan_enable = adapter->scan_enable; + uint8_t status = 0; + gboolean retval = TRUE; + + scan_enable &= ~SCAN_INQUIRY; + + dd = hci_open_dev(adapter->dev_id); + if (dd < 0) { + error("HCI device open failed: hci%d", adapter->dev_id); + return TRUE; + } + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_HOST_CTL; + rq.ocf = OCF_WRITE_SCAN_ENABLE; + rq.cparam = &scan_enable; + rq.clen = sizeof(scan_enable); + rq.rparam = &status; + rq.rlen = sizeof(status); + rq.event = EVT_CMD_COMPLETE; + + if (hci_send_req(dd, &rq, 1000) < 0) { + error("Sending write scan enable command to hci%d failed: %s (%d)", + adapter->dev_id, strerror(errno), errno); + goto failed; + } + if (status) { + error("Setting scan enable failed with status 0x%02x", status); + goto failed; + } + + set_limited_discoverable(dd, adapter->class, FALSE); + + adapter->timeout_id = 0; + retval = FALSE; + +failed: + if (dd >= 0) + hci_close_dev(dd); + + return retval; +} + +/* Section reserved to device HCI callbacks */ + +void hcid_dbus_setname_complete(bdaddr_t *local) +{ + int id, dd = -1; + read_local_name_rp rp; + struct hci_request rq; + const char *pname = (char *) rp.name; + char local_addr[18], name[249]; + + ba2str(local, local_addr); + + id = hci_devid(local_addr); + if (id < 0) { + error("No matching device id for %s", local_addr); + return; + } + + dd = hci_open_dev(id); + if (dd < 0) { + error("HCI device open failed: hci%d", id); + memset(&rp, 0, sizeof(rp)); + } else { + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_HOST_CTL; + rq.ocf = OCF_READ_LOCAL_NAME; + rq.rparam = &rp; + rq.rlen = READ_LOCAL_NAME_RP_SIZE; + rq.event = EVT_CMD_COMPLETE; + + if (hci_send_req(dd, &rq, 1000) < 0) { + error("Sending getting name command failed: %s (%d)", + strerror(errno), errno); + rp.name[0] = '\0'; + } else if (rp.status) { + error("Getting name failed with status 0x%02x", + rp.status); + rp.name[0] = '\0'; + } + hci_close_dev(dd); + } + + strncpy(name, pname, sizeof(name) - 1); + name[248] = '\0'; + pname = name; + + send_adapter_signal(connection, id, "NameChanged", + DBUS_TYPE_STRING, &pname, DBUS_TYPE_INVALID); +} + +void hcid_dbus_setscan_enable_complete(bdaddr_t *local) +{ + struct adapter *adapter; + read_scan_enable_rp rp; + struct hci_request rq; + int dd = -1; + + adapter = manager_find_adapter(local); + if (!adapter) { + error("No matching adapter found"); + return; + } + + dd = hci_open_dev(adapter->dev_id); + if (dd < 0) { + error("HCI device open failed: hci%d", adapter->dev_id); + return; + } + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_HOST_CTL; + rq.ocf = OCF_READ_SCAN_ENABLE; + rq.rparam = &rp; + rq.rlen = READ_SCAN_ENABLE_RP_SIZE; + rq.event = EVT_CMD_COMPLETE; + + if (hci_send_req(dd, &rq, 1000) < 0) { + error("Sending read scan enable command failed: %s (%d)", + strerror(errno), errno); + goto failed; + } + + if (rp.status) { + error("Getting scan enable failed with status 0x%02x", + rp.status); + goto failed; + } + + if (adapter->timeout_id) { + g_source_remove(adapter->timeout_id); + adapter->timeout_id = 0; + } + + if (adapter->scan_enable != rp.enable) + adapter_mode_changed(adapter, rp.enable); + +failed: + if (dd >= 0) + hci_close_dev(dd); +} + +void hcid_dbus_write_class_complete(bdaddr_t *local) +{ + struct adapter *adapter; + int dd; + uint8_t cls[3]; + + adapter = manager_find_adapter(local); + if (!adapter) { + error("No matching adapter found"); + return; + } + + dd = hci_open_dev(adapter->dev_id); + if (dd < 0) { + error("HCI device open failed: hci%d", adapter->dev_id); + return; + } + + if (hci_read_class_of_dev(dd, cls, 1000) < 0) { + error("Can't read class of device on hci%d: %s (%d)", + adapter->dev_id, strerror(errno), errno); + hci_close_dev(dd); + return; + } + + write_local_class(local, cls); + set_device_class(adapter->dev_id, cls); + memcpy(adapter->class, cls, 3); + + hci_close_dev(dd); +} + +void hcid_dbus_write_simple_pairing_mode_complete(bdaddr_t *local) +{ + char addr[18]; + int dev_id, dd; + uint8_t mode; + + ba2str(local, addr); + + dev_id = hci_devid(addr); + if (dev_id < 0) { + error("No matching device id for %s", addr); + return; + } + + dd = hci_open_dev(dev_id); + if (dd < 0) { + error("HCI device open failed: hci%d", dev_id); + return; + } + + if (hci_read_simple_pairing_mode(dd, &mode, 1000) < 0) { + error("Can't read class of device on hci%d: %s(%d)", + dev_id, strerror(errno), errno); + hci_close_dev(dd); + return; + } + + set_simple_pairing_mode(dev_id, mode); + + hci_close_dev(dd); +} + +int hcid_dbus_get_io_cap(bdaddr_t *local, bdaddr_t *remote, + uint8_t *cap, uint8_t *auth) +{ + struct adapter *adapter; + struct device *device; + struct agent *agent; + char addr[18]; + + adapter = manager_find_adapter(local); + if (!adapter) { + error("No matching adapter found"); + return -1; + } + + if (get_auth_requirements(local, remote, auth) < 0) + return -1; + + ba2str(remote, addr); + + device = adapter_find_device(adapter, addr); + if (device && device->agent) { + agent = device->agent; + *auth = 0x03; + } else + agent = adapter->agent; + + if (!agent) { + if (!(*auth & 0x01)) { + /* No input, no output */ + *cap = 0x03; + return 0; + } + error("No agent available for IO capability"); + return -1; + } + + *cap = agent_get_io_capability(agent); + + return 0; +} + +int hcid_dbus_set_io_cap(bdaddr_t *local, bdaddr_t *remote, + uint8_t cap, uint8_t auth) +{ + struct adapter *adapter; + struct device *device; + char addr[18]; + + adapter = manager_find_adapter(local); + if (!adapter) { + error("No matching adapter found"); + return -1; + } + + ba2str(remote, addr); + + device = adapter_get_device(connection, adapter, addr); + if (device) { + device->cap = cap; + device->auth = auth; + } + + return 0; +} + +static int inquiry_cancel(int dd, int to) +{ + struct hci_request rq; + uint8_t status; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_LINK_CTL; + rq.ocf = OCF_INQUIRY_CANCEL; + rq.rparam = &status; + rq.rlen = sizeof(status); + rq.event = EVT_CMD_COMPLETE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (status) { + errno = bt_error(status); + return -1; + } + + return 0; +} + +static int remote_name_cancel(int dd, bdaddr_t *dba, int to) +{ + remote_name_req_cancel_cp cp; + struct hci_request rq; + uint8_t status; + + memset(&rq, 0, sizeof(rq)); + memset(&cp, 0, sizeof(cp)); + + bacpy(&cp.bdaddr, dba); + + rq.ogf = OGF_LINK_CTL; + rq.ocf = OCF_REMOTE_NAME_REQ_CANCEL; + rq.cparam = &cp; + rq.clen = REMOTE_NAME_REQ_CANCEL_CP_SIZE; + rq.rparam = &status; + rq.rlen = sizeof(status); + rq.event = EVT_CMD_COMPLETE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (status) { + errno = bt_error(status); + return -1; + } + + return 0; +} + +int cancel_discovery(struct adapter *adapter) +{ + struct remote_dev_info *dev, match; + GSList *l; + int dd, err = 0; + + if (!adapter->discov_active) + goto cleanup; + + dd = hci_open_dev(adapter->dev_id); + if (dd < 0) { + err = -ENODEV; + goto cleanup; + } + + /* + * If there is a pending read remote name request means + * that the inquiry complete event was already received + */ + 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) { + dev = l->data; + if (remote_name_cancel(dd, &dev->bdaddr, 1000) < 0) { + error("Read remote name cancel failed: %s, (%d)", + strerror(errno), errno); + err = -errno; + } + } else { + if (inquiry_cancel(dd, 1000) < 0) { + error("Inquiry cancel failed:%s (%d)", + strerror(errno), errno); + err = -errno; + } + } + + hci_close_dev(dd); + +cleanup: + /* + * Reset discov_requestor and discover_state in the remote name + * request event handler or in the inquiry complete handler. + */ + g_slist_foreach(adapter->found_devices, (GFunc) g_free, NULL); + g_slist_free(adapter->found_devices); + adapter->found_devices = NULL; + + /* Disable name resolution for non D-Bus clients */ + if (!adapter->pdiscov_requestor) + adapter->discov_type &= ~RESOLVE_NAME; + + return err; +} + +static int periodic_inquiry_exit(int dd, int to) +{ + struct hci_request rq; + uint8_t status; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_LINK_CTL; + rq.ocf = OCF_EXIT_PERIODIC_INQUIRY; + rq.rparam = &status; + rq.rlen = sizeof(status); + rq.event = EVT_CMD_COMPLETE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (status) { + errno = status; + return -1; + } + + return 0; +} + +int cancel_periodic_discovery(struct adapter *adapter) +{ + struct remote_dev_info *dev, match; + GSList *l; + int dd, err = 0; + + if (!adapter->pdiscov_active) + goto cleanup; + + dd = hci_open_dev(adapter->dev_id); + if (dd < 0) { + err = -ENODEV; + goto cleanup; + } + /* 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) { + dev = l->data; + if (remote_name_cancel(dd, &dev->bdaddr, 1000) < 0) { + error("Read remote name cancel failed: %s, (%d)", + strerror(errno), errno); + err = -errno; + } + } + + /* ovewrite err if necessary: stop periodic inquiry has higher + * priority */ + if (periodic_inquiry_exit(dd, 1000) < 0) { + error("Periodic Inquiry exit failed:%s (%d)", + strerror(errno), errno); + err = -errno; + } + + hci_close_dev(dd); + +cleanup: + /* + * Reset pdiscov_requestor and pdiscov_active is done when the + * cmd complete event for exit periodic inquiry mode cmd arrives. + */ + g_slist_foreach(adapter->found_devices, (GFunc) g_free, NULL); + g_slist_free(adapter->found_devices); + adapter->found_devices = NULL; + + return err; +} + +/* Most of the functions in this module require easy access to a connection so + * we keep it global here and provide these access functions the other (few) + * modules that require access to it */ + +void set_dbus_connection(DBusConnection *conn) +{ + connection = conn; +} + +DBusConnection *get_dbus_connection(void) +{ + return connection; +} diff --git a/hcid/dbus-hci.h b/hcid/dbus-hci.h new file mode 100644 index 00000000..cb618816 --- /dev/null +++ b/hcid/dbus-hci.h @@ -0,0 +1,80 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 + * + */ + +void hcid_dbus_set_experimental(); +int hcid_dbus_use_experimental(); +int hcid_dbus_register_device(uint16_t id); +int hcid_dbus_unregister_device(uint16_t id); +int hcid_dbus_start_device(uint16_t id); +int hcid_dbus_stop_device(uint16_t id); +int hcid_dbus_request_pin(int dev, bdaddr_t *sba, struct hci_conn_info *ci); + +void hcid_dbus_inquiry_start(bdaddr_t *local); +void hcid_dbus_inquiry_complete(bdaddr_t *local); +void hcid_dbus_periodic_inquiry_start(bdaddr_t *local, uint8_t status); +void hcid_dbus_periodic_inquiry_exit(bdaddr_t *local, uint8_t status); +void hcid_dbus_inquiry_result(bdaddr_t *local, bdaddr_t *peer, uint32_t class, int8_t rssi, uint8_t *data); +void hcid_dbus_remote_class(bdaddr_t *local, bdaddr_t *peer, uint32_t class); +void hcid_dbus_remote_name(bdaddr_t *local, bdaddr_t *peer, uint8_t status, char *name); +void hcid_dbus_conn_complete(bdaddr_t *local, uint8_t status, uint16_t handle, bdaddr_t *peer); +void hcid_dbus_disconn_complete(bdaddr_t *local, uint8_t status, uint16_t handle, uint8_t reason); +void hcid_dbus_bonding_process_complete(bdaddr_t *local, bdaddr_t *peer, uint8_t status); +void hcid_dbus_setname_complete(bdaddr_t *local); +void hcid_dbus_setscan_enable_complete(bdaddr_t *local); +void hcid_dbus_write_class_complete(bdaddr_t *local); +void hcid_dbus_write_simple_pairing_mode_complete(bdaddr_t *local); +int hcid_dbus_get_io_cap(bdaddr_t *local, bdaddr_t *remote, + uint8_t *cap, uint8_t *auth); +int hcid_dbus_set_io_cap(bdaddr_t *local, bdaddr_t *remote, + uint8_t cap, uint8_t auth); +int hcid_dbus_user_confirm(bdaddr_t *sba, bdaddr_t *dba, uint32_t passkey); +int hcid_dbus_user_passkey(bdaddr_t *sba, bdaddr_t *dba); +int hcid_dbus_user_notify(bdaddr_t *sba, bdaddr_t *dba, uint32_t passkey); + +int unregister_adapter_path(const char *path); + +DBusMessage *new_authentication_return(DBusMessage *msg, uint8_t status); + +int get_default_dev_id(void); + +int cancel_discovery(struct adapter *adapter); +int cancel_periodic_discovery(struct adapter *adapter); + +int active_conn_find_by_bdaddr(const void *data, const void *user_data); +void bonding_request_free(struct bonding_request_info *dev); +int found_device_cmp(const struct remote_dev_info *d1, + const struct remote_dev_info *d2); +int found_device_add(GSList **list, bdaddr_t *bdaddr, int8_t rssi, + name_status_t name_status); +int found_device_req_name(struct adapter *dbus_data); + +int set_limited_discoverable(int dd, const uint8_t *cls, gboolean limited); +int set_service_classes(int dd, const uint8_t *cls, uint8_t value); + +int discov_timeout_handler(void *data); + +void set_dbus_connection(DBusConnection *conn); + +DBusConnection *get_dbus_connection(void); +struct adapter *adapter_find(const bdaddr_t *sba); diff --git a/hcid/dbus-sdp.c b/hcid/dbus-sdp.c new file mode 100644 index 00000000..02f9e31c --- /dev/null +++ b/hcid/dbus-sdp.c @@ -0,0 +1,1125 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 <config.h> +#endif + +#include <stdio.h> +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/param.h> +#include <sys/socket.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/hci.h> +#include <bluetooth/hci_lib.h> +#include <bluetooth/l2cap.h> +#include <bluetooth/sdp.h> +#include <bluetooth/sdp_lib.h> + +#include <netinet/in.h> + +#include <glib.h> +#include <dbus/dbus.h> +#include <gdbus.h> + +#include "hcid.h" +#include "textfile.h" +#include "adapter.h" +#include "dbus-hci.h" +#include "dbus-common.h" +#include "dbus-error.h" +#include "error.h" +#include "dbus-sdp.h" +#include "sdp-xml.h" +#include "glib-helper.h" + +#define SESSION_TIMEOUT 2000 +#define DEFAULT_XML_BUF_SIZE 1024 + +struct transaction_context { + char *src; + char *dst; + DBusConnection *conn; + DBusMessage *rq; + sdp_session_t *session; + GIOChannel *io; + guint io_id; + uuid_t uuid; + GSList *identifiers; +}; + +typedef int connect_cb_t(struct transaction_context *t); + +struct pending_connect { + DBusConnection *conn; + DBusMessage *rq; + char *src; + char *dst; + sdp_session_t *session; + connect_cb_t *conn_cb; +}; + +struct cached_session { + sdp_session_t *session; + guint timeout_id; + guint io_id; +}; + +static GSList *cached_sessions = NULL; + +static inline DBusMessage *invalid_args(DBusMessage *msg) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE ".InvalidArguments", + "Invalid arguments in method call"); +} + +static inline DBusMessage *in_progress(DBusMessage *msg, const char *str) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE ".InProgress", str); +} + +static inline DBusMessage *adapter_not_ready(DBusMessage *msg) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE ".NotReady", + "Adapter is not ready"); +} + +static inline DBusMessage *failed_strerror(DBusMessage *msg, int err) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", + strerror(err)); +} + +static gboolean session_timeout(gpointer user_data) +{ + struct cached_session *s = user_data; + + debug("sdp session timed out. closing"); + + cached_sessions = g_slist_remove(cached_sessions, s); + + g_source_remove(s->io_id); + sdp_close(s->session); + g_free(s); + + return FALSE; +} + +gboolean idle_callback(GIOChannel *io, GIOCondition cond, gpointer user_data) +{ + struct cached_session *s = user_data; + + if (cond & G_IO_NVAL) + return FALSE; + + if (cond & (G_IO_ERR | G_IO_HUP)) + debug("idle_callback: session got disconnected"); + + if (cond & G_IO_IN) + debug("got unexpected input on idle SDP socket"); + + cached_sessions = g_slist_remove(cached_sessions, s); + + g_source_remove(s->timeout_id); + sdp_close(s->session); + g_free(s); + + return FALSE; +} + +static void cache_sdp_session(sdp_session_t *sess, GIOChannel *io) +{ + struct cached_session *s; + + s = g_new0(struct cached_session, 1); + + s->session = sess; + s->timeout_id = g_timeout_add(SESSION_TIMEOUT, session_timeout, s); + s->io_id = g_io_add_watch(io, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + idle_callback, s); + + cached_sessions = g_slist_append(cached_sessions, s); + + debug("sdp session added to cache"); +} + +static int get_bdaddrs(int sock, bdaddr_t *sba, bdaddr_t *dba) +{ + struct sockaddr_l2 a; + socklen_t len; + + len = sizeof(a); + if (getsockname(sock, (struct sockaddr *) &a, &len) < 0) { + error("getsockname: %s (%d)", strerror(errno), errno); + return -1; + } + + bacpy(sba, &a.l2_bdaddr); + + len = sizeof(a); + if (getpeername(sock, (struct sockaddr *) &a, &len) < 0) { + error("getpeername: %s (%d)", strerror(errno), errno); + return -1; + } + + bacpy(dba, &a.l2_bdaddr); + + return 0; +} + +static struct cached_session *get_cached_session(bdaddr_t *src, bdaddr_t *dst) +{ + GSList *l; + + for (l = cached_sessions; l != NULL; l = l->next) { + struct cached_session *s = l->data; + int sock = sdp_get_socket(s->session); + bdaddr_t sba, dba; + + if (get_bdaddrs(sock, &sba, &dba) < 0) + continue; + + if (bacmp(&sba, src) || bacmp(&dba, dst)) + continue; + + debug("found matching session, removing from list"); + + cached_sessions = g_slist_remove(cached_sessions, s); + + return s; + } + + return NULL; +} + +static sdp_session_t *get_sdp_session(bdaddr_t *src, bdaddr_t *dst) +{ + struct cached_session *s; + sdp_session_t *session; + + s = get_cached_session(src, dst); + if (!s) { + debug("no matching session found. creating a new one"); + return sdp_connect(src, dst, SDP_NON_BLOCKING); + } + + session = s->session; + + g_source_remove(s->timeout_id); + g_source_remove(s->io_id); + g_free(s); + + return session; +} + +void append_and_grow_string(void *data, const char *str) +{ + sdp_buf_t *buff = data; + int len; + + len = strlen(str); + + if (!buff->data) { + buff->data = malloc(DEFAULT_XML_BUF_SIZE); + if (!buff->data) + return; + buff->buf_size = DEFAULT_XML_BUF_SIZE; + } + + /* Grow string */ + while (buff->buf_size < (buff->data_size + len + 1)) { + void *tmp; + uint32_t new_size; + + /* Grow buffer by a factor of 2 */ + new_size = (buff->buf_size << 1); + + tmp = realloc(buff->data, new_size); + if (!tmp) + return; + + buff->data = tmp; + buff->buf_size = new_size; + } + + /* Include the NULL character */ + memcpy(buff->data + buff->data_size, str, len + 1); + buff->data_size += len; +} + +/* list of remote and local service records */ +static GSList *pending_connects = NULL; + +static struct pending_connect *pending_connect_new(DBusConnection *conn, + DBusMessage *msg, const char *src, + const char *dst, connect_cb_t *cb) +{ + struct pending_connect *c; + + if (!dst) + return NULL; + + c = g_new0(struct pending_connect, 1); + c->src = g_strdup(src); + c->dst = g_strdup(dst); + c->conn = dbus_connection_ref(conn); + c->rq = dbus_message_ref(msg); + c->conn_cb = cb; + + return c; +} + +static void pending_connect_free(struct pending_connect *c) +{ + if (!c) + return; + + g_free(c->src); + g_free(c->dst); + + if (c->rq) + dbus_message_unref(c->rq); + + if (c->conn) + dbus_connection_unref(c->conn); + + g_free(c); +} + +static struct pending_connect *find_pending_connect(const char *dst) +{ + GSList *l; + + for (l = pending_connects; l != NULL; l = l->next) { + struct pending_connect *pending = l->data; + if (!strcmp(dst, pending->dst)) + return pending; + } + + return NULL; +} + +static int sdp_store_record(const char *src, const char *dst, uint32_t handle, uint8_t *buf, size_t size) +{ + char filename[PATH_MAX + 1], key[28], *value; + int i, err; + + create_name(filename, PATH_MAX, STORAGEDIR, src, "sdp"); + + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + snprintf(key, sizeof(key), "%17s#%08X", dst, handle); + + value = g_malloc0(size * 2 + 1); + + for (i = 0; i < size; i++) + sprintf(value + (i * 2), "%02X", buf[i]); + + err = textfile_put(filename, key, value); + + g_free(value); + + return err; +} + +static void transaction_context_free(void *udata, gboolean cache) +{ + struct transaction_context *ctxt = udata; + + if (!ctxt) + return; + + g_free(ctxt->src); + g_free(ctxt->dst); + + if (ctxt->conn) + dbus_connection_unref(ctxt->conn); + + if (ctxt->rq) + dbus_message_unref(ctxt->rq); + + if (ctxt->session && !ctxt->io) + sdp_close(ctxt->session); + + if (ctxt->session && ctxt->io) { + g_source_remove(ctxt->io_id); + + if (cache) + cache_sdp_session(ctxt->session, ctxt->io); + else + sdp_close(ctxt->session); + + g_io_channel_unref(ctxt->io); + } + + if (ctxt->identifiers) { + g_slist_foreach(ctxt->identifiers, (GFunc) g_free, NULL); + g_slist_free(ctxt->identifiers); + } + + g_free(ctxt); +} + +static gboolean search_process_cb(GIOChannel *chan, + GIOCondition cond, void *udata) +{ + struct transaction_context *ctxt = udata; + int err = 0; + + if (cond & G_IO_NVAL) { + g_io_channel_unref(chan); + return FALSE; + } + + if (cond & (G_IO_ERR | G_IO_HUP)) { + err = EIO; + goto failed; + } + + if (sdp_process(ctxt->session) < 0) + goto failed; + + return TRUE; + +failed: + if (err) { + error_failed_errno(ctxt->conn, ctxt->rq, err); + transaction_context_free(ctxt, FALSE); + } + + return TRUE; +} + +static void remote_svc_rec_completed_cb(uint8_t type, uint16_t err, + uint8_t *rsp, size_t size, void *udata) +{ + struct transaction_context *ctxt = udata; + sdp_record_t *rec; + DBusMessage *reply; + DBusMessageIter iter, array_iter; + int scanned; + + if (!ctxt) + return; + + if (err == 0xffff) { + /* Check for protocol error or I/O error */ + int sdp_err = sdp_get_error(ctxt->session); + if (sdp_err < 0) { + error("search failed: Invalid session!"); + error_failed_errno(ctxt->conn, ctxt->rq, EINVAL); + goto failed; + } + + error("search failed: %s (%d)", strerror(sdp_err), sdp_err); + error_failed_errno(ctxt->conn, ctxt->rq, sdp_err); + goto failed; + } + + if (type == SDP_ERROR_RSP) { + error_sdp_failed(ctxt->conn, ctxt->rq, err); + goto failed; + } + + /* check response PDU ID */ + if (type != SDP_SVC_ATTR_RSP) { + error("SDP error: %s (%d)", strerror(EPROTO), EPROTO); + error_failed_errno(ctxt->conn, ctxt->rq, EPROTO); + goto failed; + } + + reply = dbus_message_new_method_return(ctxt->rq); + dbus_message_iter_init_append(reply, &iter); + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_TYPE_BYTE_AS_STRING, &array_iter); + + rec = sdp_extract_pdu_safe(rsp, size, &scanned); + if (rec == NULL || size != scanned) { + error("Invalid service record!"); + goto done; + } + + sdp_store_record(ctxt->src, ctxt->dst, rec->handle, rsp, size); + + sdp_record_free(rec); + + dbus_message_iter_append_fixed_array(&array_iter, + DBUS_TYPE_BYTE, &rsp, size); + +done: + dbus_message_iter_close_container(&iter, &array_iter); + dbus_connection_send(ctxt->conn, reply, NULL); + dbus_message_unref(reply); + +failed: + transaction_context_free(ctxt, TRUE); +} + +static void remote_svc_rec_completed_xml_cb(uint8_t type, uint16_t err, + uint8_t *rsp, size_t size, + void *udata) +{ + struct transaction_context *ctxt = udata; + sdp_record_t *rec; + DBusMessage *reply; + int scanned; + sdp_buf_t result; + + if (!ctxt) + return; + + if (err == 0xffff) { + /* Check for protocol error or I/O error */ + int sdp_err = sdp_get_error(ctxt->session); + if (sdp_err < 0) { + error("search failed: Invalid session!"); + error_failed_errno(ctxt->conn, ctxt->rq, EINVAL); + goto failed; + } + + error("search failed: %s (%d)", strerror(sdp_err), sdp_err); + error_failed_errno(ctxt->conn, ctxt->rq, sdp_err); + goto failed; + } + + if (type == SDP_ERROR_RSP) { + error_sdp_failed(ctxt->conn, ctxt->rq, err); + goto failed; + } + + /* check response PDU ID */ + if (type != SDP_SVC_ATTR_RSP) { + error("SDP error: %s (%d)", strerror(EPROTO), EPROTO); + error_failed_errno(ctxt->conn, ctxt->rq, EPROTO); + goto failed; + } + + reply = dbus_message_new_method_return(ctxt->rq); + + rec = sdp_extract_pdu_safe(rsp, size, &scanned); + if (rec == NULL || size != scanned) { + error("Invalid service record!"); + goto done; + } + + sdp_store_record(ctxt->src, ctxt->dst, rec->handle, rsp, size); + + memset(&result, 0, sizeof(sdp_buf_t)); + + convert_sdp_record_to_xml(rec, &result, append_and_grow_string); + + sdp_record_free(rec); + + if (result.data) { + dbus_message_append_args(reply, + DBUS_TYPE_STRING, &result.data, + DBUS_TYPE_INVALID); + + free(result.data); + } +done: + dbus_connection_send(ctxt->conn, reply, NULL); + dbus_message_unref(reply); + +failed: + transaction_context_free(ctxt, TRUE); +} + +static void remote_svc_handles_completed_cb(uint8_t type, uint16_t err, + uint8_t *rsp, size_t size, void *udata) +{ + struct transaction_context *ctxt = udata; + DBusMessage *reply; + DBusMessageIter iter, array_iter; + uint8_t *pdata; + int csrc, tsrc; + + if (!ctxt) + return; + + if (err == 0xffff) { + /* Check for protocol error or I/O error */ + int sdp_err = sdp_get_error(ctxt->session); + if (sdp_err < 0) { + error("search failed: Invalid session!"); + error_failed_errno(ctxt->conn, ctxt->rq, EINVAL); + goto failed; + } + + error("search failed: %s (%d)", strerror(sdp_err), sdp_err); + error_failed_errno(ctxt->conn, ctxt->rq, sdp_err); + goto failed; + } + + if (type == SDP_ERROR_RSP) { + error_sdp_failed(ctxt->conn, ctxt->rq, err); + goto failed; + } + + /* check response PDU ID */ + if (type != SDP_SVC_SEARCH_RSP) { + error("SDP error: %s (%d)", strerror(EPROTO), EPROTO); + error_failed_errno(ctxt->conn, ctxt->rq, EPROTO); + goto failed; + } + + reply = dbus_message_new_method_return(ctxt->rq); + dbus_message_iter_init_append(reply, &iter); + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_TYPE_UINT32_AS_STRING, &array_iter); + + pdata = rsp; + + tsrc = ntohs(bt_get_unaligned((uint16_t *) pdata)); + if (tsrc <= 0) + goto done; + + pdata += sizeof(uint16_t); + + csrc = ntohs(bt_get_unaligned((uint16_t *) pdata)); + if (csrc <= 0) + goto done; + + pdata += sizeof(uint16_t); + + do { + uint32_t handle = ntohl(bt_get_unaligned((uint32_t*)pdata)); + pdata += sizeof(uint32_t); + + dbus_message_iter_append_basic(&array_iter, + DBUS_TYPE_UINT32, &handle); + } while (--tsrc); + + +done: + dbus_message_iter_close_container(&iter, &array_iter); + dbus_connection_send(ctxt->conn, reply, NULL); + dbus_message_unref(reply); + +failed: + transaction_context_free(ctxt, TRUE); +} + +static const char *extract_service_class(sdp_data_t *d) +{ + sdp_data_t *seq; + uuid_t *uuid; + static char uuid_str[37]; + + /* Expected sequence of UUID16 */ + if (d->attrId != SDP_ATTR_SVCLASS_ID_LIST) + return NULL; + + if (d->dtd != SDP_SEQ8 && d->dtd != SDP_SEQ16 && d->dtd != SDP_SEQ32) + return NULL; + + if (!d->val.dataseq) + return NULL; + + seq = d->val.dataseq; + if (!SDP_IS_UUID(seq->dtd)) + return NULL; + + uuid = &seq->val.uuid; + if (uuid->type != SDP_UUID16) + return NULL; + + sprintf(uuid_str, "0000%04x-0000-1000-8000-00805f9b34fb", + uuid->value.uuid16); + + return uuid_str; +} + +static int service_search_attr(struct transaction_context *ctxt, uint16_t uuid) +{ + sdp_list_t *attrids, *search; + uint32_t range = 0x0000ffff; + int ret = 0; + + sdp_uuid16_create(&ctxt->uuid, uuid); + + search = sdp_list_append(0, &ctxt->uuid); + attrids = sdp_list_append(NULL, &range); + + /* + * Create/send the search request and set the + * callback to indicate the request completion + */ + if (sdp_service_search_attr_async(ctxt->session, search, + SDP_ATTR_REQ_RANGE, attrids) < 0) + ret = -sdp_get_error(ctxt->session); + + sdp_list_free(search, NULL); + sdp_list_free(attrids, NULL); + + return ret; +} + +static void remote_svc_identifiers_completed_cb(uint8_t type, uint16_t err, + uint8_t *rsp, size_t size, void *udata) +{ + struct transaction_context *ctxt = udata; + const char *puuid; + const char *devid_uuid = "00001200-0000-1000-8000-00805f9b34fb"; + char **identifiers; + DBusMessage *reply; + GSList *l = NULL; + int scanned, extracted = 0, len = 0, recsize = 0, bytesleft = size; + uint8_t dtd = 0; + + if (!ctxt) + return; + + if (err == 0xffff) { + /* Check for protocol error or I/O error */ + int sdp_err = sdp_get_error(ctxt->session); + if (sdp_err < 0) { + error("search failed: Invalid session!"); + error_failed_errno(ctxt->conn, ctxt->rq, EINVAL); + goto failed; + } + + error("search failed: %s (%d)", strerror(sdp_err), sdp_err); + error_failed_errno(ctxt->conn, ctxt->rq, sdp_err); + goto failed; + } + + if (type == SDP_ERROR_RSP) { + error_sdp_failed(ctxt->conn, ctxt->rq, err); + goto failed; + } + + /* Check response PDU ID */ + if (type != SDP_SVC_SEARCH_ATTR_RSP) { + error("SDP error: %s (%d)", strerror(EPROTO), EPROTO); + error_failed_errno(ctxt->conn, ctxt->rq, EPROTO); + goto failed; + } + + scanned = sdp_extract_seqtype_safe(rsp, bytesleft, &dtd, &len); + rsp += scanned; + bytesleft -= scanned; + for (; extracted < len; rsp += recsize, extracted += recsize, bytesleft -= recsize) { + sdp_record_t *rec; + sdp_data_t *d; + + recsize = 0; + rec = sdp_extract_pdu_safe(rsp, bytesleft, &recsize); + if (!rec) + break; + + sdp_store_record(ctxt->src, ctxt->dst, rec->handle, rsp, recsize); + + d = sdp_data_get(rec, SDP_ATTR_SVCLASS_ID_LIST); + if (!d) { + sdp_record_free(rec); + continue; + } + + puuid = extract_service_class(d); + sdp_record_free(rec); + if (!puuid) + continue; + + /* Ignore repeated identifiers */ + l = g_slist_find_custom(ctxt->identifiers, + puuid, (GCompareFunc) strcmp); + if (l) + continue; + + ctxt->identifiers = g_slist_append(ctxt->identifiers, + g_strdup(puuid)); + } + + /* If public browse response is empty: search for L2CAP */ + if (!ctxt->identifiers && ctxt->uuid.value.uuid16 == PUBLIC_BROWSE_GROUP) + if (service_search_attr(ctxt, L2CAP_UUID) == 0) + return; /* Wait the response */ + + /* Request DeviceID if it was not returned previously */ + l = g_slist_find_custom(ctxt->identifiers, + devid_uuid, (GCompareFunc) strcmp); + if (!l && ctxt->uuid.value.uuid16 != PNP_INFO_SVCLASS_ID) + if (service_search_attr(ctxt, PNP_INFO_SVCLASS_ID) == 0) + return; /* Wait the response */ + + reply = dbus_message_new_method_return(ctxt->rq); + + identifiers = g_new(char *, g_slist_length(ctxt->identifiers)); + + for (l = ctxt->identifiers, len = 0; l; l = l->next, len++) + identifiers[len] = l->data; + + dbus_message_append_args(reply, + DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, + &identifiers, len, + DBUS_TYPE_INVALID); + + dbus_connection_send(ctxt->conn, reply, NULL); + dbus_message_unref(reply); + + if (len) + g_dbus_emit_signal(ctxt->conn, + dbus_message_get_path(ctxt->rq), + ADAPTER_INTERFACE, + "RemoteIdentifiersUpdated", + DBUS_TYPE_STRING, &ctxt->dst, + DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, + &identifiers, len, + DBUS_TYPE_INVALID); + + if (identifiers) + g_free(identifiers); + +failed: + transaction_context_free(ctxt, TRUE); +} + +static gboolean sdp_client_connect_cb(GIOChannel *chan, + GIOCondition cond, void *udata) +{ + struct pending_connect *c = udata; + struct transaction_context *ctxt = NULL; + int sdp_err, err = 0, sk; + socklen_t len; + + sk = g_io_channel_unix_get_fd(chan); + + len = sizeof(err); + if (getsockopt(sk, SOL_SOCKET, SO_ERROR, &err, &len) < 0) { + error("getsockopt(): %s (%d)", strerror(errno), errno); + err = errno; + goto failed; + } + if (err != 0) { + error("connect(): %s (%d)", strerror(err), err); + goto failed; + } + + ctxt = g_new0(struct transaction_context, 1); + ctxt->src = g_strdup(c->src); + ctxt->dst = g_strdup(c->dst); + ctxt->conn = dbus_connection_ref(c->conn); + ctxt->rq = dbus_message_ref(c->rq); + ctxt->session = c->session; + + /* set the complete transaction callback and send the search request */ + sdp_err = c->conn_cb(ctxt); + if (sdp_err < 0) { + err = -sdp_err; + error("search failed: %s (%d)", strerror(err), err); + goto failed; + } + + /* set the callback responsible for update the transaction data */ + ctxt->io_id = g_io_add_watch(chan, + G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + search_process_cb, ctxt); + ctxt->io = g_io_channel_ref(chan); + + goto done; + +failed: + error_connection_attempt_failed(c->conn, c->rq, err); + + if (ctxt) + transaction_context_free(ctxt, FALSE); + else + sdp_close(c->session); + +done: + pending_connects = g_slist_remove(pending_connects, c); + pending_connect_free(c); + + return FALSE; +} + +static struct pending_connect *connect_request(DBusConnection *conn, + DBusMessage *msg, + const char *src, + const char *dst, + connect_cb_t *cb, int *err) +{ + struct pending_connect *c; + bdaddr_t srcba, dstba; + GIOChannel *chan; + + c = pending_connect_new(conn, msg, src, dst, cb); + if (!c) { + if (err) + *err = ENOMEM; + return NULL; + } + + str2ba(src, &srcba); + str2ba(dst, &dstba); + c->session = get_sdp_session(&srcba, &dstba); + if (!c->session) { + if (err) + *err = errno; + error("sdp_connect() failed: %s (%d)", strerror(errno), errno); + pending_connect_free(c); + return NULL; + } + + chan = g_io_channel_unix_new(sdp_get_socket(c->session)); + g_io_add_watch(chan, G_IO_OUT, sdp_client_connect_cb, c); + g_io_channel_unref(chan); + pending_connects = g_slist_append(pending_connects, c); + + return c; +} + +static int remote_svc_rec_conn_cb(struct transaction_context *ctxt) +{ + sdp_list_t *attrids; + uint32_t range = 0x0000ffff; + const char *dst; + uint32_t handle; + + if (sdp_set_notify(ctxt->session, remote_svc_rec_completed_cb, ctxt) < 0) + return -EINVAL; + + dbus_message_get_args(ctxt->rq, NULL, + DBUS_TYPE_STRING, &dst, + DBUS_TYPE_UINT32, &handle, + DBUS_TYPE_INVALID); + + attrids = sdp_list_append(NULL, &range); + /* + * Create/send the search request and set the + * callback to indicate the request completion + */ + if (sdp_service_attr_async(ctxt->session, handle, + SDP_ATTR_REQ_RANGE, attrids) < 0) { + sdp_list_free(attrids, NULL); + return -sdp_get_error(ctxt->session); + } + + sdp_list_free(attrids, NULL); + + return 0; +} + +static int remote_svc_rec_conn_xml_cb(struct transaction_context *ctxt) +{ + sdp_list_t *attrids; + uint32_t range = 0x0000ffff; + const char *dst; + uint32_t handle; + + if (sdp_set_notify(ctxt->session, remote_svc_rec_completed_xml_cb, ctxt) < 0) + return -EINVAL; + + dbus_message_get_args(ctxt->rq, NULL, + DBUS_TYPE_STRING, &dst, + DBUS_TYPE_UINT32, &handle, + DBUS_TYPE_INVALID); + + attrids = sdp_list_append(NULL, &range); + /* + * Create/send the search request and set the + * callback to indicate the request completion + */ + if (sdp_service_attr_async(ctxt->session, handle, + SDP_ATTR_REQ_RANGE, attrids) < 0) { + sdp_list_free(attrids, NULL); + return -sdp_get_error(ctxt->session); + } + + sdp_list_free(attrids, NULL); + + return 0; +} + +DBusMessage *get_remote_svc_rec(DBusConnection *conn, DBusMessage *msg, + void *data, sdp_format_t format) +{ + struct adapter *adapter = data; + const char *dst; + uint32_t handle; + int err; + connect_cb_t *cb; + + if (!adapter->up) + return adapter_not_ready(msg); + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &dst, + DBUS_TYPE_UINT32, &handle, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + if (find_pending_connect(dst)) + return in_progress(msg, "Service search in progress"); + + cb = remote_svc_rec_conn_cb; + if (format == SDP_FORMAT_XML) + cb = remote_svc_rec_conn_xml_cb; + + if (!connect_request(conn, msg, adapter->address, + dst, cb, &err)) { + error("Search request failed: %s (%d)", strerror(err), err); + return failed_strerror(msg, err); + } + + return NULL; +} + +static int remote_svc_handles_conn_cb(struct transaction_context *ctxt) +{ + sdp_list_t *search = NULL; + const char *dst, *svc; + + if (sdp_set_notify(ctxt->session, remote_svc_handles_completed_cb, ctxt) < 0) + return -EINVAL; + + dbus_message_get_args(ctxt->rq, NULL, + DBUS_TYPE_STRING, &dst, + DBUS_TYPE_STRING, &svc, + DBUS_TYPE_INVALID); + + if (strlen(svc) > 0) + bt_string2uuid(&ctxt->uuid, svc); + else + sdp_uuid16_create(&ctxt->uuid, PUBLIC_BROWSE_GROUP); + + search = sdp_list_append(0, &ctxt->uuid); + + /* Create/send the search request and set the callback to indicate the request completion */ + if (sdp_service_search_async(ctxt->session, search, 64) < 0) { + error("send request failed: %s (%d)", strerror(errno), errno); + sdp_list_free(search, NULL); + return -sdp_get_error(ctxt->session); + } + + sdp_list_free(search, NULL); + + return 0; +} + +static int remote_svc_identifiers_conn_cb(struct transaction_context *ctxt) +{ + if (sdp_set_notify(ctxt->session, + remote_svc_identifiers_completed_cb, ctxt) < 0) + return -EINVAL; + + return service_search_attr(ctxt, PUBLIC_BROWSE_GROUP); +} + +DBusMessage *get_remote_svc_handles(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct adapter *adapter = data; + const char *dst, *svc; + int err; + uuid_t uuid; + + if (!adapter->up) + return adapter_not_ready(msg); + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &dst, + DBUS_TYPE_STRING, &svc, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + if (strlen(svc) > 0) { + /* Check if it is a service name string */ + if (bt_string2uuid(&uuid, svc) < 0) { + error("Invalid service class name"); + return invalid_args(msg); + } + } + + if (find_pending_connect(dst)) + return in_progress(msg, "Service search in progress"); + + if (!connect_request(conn, msg, adapter->address, + dst, remote_svc_handles_conn_cb, &err)) { + error("Search request failed: %s (%d)", strerror(err), err); + return failed_strerror(msg, err); + } + + return NULL; +} + +DBusMessage *get_remote_svc_identifiers(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct adapter *adapter = data; + const char *dst; + int err; + + if (!adapter->up) + return adapter_not_ready(msg); + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &dst, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + if (find_pending_connect(dst)) + return in_progress(msg, "Service search in progress"); + + if (!connect_request(conn, msg, adapter->address, + dst, remote_svc_identifiers_conn_cb, &err)) { + error("Search request failed: %s (%d)", strerror(err), err); + return failed_strerror(msg, err); + } + + return NULL; +} + +DBusMessage *finish_remote_svc_transact(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct cached_session *s; + const char *address; + struct adapter *adapter = data; + bdaddr_t sba, dba; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &address, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + str2ba(adapter->address, &sba); + str2ba(address, &dba); + + while ((s = get_cached_session(&sba, &dba))) { + sdp_close(s->session); + g_source_remove(s->timeout_id); + g_source_remove(s->io_id); + g_free(s); + } + + return dbus_message_new_method_return(msg); +} diff --git a/hcid/dbus-sdp.h b/hcid/dbus-sdp.h new file mode 100644 index 00000000..06484bbc --- /dev/null +++ b/hcid/dbus-sdp.h @@ -0,0 +1,42 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 + * + */ + +typedef enum { + SDP_FORMAT_XML, + SDP_FORMAT_BINARY +} sdp_format_t; + +DBusMessage *get_remote_svc_handles(DBusConnection *conn, + DBusMessage *msg, void *data); + +DBusMessage *get_remote_svc_identifiers(DBusConnection *conn, + DBusMessage *msg, void *data); + +DBusMessage *get_remote_svc_rec(DBusConnection *conn, DBusMessage *msg, + void *data, sdp_format_t format); + +DBusMessage *finish_remote_svc_transact(DBusConnection *conn, + DBusMessage *msg, void *data); + +void append_and_grow_string(void *data, const char *str); diff --git a/hcid/dbus-security.c b/hcid/dbus-security.c new file mode 100644 index 00000000..e84174bb --- /dev/null +++ b/hcid/dbus-security.c @@ -0,0 +1,1113 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * Copyright (C) 2005-2007 Johan Hedberg <johan.hedberg@nokia.com> + * + * + * 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 <config.h> +#endif + +#include <stdio.h> +#include <errno.h> +#include <stdlib.h> +#include <limits.h> +#include <sys/param.h> +#include <sys/socket.h> +#include <sys/ioctl.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/hci.h> +#include <bluetooth/hci_lib.h> +#include <bluetooth/sdp.h> + +#include <glib.h> +#include <dbus/dbus.h> +#include <gdbus.h> + +#include "adapter.h" +#include "manager.h" +#include "hcid.h" +#include "dbus-common.h" +#include "dbus-service.h" +#include "error.h" +#include "dbus-security.h" +#include "dbus-hci.h" + +#define REQUEST_TIMEOUT (60 * 1000) /* 60 seconds */ +#define AGENT_TIMEOUT (10 * 60 * 1000) /* 10 minutes */ + +struct passkey_agent { + struct adapter *adapter; + DBusConnection *conn; + char *addr; + char *name; + char *path; + GSList *pending_requests; + int exited; + guint timeout; + guint listener_id; +}; + +struct pending_agent_request { + struct passkey_agent *agent; + int dev; + bdaddr_t sba; + bdaddr_t bda; + char *path; + DBusPendingCall *call; + int old_if; + char *pin; +}; + +struct authorization_agent { + DBusConnection *conn; + char *name; + char *path; + GSList *pending_requests; + guint listener_id; +}; + +struct auth_agent_req { + struct authorization_agent *agent; + char *adapter_path; + char *address; + char *service_path; + char *uuid; + service_auth_cb cb; + void *user_data; + DBusPendingCall *call; +}; + +static struct passkey_agent *default_agent = NULL; +static struct authorization_agent *default_auth_agent = NULL; + +static void release_agent(struct passkey_agent *agent); +static void send_cancel_request(struct pending_agent_request *req); + +static void passkey_agent_free(struct passkey_agent *agent) +{ + GSList *l; + + if (!agent) + return; + + for (l = agent->pending_requests; l != NULL; l = l->next) { + struct pending_agent_request *req = l->data; + struct adapter *adapter = manager_find_adapter(&req->sba); + + hci_send_cmd(req->dev, OGF_LINK_CTL, + OCF_PIN_CODE_NEG_REPLY, 6, &req->bda); + + if (adapter) + adapter_auth_request_replied(adapter, &req->bda); + + send_cancel_request(req); + } + + if (agent->timeout) + g_source_remove(agent->timeout); + + if (!agent->exited) + release_agent(agent); + + g_free(agent->name); + g_free(agent->path); + g_free(agent->addr); + + if (agent->conn) + dbus_connection_unref(agent->conn); + + g_slist_free(agent->pending_requests); + + g_free(agent); +} + +static void agent_exited(void *user_data) +{ + struct passkey_agent *agent = user_data; + struct adapter *adapter = agent->adapter; + + debug("Passkey agent exited without calling Unregister"); + + agent->exited = 1; + + adapter->passkey_agents = g_slist_remove(adapter->passkey_agents, agent); + passkey_agent_free(agent); +} + +static gboolean agent_timeout(struct passkey_agent *agent) +{ + struct adapter *adapter = agent->adapter; + + debug("Passkey Agent at %s, %s timed out", agent->name, agent->path); + + if (adapter) + adapter->passkey_agents = g_slist_remove(adapter->passkey_agents, agent); + + agent->timeout = 0; + + passkey_agent_free(agent); + + return FALSE; +} + +static void default_agent_exited(void *data) +{ + debug("D-Bus client exited without unregistering the" + " default passkey agent"); + + default_agent->exited = 1; + + passkey_agent_free(default_agent); + default_agent = NULL; +} + +static struct passkey_agent *passkey_agent_new(struct adapter *adapter, DBusConnection *conn, + const char *name, const char *path, + const char *addr) +{ + struct passkey_agent *agent; + + agent = g_new0(struct passkey_agent, 1); + + agent->adapter = adapter; + + agent->name = g_strdup(name); + agent->path = g_strdup(path); + + if (addr) + agent->addr = g_strdup(addr); + + agent->conn = dbus_connection_ref(conn); + + return agent; +} + +static int agent_cmp(const struct passkey_agent *a, const struct passkey_agent *b) +{ + int ret; + + if (b->name) { + if (!a->name) + return -1; + ret = strcmp(a->name, b->name); + if (ret) + return ret; + } + + if (b->path) { + if (!a->path) + return -1; + ret = strcmp(a->path, b->path); + if (ret) + return ret; + } + + if (b->addr) { + if (!a->addr) + return -1; + ret = strcmp(a->addr, b->addr); + if (ret) + return ret; + } + + return 0; +} + +static inline DBusMessage *invalid_args(DBusMessage *msg) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE ".InvalidArguments", + "Invalid arguments in method call"); +} + +static DBusMessage *register_passkey_agent(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct passkey_agent *agent, ref; + struct adapter *adapter; + const char *path, *addr; + + if (!data) { + error("register_passkey_agent called without any adapter info!"); + return NULL; + } + + adapter = data; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &path, + DBUS_TYPE_STRING, &addr, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + if ((check_address(addr) < 0) || (path[0] != '/')) + return invalid_args(msg); + + memset(&ref, 0, sizeof(ref)); + + ref.name = (char *) dbus_message_get_sender(msg); + ref.addr = (char *) addr; + ref.path = (char *) path; + + if (g_slist_find_custom(adapter->passkey_agents, &ref, (GCompareFunc) agent_cmp)) + return g_dbus_create_error(msg, + ERROR_INTERFACE ".AlreadyExists", + "Passkey agent already exists"); + + agent = passkey_agent_new(adapter, conn, ref.name, path, addr); + if (!agent) + return NULL; + + /* Only add a name listener if there isn't one already for this name */ + ref.addr = NULL; + ref.path = NULL; + if (!g_slist_find_custom(adapter->passkey_agents, &ref, + (GCompareFunc) agent_cmp)) + agent->listener_id = g_dbus_add_disconnect_watch(conn, ref.name, + agent_exited, agent, NULL); + + agent->timeout = g_timeout_add(AGENT_TIMEOUT, + (GSourceFunc) agent_timeout, agent); + + adapter->passkey_agents = g_slist_append(adapter->passkey_agents, agent); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *unregister_passkey_agent(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct adapter *adapter; + GSList *match; + struct passkey_agent ref, *agent; + const char *path, *addr; + + if (!data) { + error("unregister_passkey_agent called without any adapter info!"); + return NULL; + } + + adapter = data; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &path, + DBUS_TYPE_STRING, &addr, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + memset(&ref, 0, sizeof(ref)); + + ref.name = (char *) dbus_message_get_sender(msg); + ref.path = (char *) path; + ref.addr = (char *) addr; + + match = g_slist_find_custom(adapter->passkey_agents, &ref, (GCompareFunc) agent_cmp); + if (!match) + return g_dbus_create_error(msg, + ERROR_INTERFACE ".DoesNotExist", + "Passkey agent does not exist"); + + agent = match->data; + + g_dbus_remove_watch(agent->conn, agent->listener_id); + + adapter->passkey_agents = g_slist_remove(adapter->passkey_agents, agent); + agent->exited = 1; + passkey_agent_free(agent); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *register_default_passkey_agent(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + const char *path; + + if (default_agent) + return g_dbus_create_error(msg, + ERROR_INTERFACE ".AlreadyExists", + "Passkey agent already exists"); + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &path, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + default_agent = passkey_agent_new(NULL, conn, dbus_message_get_sender(msg), + path, NULL); + if (!default_agent) + goto need_memory; + + default_agent->listener_id = g_dbus_add_disconnect_watch(conn, + default_agent->name, + default_agent_exited, + NULL, NULL); + + info("Default passkey agent (%s, %s) registered", + default_agent->name, default_agent->path); + + return dbus_message_new_method_return(msg); + +need_memory: + if (default_agent) { + default_agent->exited = 1; + passkey_agent_free(default_agent); + default_agent = NULL; + } + + return NULL; +} + +static DBusMessage *unregister_default_passkey_agent(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + const char *path, *name; + + if (!default_agent) + return g_dbus_create_error(msg, + ERROR_INTERFACE ".DoesNotExist", + "Passkey agent does not exist"); + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &path, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + name = dbus_message_get_sender(msg); + + if (strcmp(name, default_agent->name) || strcmp(path, default_agent->path)) + return g_dbus_create_error(msg, + ERROR_INTERFACE ".DoesNotExist", + "Passkey agent does not exist"); + + g_dbus_remove_watch(default_agent->conn, default_agent->listener_id); + + info("Default passkey agent (%s, %s) unregistered", + default_agent->name, default_agent->path); + + default_agent->exited = 1; + passkey_agent_free(default_agent); + default_agent = NULL; + + return dbus_message_new_method_return(msg); +} + +static struct auth_agent_req *auth_agent_req_new(struct authorization_agent *agent, + const char *adapter_path, + const char *address, + const char *service_path, + const char *uuid, + service_auth_cb cb, + void *user_data) +{ + struct auth_agent_req *req; + + req = g_new0(struct auth_agent_req, 1); + + req->agent = agent; + req->adapter_path = g_strdup(adapter_path); + req->address = g_strdup(address); + req->service_path = g_strdup(service_path); + req->uuid = g_strdup(uuid); + req->cb = cb; + req->user_data = user_data; + + return req; +} + +static void auth_agent_req_free(struct auth_agent_req *req) +{ + g_free(req->adapter_path); + g_free(req->address); + g_free(req->service_path); + g_free(req->uuid); + if (req->call) + dbus_pending_call_unref(req->call); + g_free(req); +} + +static void auth_agent_req_cancel(struct auth_agent_req *req) +{ + dbus_pending_call_cancel(req->call); +} + +static void auth_agent_cancel_requests(struct authorization_agent *agent) +{ + GSList *l; + + for (l = agent->pending_requests; l != NULL; l = l->next) { + struct auth_agent_req *req = l->data; + auth_agent_req_cancel(req); + auth_agent_req_free(req); + } +} + +static void auth_agent_call_cancel(struct auth_agent_req *req) +{ + struct authorization_agent *agent = req->agent; + DBusMessage *message; + + message = dbus_message_new_method_call(agent->name, agent->path, + "org.bluez.AuthorizationAgent", "Cancel"); + if (!message) { + error("Couldn't allocate D-Bus message"); + return; + } + + dbus_message_append_args(message, + DBUS_TYPE_STRING, &req->adapter_path, + DBUS_TYPE_STRING, &req->address, + DBUS_TYPE_STRING, &req->service_path, + DBUS_TYPE_STRING, &req->uuid, + DBUS_TYPE_INVALID); + + dbus_message_set_no_reply(message, TRUE); + + dbus_connection_send(agent->conn, message, NULL); + + dbus_message_unref(message); +} + +static void auth_agent_free(struct authorization_agent *agent) +{ + g_free(agent->name); + g_free(agent->path); + dbus_connection_unref(agent->conn); + g_slist_free(agent->pending_requests); + g_free(agent); +} + +static struct authorization_agent *auth_agent_new(DBusConnection *conn, + const char *name, + const char *path) +{ + struct authorization_agent *agent; + + agent = g_new0(struct authorization_agent, 1); + + agent->name = g_strdup(name); + agent->path = g_strdup(path); + + agent->conn = dbus_connection_ref(conn); + + return agent; +} + +static void default_auth_agent_exited(void *data) +{ + debug("D-Bus client exited without unregistering the " + "default authorization agent"); + + auth_agent_cancel_requests(default_auth_agent); + auth_agent_free(default_auth_agent); + default_auth_agent = NULL; +} + +static void auth_agent_release(struct authorization_agent *agent) +{ + DBusMessage *message; + + debug("Releasing authorization agent %s, %s", + agent->name, agent->path); + + message = dbus_message_new_method_call(agent->name, agent->path, + "org.bluez.AuthorizationAgent", "Release"); + if (!message) { + error("Couldn't allocate D-Bus message"); + return; + } + + dbus_message_set_no_reply(message, TRUE); + + dbus_connection_send(agent->conn, message, NULL); + + dbus_message_unref(message); + + if (agent == default_auth_agent) + g_dbus_remove_watch(agent->conn, agent->listener_id); +} + +static DBusMessage *register_default_auth_agent(DBusConnection *conn, + DBusMessage *msg, + void *data) +{ + const char *path; + + if (default_auth_agent) + return g_dbus_create_error(msg, + ERROR_INTERFACE ".AlreadyExists", + "Authorization agent already exists"); + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &path, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + default_auth_agent = auth_agent_new(conn, + dbus_message_get_sender(msg), path); + if (!default_auth_agent) + goto need_memory; + + default_auth_agent->listener_id = g_dbus_add_disconnect_watch(conn, + default_auth_agent->name, + default_auth_agent_exited, + NULL, NULL); + + info("Default authorization agent (%s, %s) registered", + default_auth_agent->name, default_auth_agent->path); + + return dbus_message_new_method_return(msg); + +need_memory: + if (default_auth_agent) { + auth_agent_free(default_auth_agent); + default_auth_agent = NULL; + } + + return NULL; +} + +static DBusMessage *unregister_default_auth_agent(DBusConnection *conn, + DBusMessage *msg, + void *data) +{ + const char *path, *name; + + if (!default_auth_agent) + return g_dbus_create_error(msg, + ERROR_INTERFACE ".DoesNotExist", + "Authorization agent does not exist"); + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &path, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + name = dbus_message_get_sender(msg); + + if (strcmp(name, default_auth_agent->name) || + strcmp(path, default_auth_agent->path)) + return g_dbus_create_error(msg, + ERROR_INTERFACE ".DoesNotExist", + "Authorization agent does not exist"); + + g_dbus_remove_watch(default_auth_agent->conn, + default_auth_agent->listener_id); + + info("Default authorization agent (%s, %s) unregistered", + default_auth_agent->name, default_auth_agent->path); + + auth_agent_cancel_requests(default_auth_agent); + auth_agent_free(default_auth_agent); + default_auth_agent = NULL; + + return dbus_message_new_method_return(msg); +} + +static void auth_agent_req_reply(DBusPendingCall *call, void *data) +{ + struct auth_agent_req *req = data; + DBusMessage *reply = dbus_pending_call_steal_reply(call); + DBusError err; + + debug("authorize reply"); + + dbus_error_init(&err); + dbus_set_error_from_message(&err, reply); + req->cb(&err, req->user_data); + + default_auth_agent->pending_requests = + g_slist_remove(default_auth_agent->pending_requests, req); + auth_agent_req_free(req); + + debug("auth_agent_reply: returning"); +} + +static DBusPendingCall *auth_agent_call_authorize(struct authorization_agent *agent, + const char *adapter_path, + const char *service_path, + const char *address, + const char *uuid) +{ + DBusMessage *message; + DBusPendingCall *call; + + message = dbus_message_new_method_call(agent->name, agent->path, + "org.bluez.AuthorizationAgent", "Authorize"); + if (!message) { + error("Couldn't allocate D-Bus message"); + return NULL; + } + + dbus_message_append_args(message, + DBUS_TYPE_STRING, &adapter_path, + DBUS_TYPE_STRING, &address, + DBUS_TYPE_STRING, &service_path, + DBUS_TYPE_STRING, &uuid, + DBUS_TYPE_INVALID); + + if (dbus_connection_send_with_reply(agent->conn, message, + &call, REQUEST_TIMEOUT) == FALSE) { + error("D-Bus send failed"); + dbus_message_unref(message); + return NULL; + } + + dbus_message_unref(message); + return call; +} + +int handle_authorize_request_old(struct service *service, const char *path, + const char *address, const char *uuid, + service_auth_cb cb, void *user_data) +{ + struct auth_agent_req *req; + + if (!default_auth_agent) { + debug("no default agent"); + return -EPERM; + } + + req = auth_agent_req_new(default_auth_agent, path, + address, service->object_path, + uuid, cb, user_data); + + req->call = auth_agent_call_authorize(default_auth_agent, path, + service->object_path, address, uuid); + if (!req->call) { + auth_agent_req_free(req); + return -ENOMEM; + } + + dbus_pending_call_set_notify(req->call, auth_agent_req_reply, req, + NULL); + default_auth_agent->pending_requests = + g_slist_append(default_auth_agent->pending_requests, req); + + debug("authorize request was forwarded"); + + return 0; +} + +static int auth_agent_send_cancel(struct authorization_agent *agent, + const char *adapter_path, + const char *address) +{ + struct auth_agent_req *req = NULL; + GSList *l; + + for (l = agent->pending_requests; l != NULL; l = l->next) { + req = l->data; + if (!strcmp(adapter_path, req->adapter_path) && + !strcmp(address, req->address)) + break; + } + + if (!req) + return -EIO; + + auth_agent_call_cancel(req); + auth_agent_req_cancel(req); + agent->pending_requests = g_slist_remove(agent->pending_requests, req); + auth_agent_req_free(req); + + return 0; +} + +int cancel_authorize_request_old(const char *path, const char *address) +{ + if (!default_auth_agent) + return -EIO; + + return auth_agent_send_cancel(default_auth_agent, path, address); +} + +static GDBusMethodTable security_methods[] = { + { "RegisterDefaultPasskeyAgent", "s", "", + register_default_passkey_agent }, + { "UnregisterDefaultPasskeyAgent", "s", "", + unregister_default_passkey_agent}, + { "RegisterPasskeyAgent", "ss", "", + register_passkey_agent }, + { "UnregisterPasskeyAgent", "ss", "", + unregister_passkey_agent }, + { "RegisterDefaultAuthorizationAgent", "s", "", + register_default_auth_agent }, + { "UnregisterDefaultAuthorizationAgent","s", "", + unregister_default_auth_agent }, + { } +}; + +dbus_bool_t security_init(DBusConnection *conn, const char *path) +{ + return g_dbus_register_interface(conn, path, SECURITY_INTERFACE, + security_methods, NULL, NULL, NULL, NULL); +} + +dbus_bool_t security_cleanup(DBusConnection *conn, const char *path) +{ + return g_dbus_unregister_interface(conn, path, SECURITY_INTERFACE); +} + +static DBusPendingCall *agent_request(const char *path, bdaddr_t *bda, + struct passkey_agent *agent, + dbus_bool_t numeric, int old_if) +{ + DBusMessage *message; + DBusPendingCall *call; + char bda_str[18], *ptr = bda_str; + + message = dbus_message_new_method_call(agent->name, agent->path, + "org.bluez.PasskeyAgent", "Request"); + if (message == NULL) { + error("Couldn't allocate D-Bus message"); + return NULL; + } + + ba2str(bda, bda_str); + + if (old_if) + dbus_message_append_args(message, + DBUS_TYPE_STRING, &path, + DBUS_TYPE_STRING, &ptr, + DBUS_TYPE_INVALID); + else + dbus_message_append_args(message, + DBUS_TYPE_STRING, &path, + DBUS_TYPE_STRING, &ptr, + DBUS_TYPE_BOOLEAN, &numeric, + DBUS_TYPE_INVALID); + + if (dbus_connection_send_with_reply(agent->conn, message, + &call, REQUEST_TIMEOUT) == FALSE) { + error("D-Bus send failed"); + dbus_message_unref(message); + return NULL; + } + + dbus_message_unref(message); + return call; +} + +static void passkey_agent_reply(DBusPendingCall *call, void *user_data) +{ + struct pending_agent_request *req = user_data; + struct passkey_agent *agent = req->agent; + struct adapter *adapter = manager_find_adapter(&req->sba); + pin_code_reply_cp pr; + DBusMessage *message; + DBusError err; + size_t len; + char *pin; + + /* steal_reply will always return non-NULL since the callback + * is only called after a reply has been received */ + message = dbus_pending_call_steal_reply(call); + + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, message)) { + if (!req->old_if && !strcmp(err.name, DBUS_ERROR_UNKNOWN_METHOD)) { + debug("New Request API failed, trying old one"); + req->old_if = 1; + dbus_error_free(&err); + dbus_pending_call_unref(req->call); + req->call = agent_request(req->path, &req->bda, agent, + FALSE, 1); + if (!req->call) + goto fail; + + dbus_message_unref(message); + + dbus_pending_call_set_notify(req->call, + passkey_agent_reply, + req, NULL); + return; + } + + error("Passkey agent replied with an error: %s, %s", + err.name, err.message); + + dbus_error_free(&err); + goto fail; + } + + dbus_error_init(&err); + if (!dbus_message_get_args(message, &err, + DBUS_TYPE_STRING, &pin, + DBUS_TYPE_INVALID)) { + error("Wrong passkey reply signature: %s", err.message); + dbus_error_free(&err); + goto fail; + } + + len = strlen(pin); + + if (len > 16 || len < 1) { + error("Invalid passkey length from handler"); + goto fail; + } + + set_pin_length(&req->sba, len); + + memset(&pr, 0, sizeof(pr)); + bacpy(&pr.bdaddr, &req->bda); + memcpy(pr.pin_code, pin, len); + pr.pin_len = len; + hci_send_cmd(req->dev, OGF_LINK_CTL, + OCF_PIN_CODE_REPLY, PIN_CODE_REPLY_CP_SIZE, &pr); + + goto done; + +fail: + hci_send_cmd(req->dev, OGF_LINK_CTL, + OCF_PIN_CODE_NEG_REPLY, 6, &req->bda); + +done: + if (adapter) + adapter_auth_request_replied(adapter, &req->bda); + + if (message) + dbus_message_unref(message); + + agent->pending_requests = g_slist_remove(agent->pending_requests, req); + dbus_pending_call_cancel(req->call); + if (req->call) + dbus_pending_call_unref(req->call); + g_free(req->path); + g_free(req); + + if (agent != default_agent) { + agent->adapter->passkey_agents = g_slist_remove(agent->adapter->passkey_agents, + agent); + passkey_agent_free(agent); + } +} + +static int call_passkey_agent(DBusConnection *conn, + struct passkey_agent *agent, int dev, + const char *path, bdaddr_t *sba, + bdaddr_t *dba) +{ + struct pending_agent_request *req; + struct adapter *adapter = manager_find_adapter(sba); + + if (!agent) { + debug("call_passkey_agent(): no agent available"); + goto send; + } + + debug("Calling PasskeyAgent.Request: name=%s, path=%s", + agent->name, agent->path); + + req = g_new0(struct pending_agent_request, 1); + req->dev = dev; + bacpy(&req->sba, sba); + bacpy(&req->bda, dba); + req->agent = agent; + req->path = g_strdup(path); + + req->call = agent_request(path, dba, agent, FALSE, 0); + if (!req->call) + goto failed; + + dbus_pending_call_set_notify(req->call, passkey_agent_reply, req, NULL); + + agent->pending_requests = g_slist_append(agent->pending_requests, req); + + return 0; + +failed: + g_free(req->path); + g_free(req); + +send: + hci_send_cmd(dev, OGF_LINK_CTL, OCF_PIN_CODE_NEG_REPLY, 6, dba); + + if (adapter) + adapter_auth_request_replied(adapter, dba); + + return -1; +} + +int handle_passkey_request_old(DBusConnection *conn, int dev, + struct adapter *adapter, + bdaddr_t *sba, bdaddr_t *dba) +{ + struct passkey_agent *agent = default_agent; + GSList *l; + char addr[18]; + + ba2str(dba, addr); + + for (l = adapter->passkey_agents; l != NULL; l = l->next) { + struct passkey_agent *a = l->data; + if (a != default_agent && g_slist_length(a->pending_requests) >= 1) + continue; + if (!strcmp(a->addr, addr)) { + agent = a; + break; + } + } + + return call_passkey_agent(conn, agent, dev, adapter->path, sba, dba); +} + +static void send_cancel_request(struct pending_agent_request *req) +{ + DBusMessage *message; + char address[18], *ptr = address; + + message = dbus_message_new_method_call(req->agent->name, req->agent->path, + "org.bluez.PasskeyAgent", "Cancel"); + if (message == NULL) { + error("Couldn't allocate D-Bus message"); + return; + } + + ba2str(&req->bda, address); + + dbus_message_append_args(message, + DBUS_TYPE_STRING, &req->path, + DBUS_TYPE_STRING, &ptr, + DBUS_TYPE_INVALID); + + dbus_message_set_no_reply(message, TRUE); + + dbus_connection_send(req->agent->conn, message, NULL); + + dbus_message_unref(message); + + debug("PasskeyAgent.Request(%s, %s) was canceled", req->path, address); + + dbus_pending_call_cancel(req->call); + dbus_pending_call_unref(req->call); + g_free(req->pin); + g_free(req->path); + g_free(req); +} + +static void release_agent(struct passkey_agent *agent) +{ + DBusMessage *message; + + debug("Releasing agent %s, %s", agent->name, agent->path); + + message = dbus_message_new_method_call(agent->name, agent->path, + "org.bluez.PasskeyAgent", "Release"); + if (message == NULL) { + error("Couldn't allocate D-Bus message"); + return; + } + + dbus_message_set_no_reply(message, TRUE); + + dbus_connection_send(agent->conn, message, NULL); + + dbus_message_unref(message); + + if (agent == default_agent) + g_dbus_remove_watch(agent->conn, agent->listener_id); + else { + struct passkey_agent ref; + + /* Only remove the name listener if there are no more agents + * for this name */ + memset(&ref, 0, sizeof(ref)); + ref.name = agent->name; + if (!g_slist_find_custom(agent->adapter->passkey_agents, &ref, + (GCompareFunc) agent_cmp)) + g_dbus_remove_watch(agent->conn, agent->listener_id); + } +} + +void release_default_agent_old(void) +{ + if (!default_agent) + return; + + passkey_agent_free(default_agent); + default_agent = NULL; +} + +void release_default_auth_agent(void) +{ + if (!default_auth_agent) + return; + + auth_agent_cancel_requests(default_auth_agent); + auth_agent_release(default_auth_agent); + + auth_agent_free(default_auth_agent); + default_auth_agent = NULL; +} + +void release_passkey_agents(struct adapter *adapter, bdaddr_t *bda) +{ + GSList *l, *next; + + for (l = adapter->passkey_agents; l != NULL; l = next) { + struct passkey_agent *agent = l->data; + next = l->next; + + if (bda && agent->addr) { + bdaddr_t tmp; + str2ba(agent->addr, &tmp); + if (bacmp(&tmp, bda)) + continue; + } + + adapter->passkey_agents = g_slist_remove(adapter->passkey_agents, agent); + passkey_agent_free(agent); + } +} + +void cancel_passkey_agent_requests(GSList *agents, const char *path, + bdaddr_t *addr) +{ + GSList *l, *next; + + /* First check the default agent */ + for (l = default_agent ? default_agent->pending_requests : NULL; l != NULL; l = next) { + struct pending_agent_request *req = l->data; + next = l->next; + if (!strcmp(path, req->path) && (!addr || !bacmp(addr, &req->bda))) { + send_cancel_request(req); + default_agent->pending_requests = g_slist_remove(default_agent->pending_requests, + req); + } + } + + /* and then the adapter specific agents */ + for (; agents != NULL; agents = agents->next) { + struct passkey_agent *agent = agents->data; + + for (l = agent->pending_requests; l != NULL; l = next) { + struct pending_agent_request *req = l->data; + next = l->next; + if (!strcmp(path, req->path) && (!addr || !bacmp(addr, &req->bda))) { + send_cancel_request(req); + agent->pending_requests = g_slist_remove(agent->pending_requests, req); + } + } + } +} diff --git a/hcid/dbus-security.h b/hcid/dbus-security.h new file mode 100644 index 00000000..8c23417a --- /dev/null +++ b/hcid/dbus-security.h @@ -0,0 +1,50 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 + * + */ + +#define SECURITY_INTERFACE "org.bluez.Security" + +dbus_bool_t security_init(DBusConnection *conn, const char *path); +dbus_bool_t security_cleanup(DBusConnection *conn, const char *path); + +int handle_passkey_request_old(DBusConnection *conn, int dev, + struct adapter *adapter, + bdaddr_t *sba, bdaddr_t *dba); + +int handle_confirm_request_old(DBusConnection *conn, int dev, + struct adapter *adapter, + bdaddr_t *sba, bdaddr_t *dba, + const char *pin); + +void release_default_agent_old(void); + +void release_default_auth_agent(void); + +void release_passkey_agents(struct adapter *adapter, bdaddr_t *bda); + +void cancel_passkey_agent_requests(GSList *agents, const char *path, bdaddr_t *dba); + +int handle_authorize_request_old(struct service *service, const char *path, + const char *address, const char *uuid, + service_auth_cb cb, void *user_data); +int cancel_authorize_request_old(const char *path, const char *address); diff --git a/hcid/dbus-service.c b/hcid/dbus-service.c new file mode 100644 index 00000000..d07741a3 --- /dev/null +++ b/hcid/dbus-service.c @@ -0,0 +1,746 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 <config.h> +#endif + +#include <stdio.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <dirent.h> +#include <signal.h> +#include <ctype.h> +#include <sys/types.h> +#include <sys/wait.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/hci.h> +#include <bluetooth/hci_lib.h> +#include <bluetooth/sdp.h> + +#include <glib.h> +#include <dbus/dbus.h> +#include <gdbus.h> + +#include "hcid.h" +#include "server.h" +#include "dbus-common.h" +#include "error.h" +#include "manager.h" +#include "adapter.h" +#include "agent.h" +#include "device.h" +#include "dbus-service.h" +#include "dbus-hci.h" +#include "dbus-security.h" + +#define SERVICE_INTERFACE "org.bluez.Service" + +struct service_uuids { + char *name; + char **uuids; +}; + +struct service_auth { + service_auth_cb cb; + void *user_data; +}; + +static GSList *services = NULL; +static GSList *services_uuids = NULL; + +static void service_free(struct service *service) +{ + if (!service) + return; + + g_free(service->object_path); + g_free(service->ident); + g_free(service->name); + + g_free(service); +} + +static DBusMessage *get_info(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct service *service = data; + DBusMessage *reply; + DBusMessageIter iter; + DBusMessageIter dict; + + 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); + + dbus_message_iter_append_dict_entry(&dict, "identifier", + DBUS_TYPE_STRING, &service->ident); + + dbus_message_iter_append_dict_entry(&dict, "name", + DBUS_TYPE_STRING, &service->name); + + dbus_message_iter_close_container(&iter, &dict); + + return reply; +} + +static DBusMessage *get_identifier(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + + struct service *service = data; + DBusMessage *reply; + const char *identifier = ""; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + if (service->ident) + identifier = service->ident; + + dbus_message_append_args(reply, DBUS_TYPE_STRING, &identifier, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *get_name(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + + struct service *service = data; + DBusMessage *reply; + const char *name = ""; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + if (service->name) + name = service->name; + + dbus_message_append_args(reply, DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *get_description(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + DBusMessage *reply; + const char *description = ""; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_append_args(reply, DBUS_TYPE_STRING, &description, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *get_bus_name(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + DBusMessage *reply; + const char *busname = "org.bluez"; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_append_args(reply, DBUS_TYPE_STRING, &busname, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *start(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + return g_dbus_create_error(msg, + ERROR_INTERFACE ".Failed", + strerror(EALREADY)); +} + +static DBusMessage *stop(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + return g_dbus_create_error(msg, + ERROR_INTERFACE ".Failed", + strerror(EPERM)); +} + +static DBusMessage *is_running(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + DBusMessage *reply; + dbus_bool_t running = TRUE; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_append_args(reply, + DBUS_TYPE_BOOLEAN, &running, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *is_external(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + DBusMessage *reply; + dbus_bool_t external = TRUE; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_append_args(reply, + DBUS_TYPE_BOOLEAN, &external, + DBUS_TYPE_INVALID); + + return reply; +} + +static inline DBusMessage *invalid_args(DBusMessage *msg) +{ + return g_dbus_create_error(msg, + ERROR_INTERFACE ".InvalidArguments", + "Invalid arguments in method call"); +} + +static DBusMessage *set_trusted(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct service *service = data; + const char *address; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &address, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + if (check_address(address) < 0) + return invalid_args(msg); + + write_trust(BDADDR_ANY, address, service->ident, TRUE); + + g_dbus_emit_signal(conn, service->object_path, + SERVICE_INTERFACE, "TrustAdded", + DBUS_TYPE_STRING, &address, + DBUS_TYPE_INVALID); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *list_trusted(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct service *service = data; + DBusMessage *reply; + GSList *trusts, *l; + char **addrs; + int len; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + trusts = list_trusts(BDADDR_ANY, service->ident); + + addrs = g_new(char *, g_slist_length(trusts)); + + for (l = trusts, len = 0; l; l = l->next, len++) + addrs[len] = l->data; + + dbus_message_append_args(reply, + DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, + &addrs, len, DBUS_TYPE_INVALID); + + g_free(addrs); + g_slist_foreach(trusts, (GFunc) g_free, NULL); + g_slist_free(trusts); + + return reply; +} + +static DBusMessage *is_trusted(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct service *service = data; + DBusMessage *reply; + const char *address; + dbus_bool_t trusted; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &address, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + if (check_address(address) < 0) + return invalid_args(msg); + + trusted = read_trust(BDADDR_ANY, address, service->ident); + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_append_args(reply, + DBUS_TYPE_BOOLEAN, &trusted, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *remove_trust(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct service *service = data; + const char *address; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &address, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + if (check_address(address) < 0) + return invalid_args(msg); + + write_trust(BDADDR_ANY, address, service->ident, FALSE); + + g_dbus_emit_signal(conn, service->object_path, + SERVICE_INTERFACE, "TrustRemoved", + DBUS_TYPE_STRING, &address, + DBUS_TYPE_INVALID); + + return dbus_message_new_method_return(msg); +} + +static GDBusMethodTable service_methods[] = { + { "GetInfo", "", "a{sv}", get_info }, + { "GetIdentifier", "", "s", get_identifier }, + { "GetName", "", "s", get_name }, + { "GetDescription", "", "s", get_description }, + { "GetBusName", "", "s", get_bus_name }, + { "Start", "", "", start }, + { "Stop", "", "", stop }, + { "IsRunning", "", "b", is_running }, + { "IsExternal", "", "b", is_external }, + { "SetTrusted", "s", "", set_trusted }, + { "IsTrusted", "s", "b", is_trusted }, + { "RemoveTrust", "s", "", remove_trust }, + { "ListTrusts", "", "as", list_trusted }, + { NULL, NULL, NULL, NULL } +}; + +static GDBusSignalTable service_signals[] = { + { "Started", "" }, + { "Stopped", "" }, + { "TrustAdded", "s" }, + { "TrustRemoved", "s" }, + { NULL, NULL } +}; + +static int service_cmp_path(struct service *service, const char *path) +{ + return strcmp(service->object_path, path); +} + +static int service_cmp_ident(struct service *service, const char *ident) +{ + return strcmp(service->ident, ident); +} + +static int unregister_service_for_connection(DBusConnection *connection, + struct service *service) +{ + DBusConnection *conn = get_dbus_connection(); + + debug("Unregistering service object: %s", service->object_path); + + if (!conn) + goto cleanup; + + g_dbus_emit_signal(conn, service->object_path, + SERVICE_INTERFACE, + "Stopped", DBUS_TYPE_INVALID); + + g_dbus_emit_signal(conn, BASE_PATH, MANAGER_INTERFACE, + "ServiceRemoved", + DBUS_TYPE_STRING, &service->object_path, + DBUS_TYPE_INVALID); + + if (!g_dbus_unregister_interface(conn, + service->object_path, SERVICE_INTERFACE)) { + error("D-Bus failed to unregister %s object", + service->object_path); + return -1; + } + +cleanup: + services = g_slist_remove(services, service); + service_free(service); + + return 0; +} + +static int do_unregister(struct service *service) +{ + DBusConnection *conn = get_dbus_connection(); + + return unregister_service_for_connection(conn, service); +} + +void release_services(DBusConnection *conn) +{ + debug("release_services"); + + g_slist_foreach(services, (GFunc) do_unregister, NULL); + g_slist_free(services); + services = NULL; +} + +struct service *search_service(const char *pattern) +{ + GSList *l; + const char *bus_id; + + /* Workaround for plugins: share the same bus id */ + bus_id = dbus_bus_get_unique_name(get_dbus_connection()); + if (!strcmp(bus_id, pattern)) + return NULL; + + for (l = services; l != NULL; l = l->next) { + struct service *service = l->data; + + if (service->ident && !strcmp(service->ident, pattern)) + return service; + } + + return NULL; +} + +void append_available_services(DBusMessageIter *array_iter) +{ + GSList *l; + + for (l = services; l != NULL; l = l->next) { + struct service *service = l->data; + + dbus_message_iter_append_basic(array_iter, + DBUS_TYPE_STRING, &service->object_path); + } +} + +int service_unregister(DBusConnection *conn, struct service *service) +{ + return unregister_service_for_connection(conn, service); +} + +static gint name_cmp(struct service_uuids *su, const char *name) +{ + return strcmp(su->name, name); +} + +static gint uuid_cmp(struct service_uuids *su, const char *uuid) +{ + int i; + + for (i = 0; su->uuids[i]; i++) { + if (!strcasecmp(su->uuids[i], uuid)) + return 0; + } + + return -1; +} + +struct service *search_service_by_uuid(const char *uuid) +{ + struct service_uuids *su; + struct service *service; + GSList *l; + + if (!services_uuids) + return NULL; + + l = g_slist_find_custom(services_uuids, uuid, (GCompareFunc) uuid_cmp); + if (!l) + return NULL; + + su = l->data; + service = search_service(su->name); + if (!service) + return NULL; + + return service; +} + +static void register_uuids(const char *ident, const char **uuids) +{ + struct service_uuids *su; + int i; + + if (!ident) + return; + + su = g_new0(struct service_uuids, 1); + su->name = g_strdup(ident); + + for (i = 0; uuids[i]; i++); + + su->uuids = g_new0(char *, i + 1); + + for (i = 0; uuids[i]; i++) + su->uuids[i] = g_strdup(uuids[i]); + + services_uuids = g_slist_append(services_uuids, su); +} + +static void service_uuids_free(struct service_uuids *su) +{ + int i; + + if (!su) + return; + + g_free(su->name); + + for (i = 0; su->uuids[i]; i++) + g_free(su->uuids[i]); + + g_free(su); +} + +static void unregister_uuids(const char *ident) +{ + struct service_uuids *su; + GSList *l; + + if (!services_uuids) + return; + + l = g_slist_find_custom(services_uuids, ident, (GCompareFunc) name_cmp); + if (!l) + return; + + su = l->data; + services_uuids = g_slist_remove(services_uuids, su); + + service_uuids_free(su); +} + +static struct service *create_external_service(const char *ident) +{ + struct service *service; + const char *name; + + service = g_try_new0(struct service, 1); + if (!service) { + error("OOM while allocating new external service"); + return NULL; + } + + if (!strcmp(ident, "input")) + name = "Input service"; + else if (!strcmp(ident, "audio")) + name = "Audio service"; + else if (!strcmp(ident, "network")) + name = "Network service"; + else if (!strcmp(ident, "serial")) + name = "Serial service"; + else + name = ""; + + service->ident = g_strdup(ident); + service->name = g_strdup(name); + + return service; +} + +int register_service(const char *ident, const char **uuids) +{ + DBusConnection *conn = get_dbus_connection(); + struct service *service; + char obj_path[PATH_MAX]; + int i; + + if (g_slist_find_custom(services, ident, + (GCompareFunc) service_cmp_ident)) + return -EADDRINUSE; + + snprintf(obj_path, sizeof(obj_path) - 1, + "/org/bluez/service_%s", ident); + + /* Make the path valid for D-Bus */ + for (i = strlen("/org/bluez/"); obj_path[i]; i++) { + if (!isalnum(obj_path[i])) + obj_path[i] = '_'; + } + + if (g_slist_find_custom(services, obj_path, + (GCompareFunc) service_cmp_path)) + return -EADDRINUSE; + + service = create_external_service(ident); + + debug("Registering service object: %s (%s)", + service->ident, obj_path); + + if (!g_dbus_register_interface(conn, obj_path, SERVICE_INTERFACE, + service_methods, service_signals, + NULL, service, NULL)) { + error("D-Bus failed to register %s object", obj_path); + service_free(service); + return -1; + } + + service->object_path = g_strdup(obj_path); + + services = g_slist_append(services, service); + + if (uuids) + register_uuids(ident, uuids); + + g_dbus_emit_signal(conn, BASE_PATH, MANAGER_INTERFACE, + "ServiceAdded", + DBUS_TYPE_STRING, &service->object_path, + DBUS_TYPE_INVALID); + + g_dbus_emit_signal(conn, service->object_path, + SERVICE_INTERFACE, + "Started", DBUS_TYPE_INVALID); + + return 0; +} + +void unregister_service(const char *ident) +{ + unregister_uuids(ident); +} + +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); +} + +int service_req_auth(const bdaddr_t *src, const bdaddr_t *dst, + const char *uuid, service_auth_cb cb, void *user_data) +{ + struct service_auth *auth; + struct adapter *adapter; + struct device *device; + struct agent *agent; + struct service *service; + char address[18]; + gboolean trusted; + + adapter = manager_find_adapter(src); + if (!adapter) + return -EPERM; + + /* Device connected? */ + if (!g_slist_find_custom(adapter->active_conn, + dst, active_conn_find_by_bdaddr)) + return -ENOTCONN; + + service = search_service_by_uuid(uuid); + if (!service) + return -EPERM; + + ba2str(dst, address); + trusted = read_trust(src, address, GLOBAL_TRUST); + if (!trusted) + trusted = read_trust(BDADDR_ANY, address, service->ident); + + if (trusted) { + cb(NULL, user_data); + return 0; + } + + device = adapter_find_device(adapter, address); + if (!device) + return handle_authorize_request_old(service, adapter->path, + address, uuid, cb, user_data); + + agent = (device->agent ? : adapter->agent); + if (!agent) + return handle_authorize_request_old(service, adapter->path, + address, uuid, cb, user_data); + + auth = g_try_new0(struct service_auth, 1); + if (!auth) + return -ENOMEM; + + auth->cb = cb; + auth->user_data = user_data; + + return agent_authorize(agent, device->path, uuid, agent_auth_cb, auth); +} + +int service_cancel_auth(const bdaddr_t *src, const bdaddr_t *dst) +{ + struct adapter *adapter = manager_find_adapter(src); + struct 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->agent ? : adapter->agent); + if (!agent) + return cancel_authorize_request_old(adapter->path, address); + + return agent_cancel(agent); +} diff --git a/hcid/dbus-service.h b/hcid/dbus-service.h new file mode 100644 index 00000000..d41a170a --- /dev/null +++ b/hcid/dbus-service.h @@ -0,0 +1,47 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 + * + */ + +struct service { + char *object_path; + char *ident; + char *name; +}; + +void release_services(DBusConnection *conn); + +void append_available_services(DBusMessageIter *iter); + +struct service *search_service(const char *pattern); + +struct service *search_service_by_uuid(const char *uuid); + +int service_unregister(DBusConnection *conn, struct service *service); + +int register_service(const char *ident, const char **uuids); +void unregister_service(const char *ident); + +typedef void (*service_auth_cb) (DBusError *derr, void *user_data); +int service_req_auth(const bdaddr_t *src, const bdaddr_t *dst, + const char *uuid, service_auth_cb cb, void *user_data); +int service_cancel_auth(const bdaddr_t *src, const bdaddr_t *dst); diff --git a/hcid/device.c b/hcid/device.c new file mode 100644 index 00000000..66ab2dd7 --- /dev/null +++ b/hcid/device.c @@ -0,0 +1,1601 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 <config.h> +#endif + +#include <stdio.h> +#include <errno.h> +#include <stdlib.h> +#include <stdarg.h> +#include <unistd.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <sys/param.h> +#include <sys/socket.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/hci.h> +#include <bluetooth/hci_lib.h> +#include <bluetooth/sdp.h> +#include <bluetooth/sdp_lib.h> + +#include <glib.h> +#include <dbus/dbus.h> +#include <gdbus.h> + +#include "hcid.h" +#include "sdpd.h" + +#include "logging.h" +#include "textfile.h" +#include "oui.h" + +#include "adapter.h" +#include "device.h" +#include "dbus-common.h" +#include "dbus-hci.h" +#include "dbus-service.h" +#include "error.h" +#include "glib-helper.h" +#include "agent.h" +#include "dbus-sdp.h" +#include "sdp-xml.h" + +#define MAX_DEVICES 16 +#define DISCONNECT_TIMER 2 + +#define DEVICE_INTERFACE "org.bluez.Device" + +struct browse_req { + DBusConnection *conn; + DBusMessage *msg; + struct device *device; + GSList *uuids_added; + GSList *uuids_removed; + int search_uuid; + gboolean browse; +}; + +struct hci_peer { + struct timeval lastseen; + struct timeval lastused; + + bdaddr_t bdaddr; + uint32_t class; + int8_t rssi; + uint8_t data[240]; + uint8_t name[248]; + + uint8_t pscan_rep_mode; + uint8_t pscan_period_mode; + uint8_t pscan_mode; + uint16_t clock_offset; + + struct hci_peer *next; +}; + +struct hci_conn { + bdaddr_t bdaddr; + uint16_t handle; + + struct hci_conn *next; +}; + +struct hci_dev { + int ignore; + + bdaddr_t bdaddr; + uint8_t features[8]; + uint8_t lmp_ver; + uint16_t lmp_subver; + uint16_t hci_rev; + uint16_t manufacturer; + + uint8_t ssp_mode; + uint8_t name[248]; + uint8_t class[3]; + + struct hci_peer *peers; + struct hci_conn *conns; +}; + +static struct hci_dev devices[MAX_DEVICES]; + +#define ASSERT_DEV_ID { if (dev_id >= MAX_DEVICES) return -ERANGE; } + +static GSList *drivers = NULL; + +static uint16_t uuid_list[] = { + PUBLIC_BROWSE_GROUP, + HID_SVCLASS_ID, + GENERIC_AUDIO_SVCLASS_ID, + ADVANCED_AUDIO_SVCLASS_ID, + AV_REMOTE_SVCLASS_ID, + 0 +}; + +void init_adapters(void) +{ + int i; + + for (i = 0; i < MAX_DEVICES; i++) + memset(devices + i, 0, sizeof(struct hci_dev)); +} + +static int device_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, 2000) < 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; +} + +int add_adapter(uint16_t dev_id) +{ + struct hci_dev *dev; + struct hci_dev_info di; + + ASSERT_DEV_ID; + + dev = &devices[dev_id]; + + if (hci_devinfo(dev_id, &di) < 0) { + dev->ignore = 1; + return -errno; + } + + if (hci_test_bit(HCI_RAW, &di.flags)) { + info("Device hci%d is using raw mode", dev_id); + dev->ignore = 1; + } + + if (bacmp(&di.bdaddr, BDADDR_ANY)) + bacpy(&dev->bdaddr, &di.bdaddr); + else { + int err = device_read_bdaddr(dev_id, &dev->bdaddr); + if (err < 0) + return err; + } + memcpy(dev->features, di.features, 8); + + info("Device hci%d has been added", dev_id); + + return 0; +} + +int remove_adapter(uint16_t dev_id) +{ + struct hci_dev *dev; + + ASSERT_DEV_ID; + + dev = &devices[dev_id]; + + memset(dev, 0, sizeof(struct hci_dev)); + + info("Device hci%d has been removed", dev_id); + + return 0; +} + +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 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, 2000) < 0) + error("Can't write extended inquiry response: %s (%d)", + strerror(errno), errno); +} + +int start_adapter(uint16_t dev_id) +{ + struct hci_dev *dev; + struct hci_version ver; + uint8_t features[8], inqmode; + uint8_t events[8] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0x00, 0x00 }; + char name[249]; + int dd, err; + + ASSERT_DEV_ID; + + dev = &devices[dev_id]; + + if (dev->ignore) + return 0; + + 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_local_version(dd, &ver, 1000) < 0) { + err = errno; + error("Can't read version info for hci%d: %s (%d)", + dev_id, 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, 1000) < 0) { + err = errno; + error("Can't read features for hci%d: %s (%d)", + dev_id, strerror(err), err); + hci_close_dev(dd); + return -err; + } + + memcpy(dev->features, features, 8); + + if (hci_read_class_of_dev(dd, dev->class, 1000) < 0) { + err = errno; + error("Can't read class of device on hci%d: %s (%d)", + dev_id, strerror(err), err); + hci_close_dev(dd); + return -err; + } + + if (hci_read_local_name(dd, sizeof(name), name, 2000) < 0) { + err = errno; + error("Can't read local name on hci%d: %s (%d)", + dev_id, strerror(err), err); + hci_close_dev(dd); + return -err; + } + + memcpy(dev->name, name, 248); + + if (!(features[6] & LMP_SIMPLE_PAIR)) + goto setup; + + if (hcid_dbus_use_experimental()) { + if (ioctl(dd, HCIGETAUTHINFO, NULL) < 0 && errno != EINVAL) + hci_write_simple_pairing_mode(dd, 0x01, 2000); + } + + if (hci_read_simple_pairing_mode(dd, &dev->ssp_mode, 1000) < 0) { + err = errno; + error("Can't read simple pairing mode on hci%d: %s (%d)", + dev_id, strerror(err), err); + hci_close_dev(dd); + return -err; + } + +setup: + if (ver.hci_rev > 1) { + if (features[5] & LMP_SNIFF_SUBR) + events[5] |= 0x20; + + if (features[5] & LMP_PAUSE_ENC) + events[5] |= 0x80; + + if (features[6] & LMP_EXT_INQ) + events[5] |= 0x40; + + if (features[6] & LMP_NFLUSH_PKTS) + events[7] |= 0x01; + + if (features[7] & LMP_LSTO) + events[6] |= 0x80; + + if (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(&dev->bdaddr, name) == 0) { + memcpy(dev->name, name, 248); + hci_write_local_name(dd, name, 5000); + } + + update_ext_inquiry_response(dd, dev); + + inqmode = get_inquiry_mode(dev); + if (inqmode < 1) + goto done; + + if (hci_write_inquiry_mode(dd, inqmode, 2000) < 0) { + err = errno; + error("Can't write inquiry mode for hci%d: %s (%d)", + dev_id, strerror(err), err); + hci_close_dev(dd); + return -err; + } + +done: + hci_close_dev(dd); + + info("Device hci%d has been activated", dev_id); + + return 0; +} + +int stop_adapter(uint16_t dev_id) +{ + ASSERT_DEV_ID; + + info("Device hci%d has been disabled", dev_id); + + return 0; +} + +int update_adapter(uint16_t dev_id) +{ + struct hci_dev *dev; + int dd; + + ASSERT_DEV_ID; + + dev = &devices[dev_id]; + + if (dev->ignore) + return 0; + + dd = hci_open_dev(dev_id); + if (dd < 0) { + int err = errno; + error("Can't open device hci%d: %s (%d)", + dev_id, strerror(err), err); + return -err; + } + + update_ext_inquiry_response(dd, dev); + + hci_close_dev(dd); + + return 0; +} + +int get_device_address(uint16_t dev_id, char *address, size_t size) +{ + struct hci_dev *dev; + + ASSERT_DEV_ID; + + if (size < 18) + return -ENOBUFS; + + dev = &devices[dev_id]; + + return ba2str(&dev->bdaddr, address); +} + +int get_device_class(uint16_t dev_id, uint8_t *cls) +{ + struct hci_dev *dev; + + ASSERT_DEV_ID; + + dev = &devices[dev_id]; + memcpy(cls, dev->class, 3); + + return 0; +} + +int set_device_class(uint16_t dev_id, uint8_t *cls) +{ + struct hci_dev *dev; + + ASSERT_DEV_ID; + dev = &devices[dev_id]; + memcpy(dev->class, cls, 3); + + return 0; +} + +int get_device_version(uint16_t dev_id, char *version, size_t size) +{ + struct hci_dev *dev; + char edr[7], *tmp; + int err; + + ASSERT_DEV_ID; + + if (size < 14) + return -ENOBUFS; + + dev = &devices[dev_id]; + + if ((dev->lmp_ver == 0x03 || dev->lmp_ver == 0x04) && + (dev->features[3] & (LMP_EDR_ACL_2M | LMP_EDR_ACL_3M))) + sprintf(edr, " + EDR"); + else + edr[0] = '\0'; + + tmp = lmp_vertostr(dev->lmp_ver); + + if (strlen(tmp) == 0) + err = snprintf(version, size, "not assigned"); + else + err = snprintf(version, size, "Bluetooth %s%s", tmp, edr); + + bt_free(tmp); + + return err; +} + +static int digi_revision(uint16_t dev_id, char *revision, size_t size) +{ + struct hci_request rq; + unsigned char req[] = { 0x07 }; + unsigned char buf[102]; + 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; + } + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_VENDOR_CMD; + rq.ocf = 0x000e; + rq.cparam = req; + rq.clen = sizeof(req); + rq.rparam = &buf; + rq.rlen = sizeof(buf); + + if (hci_send_req(dd, &rq, 2000) < 0) { + err = errno; + error("Can't read revision for hci%d: %s (%d)", + dev_id, strerror(err), err); + hci_close_dev(dd); + return -err; + } + + hci_close_dev(dd); + + return snprintf(revision, size, "%s", buf + 1); +} + +int get_device_revision(uint16_t dev_id, char *revision, size_t size) +{ + struct hci_dev *dev; + int err; + + ASSERT_DEV_ID; + + dev = &devices[dev_id]; + + switch (dev->manufacturer) { + case 10: + err = snprintf(revision, size, "Build %d", dev->lmp_subver); + break; + case 12: + err = digi_revision(dev_id, revision, size); + break; + case 15: + err = snprintf(revision, size, "%d.%d / %d", + dev->hci_rev & 0xff, + dev->lmp_subver >> 8, dev->lmp_subver & 0xff); + break; + default: + err = snprintf(revision, size, "0x%02x", dev->lmp_subver); + break; + } + + return err; +} + +int get_device_manufacturer(uint16_t dev_id, char *manufacturer, size_t size) +{ + char *tmp; + + ASSERT_DEV_ID; + + tmp = bt_compidtostr(devices[dev_id].manufacturer); + + return snprintf(manufacturer, size, "%s", tmp); +} + +int get_device_company(uint16_t dev_id, char *company, size_t size) +{ + char *tmp, oui[9]; + int err; + + ASSERT_DEV_ID; + + ba2oui(&devices[dev_id].bdaddr, oui); + tmp = ouitocomp(oui); + + err = snprintf(company, size, "%s", tmp); + + free(tmp); + + return err; +} + +int set_simple_pairing_mode(uint16_t dev_id, uint8_t mode) +{ + struct hci_dev *dev; + int dd; + + ASSERT_DEV_ID; + + dev = &devices[dev_id]; + + dev->ssp_mode = mode; + + dd = hci_open_dev(dev_id); + if (dd < 0) { + int err = errno; + error("Can't open device hci%d: %s (%d)", + dev_id, strerror(err), err); + return -err; + } + + update_ext_inquiry_response(dd, dev); + + hci_close_dev(dd); + + return 0; +} + +int get_device_name(uint16_t dev_id, char *name, size_t size) +{ + char tmp[249]; + int dd, err; + + ASSERT_DEV_ID; + + memset(tmp, 0, sizeof(tmp)); + + 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_local_name(dd, sizeof(tmp), tmp, 2000) < 0) { + err = errno; + error("Can't read name for hci%d: %s (%d)", + dev_id, strerror(err), err); + hci_close_dev(dd); + return -err; + } + + hci_close_dev(dd); + + memcpy(devices[dev_id].name, tmp, 248); + + return snprintf(name, size, "%s", tmp); +} + +int set_device_name(uint16_t dev_id, const char *name) +{ + struct hci_dev *dev; + int dd, err; + + ASSERT_DEV_ID; + + dev = &devices[dev_id]; + + 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_write_local_name(dd, name, 5000) < 0) { + err = errno; + error("Can't write name for hci%d: %s (%d)", + dev_id, strerror(err), err); + hci_close_dev(dd); + return -err; + } + + strncpy((char *) dev->name, name, 248); + + update_ext_inquiry_response(dd, dev); + + hci_close_dev(dd); + + return 0; +} + +int get_device_alias(uint16_t dev_id, const bdaddr_t *bdaddr, char *alias, size_t size) +{ + char filename[PATH_MAX + 1], addr[18], *tmp; + int err; + + ASSERT_DEV_ID; + + ba2str(&devices[dev_id].bdaddr, addr); + create_name(filename, PATH_MAX, STORAGEDIR, addr, "aliases"); + + ba2str(bdaddr, addr); + + tmp = textfile_get(filename, addr); + if (!tmp) + return -ENXIO; + + err = snprintf(alias, size, "%s", tmp); + + free(tmp); + + return err; +} + +int set_device_alias(uint16_t dev_id, const bdaddr_t *bdaddr, const char *alias) +{ + char filename[PATH_MAX + 1], addr[18]; + + ASSERT_DEV_ID; + + ba2str(&devices[dev_id].bdaddr, addr); + create_name(filename, PATH_MAX, STORAGEDIR, addr, "aliases"); + + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + ba2str(bdaddr, addr); + + return textfile_put(filename, addr, alias); +} + +int remove_device_alias(uint16_t dev_id, const bdaddr_t *bdaddr) +{ + char filename[PATH_MAX + 1], addr[18]; + + ASSERT_DEV_ID; + + ba2str(&devices[dev_id].bdaddr, addr); + create_name(filename, PATH_MAX, STORAGEDIR, addr, "aliases"); + + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + ba2str(bdaddr, addr); + + return textfile_del(filename, addr); +} + +int get_encryption_key_size(uint16_t dev_id, const bdaddr_t *baddr) +{ + struct hci_dev *dev; + int size; + + ASSERT_DEV_ID; + + dev = &devices[dev_id]; + + switch (dev->manufacturer) { + default: + size = -ENOENT; + break; + } + + return size; +} + +static void device_free(gpointer user_data) +{ + struct device *device = user_data; + + if (device->agent) + agent_destroy(device->agent, FALSE); + + g_slist_foreach(device->uuids, (GFunc) g_free, NULL); + g_slist_free(device->uuids); + + if (device->disconn_timer) + g_source_remove(device->disconn_timer); + + g_free(device->address); + g_free(device->path); + g_free(device); +} + +static gboolean device_is_paired(struct device *device) +{ + struct adapter *adapter = device->adapter; + char filename[PATH_MAX + 1], *str; + gboolean ret; + + create_name(filename, PATH_MAX, STORAGEDIR, + adapter->address, "linkkeys"); + str = textfile_caseget(filename, device->address); + ret = str ? TRUE : FALSE; + g_free(str); + + return ret; +} + +static char *device_get_name(struct device *device) +{ + struct adapter *adapter = device->adapter; + char filename[PATH_MAX + 1]; + + create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, "names"); + return textfile_caseget(filename, device->address); +} + +static DBusMessage *get_properties(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + struct device *device = user_data; + struct adapter *adapter = device->adapter; + DBusMessage *reply; + DBusMessageIter iter; + DBusMessageIter dict; + bdaddr_t src, dst; + char path[MAX_PATH_LENGTH]; + char buf[64]; + const char *ptr; + char *name, *ppath, **uuids; + dbus_bool_t boolean; + uint32_t class; + int i; + GSList *l; + + 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 */ + dbus_message_iter_append_dict_entry(&dict, "Address", DBUS_TYPE_STRING, + &device->address); + + /* Name */ + name = device_get_name(device); + if (name) { + dbus_message_iter_append_dict_entry(&dict, "Name", + DBUS_TYPE_STRING, &name); + } + + str2ba(adapter->address, &src); + str2ba(device->address, &dst); + + /* Class */ + if (read_remote_class(&src, &dst, &class) == 0) { + dbus_message_iter_append_dict_entry(&dict, "Class", + DBUS_TYPE_UINT32, &class); + } + + /* Alias */ + if (get_device_alias(adapter->dev_id, &dst, buf, sizeof(buf)) > 0) { + ptr = buf; + dbus_message_iter_append_dict_entry(&dict, "Alias", + DBUS_TYPE_STRING, &ptr); + } else if (name) { + dbus_message_iter_append_dict_entry(&dict, "Alias", + DBUS_TYPE_STRING, &name); + free(name); + } + + /* Paired */ + boolean = device_is_paired(device); + dbus_message_iter_append_dict_entry(&dict, "Paired", + DBUS_TYPE_BOOLEAN, &boolean); + + /* Trusted */ + boolean = read_trust(&src, device->address, GLOBAL_TRUST); + dbus_message_iter_append_dict_entry(&dict, "Trusted", + DBUS_TYPE_BOOLEAN, &boolean); + + /* Connected */ + if (g_slist_find_custom(adapter->active_conn, &dst, + active_conn_find_by_bdaddr)) + boolean = TRUE; + else + boolean = FALSE; + + dbus_message_iter_append_dict_entry(&dict, "Connected", + DBUS_TYPE_BOOLEAN, &boolean); + + /* UUIDs */ + uuids = g_new0(char *, g_slist_length(device->uuids) + 1); + for (i = 0, l = device->uuids; l; l = l->next, i++) + uuids[i] = l->data; + dbus_message_iter_append_dict_entry(&dict, "UUIDs", + DBUS_TYPE_ARRAY, &uuids); + g_free(uuids); + + /* Adapter */ + snprintf(path, sizeof(path), "/hci%d", adapter->dev_id); + ppath = path; + dbus_message_iter_append_dict_entry(&dict, "Adapter", + DBUS_TYPE_OBJECT_PATH, &ppath); + + dbus_message_iter_close_container(&iter, &dict); + + return reply; +} + +static DBusMessage *set_alias(DBusConnection *conn, DBusMessage *msg, + const char *alias, void *data) +{ + struct device *device = data; + struct adapter *adapter = device->adapter; + bdaddr_t bdaddr; + int ecode; + char *str, filename[PATH_MAX + 1], path[MAX_PATH_LENGTH]; + + str2ba(device->address, &bdaddr); + + /* Remove alias if empty string */ + if (g_str_equal(alias, "")) { + create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, + "names"); + str = textfile_caseget(filename, device->address); + ecode = remove_device_alias(adapter->dev_id, &bdaddr); + } else { + str = g_strdup(alias); + ecode = set_device_alias(adapter->dev_id, &bdaddr, alias); + } + + if (ecode < 0) + return g_dbus_create_error(msg, + ERROR_INTERFACE ".Failed", + strerror(-ecode)); + + snprintf(path, sizeof(path), "%s/hci%d", BASE_PATH, adapter->dev_id); + + g_dbus_emit_signal(conn, path, + ADAPTER_INTERFACE, "RemoteAliasChanged", + DBUS_TYPE_STRING, &device->address, + DBUS_TYPE_STRING, &str, + DBUS_TYPE_INVALID); + + dbus_connection_emit_property_changed(conn, dbus_message_get_path(msg), + DEVICE_INTERFACE, "Alias", + DBUS_TYPE_STRING, &str); + + g_free(str); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *set_trust(DBusConnection *conn, DBusMessage *msg, + dbus_bool_t value, void *data) +{ + struct device *device = data; + struct adapter *adapter = device->adapter; + bdaddr_t local; + char path[MAX_PATH_LENGTH]; + + str2ba(adapter->address, &local); + + write_trust(&local, device->address, GLOBAL_TRUST, value); + + snprintf(path, sizeof(path), "%s/hci%d", BASE_PATH, adapter->dev_id); + + g_dbus_emit_signal(conn, path, + ADAPTER_INTERFACE, + value ? "TrustAdded" : "TrustRemoved", + DBUS_TYPE_STRING, &device->address, + DBUS_TYPE_INVALID); + + dbus_connection_emit_property_changed(conn, dbus_message_get_path(msg), + DEVICE_INTERFACE, "Trusted", + DBUS_TYPE_BOOLEAN, &value); + + return dbus_message_new_method_return(msg); +} + +static inline DBusMessage *invalid_args(DBusMessage *msg) +{ + return g_dbus_create_error(msg, + ERROR_INTERFACE ".InvalidArguments", + "Invalid arguments in method call"); +} + +static DBusMessage *set_property(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + DBusMessageIter iter; + DBusMessageIter sub; + const char *property; + + 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("Trusted", property)) { + dbus_bool_t value; + + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_BOOLEAN) + return invalid_args(msg); + dbus_message_iter_get_basic(&sub, &value); + + return set_trust(conn, msg, value, data); + } else if (g_str_equal("Alias", property)) { + char *alias; + + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING) + return invalid_args(msg); + dbus_message_iter_get_basic(&sub, &alias); + + return set_alias(conn, msg, alias, data); + } + + return invalid_args(msg); +} + +static void discover_services_req_exit(void *user_data) +{ + struct device *device = user_data; + struct adapter *adapter = device->adapter; + bdaddr_t src, dst; + + debug("DiscoverDevices requestor exited"); + + str2ba(adapter->address, &src); + str2ba(device->address, &dst); + + bt_cancel_discovery(&src, &dst); +} + +static DBusMessage *discover_services(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + struct device *device = user_data; + const char *pattern; + int err; + + if (device->discov_active) + return g_dbus_create_error(msg, ERROR_INTERFACE ".InProgress", + "Discover in progress"); + + if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &pattern, + DBUS_TYPE_INVALID) == FALSE) + goto fail; + + if (strlen(pattern) == 0) { + err = device_browse(device, conn, msg, NULL); + if (err < 0) + goto fail; + } else { + uuid_t uuid; + + if (bt_string2uuid(&uuid, pattern) < 0) + return invalid_args(msg); + + err = device_browse(device, conn, msg, &uuid); + if (err < 0) + goto fail; + } + + return NULL; + +fail: + return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", + "Discovery Failed"); +} + +static DBusMessage *cancel_discover(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + struct device *device = user_data; + struct adapter *adapter = device->adapter; + bdaddr_t src, dst; + + if (!device->discov_active) + return g_dbus_create_error(msg, + ERROR_INTERFACE ".Failed", + "No pending discovery"); + + /* only the discover requestor can cancel the inquiry process */ + if (!device->discov_requestor || + strcmp(device->discov_requestor, dbus_message_get_sender(msg))) + return g_dbus_create_error(msg, + ERROR_INTERFACE ".NotAuthorized", + "Not Authorized"); + + str2ba(adapter->address, &src); + str2ba(device->address, &dst); + + if (bt_cancel_discovery(&src, &dst) < 0) + return g_dbus_create_error(msg, + ERROR_INTERFACE ".Failed", + "No pending discover"); + + return dbus_message_new_method_return(msg); +} + +static gboolean disconnect_timeout(gpointer user_data) +{ + struct device *device = user_data; + struct active_conn_info *ci; + GSList *l; + disconnect_cp cp; + bdaddr_t bda; + int dd; + + device->disconn_timer = 0; + + str2ba(device->address, &bda); + l = g_slist_find_custom(device->adapter->active_conn, + &bda, active_conn_find_by_bdaddr); + if (!l) + return FALSE; + + ci = l->data; + dd = hci_open_dev(device->adapter->dev_id); + if (dd < 0) + goto fail; + + memset(&cp, 0, sizeof(cp)); + cp.handle = htobs(ci->handle); + cp.reason = HCI_OE_USER_ENDED_CONNECTION; + + hci_send_cmd(dd, OGF_LINK_CTL, OCF_DISCONNECT, + DISCONNECT_CP_SIZE, &cp); + + close(dd); + +fail: + return FALSE; +} + +static DBusMessage *disconnect(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + struct device *device = user_data; + GSList *l; + bdaddr_t bda; + + str2ba(device->address, &bda); + l = g_slist_find_custom(device->adapter->active_conn, + &bda, active_conn_find_by_bdaddr); + if (!l) + return g_dbus_create_error(msg, + ERROR_INTERFACE ".NotConnected", + "Device is not connected"); + + g_dbus_emit_signal(conn, device->path, + DEVICE_INTERFACE, "DisconnectRequested", + DBUS_TYPE_INVALID); + + device->disconn_timer = g_timeout_add_seconds(DISCONNECT_TIMER, + disconnect_timeout, device); + + return dbus_message_new_method_return(msg); +} + +static GDBusMethodTable device_methods[] = { + { "GetProperties", "", "a{sv}", get_properties }, + { "SetProperty", "sv", "", set_property }, + { "DiscoverServices", "s", "a{us}", discover_services, + G_DBUS_METHOD_FLAG_ASYNC}, + { "CancelDiscovery", "", "", cancel_discover }, + { "Disconnect", "", "", disconnect }, + { } +}; + +static GDBusSignalTable device_signals[] = { + { "PropertyChanged", "sv" }, + { "DisconnectRequested", "" }, + { } +}; + +struct device *device_create(DBusConnection *conn, struct adapter *adapter, + const gchar *address) +{ + gchar *address_up; + struct device *device; + + device = g_try_malloc0(sizeof(struct device)); + if (device == NULL) + return NULL; + + address_up = g_ascii_strup(address, -1); + device->path = g_strdup_printf("/hci%d/dev_%s", + adapter->dev_id, address_up); + g_strdelimit(device->path, ":", '_'); + g_free(address_up); + + debug("Creating device %s", device->path); + + if (g_dbus_register_interface(conn, device->path, DEVICE_INTERFACE, + device_methods, device_signals, NULL, + device, device_free) == FALSE) { + device_free(device); + return NULL; + } + + device->address = g_strdup(address); + device->adapter = adapter; + + device->dev.path = device->path; + str2ba(device->address, &device->dev.dst); + str2ba(adapter->address, &device->dev.src); + + return device; +} + +void device_remove(DBusConnection *conn, struct device *device) +{ + GSList *list; + struct btd_device_driver *driver; + gchar *path = g_strdup(device->path); + + debug("Removing device %s", path); + + for (list = device->drivers; list; list = list->next) { + driver = (struct btd_device_driver *) list->data; + + driver->remove(&device->dev); + } + + g_dbus_unregister_interface(conn, path, DEVICE_INTERFACE); + + g_free(path); +} + +gint device_address_cmp(struct device *device, const gchar *address) +{ + return strcasecmp(device->address, address); +} + +static int cmp_by_name(const void *data, const void *user_data) +{ + const struct btd_device_driver *dev_driver = data, *driver = user_data; + + return (strcmp(dev_driver->name, driver->name)); +} + +void device_probe_drivers(struct device *device, GSList *uuids) +{ + GSList *list; + const char **uuid; + int err; + + debug("Probe drivers for %s", device->path); + + for (list = drivers; list; list = list->next) { + struct btd_device_driver *driver = list->data; + gboolean do_probe = FALSE; + + for (uuid = driver->uuids; *uuid; uuid++) { + GSList *match = g_slist_find_custom(uuids, *uuid, + (GCompareFunc) strcasecmp); + if (match) { + do_probe = TRUE; + break; + } + } + + if (do_probe == TRUE && !g_slist_find_custom(device->drivers, + driver, (GCompareFunc) cmp_by_name)) { + + err = driver->probe(&device->dev); + if (err < 0) { + error("probe failed for driver %s", + driver->name); + continue; + } + + device->drivers = g_slist_append(device->drivers, + driver); + } + } + + for (list = uuids; list; list = list->next) + device->uuids = g_slist_insert_sorted(device->uuids, + list->data, (GCompareFunc) strcmp); +} + +void device_remove_drivers(struct device *device, GSList *uuids) +{ + GSList *list; + + debug("Remove drivers for %s", device->path); + + for (list = device->drivers; list; list = list->next) { + struct btd_device_driver *driver = list->data; + const char **uuid; + + for (uuid = driver->uuids; *uuid; uuid++) { + GSList *match = g_slist_find_custom(uuids, *uuid, + (GCompareFunc) strcasecmp); + + if (!match) + continue; + + driver->remove(&device->dev); + device->drivers = g_slist_remove(device->drivers, + driver); + } + } + + for (list = uuids; list; list = list->next) + device->uuids = g_slist_remove(device->uuids, list->data); +} + +static void iter_append_record(DBusMessageIter *dict, uint32_t handle, + const char *record) +{ + DBusMessageIter entry; + + dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY, + NULL, &entry); + + dbus_message_iter_append_basic(&entry, DBUS_TYPE_UINT32, &handle); + + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &record); + + dbus_message_iter_close_container(dict, &entry); +} + +static void discover_device_reply(struct browse_req *req, sdp_list_t *recs) +{ + DBusMessage *reply; + DBusMessageIter iter, dict; + sdp_list_t *seq; + + reply = dbus_message_new_method_return(req->msg); + if (!reply) + return; + + 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_UINT32_AS_STRING DBUS_TYPE_STRING_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); + + for (seq = recs; seq; seq = seq->next) { + sdp_record_t *rec = (sdp_record_t *) seq->data; + sdp_buf_t result; + + if (!rec) + break; + + memset(&result, 0, sizeof(sdp_buf_t)); + + convert_sdp_record_to_xml(rec, &result, + append_and_grow_string); + + if (result.data) { + const char *val = (char *) result.data; + iter_append_record(&dict, rec->handle, val); + free(result.data); + } + } + + dbus_message_iter_close_container(&iter, &dict); + + dbus_connection_send(req->conn, reply, NULL); + dbus_message_unref(reply); +} + +static void services_changed(struct browse_req *req) +{ + struct device *device = req->device; + char **uuids; + GSList *l; + int i; + + uuids = g_new0(char *, g_slist_length(device->uuids) + 1); + for (i = 0, l = device->uuids; l; l = l->next, i++) + uuids[i] = l->data; + + dbus_connection_emit_property_changed(req->conn, device->path, + DEVICE_INTERFACE, "UUIDs", + DBUS_TYPE_ARRAY, &uuids); + + g_free(uuids); +} + +static void update_services(struct browse_req *req, sdp_list_t *recs) +{ + struct device *device = req->device; + sdp_list_t *seq; + + for (seq = recs; seq; seq = seq->next) { + sdp_record_t *rec = (sdp_record_t *) seq->data; + sdp_list_t *svcclass = NULL; + gchar *uuid_str; + GSList *l; + + if (!rec) + break; + + if (sdp_get_service_classes(rec, &svcclass) < 0) + continue; + + /* Extract the first element and skip the remainning */ + uuid_str = bt_uuid2string(svcclass->data); + if (!uuid_str) + continue; + + l = g_slist_find_custom(device->uuids, uuid_str, + (GCompareFunc) strcmp); + if (!l) + req->uuids_added = g_slist_append(req->uuids_added, + uuid_str); + else { + req->uuids_removed = g_slist_remove(req->uuids_removed, + l->data); + g_free(uuid_str); + } + + sdp_list_free(svcclass, free); + } +} + +static void store(struct device *device) +{ + struct adapter *adapter = device->adapter; + bdaddr_t src, dst; + char *str; + + str2ba(adapter->address, &src); + str2ba(device->address, &dst); + + if (!device->uuids) { + write_device_profiles(&src, &dst, ""); + return; + } + + str = bt_list2string(device->uuids); + write_device_profiles(&src, &dst, str); + g_free(str); +} + +static void browse_cb(sdp_list_t *recs, int err, gpointer user_data) +{ + struct browse_req *req = user_data; + struct device *device = req->device; + struct adapter *adapter = device->adapter; + bdaddr_t src, dst; + uuid_t uuid; + DBusMessage *reply; + + if (err < 0) + goto proceed; + + update_services(req, recs); + + /* Public browsing successful or Single record requested */ + if (req->browse == FALSE || (!req->search_uuid && recs)) + goto probe; + + if (uuid_list[++req->search_uuid]) { + sdp_uuid16_create(&uuid, uuid_list[req->search_uuid]); + str2ba(adapter->address, &src); + str2ba(device->address, &dst); + bt_search_service(&src, &dst, &uuid, browse_cb, user_data, NULL); + return; + } + +probe: + + if (!req->uuids_added && !req->uuids_removed) + goto proceed; + + /* Probe matching drivers for services added */ + if (req->uuids_added) + device_probe_drivers(device, req->uuids_added); + + /* Remove drivers for services removed */ + if (req->uuids_removed) + device_remove_drivers(device, req->uuids_removed); + + /* Store the device's profiles in the filesystem */ + store(device); + + /* Propagate services changes */ + services_changed(req); + +proceed: + if (dbus_message_is_method_call(req->msg, DEVICE_INTERFACE, + "DiscoverServices")) { + discover_device_reply(req, recs); + goto cleanup; + } + + g_dbus_emit_signal(req->conn, dbus_message_get_path(req->msg), + ADAPTER_INTERFACE, "DeviceCreated", + DBUS_TYPE_OBJECT_PATH, &device->path, + DBUS_TYPE_INVALID); + + /* Reply create device request */ + reply = dbus_message_new_method_return(req->msg); + if (!reply) + goto cleanup; + + dbus_message_append_args(reply, DBUS_TYPE_OBJECT_PATH, &device->path, + DBUS_TYPE_INVALID); + + dbus_connection_send(req->conn, reply, NULL); + dbus_message_unref(reply); + +cleanup: + device->discov_active = 0; + + if (device->discov_requestor) { + g_dbus_remove_watch(req->conn, device->discov_listener); + device->discov_listener = 0; + g_free(device->discov_requestor); + device->discov_requestor = NULL; + } + + if (recs != NULL) + sdp_list_free(recs, (sdp_free_func_t) sdp_record_free); + + dbus_message_unref(req->msg); + dbus_connection_unref(req->conn); + g_slist_free(req->uuids_added); + g_slist_free(req->uuids_removed); + g_free(req); +} + +int device_browse(struct device *device, DBusConnection *conn, + DBusMessage *msg, uuid_t *search) +{ + struct adapter *adapter = device->adapter; + struct browse_req *req; + bdaddr_t src, dst; + uuid_t uuid; + GSList *l; + + req = g_new0(struct browse_req, 1); + req->conn = dbus_connection_ref(conn); + req->msg = dbus_message_ref(msg); + req->device = device; + + for (l = device->uuids; l; l = l->next) + req->uuids_removed = g_slist_append(req->uuids_removed, + l->data); + + str2ba(adapter->address, &src); + str2ba(device->address, &dst); + + if (search) { + memcpy(&uuid, search, sizeof(uuid_t)); + req->browse = FALSE; + } else { + sdp_uuid16_create(&uuid, uuid_list[req->search_uuid]); + req->browse = TRUE; + } + + device->discov_active = 1; + device->discov_requestor = g_strdup(dbus_message_get_sender(msg)); + /* Track the request owner to cancel it + * automatically if the owner exits */ + device->discov_listener = g_dbus_add_disconnect_watch(conn, + dbus_message_get_sender(msg), + discover_services_req_exit, + device, NULL); + + return bt_search_service(&src, &dst, &uuid, browse_cb, req, NULL); +} + +int btd_register_device_driver(struct btd_device_driver *driver) +{ + const char **uuid; + + drivers = g_slist_append(drivers, driver); + + for (uuid = driver->uuids; *uuid; uuid++) { + debug("name %s uuid %s", driver->name, *uuid); + } + + register_service(driver->name, driver->uuids); + + return 0; +} + +void btd_unregister_device_driver(struct btd_device_driver *driver) +{ + unregister_service(driver->name); + + drivers = g_slist_remove(drivers, driver); +} diff --git a/hcid/device.h b/hcid/device.h new file mode 100644 index 00000000..e5a322b3 --- /dev/null +++ b/hcid/device.h @@ -0,0 +1,70 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 + * + */ + +#define DEVICE_INTERFACE "org.bluez.Device" + +struct btd_device { + char *path; + bdaddr_t src; + bdaddr_t dst; +}; + +struct device { + struct btd_device dev; + gchar *address; + gchar *path; + struct adapter *adapter; + GSList *uuids; + GSList *drivers; + gboolean temporary; + struct agent *agent; + guint disconn_timer; + int discov_active; /* Service discovery active */ + char *discov_requestor; /* discovery requestor unique name */ + guint discov_listener; + + /* For Secure Simple Pairing */ + uint8_t cap; + uint8_t auth; +}; + +struct device *device_create(DBusConnection *conn, struct adapter *adapter, + const gchar *address); +void device_remove(DBusConnection *conn, struct device *device); +gint device_address_cmp(struct device *device, const gchar *address); +int device_browse(struct device *device, DBusConnection *conn, + DBusMessage *msg, uuid_t *search); +void device_probe_drivers(struct device *device, GSList *uuids); + +#define BTD_UUIDS(args...) ((const char *[]) { args, NULL } ) + +struct btd_device_driver { + const char *name; + const char **uuids; + int (*probe) (struct btd_device *device); + void (*remove) (struct btd_device *device); +}; + +int btd_register_device_driver(struct btd_device_driver *driver); +void btd_unregister_device_driver(struct btd_device_driver *driver); diff --git a/hcid/hcid.8 b/hcid/hcid.8 new file mode 100644 index 00000000..a7ab0410 --- /dev/null +++ b/hcid/hcid.8 @@ -0,0 +1,101 @@ +.\" +.TH "HCID" "8" "March 2004" "hcid - HCI daemon" "System management commands" +.SH "NAME" +hcid \- Bluetooth Host Controller Interface Daemon + +.SH "SYNOPSIS" +.B hcid +[ +.B \-n +] [ +.B \-f +.I config\-file +] + +.SH "DESCRIPTION" +This manual page documents briefly the +.B hcid +daemon, which manages all the Bluetooth devices. +.B hcid +itself does not accept many command\-line options, as most of its +configuration is done in the +.B hcid.conf +file, which has its own man page. +.B hcid +can also provide a number of services via the D-BUS message bus +system. +.SH "OPTIONS" +.TP +.BI \-n +Don't fork to run daemon in background. +.TP +.BI \-d +Enable debug information output. +.TP +.BI \-s +Enable internal SDP server. +.TP +.BI \-m\ mtu\-size +Use specific MTU size for SDP server. +.TP +.BI \-f\ config\-file +Use alternate configuration file instead of /etc/bluetooth/hcid.conf +.SH "FILES" +.TP +.I /etc/bluetooth/hcid.conf +Default location of the global configuration file. + +.TP +.I /var/lib/bluetooth/nn:nn:nn:nn:nn:nn/linkkeys +Default location for link keys of paired devices. The directory +\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP +is the address of the local device. The file is line separated, with +the following columns separated by whitespace: + +\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP Remote device address. + +\fInnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn\fP Link key. + +\fIn\fP Link type integer. + +.TP +.I /var/lib/bluetooth/nn:nn:nn:nn:nn:nn/names +Default location for the device name cache. The directory +\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP +is the address of the local device. The file is line separated, with +the following columns separated by whitespace: + +\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP Remote device address. + +\fIname\fP Remote device name, terminated with newline. + +.TP +.I /var/lib/bluetooth/nn:nn:nn:nn:nn:nn/features +Default location for the features cache. The directory +\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP +is the address of the local device. The file is line separated, with +the following columns separated by whitespace: + +\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP Remote device address. + +\fInnnnnnnnnnnnnnnn\fP Remote device LMP features coded as an 8 byte bitfield. + +.TP +.I /var/lib/bluetooth/nn:nn:nn:nn:nn:nn/manufacturers +Default location for the manufacturers cache. The directory +\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP +is the address of the local device. The file is line separated, with +the following columns separated by whitespace: + +\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP Remote device address. + +\fIn\fP Remote device manufacturer integer. + +\fIn\fP Remote device LMP version integer. + +\fIn\fP Remote device LMP sub-version integer. + +.SH "SEE ALSO" +\fBhcid.conf\fR(5) +.SH "AUTHOR" +This manual page was written by Philipp Matthias Hahn and Fredrik Noring. diff --git a/hcid/hcid.conf b/hcid/hcid.conf new file mode 100644 index 00000000..b6ce3b48 --- /dev/null +++ b/hcid/hcid.conf @@ -0,0 +1,57 @@ +# +# HCI daemon configuration file. +# + +# HCId options +options { + # Automatically initialize new devices + autoinit yes; + + # Security Manager mode + # none - Security manager disabled + # auto - Use local PIN for incoming connections + # user - Always ask user for a PIN + # + security user; + + # Pairing mode + # none - Pairing disabled + # multi - Allow pairing with already paired devices + # once - Pair once and deny successive attempts + pairing multi; + + # Default PIN code for incoming connections + passkey "BlueZ"; +} + +# Default settings for HCI devices +device { + # Local device name + # %d - device id + # %h - host name + name "BlueZ (%d)"; + + # Local device class + class 0x000100; + + # Default packet type + #pkt_type DH1,DM1,HV1; + + # Inquiry and Page scan + iscan enable; pscan enable; + + # Default link mode + # none - no specific policy + # accept - always accept incoming connections + # master - become master on incoming connections, + # deny role switch on outgoing connections + lm accept; + + # Default link policy + # none - no specific policy + # rswitch - allow role switch + # hold - allow hold mode + # sniff - allow sniff mode + # park - allow park mode + lp rswitch,hold,sniff,park; +} diff --git a/hcid/hcid.conf.5 b/hcid/hcid.conf.5 new file mode 100644 index 00000000..cb5bcfa9 --- /dev/null +++ b/hcid/hcid.conf.5 @@ -0,0 +1,227 @@ +.TH "HCID.CONF" "5" "March 2004" "hcid.conf - HCI daemon" "System management commands" +.SH "NAME" +/etc/bluetooth/hcid.conf \- Configuration file for the hcid Bluetooth HCI daemon + +.SH "DESCRIPTION" +/etc/bluetooth/hcid.conf contains all the options needed by the Bluetooth Host Controller Interface daemon. + +It consists of sections and parameters. A section begins with +the name of the section followed by optional specifiers and the +parameters inside curly brackets. Sections contain parameters of +the form: +.TP +\fIname\fP \fIvalue1\fP, \fIvalue2\fP ... ; + +.PP +Any character after a hash ('#') character is ignored until newline. +Whitespace is also ignored. + + +The valid section names for +.B hcid.conf +are, at the moment: + +.TP +.B options +contains generic options for hcid and the pairing policy. +.TP +.B device +contains lower\-level options for the hci devices connected to the computer. +.SH "OPTIONS SECTION" +The following parameters may be present in an option section: + + +.TP +\fBautoinit\fP yes|no + +Automatically initialize newly connected devices. The default is \fIno\fP. + + +.TP +\fBpairing\fP none|multi|once + +\fInone\fP means that pairing is disabled. \fImulti\fP allows pairing +with already paired devices. \fIonce\fP allows pairing once and denies +successive attempts. The default hcid configuration is shipped with \fBmulti\fP +enabled + +.TP +\fBoffmode\fP noscan|devdown + +\fInoscan\fP means that page and inquiry scans are disabled when you call +SetMode("off"). \fIdevdown\fP sets the adapter into down state (same what +\fIhciconfig hci0 down\fP does). + +.TP +\fBdeviceid\fP <vendor>:<product>:<version> + +This option allows to specify the vendor and product information of the +Bluetooth device ID service record. + +.TP +\fBpasskey\fP "\fIpin\fP" + +The default PIN for incoming connections if \fBsecurity\fP has been +set to \fIauto\fP. + +.TP +\fBsecurity\fP none|auto|user + +\fInone\fP means the security manager is disabled. \fIauto\fP uses +local PIN, by default from pin_code, for incoming +connections. \fIuser\fP always asks the user for a PIN. + +.SH "DEVICE SECTION" +Parameters within a device section with no specifier, the default +device section, will be applied to all devices and device sections +where these are unspecified. The following optional device specifiers +are supported: + +.TP +\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP + +Parameters specified within this section will be applied to the device +with this \fIdevice bluetooth address\fP. All other parameters are applied from +the default section. + +.TP +\fBhci\fIn\fP + +Parameters specified within this section will be applied to the device +with this \fIdevice interface\fP, unless that device is matched by a +\fIdevice address\fP section. All other parameters are applied from +the default section. + + +.PP +\fBNote\fP: Most of the options supported in the \fBdevice\fP section are described to some extent in the bluetooth specification version 1.2 Vol2, Part E section 6. Please refer to it for technical details. + +.PP +The following parameters may be present in a device section: + +.TP +\fBname\fP "\fIname\fP" + +The device name. \fI%d\fP inserts the device id. \fI%h\fP inserts +the host name. + + +.TP +\fBclass\fP 0x\fISSDDdd\fP (three bytes) + +The Bluetooth Device Class is described in the Bluetooth Specification section 1.2 ("Assigned Numbers \- Bluetooth Baseband"). + +The default shipped with hcid is 0x000100 which simply stands for "Computer". + +The Bluetooth device class is a high\-level description of the bluetooth device, composed of three bytes: the "Major Service Class" (byte "SS" above), the "Major Device Class" (byte "DD" above) and the "Minor Device Class" (byte "dd" above). These classes describe the high\-level capabilities of the device, such as "Networking Device", "Computer", etc. This information is often used by clients who are looking for a certain type of service around them. + +Where it becomes tricky is that another type of mechanism for service discovery exists: "SDP", as in "Service Discovery Protocol". + +In practice, most Bluetooth clients scan their surroundings in two successive steps: they first look for all bluetooth devices around them and find out their "class". You can do this on Linux with the \fBhcitool scan\fP command. Then, they use SDP in order to check if a device in a given class offers the type of service that they want. + +This means that the hcid.conf "class" parameter needs to be set up properly if particular services are running on the host, such as "PAN", or "OBEX Obect Push", etc: in general a device looking for a service such as "Network Access Point" will only scan for this service on devices containing "Networking" in their major service class. + + +.IP +Major service class byte allocation (from LSB to MSB): + +Bit 1: Positioning (Location identification) + +Bit 2: Networking (LAN, Ad hoc, ...) + +Bit 3: Rendering (Printing, Speaker, ...) + +Bit 4: Capturing (Scanner, Microphone, ...) + +Bit 5: Object Transfer (v\-Inbox, v\-Folder, ...) + +Bit 6: Audio (Speaker, Microphone, Headset service, ...) + +Bit 7: Telephony (Cordless telephony, Modem, Headset service, ...) + +Bit 8: Information (WEB\-server, WAP\-server, ...) + +.IP +Example: class 0x02hhhh : the device offers networking service + + +.IP +Major device class allocation: + +0x00: Miscellaneous + +0x01: Computer (desktop,notebook, PDA, organizers, .... ) + +0x02: Phone (cellular, cordless, payphone, modem, ...) + +0x03: LAN /Network Access point + +0x04: Audio/Video (headset,speaker,stereo, video display, vcr..... + +0x05: Peripheral (mouse, joystick, keyboards, ..... ) + +0x06: Imaging (printing, scanner, camera, display, ...) + +Other values are not defined (refer to the Bluetooth specification for more details + +.IP +Minor device class allocation: the meaning of this byte depends on the major class allocation, please refer to the Bluetooth specifications for more details). + +.IP +.B Example: +if PAND runs on your server, you need to set up at least \fBclass 0x020100\fP, which stands for "Service Class: Networking" and "Device Class: Computer, Uncategorized". + + +.TP +\fBiscan\fP enable|disable +.TP +\fBpscan\fP enable|disable + +Bluetooth devices discover and connect to each other through the use of two special Bluetooth channels, the Inquiry and Page channels (described in the Bluetooth Spec Volume 1, Part A, Section 3.3.3, page 35). These two options enable the channels on the bluetooth device. + +\fBiscan enable\fP: makes the bluetooth device "discoverable" by enabling it to answer "inquiries" from other nearby bluetooth devices. + +\fBpscan enable\fP: makes the bluetooth device "connectable to" by enabling the use of the "page scan" channel. + +.TP +\fBlm\fP none|accept,master + +\fInone\fP means no specific policy. \fIaccept\fP means always accept +incoming connections. \fImaster\fP means become master on incoming +connections and deny role switch on outgoing connections. + +.TP +\fBlp\fP none|rswitch,hold,sniff,park + +\fInone\fP means no specific policy. \fIrswitch\fP means allow role +switch. \fIhold\fP means allow hold mode. \fIsniff\fP means allow +sniff mode. \fIpark\fP means allow park mode. Several options can be +combined. + +This option determines the various operational modes that are allowed for this device when it participates to a piconet. Normally hold and sniff should be enabled for standard operations. + +hold: this mode is related to synchronous communications (SCO voice channel for example). + +sniff: when in this mode, a device is only present on the piconet during determined slots of time, allowing it to do other things when it is "absent", for example to scan for other bluetooth devices. + +park: this is a mode where the device is put on standby on the piconet, for power\-saving purposes for example. + +rswitch: this is a mode that enables role\-switch (master <\-> slave) between two devices in a piconet. It is not clear whether this needs to be enabled in order to make the "lm master" setting work properly or not. + +.TP +\fBpageto\fP \fIn\fP + +Page Timeout measured in number of baseband slots. Interval length = N * 0.625 msec (1 baseband slot) + +.TP +\fBdiscovto\fP \fIn\fP + +The time in seconds that the device will stay in discoverable mode. 0 disables this feature and forces the device to be always discoverable. + +.SH "FILES" +.TP +.I /etc/bluetooth/hcid.conf +Default location of the global configuration file. + +.SH "AUTHOR" +This manual page was written by Edouard Lafargue, Fredrik Noring, Maxim Krasnyansky and Marcel Holtmann. diff --git a/hcid/hcid.h b/hcid/hcid.h new file mode 100644 index 00000000..8a4c0f48 --- /dev/null +++ b/hcid/hcid.h @@ -0,0 +1,218 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com> + * Copyright (C) 2002-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 + * + */ + +#include <time.h> +#include <sys/types.h> + +#include <glib.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/hci.h> + +#include "logging.h" + +#define HCID_CONFIG_FILE CONFIGDIR "/hcid.conf" + +#define HCID_DEFAULT_DISCOVERABLE_TIMEOUT 180 /* 3 minutes */ + +/* When all services should trust a remote device */ +#define GLOBAL_TRUST "[all]" + +enum { + HCID_SET_NAME, + HCID_SET_CLASS, + HCID_SET_VOICE, + HCID_SET_INQMODE, + HCID_SET_PAGETO, + HCID_SET_DISCOVTO, + HCID_SET_PTYPE, + HCID_SET_LM, + HCID_SET_LP, +}; + +/* + * Scanning modes, used by DEV_SET_MODE + * off: remote devices are not allowed to find or connect to this device + * connectable: remote devices are allowed to connect, but they are not + * allowed to find it. + * discoverable: remote devices are allowed to connect and find this device + * limited: limited discoverable - GIAC + IAC enabled and set limited + * bit on device class. + */ + +#define MODE_OFF 0x00 +#define MODE_CONNECTABLE 0x01 +#define MODE_DISCOVERABLE 0x02 +#define MODE_LIMITED 0x03 +#define MODE_UNKNOWN 0xff + +struct device_opts { + unsigned long flags; + char *name; + uint32_t class; + uint16_t voice; + uint8_t inqmode; + uint16_t pageto; + uint16_t pkt_type; + uint16_t link_mode; + uint16_t link_policy; + uint8_t scan; + uint8_t mode; + uint32_t discovto; +}; + +extern struct device_opts default_device; +extern struct device_opts *parser_device; + +struct device_list { + char *ref; /* HCI device or Bluetooth address */ + struct device_list *next; + struct device_opts opts; +}; + +struct hcid_opts { + char host_name[40]; + int auto_init; + int security; + int pairing; + int offmode; + char deviceid[15]; + + char *config_file; + + uint8_t pin_code[16]; + int pin_len; + + int sock; +}; +extern struct hcid_opts hcid; + +typedef enum { + REQ_PENDING, + REQ_SENT +} req_status_t; + +struct hci_req_data { + int dev_id; + int event; + req_status_t status; + bdaddr_t dba; + uint16_t ogf; + uint16_t ocf; + void *cparam; + int clen; +}; + +struct hci_req_data *hci_req_data_new(int dev_id, const bdaddr_t *dba, uint16_t ogf, uint16_t ocf, int event, const void *cparam, int clen); +void hci_req_queue_append(struct hci_req_data *data); +void hci_req_queue_remove(int dev_id, bdaddr_t *dba); + +#define HCID_SEC_NONE 0 +#define HCID_SEC_AUTO 1 +#define HCID_SEC_USER 2 + +#define HCID_PAIRING_NONE 0 +#define HCID_PAIRING_MULTI 1 +#define HCID_PAIRING_ONCE 2 + +#define HCID_OFFMODE_DEVDOWN 0 +#define HCID_OFFMODE_NOSCAN 1 + +int read_config(char *file); + +struct device_opts *alloc_device_opts(char *ref); + +uint8_t get_startup_scan(int hdev); +uint8_t get_startup_mode(int hdev); +int get_discoverable_timeout(int dev_id); + +void init_security_data(void); +void start_security_manager(int hdev); +void stop_security_manager(int hdev); +void toggle_pairing(int enable); + +void set_pin_length(bdaddr_t *sba, int length); + +void init_adapters(void); +int add_adapter(uint16_t dev_id); +int remove_adapter(uint16_t dev_id); +int start_adapter(uint16_t dev_id); +int stop_adapter(uint16_t dev_id); +int update_adapter(uint16_t dev_id); + +int get_device_address(uint16_t dev_id, char *address, size_t size); +int get_device_class(uint16_t dev_id, uint8_t *class); +int set_device_class(uint16_t dev_id, uint8_t *class); +int get_device_version(uint16_t dev_id, char *version, size_t size); +int get_device_revision(uint16_t dev_id, char *revision, size_t size); +int get_device_manufacturer(uint16_t dev_id, char *manufacturer, size_t size); +int get_device_company(uint16_t dev_id, char *company, size_t size); + +int set_simple_pairing_mode(uint16_t dev_id, uint8_t mode); +int get_device_name(uint16_t dev_id, char *name, size_t size); +int set_device_name(uint16_t dev_id, const char *name); +int get_device_alias(uint16_t dev_id, const bdaddr_t *bdaddr, char *alias, size_t size); +int set_device_alias(uint16_t dev_id, const bdaddr_t *bdaddr, const char *alias); + +int get_encryption_key_size(uint16_t dev_id, const bdaddr_t *baddr); + +int write_discoverable_timeout(bdaddr_t *bdaddr, int timeout); +int read_discoverable_timeout(bdaddr_t *bdaddr, int *timeout); +int write_device_mode(bdaddr_t *bdaddr, const char *mode); +int read_device_mode(bdaddr_t *bdaddr, char *mode, int length); +int read_on_mode(bdaddr_t *bdaddr, char *mode, int length); +int write_local_name(bdaddr_t *bdaddr, char *name); +int read_local_name(bdaddr_t *bdaddr, char *name); +int write_local_class(bdaddr_t *bdaddr, uint8_t *class); +int read_local_class(bdaddr_t *bdaddr, uint8_t *class); +int write_remote_class(bdaddr_t *local, bdaddr_t *peer, uint32_t class); +int read_remote_class(bdaddr_t *local, bdaddr_t *peer, uint32_t *class); +int write_device_name(bdaddr_t *local, bdaddr_t *peer, char *name); +int read_device_name(bdaddr_t *local, bdaddr_t *peer, char *name); +int write_remote_eir(bdaddr_t *local, bdaddr_t *peer, uint8_t *data); +int write_l2cap_info(bdaddr_t *local, bdaddr_t *peer, + uint16_t mtu_result, uint16_t mtu, + uint16_t mask_result, uint32_t mask); +int read_l2cap_info(bdaddr_t *local, bdaddr_t *peer, + uint16_t *mtu_result, uint16_t *mtu, + uint16_t *mask_result, uint32_t *mask); +int write_version_info(bdaddr_t *local, bdaddr_t *peer, uint16_t manufacturer, uint8_t lmp_ver, uint16_t lmp_subver); +int write_features_info(bdaddr_t *local, bdaddr_t *peer, unsigned char *features); +int write_lastseen_info(bdaddr_t *local, bdaddr_t *peer, struct tm *tm); +int write_lastused_info(bdaddr_t *local, bdaddr_t *peer, struct tm *tm); +int write_link_key(bdaddr_t *local, bdaddr_t *peer, unsigned char *key, uint8_t type, int length); +int read_link_key(bdaddr_t *local, bdaddr_t *peer, unsigned char *key, uint8_t *type); +int read_pin_length(bdaddr_t *local, bdaddr_t *peer); +int read_pin_code(bdaddr_t *local, bdaddr_t *peer, char *pin); +gboolean read_trust(const bdaddr_t *local, const char *addr, const char *service); +int write_trust(bdaddr_t *local, const char *addr, const char *service, gboolean trust); +GSList *list_trusts(bdaddr_t *local, const char *service); +int write_device_profiles(bdaddr_t *src, bdaddr_t *dst, const char *profiles); +int delete_entry(bdaddr_t *src, const char *storage, const char *key); + +gboolean plugin_init(GKeyFile *config); +void plugin_cleanup(void); +void __probe_servers(const char *adapter); +void __remove_servers(const char *adapter); diff --git a/hcid/kword.c b/hcid/kword.c new file mode 100644 index 00000000..3a89e5a3 --- /dev/null +++ b/hcid/kword.c @@ -0,0 +1,102 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com> + * Copyright (C) 2002-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 <config.h> +#endif + +#include <stdio.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/hci.h> +#include <bluetooth/hci_lib.h> + +#include "hcid.h" +#include "kword.h" +#include "parser.h" + +struct kword cfg_keyword[] = { + { "options", K_OPTIONS }, + { "default", K_DEVICE }, + { "device", K_DEVICE }, + { "autoinit", K_AUTOINIT }, + { "security", K_SECURITY }, + { "pairing", K_PAIRING }, + { "offmode", K_OFFMODE }, + { "deviceid", K_DEVICEID }, + { "pkt_type", K_PTYPE }, + { "lm", K_LM }, + { "lp", K_LP }, + { "iscan", K_ISCAN }, + { "pscan", K_PSCAN }, + { "name", K_NAME }, + { "class", K_CLASS }, + { "voice", K_VOICE }, + { "pageto", K_PAGETO }, + { "discovto", K_DISCOVTO }, + { "passkey", K_PASSKEY }, + + { "yes", K_YES }, + { "no", K_NO }, + { "enable", K_YES }, + { "disable", K_NO }, + { NULL , 0 } +}; + +struct kword sec_param[] = { + { "none", HCID_SEC_NONE }, + { "auto", HCID_SEC_AUTO }, + { "user", HCID_SEC_USER }, + { NULL , 0 } +}; + +struct kword pair_param[] = { + { "none", HCID_PAIRING_NONE }, + { "multi", HCID_PAIRING_MULTI }, + { "once", HCID_PAIRING_ONCE }, + { NULL , 0 } +}; + +struct kword off_param[] = { + { "devdown", HCID_OFFMODE_DEVDOWN }, + { "noscan", HCID_OFFMODE_NOSCAN }, + { NULL , 0 } +}; + +int lineno; + +int find_keyword(struct kword *kw, char *str) +{ + while (kw->str) { + if (!strcmp(str,kw->str)) + return kw->type; + kw++; + } + return -1; +} diff --git a/hcid/kword.h b/hcid/kword.h new file mode 100644 index 00000000..ac4781cc --- /dev/null +++ b/hcid/kword.h @@ -0,0 +1,37 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com> + * Copyright (C) 2002-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 + * + */ + +struct kword { + char *str; + int type; +}; +extern int lineno; + +extern struct kword cfg_keyword[]; +extern struct kword sec_param[]; +extern struct kword pair_param[]; +extern struct kword off_param[]; + +int find_keyword(struct kword *kw, char *str); diff --git a/hcid/lexer.l b/hcid/lexer.l new file mode 100644 index 00000000..768a0783 --- /dev/null +++ b/hcid/lexer.l @@ -0,0 +1,160 @@ +%{ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com> + * Copyright (C) 2002-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 <config.h> +#endif + +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <sys/socket.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/hci.h> +#include <bluetooth/hci_lib.h> + +#include "hcid.h" +#include "kword.h" +#include "parser.h" + +static char str_buf[255]; + +#define ECHO {;} +#define YY_DECL int yylex(void) + +int cfg_error(const char *ftm, ...); +int yyerror(char *str); + +%} + +%option nounput + +hex 0x[0-9a-zA-Z]+ +num [0-9]+ +kword [A-Za-z0-9\_\-]+ +word [A-Za-z0-9\-\_+=\!\$\#\%\&\*\^\@@\\\~\.]+ +wordnm {word}:{num} +list ({word}\,*)+ +comment \#.*\n +fname [A-Za-z0-9\_\.\-]+ +path (\/{fname})+ +string \".*\" +hci hci[0-9]+ +hextuple [0-9a-zA-Z][0-9a-zA-Z] +hexquad {hextuple}{hextuple} +bdaddr {hextuple}:{hextuple}:{hextuple}:{hextuple}:{hextuple}:{hextuple} +id {hexquad}:{hexquad} + +%x OPTION PARAM + +%% +[ \t] { + /* Skip spaces and tabs */ + ; +} + +{comment} { + /* Skip comments */ + lineno++; +} + +\n { + lineno++; +} + +{hci} { + yylval.str = yytext; + return HCI; +} + +{bdaddr} { + yylval.str = yytext; + return BDADDR; +} + +{hex} { + yylval.num = strtol(yytext, NULL, 16); + return NUM; +} + +{num} { + yylval.num = atoi(yytext); + return NUM; +} + +{kword} { + int kw = find_keyword(cfg_keyword, yytext); + if( kw != -1 ) + return kw; + + yylval.str = yytext; + return WORD; +} + +{word} { + yylval.str = yytext; + return WORD; +} + +{string} { + if (yyleng > sizeof(str_buf) - 1) { + yyerror("string too long"); + return 0; + } + + strncpy(str_buf, yytext + 1, yyleng - 2); + str_buf[yyleng - 2] = '\0'; + + yylval.str = str_buf; + return STRING; +} + +{list} { + yylval.str = yytext; + return LIST; +} + +{path} { + yylval.str = yytext; + return PATH; +} + +{id} { + yylval.str = yytext; + return ID; +} + +. { + return *yytext; +} + +%% + +int yywrap(void) +{ + return 1; +} diff --git a/hcid/list-devices b/hcid/list-devices new file mode 100755 index 00000000..ec6c580a --- /dev/null +++ b/hcid/list-devices @@ -0,0 +1,52 @@ +#!/usr/bin/python + +import dbus + +bus = dbus.SystemBus() + +manager = dbus.Interface(bus.get_object("org.bluez", "/"), + "org.bluez.Manager") + +def extract_uuids(uuid_list): + list = "" + for uuid in uuid_list: + if (uuid.endswith("-0000-1000-8000-00805f9b34fb")): + if (uuid.startswith("0000")): + val = "0x" + uuid[4:8] + else: + val = "0x" + uuid[0:8] + else: + val = str(uuid) + list = list + val + " " + return list + +adapter_list = manager.ListAdapters() + +for i in adapter_list: + adapter = dbus.Interface(bus.get_object("org.bluez", i), + "org.bluez.Adapter") + print "[ " + i + " ]" + + properties = adapter.GetProperties() + for key in properties.keys(): + print " %s = %s" % (key, properties[key]) + + device_list = adapter.ListDevices() + + for n in device_list: + device = dbus.Interface(bus.get_object("org.bluez", n), + "org.bluez.Device") + print " [ " + n + " ]" + + properties = device.GetProperties() + for key in properties.keys(): + value = properties[key] + if (key == "UUIDs"): + list = extract_uuids(value) + print " %s = %s" % (key, list) + elif (key == "Class"): + print " %s = 0x%06x" % (key, value) + else: + print " %s = %s" % (key, value) + + print diff --git a/hcid/main.c b/hcid/main.c new file mode 100644 index 00000000..f75f6c1f --- /dev/null +++ b/hcid/main.c @@ -0,0 +1,971 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com> + * Copyright (C) 2002-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 <config.h> +#endif + +#include <stdio.h> +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <signal.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/wait.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/hci.h> +#include <bluetooth/hci_lib.h> + +#include <glib.h> + +#include <dbus/dbus.h> + +#include "hcid.h" +#include "sdpd.h" +#include "adapter.h" +#include "dbus-common.h" +#include "dbus-service.h" +#include "dbus-database.h" +#include "dbus-hci.h" +#include "device.h" +#include "agent.h" + +struct hcid_opts hcid; +struct device_opts default_device; +struct device_opts *parser_device; +static struct device_list *device_list = NULL; +static int child_pipe[2]; + +static GKeyFile *load_config(const char *file) +{ + GError *err = NULL; + GKeyFile *keyfile; + + keyfile = g_key_file_new(); + + if (!g_key_file_load_from_file(keyfile, file, 0, &err)) { + error("Parsing %s failed: %s", file, err->message); + g_error_free(err); + g_key_file_free(keyfile); + return NULL; + } + + return keyfile; +} + +static inline void init_device_defaults(struct device_opts *device_opts) +{ + memset(device_opts, 0, sizeof(*device_opts)); + device_opts->scan = SCAN_PAGE; + device_opts->mode = MODE_CONNECTABLE; + device_opts->name = g_strdup("BlueZ"); + device_opts->discovto = HCID_DEFAULT_DISCOVERABLE_TIMEOUT; +} + +struct device_opts *alloc_device_opts(char *ref) +{ + struct device_list *device; + + device = g_try_new(struct device_list, 1); + if (!device) { + info("Can't allocate devlist opts buffer: %s (%d)", + strerror(errno), errno); + exit(1); + } + + device->ref = g_strdup(ref); + device->next = device_list; + device_list = device; + + memcpy(&device->opts, &default_device, sizeof(struct device_opts)); + device->opts.name = g_strdup(default_device.name); + + return &device->opts; +} + +static void free_device_opts(void) +{ + struct device_list *device, *next; + + g_free(default_device.name); + + for (device = device_list; device; device = next) { + g_free(device->ref); + g_free(device->opts.name); + next = device->next; + g_free(device); + } + + device_list = NULL; +} + +static inline struct device_opts *find_device_opts(char *ref) +{ + struct device_list *device; + + for (device = device_list; device; device = device->next) + if (!strcmp(ref, device->ref)) + return &device->opts; + + return NULL; +} + +static struct device_opts *get_device_opts(int hdev) +{ + struct device_opts *device_opts = NULL; + struct hci_dev_info di; + + /* First try to get BD_ADDR based settings ... */ + if (hci_devinfo(hdev, &di) == 0) { + char addr[18]; + ba2str(&di.bdaddr, addr); + device_opts = find_device_opts(addr); + } + + /* ... then try HCI based settings ... */ + if (!device_opts) { + char ref[8]; + snprintf(ref, sizeof(ref) - 1, "hci%d", hdev); + device_opts = find_device_opts(ref); + } + + /* ... and last use the default settings. */ + if (!device_opts) + device_opts = &default_device; + + return device_opts; +} + +static struct device_opts *get_opts(int hdev) +{ + struct device_opts *device_opts = NULL; + struct hci_dev_info di; + char addr[18]; + int sock; + + if (hdev < 0) + return NULL; + + sock = hci_open_dev(hdev); + if (sock < 0) + goto no_address; + + if (hci_devinfo(hdev, &di) < 0) { + close(sock); + goto no_address; + } + + close(sock); + + ba2str(&di.bdaddr, addr); + device_opts = find_device_opts(addr); + +no_address: + if (!device_opts) { + char ref[8]; + snprintf(ref, sizeof(ref) - 1, "hci%d", hdev); + device_opts = find_device_opts(ref); + } + + if (!device_opts) + device_opts = &default_device; + + return device_opts; +} + +uint8_t get_startup_scan(int hdev) +{ + struct device_opts *device_opts = get_opts(hdev); + if (!device_opts) + return SCAN_DISABLED; + + return device_opts->scan; +} + +uint8_t get_startup_mode(int hdev) +{ + struct device_opts *device_opts = get_opts(hdev); + if (!device_opts) + return MODE_OFF; + + return device_opts->mode; +} + +int get_discoverable_timeout(int hdev) +{ + struct device_opts *device_opts = NULL; + struct hci_dev_info di; + char addr[18]; + int sock, timeout; + + if (hdev < 0) + return HCID_DEFAULT_DISCOVERABLE_TIMEOUT; + + sock = hci_open_dev(hdev); + if (sock < 0) + goto no_address; + + if (hci_devinfo(hdev, &di) < 0) { + close(sock); + goto no_address; + } + + close(sock); + + if (read_discoverable_timeout(&di.bdaddr, &timeout) == 0) + return timeout; + + ba2str(&di.bdaddr, addr); + device_opts = find_device_opts(addr); + +no_address: + if (!device_opts) { + char ref[8]; + snprintf(ref, sizeof(ref) - 1, "hci%d", hdev); + device_opts = find_device_opts(ref); + } + + if (!device_opts) + device_opts = &default_device; + + return device_opts->discovto; +} + +void update_service_classes(const bdaddr_t *bdaddr, uint8_t value) +{ + struct hci_dev_list_req *dl; + struct hci_dev_req *dr; + int i, sk; + + sk = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI); + if (sk < 0) + return; + + dl = g_malloc0(HCI_MAX_DEV * sizeof(*dr) + sizeof(*dl)); + + dl->dev_num = HCI_MAX_DEV; + dr = dl->dev_req; + + if (ioctl(sk, HCIGETDEVLIST, dl) < 0) { + close(sk); + g_free(dl); + return; + } + + dr = dl->dev_req; + + for (i = 0; i < dl->dev_num; i++, dr++) { + struct hci_dev_info di; + uint8_t cls[3]; + int dd; + + if (hci_devinfo(dr->dev_id, &di) < 0) + continue; + + if (hci_test_bit(HCI_RAW, &di.flags)) + continue; + + if (!hci_test_bit(HCI_UP, &di.flags)) + continue; + + if (get_device_class(di.dev_id, cls) < 0) + continue; + + dd = hci_open_dev(di.dev_id); + if (dd < 0) + continue; + + set_service_classes(dd, cls, value); + + hci_close_dev(dd); + + update_adapter(di.dev_id); + } + + g_free(dl); + + close(sk); +} + +/* + * Device name expansion + * %d - device id + */ +static char *expand_name(char *dst, int size, char *str, int dev_id) +{ + register int sp, np, olen; + char *opt, buf[10]; + + if (!str && !dst) + return NULL; + + sp = np = 0; + while (np < size - 1 && str[sp]) { + switch (str[sp]) { + case '%': + opt = NULL; + + switch (str[sp+1]) { + case 'd': + sprintf(buf, "%d", dev_id); + opt = buf; + break; + + case 'h': + opt = hcid.host_name; + break; + + case '%': + dst[np++] = str[sp++]; + /* fall through */ + default: + sp++; + continue; + } + + if (opt) { + /* substitute */ + olen = strlen(opt); + if (np + olen < size - 1) + memcpy(dst + np, opt, olen); + np += olen; + } + sp += 2; + continue; + + case '\\': + sp++; + /* fall through */ + default: + dst[np++] = str[sp++]; + break; + } + } + dst[np] = '\0'; + return dst; +} + +static gboolean child_exit(GIOChannel *io, GIOCondition cond, void *user_data) +{ + int status, fd = g_io_channel_unix_get_fd(io); + pid_t child_pid; + + if (read(fd, &child_pid, sizeof(child_pid)) != sizeof(child_pid)) { + error("child_exit: unable to read child pid from pipe"); + return TRUE; + } + + if (waitpid(child_pid, &status, 0) != child_pid) + error("waitpid(%d) failed", child_pid); + else + debug("child %d exited", child_pid); + + return TRUE; +} + +static void at_child_exit(void) +{ + pid_t pid = getpid(); + + if (write(child_pipe[1], &pid, sizeof(pid)) != sizeof(pid)) + error("unable to write to child pipe"); +} + +static void configure_device(int dev_id) +{ + struct device_opts *device_opts; + struct hci_dev_req dr; + struct hci_dev_info di; + char mode[14]; + int dd; + + device_opts = get_device_opts(dev_id); + + if (hci_devinfo(dev_id, &di) < 0) + return; + + if (hci_test_bit(HCI_RAW, &di.flags)) + return; + + /* Set default discoverable timeout if not set */ + if (!(device_opts->flags & (1 << HCID_SET_DISCOVTO))) + device_opts->discovto = HCID_DEFAULT_DISCOVERABLE_TIMEOUT; + + /* Set scan mode */ + if (read_device_mode(&di.bdaddr, mode, sizeof(mode)) == 0) { + if (!strcmp(mode, "off") && hcid.offmode == HCID_OFFMODE_NOSCAN) { + device_opts->mode = MODE_OFF; + device_opts->scan = SCAN_DISABLED; + } else if (!strcmp(mode, "connectable")) { + device_opts->mode = MODE_CONNECTABLE; + device_opts->scan = SCAN_PAGE; + } else if (!strcmp(mode, "discoverable")) { + /* Set discoverable only if timeout is 0 */ + if (!get_discoverable_timeout(dev_id)) { + device_opts->scan = SCAN_PAGE | SCAN_INQUIRY; + device_opts->mode = MODE_DISCOVERABLE; + } else { + device_opts->scan = SCAN_PAGE; + device_opts->mode = MODE_CONNECTABLE; + } + } else if (!strcmp(mode, "limited")) { + /* Set discoverable only if timeout is 0 */ + if (!get_discoverable_timeout(dev_id)) { + device_opts->scan = SCAN_PAGE | SCAN_INQUIRY; + device_opts->mode = MODE_LIMITED; + } else { + device_opts->scan = SCAN_PAGE; + device_opts->mode = MODE_CONNECTABLE; + } + } + } + + /* Do configuration in the separate process */ + switch (fork()) { + case 0: + atexit(at_child_exit); + break; + case -1: + error("Fork failed. Can't init device hci%d: %s (%d)", + dev_id, strerror(errno), errno); + default: + return; + } + + dd = hci_open_dev(dev_id); + if (dd < 0) { + error("Can't open device hci%d: %s (%d)", + dev_id, strerror(errno), errno); + exit(1); + } + + memset(&dr, 0, sizeof(dr)); + dr.dev_id = dev_id; + + /* Set packet type */ + if ((device_opts->flags & (1 << HCID_SET_PTYPE))) { + dr.dev_opt = device_opts->pkt_type; + if (ioctl(dd, HCISETPTYPE, (unsigned long) &dr) < 0) { + error("Can't set packet type on hci%d: %s (%d)", + dev_id, strerror(errno), errno); + } + } + + /* Set link mode */ + if ((device_opts->flags & (1 << HCID_SET_LM))) { + dr.dev_opt = device_opts->link_mode; + if (ioctl(dd, HCISETLINKMODE, (unsigned long) &dr) < 0) { + error("Can't set link mode on hci%d: %s (%d)", + dev_id, strerror(errno), errno); + } + } + + /* Set link policy */ + if ((device_opts->flags & (1 << HCID_SET_LP))) { + dr.dev_opt = device_opts->link_policy; + if (ioctl(dd, HCISETLINKPOL, (unsigned long) &dr) < 0) { + error("Can't set link policy on hci%d: %s (%d)", + dev_id, strerror(errno), errno); + } + } + + /* Set device name */ + if ((device_opts->flags & (1 << HCID_SET_NAME)) && device_opts->name) { + change_local_name_cp cp; + + memset(cp.name, 0, sizeof(cp.name)); + expand_name((char *) cp.name, sizeof(cp.name), + device_opts->name, dev_id); + + hci_send_cmd(dd, OGF_HOST_CTL, OCF_CHANGE_LOCAL_NAME, + CHANGE_LOCAL_NAME_CP_SIZE, &cp); + } + + /* Set device class */ + if ((device_opts->flags & (1 << HCID_SET_CLASS))) { + write_class_of_dev_cp cp; + uint32_t class; + uint8_t cls[3]; + + if (read_local_class(&di.bdaddr, cls) < 0) { + class = htobl(device_opts->class); + cls[2] = get_service_classes(&di.bdaddr); + memcpy(cp.dev_class, &class, 3); + } else { + if (!(device_opts->scan & SCAN_INQUIRY)) + cls[1] &= 0xdf; /* Clear discoverable bit */ + cls[2] = get_service_classes(&di.bdaddr); + memcpy(cp.dev_class, cls, 3); + } + + hci_send_cmd(dd, OGF_HOST_CTL, OCF_WRITE_CLASS_OF_DEV, + WRITE_CLASS_OF_DEV_CP_SIZE, &cp); + } + + /* Set page timeout */ + if ((device_opts->flags & (1 << HCID_SET_PAGETO))) { + write_page_timeout_cp cp; + + cp.timeout = htobs(device_opts->pageto); + hci_send_cmd(dd, OGF_HOST_CTL, OCF_WRITE_PAGE_TIMEOUT, + WRITE_PAGE_TIMEOUT_CP_SIZE, &cp); + } + + /* Set voice setting */ + if ((device_opts->flags & (1 << HCID_SET_VOICE))) { + write_voice_setting_cp cp; + + cp.voice_setting = htobl(device_opts->voice); + hci_send_cmd(dd, OGF_HOST_CTL, OCF_WRITE_VOICE_SETTING, + WRITE_VOICE_SETTING_CP_SIZE, &cp); + } + + exit(0); +} + +static void init_device(int dev_id) +{ + struct hci_dev_info di; + int dd; + + /* Do initialization in the separate process */ + switch (fork()) { + case 0: + atexit(at_child_exit); + break; + case -1: + error("Fork failed. Can't init device hci%d: %s (%d)", + dev_id, strerror(errno), errno); + default: + return; + } + + dd = hci_open_dev(dev_id); + if (dd < 0) { + error("Can't open device hci%d: %s (%d)", + dev_id, strerror(errno), errno); + exit(1); + } + + /* Start HCI device */ + if (ioctl(dd, HCIDEVUP, dev_id) < 0 && errno != EALREADY) { + error("Can't init device hci%d: %s (%d)", + dev_id, strerror(errno), errno); + goto fail; + } + + if (hci_devinfo(dev_id, &di) < 0) + goto fail; + + if (hci_test_bit(HCI_RAW, &di.flags)) + goto done; + + if (hcid.offmode == HCID_OFFMODE_DEVDOWN) { + char mode[16]; + + if (read_device_mode(&di.bdaddr, mode, sizeof(mode)) == 0 && + strcmp(mode, "off") == 0) { + ioctl(dd, HCIDEVDOWN, dev_id); + goto done; + } + } + +done: + hci_close_dev(dd); + exit(0); + +fail: + hci_close_dev(dd); + exit(1); +} + +static void device_devreg_setup(int dev_id) +{ + struct hci_dev_info di; + + if (hcid.auto_init) + init_device(dev_id); + + if (hci_devinfo(dev_id, &di) < 0) + return; + + if (!hci_test_bit(HCI_RAW, &di.flags)) + hcid_dbus_register_device(dev_id); +} + +static void device_devup_setup(int dev_id) +{ + add_adapter(dev_id); + if (hcid.auto_init) + configure_device(dev_id); + if (hcid.security) + start_security_manager(dev_id); + start_adapter(dev_id); + hcid_dbus_start_device(dev_id); +} + +static void init_all_devices(int ctl) +{ + struct hci_dev_list_req *dl; + struct hci_dev_req *dr; + int i; + + dl = g_try_malloc0(HCI_MAX_DEV * sizeof(struct hci_dev_req) + sizeof(uint16_t)); + if (!dl) { + info("Can't allocate devlist buffer: %s (%d)", + strerror(errno), errno); + exit(1); + } + + dl->dev_num = HCI_MAX_DEV; + dr = dl->dev_req; + + if (ioctl(ctl, HCIGETDEVLIST, (void *) dl) < 0) { + info("Can't get device list: %s (%d)", + strerror(errno), errno); + exit(1); + } + + for (i = 0; i < dl->dev_num; i++, dr++) { + info("HCI dev %d registered", dr->dev_id); + device_devreg_setup(dr->dev_id); + if (hci_test_bit(HCI_UP, &dr->dev_opt)) { + info("HCI dev %d already up", dr->dev_id); + device_devup_setup(dr->dev_id); + } + } + + g_free(dl); +} + +static void init_defaults(void) +{ + hcid.auto_init = 1; + hcid.security = HCID_SEC_AUTO; + + init_device_defaults(&default_device); +} + +static inline void device_event(GIOChannel *chan, evt_stack_internal *si) +{ + evt_si_device *sd = (void *) &si->data; + + switch (sd->event) { + case HCI_DEV_REG: + info("HCI dev %d registered", sd->dev_id); + device_devreg_setup(sd->dev_id); + break; + + case HCI_DEV_UNREG: + info("HCI dev %d unregistered", sd->dev_id); + hcid_dbus_unregister_device(sd->dev_id); + remove_adapter(sd->dev_id); + break; + + case HCI_DEV_UP: + info("HCI dev %d up", sd->dev_id); + device_devup_setup(sd->dev_id); + break; + + case HCI_DEV_DOWN: + info("HCI dev %d down", sd->dev_id); + hcid_dbus_stop_device(sd->dev_id); + if (hcid.security) + stop_security_manager(sd->dev_id); + stop_adapter(sd->dev_id); + break; + } +} + +static gboolean io_stack_event(GIOChannel *chan, GIOCondition cond, gpointer data) +{ + unsigned char buf[HCI_MAX_FRAME_SIZE], *ptr; + evt_stack_internal *si; + hci_event_hdr *eh; + int type; + size_t len; + GIOError err; + + ptr = buf; + + if ((err = g_io_channel_read(chan, (gchar *) buf, sizeof(buf), &len))) { + if (err == G_IO_ERROR_AGAIN) + return TRUE; + + error("Read from control socket failed: %s (%d)", + strerror(errno), errno); + return FALSE; + } + + type = *ptr++; + + if (type != HCI_EVENT_PKT) + return TRUE; + + eh = (hci_event_hdr *) ptr; + if (eh->evt != EVT_STACK_INTERNAL) + return TRUE; + + ptr += HCI_EVENT_HDR_SIZE; + + si = (evt_stack_internal *) ptr; + switch (si->type) { + case EVT_SI_DEVICE: + device_event(chan, si); + break; + } + + return TRUE; +} + +static GMainLoop *event_loop; + +static void sig_term(int sig) +{ + g_main_loop_quit(event_loop); +} + +static void sig_hup(int sig) +{ + info("Reloading config file"); + + free_device_opts(); + + init_defaults(); + + if (read_config(hcid.config_file) < 0) + error("Config reload failed"); + + init_security_data(); + + init_all_devices(hcid.sock); +} + +static void sig_debug(int sig) +{ + toggle_debug(); +} + +static void usage(void) +{ + printf("hcid - HCI daemon ver %s\n", VERSION); + printf("Usage: \n"); + printf("\thcid [-n] [-d] [-m mtu] [-f config file]\n"); +} + +int main(int argc, char *argv[]) +{ + struct sockaddr_hci addr; + struct hci_filter flt; + struct sigaction sa; + GIOChannel *ctl_io, *child_io; + uint16_t mtu = 0; + int opt, daemonize = 1, debug = 0, sdp = 1, experimental = 0; + GKeyFile *config; + + /* Default HCId settings */ + memset(&hcid, 0, sizeof(hcid)); + hcid.auto_init = 1; + hcid.config_file = HCID_CONFIG_FILE; + hcid.security = HCID_SEC_AUTO; + hcid.pairing = HCID_PAIRING_MULTI; + hcid.offmode = HCID_OFFMODE_NOSCAN; + + if (gethostname(hcid.host_name, sizeof(hcid.host_name) - 1) < 0) + strcpy(hcid.host_name, "noname"); + + strcpy((char *) hcid.pin_code, "BlueZ"); + hcid.pin_len = 5; + + init_defaults(); + + while ((opt = getopt(argc, argv, "ndsm:xf:")) != EOF) { + switch (opt) { + case 'n': + daemonize = 0; + break; + + case 'd': + debug = 1; + break; + + case 's': + sdp = 1; + break; + + case 'm': + mtu = atoi(optarg); + break; + + case 'x': + experimental = 1; + break; + + case 'f': + hcid.config_file = g_strdup(optarg); + break; + + default: + usage(); + exit(1); + } + } + + if (daemonize && daemon(0, 0)) { + error("Can't daemonize: %s (%d)", strerror(errno), errno); + exit(1); + } + + umask(0077); + + start_logging("hcid", "Bluetooth HCI daemon"); + + memset(&sa, 0, sizeof(sa)); + sa.sa_flags = SA_NOCLDSTOP; + sa.sa_handler = sig_term; + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + sa.sa_handler = sig_hup; + sigaction(SIGHUP, &sa, NULL); + + sa.sa_handler = sig_debug; + sigaction(SIGUSR2, &sa, NULL); + + sa.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &sa, NULL); + + if (debug) { + info("Enabling debug information"); + enable_debug(); + } + + /* Create and bind HCI socket */ + if ((hcid.sock = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI)) < 0) { + error("Can't open HCI socket: %s (%d)", + strerror(errno), errno); + exit(1); + } + + /* Set filter */ + hci_filter_clear(&flt); + hci_filter_set_ptype(HCI_EVENT_PKT, &flt); + hci_filter_set_event(EVT_STACK_INTERNAL, &flt); + if (setsockopt(hcid.sock, SOL_HCI, HCI_FILTER, &flt, sizeof(flt)) < 0) { + error("Can't set filter: %s (%d)", + strerror(errno), errno); + exit(1); + } + + addr.hci_family = AF_BLUETOOTH; + addr.hci_dev = HCI_DEV_NONE; + if (bind(hcid.sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + error("Can't bind HCI socket: %s (%d)", + strerror(errno), errno); + exit(1); + } + + config = load_config(CONFIGDIR "/main.conf"); + + if (read_config(hcid.config_file) < 0) + error("Config load failed"); + + if (pipe(child_pipe) < 0) { + error("pipe(): %s (%d)", strerror(errno), errno); + exit(1); + } + + child_io = g_io_channel_unix_new(child_pipe[0]); + g_io_channel_set_close_on_unref(child_io, TRUE); + g_io_add_watch(child_io, + G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL, + child_exit, NULL); + g_io_channel_unref(child_io); + + init_adapters(); + + agent_init(); + + if (experimental) + hcid_dbus_set_experimental(); + + if (hcid_dbus_init() < 0) { + error("Unable to get on D-Bus"); + exit(1); + } + + start_sdp_server(mtu, hcid.deviceid, SDP_SERVER_COMPAT); + set_service_classes_callback(update_service_classes); + + /* Loading plugins has to be done after D-Bus has been setup since + * the plugins might wanna expose some paths on the bus. However the + * best order of how to init various subsystems of the Bluetooth + * daemon needs to be re-worked. */ + plugin_init(config); + + init_security_data(); + + event_loop = g_main_loop_new(NULL, FALSE); + + ctl_io = g_io_channel_unix_new(hcid.sock); + g_io_channel_set_close_on_unref(ctl_io, TRUE); + + g_io_add_watch(ctl_io, G_IO_IN, io_stack_event, NULL); + + g_io_channel_unref(ctl_io); + + /* Initialize already connected devices */ + init_all_devices(hcid.sock); + + g_main_loop_run(event_loop); + + hcid_dbus_unregister(); + + plugin_cleanup(); + + stop_sdp_server(); + + free_device_opts(); + + agent_exit(); + + hcid_dbus_exit(); + + g_main_loop_unref(event_loop); + + if (config) + g_key_file_free(config); + + info("Exit"); + + stop_logging(); + + return 0; +} diff --git a/hcid/manager.c b/hcid/manager.c new file mode 100644 index 00000000..c2d5fdf1 --- /dev/null +++ b/hcid/manager.c @@ -0,0 +1,683 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 <config.h> +#endif + +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <unistd.h> +#include <sys/ioctl.h> +#include <sys/socket.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/hci.h> +#include <bluetooth/hci_lib.h> +#include <bluetooth/sdp.h> +#include <bluetooth/sdp_lib.h> + +#include <glib.h> + +#include <dbus/dbus.h> + +#include <gdbus.h> + +#include "hcid.h" +#include "sdpd.h" +#include "adapter.h" +#include "dbus-common.h" +#include "error.h" +#include "dbus-error.h" +#include "dbus-hci.h" +#include "dbus-service.h" +#include "dbus-database.h" +#include "dbus-security.h" +#include "sdp-xml.h" + +#include "manager.h" + +static DBusConnection *connection = NULL; +static int default_adapter_id = -1; +static GSList *adapters = NULL; + +static inline DBusMessage *invalid_args(DBusMessage *msg) +{ + return g_dbus_create_error(msg, + ERROR_INTERFACE ".InvalidArguments", + "Invalid arguments in method call"); +} + +static inline DBusMessage *no_such_adapter(DBusMessage *msg) +{ + return g_dbus_create_error(msg, + ERROR_INTERFACE ".NoSuchAdapter", + "No such adapter"); +} + +static inline DBusMessage *no_such_service(DBusMessage *msg) +{ + return g_dbus_create_error(msg, + ERROR_INTERFACE ".NoSuchService", + "No such service"); +} + +static inline DBusMessage *failed_strerror(DBusMessage *msg, int err) +{ + return g_dbus_create_error(msg, + ERROR_INTERFACE ".Failed", + strerror(err)); +} + +static DBusMessage *interface_version(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + DBusMessage *reply; + dbus_uint32_t version = 0; + + 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_append_args(reply, DBUS_TYPE_UINT32, &version, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *old_default_adapter(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + DBusMessage *reply; + char path[MAX_PATH_LENGTH], *path_ptr = path; + + if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) + return invalid_args(msg); + + if (default_adapter_id < 0) + return no_such_adapter(msg); + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + snprintf(path, sizeof(path), "%s/hci%d", BASE_PATH, default_adapter_id); + + dbus_message_append_args(reply, DBUS_TYPE_STRING, &path_ptr, + DBUS_TYPE_INVALID); + + return reply; +} + +static int find_by_address(const char *str) +{ + struct hci_dev_list_req *dl; + struct hci_dev_req *dr; + bdaddr_t ba; + int i, sk; + int devid = -1; + + sk = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI); + if (sk < 0) + return -1; + + dl = g_malloc0(HCI_MAX_DEV * sizeof(*dr) + sizeof(*dl)); + + dl->dev_num = HCI_MAX_DEV; + dr = dl->dev_req; + + if (ioctl(sk, HCIGETDEVLIST, dl) < 0) + goto out; + + dr = dl->dev_req; + str2ba(str, &ba); + + for (i = 0; i < dl->dev_num; i++, dr++) { + struct hci_dev_info di; + + if (hci_devinfo(dr->dev_id, &di) < 0) + continue; + + if (hci_test_bit(HCI_RAW, &di.flags)) + continue; + + if (!bacmp(&ba, &di.bdaddr)) { + devid = dr->dev_id; + break; + } + } + +out: + g_free(dl); + close(sk); + return devid; +} + +static DBusMessage *old_find_adapter(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + DBusMessage *reply; + char path[MAX_PATH_LENGTH], *path_ptr = path; + struct hci_dev_info di; + const char *pattern; + int dev_id; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &pattern, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + /* hci_devid() would make sense to use here, except it + is restricted to devices which are up */ + if (!strncmp(pattern, "hci", 3) && strlen(pattern) >= 4) + dev_id = atoi(pattern + 3); + else + dev_id = find_by_address(pattern); + + if (dev_id < 0) + return no_such_adapter(msg); + + if (hci_devinfo(dev_id, &di) < 0) + return no_such_adapter(msg); + + if (hci_test_bit(HCI_RAW, &di.flags)) + return no_such_adapter(msg); + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + snprintf(path, sizeof(path), "%s/hci%d", BASE_PATH, dev_id); + + dbus_message_append_args(reply, DBUS_TYPE_STRING, &path_ptr, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *old_list_adapters(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + DBusMessageIter iter; + DBusMessageIter array_iter; + DBusMessage *reply; + struct hci_dev_list_req *dl; + struct hci_dev_req *dr; + int i, sk; + + if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) + return invalid_args(msg); + + sk = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI); + if (sk < 0) + return failed_strerror(msg, errno); + + dl = g_malloc0(HCI_MAX_DEV * sizeof(*dr) + sizeof(*dl)); + + dl->dev_num = HCI_MAX_DEV; + dr = dl->dev_req; + + if (ioctl(sk, HCIGETDEVLIST, dl) < 0) { + int err = errno; + close(sk); + g_free(dl); + return failed_strerror(msg, err); + } + + dr = dl->dev_req; + + reply = dbus_message_new_method_return(msg); + if (!reply) { + close(sk); + g_free(dl); + return NULL; + } + + dbus_message_iter_init_append(reply, &iter); + + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_TYPE_STRING_AS_STRING, &array_iter); + + for (i = 0; i < dl->dev_num; i++, dr++) { + char path[MAX_PATH_LENGTH], *path_ptr = path; + struct hci_dev_info di; + + if (hci_devinfo(dr->dev_id, &di) < 0) + continue; + + if (hci_test_bit(HCI_RAW, &di.flags)) + continue; + + snprintf(path, sizeof(path), "%s/%s", BASE_PATH, di.name); + + dbus_message_iter_append_basic(&array_iter, + DBUS_TYPE_STRING, &path_ptr); + } + + dbus_message_iter_close_container(&iter, &array_iter); + + g_free(dl); + + close(sk); + + return reply; +} + +static DBusMessage *find_service(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + DBusMessage *reply; + const char *pattern; + struct service *service; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &pattern, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + service = search_service(pattern); + if (!service) + return no_such_service(msg); + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_append_args(reply, DBUS_TYPE_STRING, &service->object_path, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *list_services(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + DBusMessage *reply; + DBusMessageIter iter; + DBusMessageIter array_iter; + + 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_STRING_AS_STRING, &array_iter); + + append_available_services(&array_iter); + + dbus_message_iter_close_container(&iter, &array_iter); + + return reply; +} + +static DBusMessage *activate_service(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + DBusMessage *reply; + const char *pattern; + struct service *service; + const char *busname = "org.bluez"; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &pattern, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + service = search_service(pattern); + if (!service) + return no_such_service(msg); + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_append_args(reply, DBUS_TYPE_STRING, &busname, + DBUS_TYPE_INVALID); + + return reply; +} + +static GDBusMethodTable old_manager_methods[] = { + { "InterfaceVersion", "", "u", interface_version }, + { "DefaultAdapter", "", "s", old_default_adapter }, + { "FindAdapter", "s", "s", old_find_adapter }, + { "ListAdapters", "", "as", old_list_adapters }, + { "FindService", "s", "s", find_service }, + { "ListServices", "", "as", list_services }, + { "ActivateService", "s", "s", activate_service }, + { NULL, NULL, NULL, NULL } +}; + +static GDBusSignalTable old_manager_signals[] = { + { "AdapterAdded", "s" }, + { "AdapterRemoved", "s" }, + { "DefaultAdapterChanged", "s" }, + { "ServiceAdded", "s" }, + { "ServiceRemoved", "s" }, + { NULL, NULL } +}; + +static DBusMessage *default_adapter(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + DBusMessage *reply; + struct adapter *adapter; + char *path; + + adapter = manager_find_adapter_by_id(default_adapter_id); + if (!adapter) + return no_such_adapter(msg); + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + path = adapter->path + ADAPTER_PATH_INDEX; + + dbus_message_append_args(reply, DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *find_adapter(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + DBusMessage *reply; + struct adapter *adapter; + char *path; + struct hci_dev_info di; + const char *pattern; + int dev_id; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &pattern, + DBUS_TYPE_INVALID)) + return NULL; + + /* hci_devid() would make sense to use here, except it + is restricted to devices which are up */ + if (!strncmp(pattern, "hci", 3) && strlen(pattern) >= 4) + dev_id = atoi(pattern + 3); + else + dev_id = find_by_address(pattern); + + if (dev_id < 0) + return no_such_adapter(msg); + + if (hci_devinfo(dev_id, &di) < 0) + return no_such_adapter(msg); + + if (hci_test_bit(HCI_RAW, &di.flags)) + return no_such_adapter(msg); + + adapter = manager_find_adapter_by_id(dev_id); + if (!adapter) + return no_such_adapter(msg); + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + path = adapter->path + ADAPTER_PATH_INDEX; + + dbus_message_append_args(reply, DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *list_adapters(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + DBusMessageIter iter; + DBusMessageIter array_iter; + DBusMessage *reply; + GSList *l; + + 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 = adapters; l; l = l->next) { + struct adapter *adapter = l->data; + char *path; + struct hci_dev_info di; + + if (hci_devinfo(adapter->dev_id, &di) < 0) + continue; + + if (hci_test_bit(HCI_RAW, &di.flags)) + continue; + + path = adapter->path + ADAPTER_PATH_INDEX; + + dbus_message_iter_append_basic(&array_iter, + DBUS_TYPE_OBJECT_PATH, &path); + } + + dbus_message_iter_close_container(&iter, &array_iter); + + return reply; +} + +static GDBusMethodTable manager_methods[] = { + { "DefaultAdapter", "", "o", default_adapter }, + { "FindAdapter", "s", "o", find_adapter }, + { "ListAdapters", "", "ao", list_adapters }, + { } +}; + +static GDBusSignalTable manager_signals[] = { + { "AdapterAdded", "o" }, + { "AdapterRemoved", "o" }, + { "DefaultAdapterChanged", "o" }, + { } +}; + +dbus_bool_t manager_init(DBusConnection *conn, const char *path) +{ + connection = conn; + + if (hcid_dbus_use_experimental()) { + debug("Registering experimental manager interface"); + g_dbus_register_interface(conn, "/", MANAGER_INTERFACE, + manager_methods, manager_signals, + NULL, NULL, NULL); + } + + return g_dbus_register_interface(conn, path, MANAGER_INTERFACE, + old_manager_methods, old_manager_signals, + NULL, NULL, NULL); +} + +void manager_cleanup(DBusConnection *conn, const char *path) +{ + g_dbus_unregister_interface(conn, path, MANAGER_INTERFACE); + + if (hcid_dbus_use_experimental()) + g_dbus_unregister_interface(conn, "/", MANAGER_INTERFACE); +} + +static gint adapter_id_cmp(gconstpointer a, gconstpointer b) +{ + const struct adapter *adapter = a; + uint16_t id = GPOINTER_TO_UINT(b); + + return adapter->dev_id == id ? 0 : -1; +} + +static gint adapter_path_cmp(gconstpointer a, gconstpointer b) +{ + const struct adapter *adapter = a; + const char *path = b; + + return strcmp(adapter->path, path); +} + +static gint adapter_address_cmp(gconstpointer a, gconstpointer b) +{ + const struct adapter *adapter = a; + const char *address = b; + + return strcmp(adapter->address, address); +} + +struct adapter *manager_find_adapter(const bdaddr_t *sba) +{ + GSList *match; + char address[18]; + + ba2str(sba, address); + + match = g_slist_find_custom(adapters, address, adapter_address_cmp); + if (!match) + return NULL; + + return match->data; +} + +struct adapter *manager_find_adapter_by_path(const char *path) +{ + GSList *match; + + match = g_slist_find_custom(adapters, path, adapter_path_cmp); + if (!match) + return NULL; + + return match->data; +} + +struct adapter *manager_find_adapter_by_id(int id) +{ + GSList *match; + + match = g_slist_find_custom(adapters, GINT_TO_POINTER(id), adapter_id_cmp); + if (!match) + return NULL; + + return match->data; +} + +void manager_add_adapter(struct adapter *adapter) +{ + const char *ptr = adapter->path + ADAPTER_PATH_INDEX; + + if (hcid_dbus_use_experimental()) { + g_dbus_emit_signal(connection, "/", + MANAGER_INTERFACE, "AdapterAdded", + DBUS_TYPE_OBJECT_PATH, &ptr, + DBUS_TYPE_INVALID); + } + + g_dbus_emit_signal(connection, BASE_PATH, + MANAGER_INTERFACE, "AdapterAdded", + DBUS_TYPE_STRING, &adapter->path, + DBUS_TYPE_INVALID); + + adapters = g_slist_append(adapters, adapter); +} + +void manager_remove_adapter(struct adapter *adapter) +{ + const char *ptr = adapter->path + ADAPTER_PATH_INDEX; + + if (hcid_dbus_use_experimental()) { + g_dbus_emit_signal(connection, "/", + MANAGER_INTERFACE, "AdapterRemoved", + DBUS_TYPE_OBJECT_PATH, &ptr, + DBUS_TYPE_INVALID); + } + + g_dbus_emit_signal(connection, BASE_PATH, + MANAGER_INTERFACE, "AdapterRemoved", + DBUS_TYPE_STRING, &adapter->path, + DBUS_TYPE_INVALID); + + if ((default_adapter_id == adapter->dev_id || default_adapter_id < 0)) { + int new_default = hci_get_route(NULL); + + default_adapter_id = new_default; + if (new_default >= 0) { + if (hcid_dbus_use_experimental()) { + g_dbus_emit_signal(connection, "/", + MANAGER_INTERFACE, + "DefaultAdapterChanged", + DBUS_TYPE_OBJECT_PATH, &ptr, + DBUS_TYPE_INVALID); + } + g_dbus_emit_signal(connection, BASE_PATH, + MANAGER_INTERFACE, + "DefaultAdapterChanged", + DBUS_TYPE_STRING, &adapter->path, + DBUS_TYPE_INVALID); + } else { + g_dbus_emit_signal(connection, BASE_PATH, + MANAGER_INTERFACE, + "DefaultAdapterChanged", + DBUS_TYPE_STRING, &adapter->path, + DBUS_TYPE_INVALID); + } + } + + adapters = g_slist_remove(adapters, adapter); +} + +int manager_get_default_adapter() +{ + return default_adapter_id; +} + +void manager_set_default_adapter(int id) +{ + struct adapter *adapter = manager_find_adapter_by_id(id); + const char *ptr = adapter->path + ADAPTER_PATH_INDEX; + + default_adapter_id = id; + + if (hcid_dbus_use_experimental()) + g_dbus_emit_signal(connection, "/", + MANAGER_INTERFACE, + "DefaultAdapterChanged", + DBUS_TYPE_OBJECT_PATH, &ptr, + DBUS_TYPE_INVALID); + + g_dbus_emit_signal(connection, BASE_PATH, + MANAGER_INTERFACE, + "DefaultAdapterChanged", + DBUS_TYPE_STRING, &adapter->path, + DBUS_TYPE_INVALID); +} diff --git a/hcid/manager.h b/hcid/manager.h new file mode 100644 index 00000000..2065d5ac --- /dev/null +++ b/hcid/manager.h @@ -0,0 +1,36 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 + * + */ + +#define MANAGER_INTERFACE "org.bluez.Manager" + +dbus_bool_t manager_init(DBusConnection *conn, const char *path); +void manager_cleanup(DBusConnection *conn, const char *path); + +struct adapter *manager_find_adapter(const bdaddr_t *sba); +struct adapter *manager_find_adapter_by_path(const char *path); +struct adapter *manager_find_adapter_by_id(int id); +void manager_add_adapter(struct adapter *adapter); +void manager_remove_adapter(struct adapter *adapter); +int manager_get_default_adapter(); +void manager_set_default_adapter(int id); diff --git a/hcid/parser.y b/hcid/parser.y new file mode 100644 index 00000000..c8b9a12d --- /dev/null +++ b/hcid/parser.y @@ -0,0 +1,360 @@ +%{ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com> + * Copyright (C) 2002-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 <config.h> +#endif + +#include <stdio.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> + +#include <sys/socket.h> +#include <asm/types.h> + +#include <glib.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/hci.h> +#include <bluetooth/hci_lib.h> + +#include "hcid.h" +#include "kword.h" + +int cfg_error(const char *fmt, ...); + +int yyparse(void); +int yylex(void); +int yyerror(char *s); + +void yylex_destroy(void); + +%} + +%union { + char *str; + long num; +} + +%token K_OPTIONS K_DEVICE +%token K_AUTOINIT K_SECURITY K_PAIRING K_OFFMODE K_DEVICEID +%token K_PTYPE K_NAME K_CLASS K_VOICE K_PAGETO K_LM K_LP K_ISCAN K_PSCAN K_DISCOVTO +%token K_PASSKEY +%token K_YES K_NO + +%token <str> WORD PATH STRING LIST HCI BDADDR ID +%token <num> NUM + +%type <num> bool pkt_type link_mode link_policy sec_mode pair_mode off_mode +%type <str> dev_name dev_id hci bdaddr + +%% +config: statement | config statement; +statement: + K_OPTIONS hcid_options + + | device device_options + + | WORD { + cfg_error("Invalid statement '%s'", $1); + } + + | error { + yyclearin; yyerrok; + } + ; + +device: + K_DEVICE { + parser_device = &default_device; + } + + | K_DEVICE hci { + parser_device = alloc_device_opts($2); + } + + | K_DEVICE bdaddr { + parser_device = alloc_device_opts($2); + } + ; + +hcid_options: '{' hcid_opts '}'; +hcid_opts: | hcid_opt ';' | error ';' | hcid_opts hcid_opt ';'; +hcid_opt: + K_AUTOINIT bool { + hcid.auto_init = $2; + } + + | K_SECURITY sec_mode { + hcid.security = $2; + } + + | K_PAIRING pair_mode { + hcid.pairing = $2; + } + + | K_OFFMODE off_mode { + hcid.offmode = $2; + } + + | K_DEVICEID dev_id { + strncpy((char *) hcid.deviceid, $2, 15); + } + + | K_PASSKEY STRING { + strncpy((char *) hcid.pin_code, $2, 16); + hcid.pin_len = strlen($2); + if (hcid.pin_len > 16) + hcid.pin_len = 16; + } + + + | WORD { + cfg_error("Unknown option '%s'", $1); + } + ; + +sec_mode: + WORD { + int opt = find_keyword(sec_param, $1); + if (opt < 0) { + cfg_error("Unknown security mode '%s'", $1); + $$ = 0; + } else + $$ = opt; + } + + | K_NO { + $$ = HCID_SEC_NONE; + } + ; + +pair_mode: + WORD { + int opt = find_keyword(pair_param, $1); + if (opt < 0) { + cfg_error("Unknown pairing mode '%s'", $1); + $$ = 0; + } else + $$ = opt; + } + ; + +off_mode: + WORD { + int opt = find_keyword(off_param, $1); + if (opt < 0) { + cfg_error("Unknown off mode '%s'", $1); + $$ = 0; + } else + $$ = opt; + } + ; + +dev_id: + ID { + } + ; + +device_options: '{' device_opts '}'; +device_opts: | device_opt ';' | error ';' | device_opts device_opt ';'; +device_opt: + K_PTYPE pkt_type { + parser_device->flags |= (1 << HCID_SET_PTYPE); + parser_device->pkt_type = $2; + } + + | K_LM link_mode { + parser_device->flags |= (1 << HCID_SET_LM); + parser_device->link_mode = $2; + } + + | K_LP link_policy { + parser_device->flags |= (1 << HCID_SET_LP); + parser_device->link_policy = $2; + } + + | K_NAME dev_name { + if (parser_device->name) + g_free(parser_device->name); + parser_device->flags |= (1 << HCID_SET_NAME); + parser_device->name = g_strdup($2); + } + + | K_CLASS NUM { + parser_device->flags |= (1 << HCID_SET_CLASS); + parser_device->class = $2; + } + + | K_VOICE NUM { + parser_device->flags |= (1 << HCID_SET_VOICE); + parser_device->voice = $2; + } + + | K_PAGETO NUM { + parser_device->flags |= (1 << HCID_SET_PAGETO); + parser_device->pageto = $2; + } + + | K_DISCOVTO NUM { + parser_device->flags |= (1 << HCID_SET_DISCOVTO); + parser_device->discovto = $2; + } + + | K_ISCAN bool { + if ($2) + parser_device->scan |= SCAN_INQUIRY; + else + parser_device->scan &= ~SCAN_INQUIRY; + } + + | K_PSCAN bool { + if ($2) + parser_device->scan |= SCAN_PAGE; + else + parser_device->scan &= ~SCAN_PAGE; + } + + | WORD { + cfg_error("Unknown option '%s'",$1); + YYABORT; + } + ; + +dev_name: + WORD { + $$ = $1; + } + + | STRING { + $$ = $1; + } + ; + +hci: + HCI { + $$ = $1; + } + ; + +bdaddr: + BDADDR { + $$ = $1; + } + ; + +pkt_type: + WORD { + unsigned int opt; + if (!hci_strtoptype($1, &opt)) + cfg_error("Unknown packet type '%s'", $1); + $$ = opt; + } + + | LIST { + unsigned int opt; + if (!hci_strtoptype($1, &opt)) + cfg_error("Unknown packet type '%s'", $1); + $$ = opt; + } + ; + +link_mode: + WORD { + unsigned int opt; + if (!hci_strtolm($1, &opt)) + cfg_error("Unknown link mode '%s'", $1); + $$ = opt; + } + + | LIST { + unsigned int opt; + if (!hci_strtolm($1, &opt)) + cfg_error("Unknown link mode '%s'", $1); + $$ = opt; + } + ; + +link_policy: + WORD { + unsigned int opt; + if (!hci_strtolp($1, &opt)) + cfg_error("Unknown link policy '%s'", $1); + $$ = opt; + } + + | LIST { + unsigned int opt; + if (!hci_strtolp($1, &opt)) + cfg_error("Unknown link policy '%s'", $1); + $$ = opt; + } + ; + +bool: K_YES { $$ = 1; } | K_NO { $$ = 0; }; + +%% + +int yyerror(char *s) +{ + error("%s line %d", s, lineno); + return 0; +} + +int cfg_error(const char *fmt, ...) +{ + char buf[255]; + va_list ap; + + va_start(ap, fmt); + vsnprintf(buf,sizeof(buf),fmt,ap); + va_end(ap); + + yyerror(buf); + return 0; +} + +/* + * Read config file. + */ +int read_config(char *file) +{ + extern FILE *yyin; + + if (!(yyin = fopen(file, "r"))) { + error("Can't open config file %s", file); + return -1; + } + + lineno = 1; + yyparse(); + + fclose(yyin); + + return 0; +} diff --git a/hcid/plugin.c b/hcid/plugin.c new file mode 100644 index 00000000..331a832f --- /dev/null +++ b/hcid/plugin.c @@ -0,0 +1,190 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 <config.h> +#endif + +#include <glib.h> +#include <gmodule.h> +#include <string.h> + +#include <sys/stat.h> +#include <errno.h> + +#include <bluetooth/bluetooth.h> + +#include "logging.h" + +#include "plugin.h" + +static GSList *plugins = NULL; + +struct bluetooth_plugin { + GModule *module; + struct bluetooth_plugin_desc *desc; +}; + +static gboolean add_plugin(GModule *module, struct bluetooth_plugin_desc *desc) +{ + struct bluetooth_plugin *plugin; + + if (desc->init() < 0) + return FALSE; + + plugin = g_try_new0(struct bluetooth_plugin, 1); + if (plugin == NULL) + return FALSE; + + plugin->module = module; + plugin->desc = desc; + + plugins = g_slist_append(plugins, plugin); + + return TRUE; +} + +static gboolean is_disabled(const char *name, char **list) +{ + int i; + + for (i = 0; list[i] != NULL; i++) { + char *str; + gboolean equal; + + str = g_strdup_printf("lib%s.so", list[i]); + + equal = g_str_equal(str, name); + + g_free(str); + + if (equal) + return TRUE; + } + + return FALSE; +} + +gboolean plugin_init(GKeyFile *config) +{ + GDir *dir; + const gchar *file; + gchar **disabled; + + if (strlen(PLUGINDIR) == 0) + return FALSE; + + if (config) + disabled = g_key_file_get_string_list(config, "General", + "DisablePlugins", + NULL, NULL); + else + disabled = NULL; + + debug("Loading plugins %s", PLUGINDIR); + + dir = g_dir_open(PLUGINDIR, 0, NULL); + if (!dir) { + g_strfreev(disabled); + return FALSE; + } + + while ((file = g_dir_read_name(dir)) != NULL) { + GModule *module; + struct bluetooth_plugin_desc *desc; + gchar *filename; + struct stat st; + + if (g_str_has_prefix(file, "lib") == TRUE || + g_str_has_suffix(file, ".so") == FALSE) + continue; + + if (disabled && is_disabled(file, disabled)) + continue; + + filename = g_build_filename(PLUGINDIR, file, NULL); + + if (stat(filename, &st) < 0) { + error("Can't load plugin %s: %s (%d)", filename, + strerror(errno), errno); + g_free(filename); + continue; + } + + module = g_module_open(filename, G_MODULE_BIND_LOCAL); + if (module == NULL) { + error("Can't load plugin: %s", g_module_error()); + g_free(filename); + continue; + } + + g_free(filename); + + debug("%s", g_module_name(module)); + + if (g_module_symbol(module, "bluetooth_plugin_desc", + (gpointer) &desc) == FALSE) { + error("Can't load plugin description"); + g_module_close(module); + continue; + } + + if (desc == NULL || desc->init == NULL) { + g_module_close(module); + continue; + } + + if (add_plugin(module, desc) == FALSE) { + error("Can't init plugin %s", g_module_name(module)); + g_module_close(module); + } + } + + g_dir_close(dir); + + g_strfreev(disabled); + + return TRUE; +} + +void plugin_cleanup(void) +{ + GSList *list; + + debug("Cleanup plugins"); + + for (list = plugins; list; list = list->next) { + struct bluetooth_plugin *plugin = list->data; + + debug("%s", g_module_name(plugin->module)); + + if (plugin->desc->exit) + plugin->desc->exit(); + + g_module_close(plugin->module); + + g_free(plugin); + } + + g_slist_free(plugins); +} diff --git a/hcid/plugin.h b/hcid/plugin.h new file mode 100644 index 00000000..9248aab6 --- /dev/null +++ b/hcid/plugin.h @@ -0,0 +1,33 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 + * + */ + +struct bluetooth_plugin_desc { + const char *name; + int (*init) (void); + void (*exit) (void); +}; + +#define BLUETOOTH_PLUGIN_DEFINE(name,init,exit) \ + struct bluetooth_plugin_desc bluetooth_plugin_desc = { \ + name, init, exit \ + }; diff --git a/hcid/security.c b/hcid/security.c new file mode 100644 index 00000000..0bfbcbec --- /dev/null +++ b/hcid/security.c @@ -0,0 +1,1037 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com> + * Copyright (C) 2002-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 <config.h> +#endif + +#include <stdio.h> +#include <errno.h> +#include <ctype.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <signal.h> +#include <time.h> +#include <sys/time.h> +#include <sys/param.h> +#include <sys/ioctl.h> +#include <sys/socket.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/hci.h> +#include <bluetooth/hci_lib.h> + +#include <glib.h> + +#include <dbus/dbus.h> + +#include "hcid.h" +#include "textfile.h" +#include "adapter.h" +#include "dbus-hci.h" + +struct g_io_info { + GIOChannel *channel; + int watch_id; + int pin_length; +}; + +static struct g_io_info io_data[HCI_MAX_DEV]; + +static int pairing = HCID_PAIRING_MULTI; + +static GSList *hci_req_queue = NULL; + +struct hci_req_data *hci_req_data_new(int dev_id, const bdaddr_t *dba, uint16_t ogf, uint16_t ocf, int event, const void *cparam, int clen) +{ + struct hci_req_data *data; + + data = g_new0(struct hci_req_data, 1); + + data->cparam = g_malloc(clen); + memcpy(data->cparam, cparam, clen); + + bacpy(&data->dba, dba); + + data->dev_id = dev_id; + data->status = REQ_PENDING; + data->ogf = ogf; + data->ocf = ocf; + data->event = event; + data->clen = clen; + + return data; +} + +static int hci_req_find_by_devid(const void *data, const void *user_data) +{ + const struct hci_req_data *req = data; + const int *dev_id = user_data; + + return (*dev_id - req->dev_id); +} + +static void hci_req_queue_process(int dev_id) +{ + int dd, ret_val; + + /* send the next pending cmd */ + dd = hci_open_dev(dev_id); + do { + struct hci_req_data *data; + GSList *l = g_slist_find_custom(hci_req_queue, &dev_id, hci_req_find_by_devid); + + if (!l) + break; + + data = l->data; + data->status = REQ_SENT; + + ret_val = hci_send_cmd(dd, data->ogf, data->ocf, data->clen, data->cparam); + if (ret_val < 0) { + hci_req_queue = g_slist_remove(hci_req_queue, data); + g_free(data->cparam); + g_free(data); + } + + } while(ret_val < 0); + + hci_close_dev(dd); +} + +void hci_req_queue_append(struct hci_req_data *data) +{ + GSList *l; + struct hci_req_data *match; + + + hci_req_queue = g_slist_append(hci_req_queue, data); + + l = g_slist_find_custom(hci_req_queue, &data->dev_id, hci_req_find_by_devid); + match = l->data; + + if (match->status == REQ_SENT) + return; + + hci_req_queue_process(data->dev_id); +} + +void hci_req_queue_remove(int dev_id, bdaddr_t *dba) +{ + GSList *cur, *next; + struct hci_req_data *req; + + for (cur = hci_req_queue; cur != NULL; cur = next) { + req = cur->data; + next = cur->next; + if ((req->dev_id != dev_id) || (bacmp(&req->dba, dba))) + continue; + + hci_req_queue = g_slist_remove(hci_req_queue, req); + g_free(req->cparam); + g_free(req); + } +} + +static void check_pending_hci_req(int dev_id, int event) +{ + struct hci_req_data *data; + GSList *l; + + if (!hci_req_queue) + return; + + /* find the first element(pending)*/ + l = g_slist_find_custom(hci_req_queue, &dev_id, hci_req_find_by_devid); + + if (!l) + return; + + data = l->data; + + /* skip if there is pending confirmation */ + if (data->status == REQ_SENT) { + if (data->event != event) + return; + + /* remove the confirmed cmd */ + hci_req_queue = g_slist_remove(hci_req_queue, data); + g_free(data->cparam); + g_free(data); + } + + hci_req_queue_process(dev_id); +} + +static int get_handle(int dev, bdaddr_t *sba, bdaddr_t *dba, uint16_t *handle) +{ + struct hci_conn_list_req *cl; + struct hci_conn_info *ci; + char addr[18]; + int i; + + cl = g_malloc0(10 * sizeof(*ci) + sizeof(*cl)); + + ba2str(sba, addr); + cl->dev_id = hci_devid(addr); + cl->conn_num = 10; + ci = cl->conn_info; + + if (ioctl(dev, HCIGETCONNLIST, (void *) cl) < 0) { + g_free(cl); + return -EIO; + } + + for (i = 0; i < cl->conn_num; i++, ci++) { + if (bacmp(&ci->bdaddr, dba) == 0) { + *handle = ci->handle; + g_free(cl); + return 0; + } + } + + g_free(cl); + + return -ENOENT; +} + +static inline int get_bdaddr(int dev, bdaddr_t *sba, uint16_t handle, bdaddr_t *dba) +{ + struct hci_conn_list_req *cl; + struct hci_conn_info *ci; + char addr[18]; + int i; + + cl = g_malloc0(10 * sizeof(*ci) + sizeof(*cl)); + + ba2str(sba, addr); + cl->dev_id = hci_devid(addr); + cl->conn_num = 10; + ci = cl->conn_info; + + if (ioctl(dev, HCIGETCONNLIST, (void *) cl) < 0) { + g_free(cl); + return -EIO; + } + + for (i = 0; i < cl->conn_num; i++, ci++) + if (ci->handle == handle) { + bacpy(dba, &ci->bdaddr); + g_free(cl); + return 0; + } + + g_free(cl); + + return -ENOENT; +} + +static inline void update_lastseen(bdaddr_t *sba, bdaddr_t *dba) +{ + time_t t; + struct tm *tm; + + t = time(NULL); + tm = gmtime(&t); + + write_lastseen_info(sba, dba, tm); +} + +static inline void update_lastused(bdaddr_t *sba, bdaddr_t *dba) +{ + time_t t; + struct tm *tm; + + t = time(NULL); + tm = gmtime(&t); + + write_lastused_info(sba, dba, tm); +} + +/* Link Key handling */ + +static void link_key_request(int dev, bdaddr_t *sba, bdaddr_t *dba) +{ + struct hci_auth_info_req req; + unsigned char key[16]; + char sa[18], da[18]; + uint8_t type; + int err; + + ba2str(sba, sa); ba2str(dba, da); + info("link_key_request (sba=%s, dba=%s)", sa, da); + + memset(&req, 0, sizeof(req)); + bacpy(&req.bdaddr, dba); + + err = ioctl(dev, HCIGETAUTHINFO, (unsigned long) &req); + if (err < 0 && errno != EINVAL) + debug("HCIGETAUTHINFO failed %s (%d)", + strerror(errno), errno); + else + req.type = 0x00; + + debug("kernel auth requirements = 0x%02x", req.type); + + err = read_link_key(sba, dba, key, &type); + if (err < 0) { + /* Link key not found */ + hci_send_cmd(dev, OGF_LINK_CTL, OCF_LINK_KEY_NEG_REPLY, 6, dba); + } else { + /* Link key found */ + link_key_reply_cp lr; + memcpy(lr.link_key, key, 16); + bacpy(&lr.bdaddr, dba); + + debug("stored link key type = 0x%02x", type); + + if ((type == 0x03 || type == 0x04) && (req.type & 0x01)) + hci_send_cmd(dev, OGF_LINK_CTL, + OCF_LINK_KEY_NEG_REPLY, 6, dba); + else + hci_send_cmd(dev, OGF_LINK_CTL, OCF_LINK_KEY_REPLY, + LINK_KEY_REPLY_CP_SIZE, &lr); + } +} + +static void link_key_notify(int dev, bdaddr_t *sba, void *ptr) +{ + evt_link_key_notify *evt = ptr; + bdaddr_t *dba = &evt->bdaddr; + char sa[18], da[18]; + int dev_id, err; + + ba2str(sba, sa); ba2str(dba, da); + info("link_key_notify (sba=%s, dba=%s)", sa, da); + + dev_id = hci_devid(sa); + + err = write_link_key(sba, dba, evt->link_key, evt->key_type, + io_data[dev_id].pin_length); + if (err < 0) { + uint16_t handle; + + error("write_link_key: %s (%d)", strerror(-err), -err); + + hcid_dbus_bonding_process_complete(sba, dba, HCI_MEMORY_FULL); + + if (get_handle(dev, sba, dba, &handle) == 0) { + disconnect_cp cp; + + memset(&cp, 0, sizeof(cp)); + cp.handle = htobs(handle); + cp.reason = HCI_OE_LOW_RESOURCES; + + hci_send_cmd(dev, OGF_LINK_CTL, OCF_DISCONNECT, + DISCONNECT_CP_SIZE, &cp); + } + } else + hcid_dbus_bonding_process_complete(sba, dba, 0); + + io_data[dev_id].pin_length = -1; +} + +static void return_link_keys(int dev, bdaddr_t *sba, void *ptr) +{ + evt_return_link_keys *evt = ptr; + uint8_t num = evt->num_keys; + unsigned char key[16]; + char sa[18], da[18]; + bdaddr_t dba; + int i; + + ba2str(sba, sa); + ptr++; + + for (i = 0; i < num; i++) { + bacpy(&dba, ptr); ba2str(&dba, da); + memcpy(key, ptr + 6, 16); + + info("return_link_keys (sba=%s, dba=%s)", sa, da); + + ptr += 22; + } +} + +/* Simple Pairing handling */ + +static void user_confirm_request(int dev, bdaddr_t *sba, void *ptr) +{ + evt_user_confirm_request *req = ptr; + + if (hcid_dbus_user_confirm(sba, &req->bdaddr, + btohl(req->passkey)) < 0) + hci_send_cmd(dev, OGF_LINK_CTL, + OCF_USER_CONFIRM_NEG_REPLY, 6, ptr); +} + +static void user_passkey_request(int dev, bdaddr_t *sba, void *ptr) +{ + evt_user_passkey_request *req = ptr; + + if (hcid_dbus_user_passkey(sba, &req->bdaddr) < 0) + hci_send_cmd(dev, OGF_LINK_CTL, + OCF_USER_PASSKEY_NEG_REPLY, 6, ptr); +} + +static void user_passkey_notify(int dev, bdaddr_t *sba, void *ptr) +{ + evt_user_passkey_notify *req = ptr; + + hcid_dbus_user_notify(sba, &req->bdaddr, btohl(req->passkey)); +} + +static void remote_oob_data_request(int dev, bdaddr_t *sba, void *ptr) +{ + hci_send_cmd(dev, OGF_LINK_CTL, OCF_REMOTE_OOB_DATA_NEG_REPLY, 6, ptr); +} + +static void io_capa_request(int dev, bdaddr_t *sba, bdaddr_t *dba) +{ + char sa[18], da[18]; + uint8_t cap, auth; + + ba2str(sba, sa); ba2str(dba, da); + info("io_capa_request (sba=%s, dba=%s)", sa, da); + + if (hcid_dbus_get_io_cap(sba, dba, &cap, &auth) < 0) { + io_capability_neg_reply_cp cp; + memset(&cp, 0, sizeof(cp)); + bacpy(&cp.bdaddr, dba); + cp.reason = HCI_PAIRING_NOT_ALLOWED; + hci_send_cmd(dev, OGF_LINK_CTL, OCF_IO_CAPABILITY_NEG_REPLY, + IO_CAPABILITY_NEG_REPLY_CP_SIZE, &cp); + } else { + io_capability_reply_cp cp; + memset(&cp, 0, sizeof(cp)); + bacpy(&cp.bdaddr, dba); + cp.capability = cap; + cp.oob_data = 0x00; + cp.authentication = auth; + hci_send_cmd(dev, OGF_LINK_CTL, OCF_IO_CAPABILITY_REPLY, + IO_CAPABILITY_REPLY_CP_SIZE, &cp); + } +} + +static void io_capa_response(int dev, bdaddr_t *sba, void *ptr) +{ + evt_io_capability_response *evt = ptr; + char sa[18], da[18]; + + ba2str(sba, sa); ba2str(&evt->bdaddr, da); + info("io_capa_response (sba=%s, dba=%s)", sa, da); + + hcid_dbus_set_io_cap(sba, &evt->bdaddr, + evt->capability, evt->authentication); +} + +/* PIN code handling */ + +void set_pin_length(bdaddr_t *sba, int length) +{ + char addr[18]; + int dev_id; + + ba2str(sba, addr); + dev_id = hci_devid(addr); + + io_data[dev_id].pin_length = length; +} + +static void pin_code_request(int dev, bdaddr_t *sba, bdaddr_t *dba) +{ + pin_code_reply_cp pr; + struct hci_conn_info_req *cr; + struct hci_conn_info *ci; + unsigned char key[16]; + char sa[18], da[18], pin[17]; + int err, pinlen; + + memset(&pr, 0, sizeof(pr)); + bacpy(&pr.bdaddr, dba); + + ba2str(sba, sa); ba2str(dba, da); + info("pin_code_request (sba=%s, dba=%s)", sa, da); + + cr = g_malloc0(sizeof(*cr) + sizeof(*ci)); + + bacpy(&cr->bdaddr, dba); + cr->type = ACL_LINK; + if (ioctl(dev, HCIGETCONNINFO, (unsigned long) cr) < 0) { + error("Can't get conn info: %s (%d)", strerror(errno), errno); + goto reject; + } + ci = cr->conn_info; + + memset(pin, 0, sizeof(pin)); + pinlen = read_pin_code(sba, dba, pin); + + if (pairing == HCID_PAIRING_ONCE) { + err = read_link_key(sba, dba, key, NULL); + if (!err) { + ba2str(dba, da); + error("PIN code request for already paired device %s", da); + goto reject; + } + } else if (pairing == HCID_PAIRING_NONE) + goto reject; + + if (hcid.security == HCID_SEC_AUTO && !ci->out) { + set_pin_length(sba, hcid.pin_len); + memcpy(pr.pin_code, hcid.pin_code, hcid.pin_len); + pr.pin_len = hcid.pin_len; + hci_send_cmd(dev, OGF_LINK_CTL, OCF_PIN_CODE_REPLY, + PIN_CODE_REPLY_CP_SIZE, &pr); + } else { + if (pinlen > 0) { + set_pin_length(sba, pinlen); + memcpy(pr.pin_code, pin, pinlen); + pr.pin_len = pinlen; + hci_send_cmd(dev, OGF_LINK_CTL, OCF_PIN_CODE_REPLY, + PIN_CODE_REPLY_CP_SIZE, &pr); + } else { + /* Request PIN from passkey agent */ + if (hcid_dbus_request_pin(dev, sba, ci) < 0) + goto reject; + } + } + + g_free(cr); + + return; + +reject: + g_free(cr); + + hci_send_cmd(dev, OGF_LINK_CTL, OCF_PIN_CODE_NEG_REPLY, 6, dba); +} + +static inline void cmd_status(int dev, bdaddr_t *sba, void *ptr) +{ + evt_cmd_status *evt = ptr; + uint16_t opcode = btohs(evt->opcode); + + if (evt->status) + return; + + if (opcode == cmd_opcode_pack(OGF_LINK_CTL, OCF_INQUIRY)) + hcid_dbus_inquiry_start(sba); +} + +static inline void cmd_complete(int dev, bdaddr_t *sba, void *ptr) +{ + evt_cmd_complete *evt = ptr; + uint16_t opcode = btohs(evt->opcode); + uint8_t status; + + switch (opcode) { + case cmd_opcode_pack(OGF_LINK_CTL, OCF_PERIODIC_INQUIRY): + status = *((uint8_t *) ptr + EVT_CMD_COMPLETE_SIZE); + hcid_dbus_periodic_inquiry_start(sba, status); + break; + case cmd_opcode_pack(OGF_LINK_CTL, OCF_EXIT_PERIODIC_INQUIRY): + status = *((uint8_t *) ptr + EVT_CMD_COMPLETE_SIZE); + hcid_dbus_periodic_inquiry_exit(sba, status); + break; + case cmd_opcode_pack(OGF_LINK_CTL, OCF_INQUIRY_CANCEL): + hcid_dbus_inquiry_complete(sba); + break; + case cmd_opcode_pack(OGF_HOST_CTL, OCF_CHANGE_LOCAL_NAME): + hcid_dbus_setname_complete(sba); + break; + case cmd_opcode_pack(OGF_HOST_CTL, OCF_WRITE_SCAN_ENABLE): + hcid_dbus_setscan_enable_complete(sba); + break; + case cmd_opcode_pack(OGF_HOST_CTL, OCF_WRITE_CLASS_OF_DEV): + hcid_dbus_write_class_complete(sba); + break; + case cmd_opcode_pack(OGF_HOST_CTL, OCF_WRITE_SIMPLE_PAIRING_MODE): + hcid_dbus_write_simple_pairing_mode_complete(sba); + break; + }; +} + +static inline void remote_name_information(int dev, bdaddr_t *sba, void *ptr) +{ + evt_remote_name_req_complete *evt = ptr; + bdaddr_t dba; + char name[249]; + + memset(name, 0, sizeof(name)); + bacpy(&dba, &evt->bdaddr); + + if (!evt->status) { + char *end; + memcpy(name, evt->name, 248); + /* It's ok to cast end between const and non-const since + * we know it points to inside of name which is non-const */ + if (!g_utf8_validate(name, -1, (const char **) &end)) + *end = '\0'; + write_device_name(sba, &dba, name); + } + + hcid_dbus_remote_name(sba, &dba, evt->status, name); +} + +static inline void remote_version_information(int dev, bdaddr_t *sba, void *ptr) +{ + evt_read_remote_version_complete *evt = ptr; + bdaddr_t dba; + + if (evt->status) + return; + + if (get_bdaddr(dev, sba, btohs(evt->handle), &dba) < 0) + return; + + write_version_info(sba, &dba, btohs(evt->manufacturer), + evt->lmp_ver, btohs(evt->lmp_subver)); +} + +static inline void inquiry_complete(int dev, bdaddr_t *sba, void *ptr) +{ + hcid_dbus_inquiry_complete(sba); +} + +static inline void inquiry_result(int dev, bdaddr_t *sba, int plen, void *ptr) +{ + uint8_t num = *(uint8_t *) ptr++; + int i; + + for (i = 0; i < num; i++) { + inquiry_info *info = ptr; + uint32_t class = info->dev_class[0] + | (info->dev_class[1] << 8) + | (info->dev_class[2] << 16); + + hcid_dbus_inquiry_result(sba, &info->bdaddr, class, 0, NULL); + + update_lastseen(sba, &info->bdaddr); + + ptr += INQUIRY_INFO_SIZE; + } +} + +static inline void inquiry_result_with_rssi(int dev, bdaddr_t *sba, int plen, void *ptr) +{ + uint8_t num = *(uint8_t *) ptr++; + int i; + + if (!num) + return; + + if ((plen - 1) / num == INQUIRY_INFO_WITH_RSSI_AND_PSCAN_MODE_SIZE) { + for (i = 0; i < num; i++) { + inquiry_info_with_rssi_and_pscan_mode *info = ptr; + uint32_t class = info->dev_class[0] + | (info->dev_class[1] << 8) + | (info->dev_class[2] << 16); + + hcid_dbus_inquiry_result(sba, &info->bdaddr, + class, info->rssi, NULL); + + update_lastseen(sba, &info->bdaddr); + + ptr += INQUIRY_INFO_WITH_RSSI_AND_PSCAN_MODE_SIZE; + } + } else { + for (i = 0; i < num; i++) { + inquiry_info_with_rssi *info = ptr; + uint32_t class = info->dev_class[0] + | (info->dev_class[1] << 8) + | (info->dev_class[2] << 16); + + hcid_dbus_inquiry_result(sba, &info->bdaddr, + class, info->rssi, NULL); + + update_lastseen(sba, &info->bdaddr); + + ptr += INQUIRY_INFO_WITH_RSSI_SIZE; + } + } +} + +static inline void extended_inquiry_result(int dev, bdaddr_t *sba, int plen, void *ptr) +{ + uint8_t num = *(uint8_t *) ptr++; + int i; + + for (i = 0; i < num; i++) { + extended_inquiry_info *info = ptr; + uint32_t class = info->dev_class[0] + | (info->dev_class[1] << 8) + | (info->dev_class[2] << 16); + + hcid_dbus_inquiry_result(sba, &info->bdaddr, class, + info->rssi, info->data); + + update_lastseen(sba, &info->bdaddr); + + ptr += EXTENDED_INQUIRY_INFO_SIZE; + } +} + +static inline void remote_features_information(int dev, bdaddr_t *sba, void *ptr) +{ + evt_read_remote_features_complete *evt = ptr; + bdaddr_t dba; + + if (evt->status) + return; + + if (get_bdaddr(dev, sba, btohs(evt->handle), &dba) < 0) + return; + + write_features_info(sba, &dba, evt->features); +} + +static inline void conn_complete(int dev, int dev_id, bdaddr_t *sba, void *ptr) +{ + evt_conn_complete *evt = ptr; + char filename[PATH_MAX]; + remote_name_req_cp cp_name; + struct hci_req_data *data; + char local_addr[18], peer_addr[18], *str; + + if (evt->link_type != ACL_LINK) + return; + + hcid_dbus_conn_complete(sba, evt->status, btohs(evt->handle), + &evt->bdaddr); + + if (evt->status) + return; + + update_lastused(sba, &evt->bdaddr); + + /* Request remote name */ + memset(&cp_name, 0, sizeof(cp_name)); + bacpy(&cp_name.bdaddr, &evt->bdaddr); + cp_name.pscan_rep_mode = 0x02; + + data = hci_req_data_new(dev_id, &evt->bdaddr, OGF_LINK_CTL, + OCF_REMOTE_NAME_REQ, EVT_REMOTE_NAME_REQ_COMPLETE, + &cp_name, REMOTE_NAME_REQ_CP_SIZE); + + hci_req_queue_append(data); + + /* check if the remote version needs be requested */ + ba2str(sba, local_addr); + ba2str(&evt->bdaddr, peer_addr); + + create_name(filename, sizeof(filename), STORAGEDIR, local_addr, "manufacturers"); + + str = textfile_get(filename, peer_addr); + if (!str) { + read_remote_version_cp cp; + + memset(&cp, 0, sizeof(cp)); + cp.handle = evt->handle; + + data = hci_req_data_new(dev_id, &evt->bdaddr, OGF_LINK_CTL, + OCF_READ_REMOTE_VERSION, EVT_READ_REMOTE_VERSION_COMPLETE, + &cp, READ_REMOTE_VERSION_CP_SIZE); + + hci_req_queue_append(data); + } else + free(str); +} + +static inline void disconn_complete(int dev, bdaddr_t *sba, void *ptr) +{ + evt_disconn_complete *evt = ptr; + + hcid_dbus_disconn_complete(sba, evt->status, btohs(evt->handle), + evt->reason); +} + +static inline void auth_complete(int dev, bdaddr_t *sba, void *ptr) +{ + evt_auth_complete *evt = ptr; + bdaddr_t dba; + + if (get_bdaddr(dev, sba, btohs(evt->handle), &dba) < 0) + return; + + if (evt->status) + hcid_dbus_bonding_process_complete(sba, &dba, evt->status); +} + +static inline void conn_request(int dev, bdaddr_t *sba, void *ptr) +{ + evt_conn_request *evt = ptr; + uint32_t class = evt->dev_class[0] | (evt->dev_class[1] << 8) + | (evt->dev_class[2] << 16); + + hcid_dbus_remote_class(sba, &evt->bdaddr, class); + + write_remote_class(sba, &evt->bdaddr, class); +} + +static gboolean io_security_event(GIOChannel *chan, GIOCondition cond, gpointer data) +{ + unsigned char buf[HCI_MAX_EVENT_SIZE], *ptr = buf; + struct hci_dev_info *di = data; + int type, dev; + size_t len; + hci_event_hdr *eh; + GIOError err; + + if (cond & (G_IO_NVAL | G_IO_HUP | G_IO_ERR)) { + g_io_channel_unref(chan); + return FALSE; + } + + if ((err = g_io_channel_read(chan, (gchar *) buf, sizeof(buf), &len))) { + if (err == G_IO_ERROR_AGAIN) + return TRUE; + g_io_channel_unref(chan); + return FALSE; + } + + type = *ptr++; + + if (type != HCI_EVENT_PKT) + return TRUE; + + eh = (hci_event_hdr *) ptr; + ptr += HCI_EVENT_HDR_SIZE; + + dev = g_io_channel_unix_get_fd(chan); + + ioctl(dev, HCIGETDEVINFO, (void *) di); + + if (hci_test_bit(HCI_RAW, &di->flags)) + return TRUE; + + switch (eh->evt) { + case EVT_CMD_STATUS: + cmd_status(dev, &di->bdaddr, ptr); + break; + + case EVT_CMD_COMPLETE: + cmd_complete(dev, &di->bdaddr, ptr); + break; + + case EVT_REMOTE_NAME_REQ_COMPLETE: + remote_name_information(dev, &di->bdaddr, ptr); + break; + + case EVT_READ_REMOTE_VERSION_COMPLETE: + remote_version_information(dev, &di->bdaddr, ptr); + break; + + case EVT_READ_REMOTE_FEATURES_COMPLETE: + remote_features_information(dev, &di->bdaddr, ptr); + break; + + case EVT_INQUIRY_COMPLETE: + inquiry_complete(dev, &di->bdaddr, ptr); + break; + + case EVT_INQUIRY_RESULT: + inquiry_result(dev, &di->bdaddr, eh->plen, ptr); + break; + + case EVT_INQUIRY_RESULT_WITH_RSSI: + inquiry_result_with_rssi(dev, &di->bdaddr, eh->plen, ptr); + break; + + case EVT_EXTENDED_INQUIRY_RESULT: + extended_inquiry_result(dev, &di->bdaddr, eh->plen, ptr); + break; + + case EVT_CONN_COMPLETE: + conn_complete(dev, di->dev_id, &di->bdaddr, ptr); + break; + + case EVT_DISCONN_COMPLETE: + disconn_complete(dev, &di->bdaddr, ptr); + break; + + case EVT_AUTH_COMPLETE: + auth_complete(dev, &di->bdaddr, ptr); + break; + + case EVT_CONN_REQUEST: + conn_request(dev, &di->bdaddr, ptr); + break; + } + + /* Check for pending command request */ + check_pending_hci_req(di->dev_id, eh->evt); + + if (hci_test_bit(HCI_SECMGR, &di->flags)) + return TRUE; + + switch (eh->evt) { + case EVT_PIN_CODE_REQ: + pin_code_request(dev, &di->bdaddr, (bdaddr_t *) ptr); + break; + + case EVT_LINK_KEY_REQ: + link_key_request(dev, &di->bdaddr, (bdaddr_t *) ptr); + break; + + case EVT_LINK_KEY_NOTIFY: + link_key_notify(dev, &di->bdaddr, ptr); + break; + + case EVT_RETURN_LINK_KEYS: + return_link_keys(dev, &di->bdaddr, ptr); + break; + + case EVT_IO_CAPABILITY_REQUEST: + io_capa_request(dev, &di->bdaddr, (bdaddr_t *) ptr); + break; + + case EVT_IO_CAPABILITY_RESPONSE: + io_capa_response(dev, &di->bdaddr, ptr); + break; + + case EVT_USER_CONFIRM_REQUEST: + user_confirm_request(dev, &di->bdaddr, ptr); + break; + + case EVT_USER_PASSKEY_REQUEST: + user_passkey_request(dev, &di->bdaddr, ptr); + break; + + case EVT_USER_PASSKEY_NOTIFY: + user_passkey_notify(dev, &di->bdaddr, ptr); + break; + + case EVT_REMOTE_OOB_DATA_REQUEST: + remote_oob_data_request(dev, &di->bdaddr, ptr); + break; + } + + return TRUE; +} + +void start_security_manager(int hdev) +{ + GIOChannel *chan = io_data[hdev].channel; + struct hci_dev_info *di; + struct hci_filter flt; + read_stored_link_key_cp cp; + int dev; + + if (chan) + return; + + info("Starting security manager %d", hdev); + + if ((dev = hci_open_dev(hdev)) < 0) { + error("Can't open device hci%d: %s (%d)", + hdev, strerror(errno), errno); + return; + } + + /* Set filter */ + hci_filter_clear(&flt); + hci_filter_set_ptype(HCI_EVENT_PKT, &flt); + hci_filter_set_event(EVT_CMD_STATUS, &flt); + hci_filter_set_event(EVT_CMD_COMPLETE, &flt); + hci_filter_set_event(EVT_PIN_CODE_REQ, &flt); + hci_filter_set_event(EVT_LINK_KEY_REQ, &flt); + hci_filter_set_event(EVT_LINK_KEY_NOTIFY, &flt); + hci_filter_set_event(EVT_RETURN_LINK_KEYS, &flt); + hci_filter_set_event(EVT_IO_CAPABILITY_REQUEST, &flt); + hci_filter_set_event(EVT_IO_CAPABILITY_RESPONSE, &flt); + hci_filter_set_event(EVT_USER_CONFIRM_REQUEST, &flt); + hci_filter_set_event(EVT_USER_PASSKEY_REQUEST, &flt); + hci_filter_set_event(EVT_REMOTE_OOB_DATA_REQUEST, &flt); + hci_filter_set_event(EVT_USER_PASSKEY_NOTIFY, &flt); + hci_filter_set_event(EVT_KEYPRESS_NOTIFY, &flt); + hci_filter_set_event(EVT_SIMPLE_PAIRING_COMPLETE, &flt); + hci_filter_set_event(EVT_AUTH_COMPLETE, &flt); + hci_filter_set_event(EVT_REMOTE_NAME_REQ_COMPLETE, &flt); + hci_filter_set_event(EVT_READ_REMOTE_VERSION_COMPLETE, &flt); + hci_filter_set_event(EVT_READ_REMOTE_FEATURES_COMPLETE, &flt); + hci_filter_set_event(EVT_REMOTE_HOST_FEATURES_NOTIFY, &flt); + hci_filter_set_event(EVT_INQUIRY_COMPLETE, &flt); + hci_filter_set_event(EVT_INQUIRY_RESULT, &flt); + hci_filter_set_event(EVT_INQUIRY_RESULT_WITH_RSSI, &flt); + hci_filter_set_event(EVT_EXTENDED_INQUIRY_RESULT, &flt); + hci_filter_set_event(EVT_CONN_REQUEST, &flt); + hci_filter_set_event(EVT_CONN_COMPLETE, &flt); + hci_filter_set_event(EVT_DISCONN_COMPLETE, &flt); + if (setsockopt(dev, SOL_HCI, HCI_FILTER, &flt, sizeof(flt)) < 0) { + error("Can't set filter on hci%d: %s (%d)", + hdev, strerror(errno), errno); + close(dev); + return; + } + + di = g_new(struct hci_dev_info, 1); + if (hci_devinfo(hdev, di) < 0) { + error("Can't get device info: %s (%d)", + strerror(errno), errno); + close(dev); + g_free(di); + return; + } + + chan = g_io_channel_unix_new(dev); + g_io_channel_set_close_on_unref(chan, TRUE); + io_data[hdev].watch_id = g_io_add_watch_full(chan, G_PRIORITY_HIGH, + G_IO_IN | G_IO_NVAL | G_IO_HUP | G_IO_ERR, + io_security_event, di, (GDestroyNotify) g_free); + io_data[hdev].channel = chan; + io_data[hdev].pin_length = -1; + + if (hci_test_bit(HCI_RAW, &di->flags)) + return; + + bacpy(&cp.bdaddr, BDADDR_ANY); + cp.read_all = 1; + + hci_send_cmd(dev, OGF_HOST_CTL, OCF_READ_STORED_LINK_KEY, + READ_STORED_LINK_KEY_CP_SIZE, (void *) &cp); +} + +void stop_security_manager(int hdev) +{ + GIOChannel *chan = io_data[hdev].channel; + + if (!chan) + return; + + info("Stopping security manager %d", hdev); + + g_source_remove(io_data[hdev].watch_id); + g_io_channel_unref(io_data[hdev].channel); + io_data[hdev].watch_id = -1; + io_data[hdev].channel = NULL; + io_data[hdev].pin_length = -1; +} + +void init_security_data(void) +{ + pairing = hcid.pairing; +} diff --git a/hcid/server.c b/hcid/server.c new file mode 100644 index 00000000..da240112 --- /dev/null +++ b/hcid/server.c @@ -0,0 +1,68 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 <config.h> +#endif + +#include <glib.h> + +#include "server.h" + +static GSList *servers = NULL; + +int bt_register_server(struct bt_server *server) +{ + servers = g_slist_append(servers, server); + + return 0; +} + +void bt_unregister_server(struct bt_server *server) +{ + servers = g_slist_remove(servers, server); +} + +void __probe_servers(const char *adapter) +{ + GSList *list; + + for (list = servers; list; list = list->next) { + struct bt_server *server = list->data; + + if (server->probe) + server->probe(adapter); + } +} + +void __remove_servers(const char *adapter) +{ + GSList *list; + + for (list = servers; list; list = list->next) { + struct bt_server *server = list->data; + + if (server->remove) + server->remove(adapter); + } +} diff --git a/hcid/server.h b/hcid/server.h new file mode 100644 index 00000000..f60ab88b --- /dev/null +++ b/hcid/server.h @@ -0,0 +1,31 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 + * + */ + +struct bt_server { + const char *uuid; + int (*probe) (const char *adapter); + void (*remove) (const char *adapter); +}; + +int bt_register_server(struct bt_server *server); +void bt_unregister_server(struct bt_server *server); diff --git a/hcid/service-did.xml b/hcid/service-did.xml new file mode 100644 index 00000000..52eb68c0 --- /dev/null +++ b/hcid/service-did.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8" ?> + +<record> + <attribute id="0x0001"> + <sequence> + <uuid value="0x1200"/> + </sequence> + </attribute> + + <attribute id="0x0200"> + <uint16 value="0x0102" name="id"/> + </attribute> + + <attribute id="0x0201"> + <uint16 value="0x0a12" name="vendor"/> + </attribute> + + <attribute id="0x0202"> + <uint16 value="0x4711" name="product"/> + </attribute> + + <attribute id="0x0203"> + <uint16 value="0x0000" name="version"/> + </attribute> + + <attribute id="0x0204"> + <boolean value="true"/> + </attribute> + + <attribute id="0x0205"> + <uint16 value="0x0002" name="source"/> + </attribute> +</record> diff --git a/hcid/service-ftp.xml b/hcid/service-ftp.xml new file mode 100644 index 00000000..1bda8857 --- /dev/null +++ b/hcid/service-ftp.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="UTF-8" ?> + +<record> + <attribute id="0x0001"> + <sequence> + <uuid value="0x1106"/> + </sequence> + </attribute> + + <attribute id="0x0004"> + <sequence> + <sequence> + <uuid value="0x0100"/> + </sequence> + <sequence> + <uuid value="0x0003"/> + <uint8 value="23" name="channel"/> + </sequence> + <sequence> + <uuid value="0x0008"/> + </sequence> + </sequence> + </attribute> + + <attribute id="0x0009"> + <sequence> + <sequence> + <uuid value="0x1106"/> + <uint16 value="0x0100" name="version"/> + </sequence> + </sequence> + </attribute> + + <attribute id="0x0100"> + <text value="OBEX File Transfer" name="name"/> + </attribute> +</record> diff --git a/hcid/service-opp.xml b/hcid/service-opp.xml new file mode 100644 index 00000000..351b4a41 --- /dev/null +++ b/hcid/service-opp.xml @@ -0,0 +1,50 @@ +<?xml version="1.0" encoding="UTF-8" ?> + +<record> + <attribute id="0x0001"> + <sequence> + <uuid value="0x1105"/> + </sequence> + </attribute> + + <attribute id="0x0004"> + <sequence> + <sequence> + <uuid value="0x0100"/> + </sequence> + <sequence> + <uuid value="0x0003"/> + <uint8 value="23" name="channel"/> + </sequence> + <sequence> + <uuid value="0x0008"/> + </sequence> + </sequence> + </attribute> + + <attribute id="0x0009"> + <sequence> + <sequence> + <uuid value="0x1105"/> + <uint16 value="0x0100" name="version"/> + </sequence> + </sequence> + </attribute> + + <attribute id="0x0100"> + <text value="OBEX Object Push" name="name"/> + </attribute> + + <attribute id="0x0303"> + <sequence> + <uint8 value="0x01"/> + <uint8 value="0x01"/> + <uint8 value="0x02"/> + <uint8 value="0x03"/> + <uint8 value="0x04"/> + <uint8 value="0x05"/> + <uint8 value="0x06"/> + <uint8 value="0xff"/> + </sequence> + </attribute> +</record> diff --git a/hcid/service-record.dtd b/hcid/service-record.dtd new file mode 100644 index 00000000..f53be5d0 --- /dev/null +++ b/hcid/service-record.dtd @@ -0,0 +1,66 @@ +<!ELEMENT record (attribute)*> + +<!ELEMENT attribute (sequence|alternate|text|url|uuid|boolean|uint8|uint16|uint32|uint64|nil)+> +<!ATTLIST attribute id CDATA #REQUIRED> + +<!ELEMENT sequence (sequence|alternate|text|url|uuid|boolean|uint8|uint16|uint32|uint64|uint128|int8|int16|int32|int64|int128|nil)+> + +<!ELEMENT alternate (sequence|alternate|text|url|uuid|boolean|uint8|uint16|uint32|uint64|uint128|int8|int16|int32|int64|int128|nil)+> + +<!ELEMENT text EMPTY> +<!ATTLIST text value CDATA #REQUIRED> +<!ATTLIST text name CDATA> +<!ATTLIST text encoding (normal|hex) "normal"> + +<!ELEMENT url EMPTY> +<!ATTLIST url value CDATA #REQUIRED> +<!ATTLIST url name CDATA> + +<!ELEMENT uuid EMPTY> +<!ATTLIST uuid value CDATA #REQUIRED> + +<!ELEMENT boolean EMPTY> +<!ATTLIST boolean value CDATA #REQUIRED> +<!ATTLIST boolean name CDATA> + +<!ELEMENT uint8 EMPTY> +<!ATTLIST uint8 value CDATA #REQUIRED> +<!ATTLIST uint8 name CDATA> + +<!ELEMENT uint16 EMPTY> +<!ATTLIST uint16 value CDATA #REQUIRED> +<!ATTLIST uint16 name CDATA> + +<!ELEMENT uint32 EMPTY> +<!ATTLIST uint32 value CDATA #REQUIRED> +<!ATTLIST uint32 name CDATA> + +<!ELEMENT uint64 EMPTY> +<!ATTLIST uint64 value CDATA #REQUIRED> +<!ATTLIST uint64 name CDATA> + +<!ELEMENT uint128 EMPTY> +<!ATTLIST uint128 value CDATA #REQUIRED> +<!ATTLIST uint128 name CDATA> + +<!ELEMENT int8 EMPTY> +<!ATTLIST int8 value CDATA #REQUIRED> +<!ATTLIST int8 name CDATA> + +<!ELEMENT int16 EMPTY> +<!ATTLIST int16 value CDATA #REQUIRED> +<!ATTLIST int16 name CDATA> + +<!ELEMENT int32 EMPTY> +<!ATTLIST int32 value CDATA #REQUIRED> +<!ATTLIST int32 name CDATA> + +<!ELEMENT int64 EMPTY> +<!ATTLIST int64 value CDATA #REQUIRED> +<!ATTLIST int64 name CDATA> + +<!ELEMENT int128 EMPTY> +<!ATTLIST int128 value CDATA #REQUIRED> +<!ATTLIST int128 name CDATA> + +<!ELEMENT nil EMPTY> diff --git a/hcid/service-spp.xml b/hcid/service-spp.xml new file mode 100644 index 00000000..2b156c3f --- /dev/null +++ b/hcid/service-spp.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8" ?> + +<record> + <attribute id="0x0001"> + <sequence> + <uuid value="0x1101"/> + </sequence> + </attribute> + + <attribute id="0x0004"> + <sequence> + <sequence> + <uuid value="0x0100"/> + </sequence> + <sequence> + <uuid value="0x0003"/> + <uint8 value="23" name="channel"/> + </sequence> + </sequence> + </attribute> + + <attribute id="0x0100"> + <text value="COM5" name="name"/> + </attribute> +</record> diff --git a/hcid/simple-agent b/hcid/simple-agent new file mode 100755 index 00000000..0d3dc1f7 --- /dev/null +++ b/hcid/simple-agent @@ -0,0 +1,112 @@ +#!/usr/bin/python + +import gobject + +import sys +import dbus +import dbus.service +import dbus.mainloop.glib + +class Rejected(dbus.DBusException): + _dbus_error_name = "org.bluez.Error.Rejected" + +class Agent(dbus.service.Object): + exit_on_release = True + + def set_exit_on_release(self, exit_on_release): + self.exit_on_release = exit_on_release + + @dbus.service.method("org.bluez.Agent", + in_signature="", out_signature="") + def Release(self): + print "Release" + if self.exit_on_release: + mainloop.quit() + + @dbus.service.method("org.bluez.Agent", + in_signature="os", out_signature="") + def Authorize(self, device, uuid): + print "Authorize (%s, %s)" % (device, uuid) + + @dbus.service.method("org.bluez.Agent", + in_signature="o", out_signature="s") + def RequestPinCode(self, device): + print "RequestPinCode (%s)" % (device) + return raw_input("Enter PIN Code: ") + + @dbus.service.method("org.bluez.Agent", + in_signature="o", out_signature="u") + def RequestPasskey(self, device): + print "RequestPasskey (%s)" % (device) + passkey = raw_input("Enter passkey: ") + return dbus.UInt32(passkey) + + @dbus.service.method("org.bluez.Agent", + in_signature="ou", out_signature="") + def DisplayPasskey(self, device, passkey): + print "DisplayPasskey (%s, %d)" % (device, passkey) + + @dbus.service.method("org.bluez.Agent", + in_signature="ou", out_signature="") + def RequestConfirmation(self, device, passkey): + print "RequestConfirmation (%s, %d)" % (device, passkey) + confirm = raw_input("Confirm passkey (yes/no): ") + if (confirm == "yes"): + return + raise Rejected("Passkey doesn't match") + + @dbus.service.method("org.bluez.Agent", + in_signature="s", out_signature="") + def ConfirmModeChange(self, mode): + print "ConfirmModeChange (%s)" % (mode) + + @dbus.service.method("org.bluez.Agent", + in_signature="", out_signature="") + def Cancel(self): + print "Cancel" + +def create_device_reply(device): + print "New device (%s)" % (device) + mainloop.quit() + +def create_device_error(error): + print "Creating device failed: %s" % (error) + mainloop.quit() + +if __name__ == '__main__': + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + + bus = dbus.SystemBus() + manager = dbus.Interface(bus.get_object("org.bluez", "/"), + "org.bluez.Manager") + + if len(sys.argv) > 1: + path = manager.FindAdapter(sys.argv[1]) + else: + path = manager.DefaultAdapter() + + adapter = dbus.Interface(bus.get_object("org.bluez", path), + "org.bluez.Adapter") + + path = "/test/agent" + agent = Agent(bus, path) + + mainloop = gobject.MainLoop() + + if len(sys.argv) > 2: + if len(sys.argv) > 3: + device = adapter.FindDevice(sys.argv[2]) + adapter.RemoveDevice(device) + + agent.set_exit_on_release(False) + adapter.CreatePairedDevice(sys.argv[2], path, "DisplayYesNo", + reply_handler=create_device_reply, + error_handler=create_device_error) + else: + adapter.RegisterAgent(path, "DisplayYesNo") + print "Agent registered" + + mainloop.run() + + #adapter.UnregisterAgent(path) + #print "Agent unregistered" diff --git a/hcid/simple-service b/hcid/simple-service new file mode 100755 index 00000000..5279a3a6 --- /dev/null +++ b/hcid/simple-service @@ -0,0 +1,127 @@ +#!/usr/bin/python + +import sys +import time +import dbus + +xml = ' \ +<?xml version="1.0" encoding="UTF-8" ?> \ +<record> \ + <attribute id="0x0001"> \ + <sequence> \ + <uuid value="0x1101"/> \ + </sequence> \ + </attribute> \ + \ + <attribute id="0x0002"> \ + <uint32 value="0"/> \ + </attribute> \ + \ + <attribute id="0x0003"> \ + <uuid value="00001101-0000-1000-8000-00805f9b34fb"/> \ + </attribute> \ + \ + <attribute id="0x0004"> \ + <sequence> \ + <sequence> \ + <uuid value="0x0100"/> \ + </sequence> \ + <sequence> \ + <uuid value="0x0003"/> \ + <uint8 value="23"/> \ + </sequence> \ + </sequence> \ + </attribute> \ + \ + <attribute id="0x0005"> \ + <sequence> \ + <uuid value="0x1002"/> \ + </sequence> \ + </attribute> \ + \ + <attribute id="0x0006"> \ + <sequence> \ + <uint16 value="0x656e"/> \ + <uint16 value="0x006a"/> \ + <uint16 value="0x0100"/> \ + </sequence> \ + </attribute> \ + \ + <attribute id="0x0007"> \ + <uint32 value="0"/> \ + </attribute> \ + \ + <attribute id="0x0008"> \ + <uint8 value="0xff"/> \ + </attribute> \ + \ + <attribute id="0x0009"> \ + <sequence> \ + <sequence> \ + <uuid value="0x1101"/> \ + <uint16 value="0x0100"/> \ + </sequence> \ + </sequence> \ + </attribute> \ + \ + <attribute id="0x000a"> \ + <url value="http://www.bluez.org/"/> \ + </attribute> \ + \ + <attribute id="0x000b"> \ + <url value="http://www.bluez.org/"/> \ + </attribute> \ + \ + <attribute id="0x000c"> \ + <url value="http://www.bluez.org/"/> \ + </attribute> \ + \ + <attribute id="0x0100"> \ + <text value="Serial Port"/> \ + </attribute> \ + \ + <attribute id="0x0101"> \ + <text value="Serial Port Service"/> \ + </attribute> \ + \ + <attribute id="0x0102"> \ + <text value="BlueZ"/> \ + </attribute> \ + \ + <attribute id="0x0200"> \ + <sequence> \ + <uint16 value="0x0100"/> \ + </sequence> \ + </attribute> \ + \ + <attribute id="0x0201"> \ + <uint32 value="0"/> \ + </attribute> \ +</record> \ +' + +bus = dbus.SystemBus() +manager = dbus.Interface(bus.get_object("org.bluez", "/"), + "org.bluez.Manager") + +if len(sys.argv) > 1: + path = manager.FindAdapter(sys.argv[1]) +else: + path = manager.DefaultAdapter() + +adapter = dbus.Interface(bus.get_object("org.bluez", path), + "org.bluez.Adapter") + +handle = adapter.AddServiceRecord(xml) + +print "Service record with handle 0x%04x added" % (handle) + +print "Press CTRL-C to remove service record" + +try: + time.sleep(1000) + print "Terminating session" +except: + pass + +adapter.RemoveServiceRecord(dbus.UInt32(handle)) diff --git a/hcid/storage.c b/hcid/storage.c new file mode 100644 index 00000000..595e9a22 --- /dev/null +++ b/hcid/storage.c @@ -0,0 +1,707 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 <config.h> +#endif + +#include <stdio.h> +#include <errno.h> +#include <ctype.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> +#include <time.h> +#include <sys/file.h> +#include <sys/stat.h> +#include <sys/param.h> +#include <sys/socket.h> + +#include <glib.h> + +#include <bluetooth/bluetooth.h> + +#include "textfile.h" +#include "hcid.h" + +static inline int create_filename(char *buf, size_t size, const bdaddr_t *bdaddr, const char *name) +{ + char addr[18]; + + ba2str(bdaddr, addr); + + return create_name(buf, size, STORAGEDIR, addr, name); +} + +int write_discoverable_timeout(bdaddr_t *bdaddr, int timeout) +{ + char filename[PATH_MAX + 1], str[32]; + + snprintf(str, sizeof(str), "%d", timeout); + + create_filename(filename, PATH_MAX, bdaddr, "config"); + + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + return textfile_put(filename, "discovto", str); +} + +int read_discoverable_timeout(bdaddr_t *bdaddr, int *timeout) +{ + char filename[PATH_MAX + 1], *str; + + create_filename(filename, PATH_MAX, bdaddr, "config"); + + str = textfile_get(filename, "discovto"); + if (!str) + return -ENOENT; + + if (sscanf(str, "%d", timeout) != 1) { + free(str); + return -ENOENT; + } + + free(str); + + return 0; +} + +int write_device_mode(bdaddr_t *bdaddr, const char *mode) +{ + char filename[PATH_MAX + 1]; + + create_filename(filename, PATH_MAX, bdaddr, "config"); + + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + if (strcmp(mode, "off") != 0) + textfile_put(filename, "onmode", mode); + + return textfile_put(filename, "mode", mode); +} + +int read_device_mode(bdaddr_t *bdaddr, char *mode, int length) +{ + char filename[PATH_MAX + 1], *str; + + create_filename(filename, PATH_MAX, bdaddr, "config"); + + str = textfile_get(filename, "mode"); + if (!str) + return -ENOENT; + + strncpy(mode, str, length); + mode[length - 1] = '\0'; + + free(str); + + return 0; +} + +int read_on_mode(bdaddr_t *bdaddr, char *mode, int length) +{ + char filename[PATH_MAX + 1], *str; + + create_filename(filename, PATH_MAX, bdaddr, "config"); + + str = textfile_get(filename, "onmode"); + if (!str) + return -ENOENT; + + strncpy(mode, str, length); + mode[length - 1] = '\0'; + + free(str); + + return 0; +} + +int write_local_name(bdaddr_t *bdaddr, char *name) +{ + char filename[PATH_MAX + 1], str[249]; + int i; + + memset(str, 0, sizeof(str)); + for (i = 0; i < 248 && name[i]; i++) + if ((unsigned char) name[i] < 32 || name[i] == 127) + str[i] = '.'; + else + str[i] = name[i]; + + create_filename(filename, PATH_MAX, bdaddr, "config"); + + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + return textfile_put(filename, "name", str); +} + +int read_local_name(bdaddr_t *bdaddr, char *name) +{ + char filename[PATH_MAX + 1], *str; + int len; + + create_filename(filename, PATH_MAX, bdaddr, "config"); + + str = textfile_get(filename, "name"); + if (!str) + return -ENOENT; + + len = strlen(str); + if (len > 248) + str[248] = '\0'; + strcpy(name, str); + + free(str); + + return 0; +} + +int write_local_class(bdaddr_t *bdaddr, uint8_t *class) +{ + char filename[PATH_MAX + 1], str[9]; + + sprintf(str, "0x%2.2x%2.2x%2.2x", class[2], class[1], class[0]); + + create_filename(filename, PATH_MAX, bdaddr, "config"); + + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + return textfile_put(filename, "class", str); +} + +int read_local_class(bdaddr_t *bdaddr, uint8_t *class) +{ + char filename[PATH_MAX + 1], tmp[3], *str; + int i; + + create_filename(filename, PATH_MAX, bdaddr, "config"); + + str = textfile_get(filename, "class"); + if (!str) + return -ENOENT; + + memset(tmp, 0, sizeof(tmp)); + for (i = 0; i < 3; i++) { + memcpy(tmp, str + (i * 2) + 2, 2); + class[2 - i] = (uint8_t) strtol(tmp, NULL, 16); + } + + free(str); + + return 0; +} + +int write_remote_class(bdaddr_t *local, bdaddr_t *peer, uint32_t class) +{ + char filename[PATH_MAX + 1], addr[18], str[9]; + + create_filename(filename, PATH_MAX, local, "classes"); + + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + ba2str(peer, addr); + sprintf(str, "0x%6.6x", class); + + return textfile_put(filename, addr, str); +} + +int read_remote_class(bdaddr_t *local, bdaddr_t *peer, uint32_t *class) +{ + char filename[PATH_MAX + 1], addr[18], *str; + + create_filename(filename, PATH_MAX, local, "classes"); + + ba2str(peer, addr); + + str = textfile_get(filename, addr); + if (!str) + return -ENOENT; + + if (sscanf(str, "%x", class) != 1) { + free(str); + return -ENOENT; + } + + free(str); + + return 0; +} + +int write_device_name(bdaddr_t *local, bdaddr_t *peer, char *name) +{ + char filename[PATH_MAX + 1], addr[18], str[249]; + int i; + + memset(str, 0, sizeof(str)); + for (i = 0; i < 248 && name[i]; i++) + if ((unsigned char) name[i] < 32 || name[i] == 127) + str[i] = '.'; + else + str[i] = name[i]; + + create_filename(filename, PATH_MAX, local, "names"); + + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + ba2str(peer, addr); + return textfile_put(filename, addr, str); +} + +int read_device_name(bdaddr_t *local, bdaddr_t *peer, char *name) +{ + char filename[PATH_MAX + 1], addr[18], *str; + int len; + + create_filename(filename, PATH_MAX, local, "names"); + + ba2str(peer, addr); + str = textfile_get(filename, addr); + if (!str) + return -ENOENT; + + len = strlen(str); + if (len > 248) + str[248] = '\0'; + strcpy(name, str); + + free(str); + + return 0; +} + +int write_remote_eir(bdaddr_t *local, bdaddr_t *peer, uint8_t *data) +{ + char filename[PATH_MAX + 1], addr[18], str[481]; + int i; + + memset(str, 0, sizeof(str)); + for (i = 0; i < 240; i++) + sprintf(str + (i * 2), "%2.2X", data[i]); + + create_filename(filename, PATH_MAX, local, "eir"); + + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + ba2str(peer, addr); + return textfile_put(filename, addr, str); +} + +int write_l2cap_info(bdaddr_t *local, bdaddr_t *peer, + uint16_t mtu_result, uint16_t mtu, + uint16_t mask_result, uint32_t mask) +{ + char filename[PATH_MAX + 1], addr[18], str[18]; + + if (mask_result) + snprintf(str, sizeof(str), "%d -1", mtu_result ? -1 : mtu); + else + snprintf(str, sizeof(str), "%d 0x%08x", mtu_result ? -1 : mtu, mask); + + create_filename(filename, PATH_MAX, local, "l2cap"); + + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + ba2str(peer, addr); + return textfile_put(filename, addr, str); +} + +int read_l2cap_info(bdaddr_t *local, bdaddr_t *peer, + uint16_t *mtu_result, uint16_t *mtu, + uint16_t *mask_result, uint32_t *mask) +{ + char filename[PATH_MAX + 1], addr[18], *str, *space, *msk; + + create_filename(filename, PATH_MAX, local, "l2cap"); + + ba2str(peer, addr); + str = textfile_get(filename, addr); + if (!str) + return -ENOENT; + + space = strchr(str, ' '); + if (!space) { + free(str); + return -ENOENT; + } + + msk = space + 1; + *space = '\0'; + + if (mtu_result && mtu) { + if (str[0] == '-') + *mtu_result = 0x0001; + else { + *mtu_result = 0; + *mtu = (uint16_t) strtol(str, NULL, 0); + } + } + + if (mask_result && mask) { + if (msk[0] == '-') + *mask_result = 0x0001; + else { + *mask_result = 0; + *mask = (uint32_t) strtol(msk, NULL, 16); + } + } + + free(str); + + return 0; +} + +int write_version_info(bdaddr_t *local, bdaddr_t *peer, uint16_t manufacturer, uint8_t lmp_ver, uint16_t lmp_subver) +{ + char filename[PATH_MAX + 1], addr[18], str[16]; + + memset(str, 0, sizeof(str)); + sprintf(str, "%d %d %d", manufacturer, lmp_ver, lmp_subver); + + create_filename(filename, PATH_MAX, local, "manufacturers"); + + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + ba2str(peer, addr); + return textfile_put(filename, addr, str); +} + +int write_features_info(bdaddr_t *local, bdaddr_t *peer, unsigned char *features) +{ + char filename[PATH_MAX + 1], addr[18], str[17]; + int i; + + memset(str, 0, sizeof(str)); + for (i = 0; i < 8; i++) + sprintf(str + (i * 2), "%2.2X", features[i]); + + create_filename(filename, PATH_MAX, local, "features"); + + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + ba2str(peer, addr); + return textfile_put(filename, addr, str); +} + +int write_lastseen_info(bdaddr_t *local, bdaddr_t *peer, struct tm *tm) +{ + char filename[PATH_MAX + 1], addr[18], str[24]; + + memset(str, 0, sizeof(str)); + strftime(str, sizeof(str), "%Y-%m-%d %H:%M:%S %Z", tm); + + create_filename(filename, PATH_MAX, local, "lastseen"); + + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + ba2str(peer, addr); + return textfile_put(filename, addr, str); +} + +int write_lastused_info(bdaddr_t *local, bdaddr_t *peer, struct tm *tm) +{ + char filename[PATH_MAX + 1], addr[18], str[24]; + + memset(str, 0, sizeof(str)); + strftime(str, sizeof(str), "%Y-%m-%d %H:%M:%S %Z", tm); + + create_filename(filename, PATH_MAX, local, "lastused"); + + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + ba2str(peer, addr); + return textfile_put(filename, addr, str); +} + +int write_link_key(bdaddr_t *local, bdaddr_t *peer, unsigned char *key, uint8_t type, int length) +{ + char filename[PATH_MAX + 1], addr[18], str[38]; + int i; + + memset(str, 0, sizeof(str)); + for (i = 0; i < 16; i++) + sprintf(str + (i * 2), "%2.2X", key[i]); + sprintf(str + 32, " %d %d", type, length); + + create_filename(filename, PATH_MAX, local, "linkkeys"); + + create_file(filename, S_IRUSR | S_IWUSR); + + ba2str(peer, addr); + + if (length < 0) { + char *tmp = textfile_get(filename, addr); + if (tmp) { + if (strlen(tmp) > 34) + memcpy(str + 34, tmp + 34, 3); + free(tmp); + } + } + + return textfile_put(filename, addr, str); +} + +int read_link_key(bdaddr_t *local, bdaddr_t *peer, unsigned char *key, uint8_t *type) +{ + char filename[PATH_MAX + 1], addr[18], tmp[3], *str; + int i; + + create_filename(filename, PATH_MAX, local, "linkkeys"); + + ba2str(peer, addr); + str = textfile_get(filename, addr); + if (!str) + return -ENOENT; + + memset(tmp, 0, sizeof(tmp)); + for (i = 0; i < 16; i++) { + memcpy(tmp, str + (i * 2), 2); + key[i] = (uint8_t) strtol(tmp, NULL, 16); + } + + if (type) { + memcpy(tmp, str + 33, 2); + *type = (uint8_t) strtol(tmp, NULL, 10); + } + + free(str); + + return 0; +} + +int read_pin_length(bdaddr_t *local, bdaddr_t *peer) +{ + char filename[PATH_MAX + 1], addr[18], *str; + int len; + + create_filename(filename, PATH_MAX, local, "linkkeys"); + + ba2str(peer, addr); + str = textfile_get(filename, addr); + if (!str) + return -ENOENT; + + if (strlen(str) < 36) { + free(str); + return -ENOENT; + } + + len = atoi(str + 35); + + free(str); + + return len; +} + +int read_pin_code(bdaddr_t *local, bdaddr_t *peer, char *pin) +{ + char filename[PATH_MAX + 1], addr[18], *str; + int len; + + create_filename(filename, PATH_MAX, local, "pincodes"); + + ba2str(peer, addr); + str = textfile_get(filename, addr); + if (!str) + return -ENOENT; + + strncpy(pin, str, 16); + len = strlen(pin); + + free(str); + + return len; +} + +static GSList *service_string_to_list(char *services) +{ + GSList *l = NULL; + char *start = services; + int i, finished = 0; + + for (i = 0; !finished; i++) { + if (services[i] == '\0') + finished = 1; + + if (services[i] == ' ' || services[i] == '\0') { + services[i] = '\0'; + l = g_slist_append(l, start); + start = services + i + 1; + } + } + + return l; +} + +static char *service_list_to_string(GSList *services) +{ + char str[1024]; + int len = 0; + + if (!services) + return g_strdup(""); + + memset(str, 0, sizeof(str)); + + while (services) { + int ret; + char *ident = services->data; + + ret = snprintf(str + len, sizeof(str) - len - 1, "%s%s", + ident, services->next ? " " : ""); + + if (ret > 0) + len += ret; + + services = services->next; + } + + return g_strdup(str); +} + +int write_trust(bdaddr_t *local, const char *addr, const char *service, + gboolean trust) +{ + char filename[PATH_MAX + 1], *str; + GSList *services = NULL, *match; + gboolean trusted; + int ret; + + create_filename(filename, PATH_MAX, local, "trusts"); + + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + str = textfile_caseget(filename, addr); + if (str) + services = service_string_to_list(str); + + match = g_slist_find_custom(services, service, (GCompareFunc) strcmp); + trusted = match ? TRUE : FALSE; + + /* If the old setting is the same as the requested one, we're done */ + if (trusted == trust) { + g_slist_free(services); + if (str) + free(str); + return 0; + } + + if (trust) + services = g_slist_append(services, (void *) service); + else + services = g_slist_remove(services, match->data); + + /* Remove the entry if the last trusted service was removed */ + if (!trust && !services) + ret = textfile_casedel(filename, addr); + else { + char *new_str = service_list_to_string(services); + ret = textfile_caseput(filename, addr, new_str); + free(new_str); + } + + g_slist_free(services); + + if (str) + free(str); + + return ret; +} + +gboolean read_trust(const bdaddr_t *local, const char *addr, const char *service) +{ + char filename[PATH_MAX + 1], *str; + GSList *services; + gboolean ret; + + create_filename(filename, PATH_MAX, local, "trusts"); + + str = textfile_caseget(filename, addr); + if (!str) + return FALSE; + + services = service_string_to_list(str); + + if (g_slist_find_custom(services, service, (GCompareFunc) strcmp)) + ret = TRUE; + else + ret = FALSE; + + g_slist_free(services); + free(str); + + return ret; +} + +struct trust_list { + GSList *trusts; + const char *service; +}; + +static void append_trust(char *key, char *value, void *data) +{ + struct trust_list *list = data; + + if (strstr(value, list->service)) + list->trusts = g_slist_append(list->trusts, g_strdup(key)); +} + +GSList *list_trusts(bdaddr_t *local, const char *service) +{ + char filename[PATH_MAX + 1]; + struct trust_list list; + + create_filename(filename, PATH_MAX, local, "trusts"); + + list.trusts = NULL; + list.service = service; + + if (textfile_foreach(filename, append_trust, &list) < 0) + return NULL; + + return list.trusts; +} + +int write_device_profiles(bdaddr_t *src, bdaddr_t *dst, const char *profiles) +{ + char filename[PATH_MAX + 1], addr[18]; + + if (!profiles) + return -EINVAL; + + create_filename(filename, PATH_MAX, src, "profiles"); + + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + ba2str(dst, addr); + return textfile_put(filename, addr, profiles); +} + +int delete_entry(bdaddr_t *src, const char *storage, const char *key) +{ + char filename[PATH_MAX + 1]; + + create_filename(filename, PATH_MAX, src, storage); + + return textfile_del(filename, key); +} diff --git a/hcid/telephony.c b/hcid/telephony.c new file mode 100644 index 00000000..f68b97de --- /dev/null +++ b/hcid/telephony.c @@ -0,0 +1,44 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 <config.h> +#endif + +#include <glib.h> + +#include "telephony.h" + +static GSList *drivers = NULL; + +int bt_telephony_register_driver(struct bt_telephony_driver *driver) +{ + drivers = g_slist_append(drivers, driver); + + return 0; +} + +void bt_telephony_unregister_driver(struct bt_telephony_driver *driver) +{ + drivers = g_slist_remove(drivers, driver); +} diff --git a/hcid/telephony.h b/hcid/telephony.h new file mode 100644 index 00000000..b360a621 --- /dev/null +++ b/hcid/telephony.h @@ -0,0 +1,26 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 + * + */ + +struct bt_telephony_driver { + const char *name; +}; diff --git a/hcid/test-adapter b/hcid/test-adapter new file mode 100755 index 00000000..a4612257 --- /dev/null +++ b/hcid/test-adapter @@ -0,0 +1,90 @@ +#!/usr/bin/python + +import sys +import dbus +import time + +bus = dbus.SystemBus() + +manager = dbus.Interface(bus.get_object("org.bluez", "/"), "org.bluez.Manager") + +adapter = dbus.Interface(bus.get_object("org.bluez", manager.DefaultAdapter()), + "org.bluez.Adapter") + +if (len(sys.argv) < 2): + print "Usage: %s <command>" % (sys.argv[0]) + print "" + print " address" + print " name [name]" + print " mode [mode]" + print " requestmode <mode>" + print " discoverabletimeout [timeout]" + print " periodicdiscovery [on/off]" + print " addservicerecord <file>" + sys.exit(1) + +if (sys.argv[1] == "address"): + properties = adapter.GetProperties() + print properties["Address"] + sys.exit(0) + +if (sys.argv[1] == "name"): + if (len(sys.argv) < 3): + properties = adapter.GetProperties() + print properties["Name"] + else: + adapter.SetProperty("Name", sys.argv[2]) + sys.exit(0) + +if (sys.argv[1] == "mode"): + if (len(sys.argv) < 3): + properties = adapter.GetProperties() + print properties["Mode"] + else: + adapter.SetProperty("Mode", sys.argv[2]) + sys.exit(0) + +if (sys.argv[1] == "requestmode"): + if (len(sys.argv) < 3): + print "Need mode parameter" + else: + adapter.RequestMode(sys.argv[2]) + sys.exit(0) + +if (sys.argv[1] == "discoverabletimeout"): + if (len(sys.argv) < 3): + properties = adapter.GetProperties() + print properties["DiscoverableTimeout"] + else: + adapter.SetProperty("DiscoverableTimeout", sys.argv[2]) + sys.exit(0) + +if (sys.argv[1] == "periodicdiscovery"): + if (len(sys.argv) < 3): + properties = adapter.GetProperties() + print properties["PeriodicDiscovery"] + else: + if (sys.argv[2] == "on"): + value = dbus.Boolean(1) + elif (sys.argv[2] == "off"): + value = dbus.Boolean(0) + else: + value = dbus.Boolean(sys.argv[2]) + adapter.SetProperty("PeriodicDiscovery", value) + time.sleep(120) + sys.exit(0) + +if (sys.argv[1] == "addservicerecord"): + if (len(sys.argv) < 3): + print "Need file parameter" + else: + f = open(sys.argv[2]) + record = f.read() + f.close() + handle = adapter.AddServiceRecord(record) + print "0x%x" % (handle) + time.sleep(120) + sys.exit(0) + +print "Unknown command" +sys.exit(1) diff --git a/hcid/test-device b/hcid/test-device new file mode 100755 index 00000000..05a23d37 --- /dev/null +++ b/hcid/test-device @@ -0,0 +1,124 @@ +#!/usr/bin/python + +import sys +import dbus +import re + +bus = dbus.SystemBus() + +manager = dbus.Interface(bus.get_object("org.bluez", "/"), "org.bluez.Manager") + +adapter = dbus.Interface(bus.get_object("org.bluez", manager.DefaultAdapter()), + "org.bluez.Adapter") + +if (len(sys.argv) < 2): + print "Usage: %s <command>" % (sys.argv[0]) + print "" + print " list" + print " create <address>" + print " remove <path>" + print " discover <address> [pattern]" + print " class <address>" + print " name <address>" + print " alias <address> [alias]" + print " trusted <address> [yes/no]" + sys.exit(1) + +if (sys.argv[1] == "list"): + list = adapter.ListDevices() + print list + sys.exit(0) + +if (sys.argv[1] == "create"): + if (len(sys.argv) < 3): + print "Need address parameter" + else: + device = adapter.CreateDevice(sys.argv[2]) + print device + sys.exit(0) + +if (sys.argv[1] == "remove"): + if (len(sys.argv) < 3): + print "Need object path parameter" + else: + adapter.RemoveDevice(sys.argv[2]) + sys.exit(0) + +if (sys.argv[1] == "discover"): + if (len(sys.argv) < 3): + print "Need address parameter" + else: + path = adapter.FindDevice(sys.argv[2]) + device = dbus.Interface(bus.get_object("org.bluez", path), + "org.bluez.Device") + if (len(sys.argv) < 4): + pattern = "" + else: + pattern = sys.argv[3] + services = device.DiscoverServices(pattern); + for key in services.keys(): + p = re.compile(">.*?<") + xml = p.sub("><", services[key].replace("\n", "")) + print "[ 0x%5x ]" % (key) + print xml + print + sys.exit(0) + +if (sys.argv[1] == "class"): + if (len(sys.argv) < 3): + print "Need address parameter" + else: + path = adapter.FindDevice(sys.argv[2]) + device = dbus.Interface(bus.get_object("org.bluez", path), + "org.bluez.Device") + properties = device.GetProperties() + print "0x%06x" % (properties["Class"]) + sys.exit(0) + +if (sys.argv[1] == "name"): + if (len(sys.argv) < 3): + print "Need address parameter" + else: + path = adapter.FindDevice(sys.argv[2]) + device = dbus.Interface(bus.get_object("org.bluez", path), + "org.bluez.Device") + properties = device.GetProperties() + print properties["Name"] + sys.exit(0) + +if (sys.argv[1] == "alias"): + if (len(sys.argv) < 3): + print "Need address parameter" + else: + path = adapter.FindDevice(sys.argv[2]) + device = dbus.Interface(bus.get_object("org.bluez", path), + "org.bluez.Device") + if (len(sys.argv) < 4): + properties = device.GetProperties() + print properties["Alias"] + else: + device.SetProperty("Alias", sys.argv[3]) + sys.exit(0) + +if (sys.argv[1] == "trusted"): + if (len(sys.argv) < 3): + print "Need address parameter" + else: + path = adapter.FindDevice(sys.argv[2]) + device = dbus.Interface(bus.get_object("org.bluez", path), + "org.bluez.Device") + if (len(sys.argv) < 4): + properties = device.GetProperties() + print properties["Trusted"] + else: + if (sys.argv[3] == "yes"): + value = dbus.Boolean(1) + elif (sys.argv[3] == "no"): + value = dbus.Boolean(0) + else: + value = dbus.Boolean(sys.argv[3]) + device.SetProperty("Trusted", value) + sys.exit(0) + +print "Unknown command" +sys.exit(1) diff --git a/hcid/test-discovery b/hcid/test-discovery new file mode 100755 index 00000000..874de66f --- /dev/null +++ b/hcid/test-discovery @@ -0,0 +1,43 @@ +#!/usr/bin/python + +import gobject + +import dbus +import dbus.mainloop.glib + +def device_found(address, properties): + print "[ " + address + " ]" + + for key in properties.keys(): + value = properties[key] + if (key == "Class"): + print " %s = 0x%06x" % (key, value) + else: + print " %s = %s" % (key, value) + +def discovery_completed(): + mainloop.quit() + +if __name__ == '__main__': + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + + bus = dbus.SystemBus() + manager = dbus.Interface(bus.get_object("org.bluez", "/"), + "org.bluez.Manager") + + path = manager.DefaultAdapter() + adapter = dbus.Interface(bus.get_object("org.bluez", path), + "org.bluez.Adapter") + + bus.add_signal_receiver(device_found, + dbus_interface = "org.bluez.Adapter", + signal_name = "DeviceFound") + + bus.add_signal_receiver(discovery_completed, + dbus_interface = "org.bluez.Adapter", + signal_name = "DiscoveryCompleted") + + adapter.DiscoverDevices() + + mainloop = gobject.MainLoop() + mainloop.run() diff --git a/hcid/test-manager b/hcid/test-manager new file mode 100755 index 00000000..759b6a48 --- /dev/null +++ b/hcid/test-manager @@ -0,0 +1,27 @@ +#!/usr/bin/python + +import gobject + +import dbus +import dbus.mainloop.glib + +def adapter_added(path): + print "Adapter with path %s added" % (path) + +def adapter_removed(path): + print "Adapter with path %s removed" % (path) + +if __name__ == "__main__": + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + + bus = dbus.SystemBus() + + manager = dbus.Interface(bus.get_object('org.bluez', '/org/bluez'), + 'org.bluez.Manager') + + manager.connect_to_signal("AdapterAdded", adapter_added) + + manager.connect_to_signal("AdapterRemoved", adapter_removed) + + mainloop = gobject.MainLoop() + mainloop.run() |