diff options
author | Luiz Augusto von Dentz <luiz.dentz@openbossa.org> | 2009-01-11 10:18:23 -0300 |
---|---|---|
committer | Johan Hedberg <johan.hedberg@nokia.com> | 2009-01-11 19:11:59 +0200 |
commit | 2aad1cf41acbc69481915d24cef33fa721d4e1fd (patch) | |
tree | 3415f14eb928bc6da4c86bd8600697d31c062c97 /src/device.c | |
parent | 60ec17b68922904833973af118ff91e0f1df1b04 (diff) |
Move bonding creation to device.c.
Diffstat (limited to 'src/device.c')
-rw-r--r-- | src/device.c | 692 |
1 files changed, 669 insertions, 23 deletions
diff --git a/src/device.c b/src/device.c index f51184ca..7891d1e5 100644 --- a/src/device.c +++ b/src/device.c @@ -35,6 +35,7 @@ #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> @@ -65,6 +66,22 @@ struct btd_driver_data { void *priv; }; +struct bonding_req { + DBusConnection *conn; + DBusMessage *msg; + GIOChannel *io; + guint io_id; + guint listener_id; + struct btd_device *device; +}; + +struct authentication_req { + auth_type_t type; + void *cb; + struct agent *agent; + struct btd_device *device; +}; + struct btd_device { bdaddr_t bdaddr; gchar *path; @@ -78,6 +95,8 @@ struct btd_device { char *discov_requestor; /* discovery requestor unique name */ guint discov_listener; guint discov_timer; + struct bonding_req *bonding; + struct authentication_req *authr; /* authentication request */ /* For Secure Simple Pairing */ uint8_t cap; @@ -111,6 +130,39 @@ static uint16_t uuid_list[] = { static GSList *device_drivers = NULL; +static DBusHandlerResult error_connection_attempt_failed(DBusConnection *conn, + DBusMessage *msg, int err) +{ + return error_common_reply(conn, msg, + ERROR_INTERFACE ".ConnectionAttemptFailed", + err > 0 ? strerror(err) : "Connection attempt failed"); +} + +static DBusHandlerResult error_failed(DBusConnection *conn, + DBusMessage *msg, const char * desc) +{ + return error_common_reply(conn, msg, ERROR_INTERFACE ".Failed", desc); +} + +static DBusHandlerResult error_failed_errno(DBusConnection *conn, + DBusMessage *msg, int err) +{ + const char *desc = strerror(err); + + return error_failed(conn, msg, desc); +} + +static inline DBusMessage *no_such_adapter(DBusMessage *msg) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE ".NoSuchAdapter", + "No such adapter"); +} + +static inline DBusMessage *in_progress(DBusMessage *msg, const char *str) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE ".InProgress", str); +} + static void device_free(gpointer user_data) { struct btd_device *device = user_data; @@ -254,13 +306,6 @@ static DBusMessage *get_properties(DBusConnection *conn, ptr = adapter_get_path(adapter); dict_append_entry(&dict, "Adapter", DBUS_TYPE_OBJECT_PATH, &ptr); - if (read_remote_eir(&src, &device->bdaddr, NULL) < 0) - boolean = TRUE; - else - boolean = FALSE; - - dict_append_entry(&dict, "LegacyPairing", DBUS_TYPE_BOOLEAN, &boolean); - dbus_message_iter_close_container(&iter, &dict); return reply; @@ -523,7 +568,7 @@ gboolean device_get_connected(struct btd_device *device) return device->connected; } -void device_set_connected(DBusConnection *conn, struct btd_device *device, +void device_set_connected(struct btd_device *device, DBusConnection *conn, gboolean connected) { device->connected = connected; @@ -579,7 +624,61 @@ struct btd_device *device_create(DBusConnection *conn, struct btd_adapter *adapt return device; } -void device_remove(DBusConnection *conn, struct btd_device *device) +static void device_remove_bonding(struct btd_device *device, DBusConnection *conn) +{ + struct active_conn_info *ci; + char filename[PATH_MAX + 1]; + char *str, srcaddr[18], dstaddr[18]; + int dd, dev_id; + bdaddr_t bdaddr; + gboolean paired; + + adapter_get_address(device->adapter, &bdaddr); + ba2str(&bdaddr, srcaddr); + ba2str(&device->bdaddr, dstaddr); + + dev_id = adapter_get_dev_id(device->adapter); + + dd = hci_open_dev(dev_id); + if (dd < 0) + return; + + create_name(filename, PATH_MAX, STORAGEDIR, srcaddr, + "linkkeys"); + + /* textfile_del doesn't return an error when the key is not found */ + str = textfile_caseget(filename, dstaddr); + paired = str ? TRUE : FALSE; + g_free(str); + + if (!paired) + return; + + /* Delete the link key from storage */ + textfile_casedel(filename, dstaddr); + + /* Delete the link key from the Bluetooth chip */ + hci_delete_stored_link_key(dd, &device->bdaddr, 0, HCI_REQ_TIMEOUT); + + /* Send the HCI disconnect command */ + ci = adapter_search_active_conn_by_bdaddr(device->adapter, + &device->bdaddr); + if (ci) { + int err = hci_disconnect(dd, htobs(ci->handle), + HCI_OE_USER_ENDED_CONNECTION, + HCI_REQ_TIMEOUT); + if (err < 0) + error("Disconnect: %s (%d)", strerror(-err), -err); + } + + hci_close_dev(dd); + + paired = FALSE; + emit_property_changed(conn, device->path, DEVICE_INTERFACE, + "Paired", DBUS_TYPE_BOOLEAN, &paired); +} + +void device_remove(struct btd_device *device, DBusConnection *conn) { GSList *list; struct btd_device_driver *driver; @@ -587,6 +686,12 @@ void device_remove(DBusConnection *conn, struct btd_device *device) debug("Removing device %s", path); + if (device->bonding) + device_cancel_bonding(device, HCI_OE_USER_ENDED_CONNECTION); + + if (!device->temporary) + device_remove_bonding(device, conn); + for (list = device->drivers; list; list = list->next) { struct btd_driver_data *driver_data = list->data; driver = driver_data->driver; @@ -1280,15 +1385,356 @@ static gboolean start_discovery(gpointer user_data) return FALSE; } -int device_set_paired(DBusConnection *conn, struct btd_device *device, - struct bonding_request_info *bonding) +DBusMessage *new_authentication_return(DBusMessage *msg, uint8_t status) { - dbus_bool_t paired = TRUE; + 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 0x13: /* user ended the connection */ + 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 void bonding_request_free(struct bonding_req *bonding) +{ + struct btd_device *device; + + if (!bonding) + return; + + if (bonding->listener_id) + g_dbus_remove_watch(bonding->conn, bonding->listener_id); - device_set_temporary(device, FALSE); + if (bonding->msg) + dbus_message_unref(bonding->msg); + + if (bonding->conn) + dbus_connection_unref(bonding->conn); + + if (bonding->io_id) + g_source_remove(bonding->io_id); + + if (bonding->io) + g_io_channel_unref(bonding->io); + + device = bonding->device; + + if (device && device->agent) { + agent_destroy(device->agent, FALSE); + device->agent = NULL; + } + + device->bonding = NULL; + g_free(bonding); +} + +static void device_set_paired(struct btd_device *device, gboolean value) +{ + DBusConnection *conn = get_dbus_connection(); emit_property_changed(conn, device->path, DEVICE_INTERFACE, "Paired", - DBUS_TYPE_BOOLEAN, &paired); + DBUS_TYPE_BOOLEAN, &value); +} + +static void device_agent_removed(struct agent *agent, void *user_data) +{ + struct btd_device *device = user_data; + + device_set_agent(device, NULL); + + if (device->authr) + device->authr->agent = NULL; +} + +static struct bonding_req *bonding_request_new(DBusConnection *conn, + DBusMessage *msg, + struct btd_device *device, + const char *agent_path, + uint8_t capability) +{ + struct bonding_req *bonding; + const char *name = dbus_message_get_sender(msg); + struct agent *agent; + + debug("%s: requesting bonding", device->path); + + if (!agent_path) + goto proceed; + + agent = agent_create(device->adapter, name, agent_path, + capability, + device_agent_removed, + device); + if (!agent) { + error("Unable to create a new agent"); + return NULL; + } + + device->agent = agent; + + debug("Temporary agent registered for %s at %s:%s", + device->path, name, agent_path); + +proceed: + + bonding = g_new0(struct bonding_req, 1); + + bonding->conn = dbus_connection_ref(conn); + bonding->msg = dbus_message_ref(msg); + + return bonding; +} + +static gboolean create_bonding_io_cb(GIOChannel *io, GIOCondition cond, + struct btd_device *device) +{ + struct hci_request rq; + auth_requested_cp cp; + evt_cmd_status rp; + struct l2cap_conninfo cinfo; + socklen_t len; + int sk, dd, ret; + + if (!device->bonding) { + /* If we come here it implies a bug somewhere */ + debug("create_bonding_io_cb: no pending bonding!"); + g_io_channel_close(io); + return FALSE; + } + + if (cond & G_IO_NVAL) { + if (device->bonding) { + DBusMessage *reply; + + reply = new_authentication_return(device->bonding->msg, + 0x09); + g_dbus_send_message(device->bonding->conn, reply); + } + + goto cleanup; + } + + if (cond & (G_IO_HUP | G_IO_ERR)) { + debug("Hangup or error on bonding IO channel"); + + if (device->bonding) + error_connection_attempt_failed(device->bonding->conn, + device->bonding->msg, + ENETDOWN); + + 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(device->bonding->conn, device->bonding->msg, + errno); + goto failed; + } + + if (ret != 0) { + if (device->bonding) + error_connection_attempt_failed(device->bonding->conn, + device->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(device->bonding->conn, device->bonding->msg, + errno); + goto failed; + } + + dd = hci_open_dev(adapter_get_dev_id(device->adapter)); + if (dd < 0) { + DBusMessage *reply = no_such_adapter(device->bonding->msg); + g_dbus_send_message(device->bonding->conn, reply); + 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, HCI_REQ_TIMEOUT) < 0) { + error("Unable to send HCI request: %s (%d)", + strerror(errno), errno); + error_failed_errno(device->bonding->conn, device->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(device->bonding->conn, device->bonding->msg, + bt_error(rp.status)); + hci_close_dev(dd); + goto failed; + } + + hci_close_dev(dd); + + device->bonding->io_id = g_io_add_watch(io, + G_IO_NVAL | G_IO_HUP | G_IO_ERR, + (GIOFunc) create_bonding_io_cb, + device); + + return FALSE; + +failed: + g_io_channel_close(io); + +cleanup: + device->bonding->io_id = 0; + bonding_request_free(device->bonding); + + return FALSE; +} + +static void create_bond_req_exit(DBusConnection *conn, void *user_data) +{ + struct btd_device *device = user_data; + + debug("%s: requestor exited before bonding was completed", device->path); + + if (device->authr) + device_cancel_authentication(device); + + if (device->bonding) { + device->bonding->listener_id = 0; + g_io_channel_close(device->bonding->io); + bonding_request_free(device->bonding); + } +} + +DBusMessage *device_create_bonding(struct btd_device *device, + DBusConnection *conn, + DBusMessage *msg, + const char *agent_path, + uint8_t capability) +{ + char filename[PATH_MAX + 1]; + char *str, srcaddr[18], dstaddr[18]; + struct btd_adapter *adapter = device->adapter; + struct bonding_req *bonding; + bdaddr_t src; + int sk; + + adapter_get_address(adapter, &src); + ba2str(&src, srcaddr); + ba2str(&device->bdaddr, dstaddr); + + if (device->bonding) + return in_progress(msg, "Bonding in progress"); + + /* check if a link key already exists */ + create_name(filename, PATH_MAX, STORAGEDIR, srcaddr, + "linkkeys"); + + str = textfile_caseget(filename, dstaddr); + if (str) { + free(str); + return g_dbus_create_error(msg, + ERROR_INTERFACE ".AlreadyExists", + "Bonding already exists"); + } + + sk = l2raw_connect(&src, &device->bdaddr); + if (sk < 0) + return g_dbus_create_error(msg, + ERROR_INTERFACE ".ConnectionAttemptFailed", + "Connection attempt failed"); + + bonding = bonding_request_new(conn, msg, device, 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_io_cb, + device); + + bonding->listener_id = g_dbus_add_disconnect_watch(conn, + dbus_message_get_sender(msg), + create_bond_req_exit, device, + NULL); + + device->bonding = bonding; + bonding->device = device; + + return NULL; +} + +void device_bonding_complete(struct btd_device *device, uint8_t status) +{ + struct bonding_req *bonding = device->bonding; + + if (status) + goto failed; + + device->temporary = FALSE; /* If we were initiators start service discovery immediately. * However if the other end was the initator wait a few seconds @@ -1302,18 +1748,218 @@ int device_set_paired(DBusConnection *conn, struct btd_device *device, device->discov_timer = 0; } - return device_browse(device, bonding->conn, bonding->msg, - NULL, FALSE); + device_browse(device, bonding->conn, bonding->msg, + NULL, FALSE); + } else if (!device->discov_active && !device->discov_timer) { + /* If we are not initiators and there is no currently active + * discovery or discovery timer, set the discovery timer */ + device->discov_timer = g_timeout_add_seconds(DISCOVERY_TIMER, + start_discovery, + device); } - /* If we are not initiators and there is no currently active discovery - * or discovery timer, set the discovery timer */ - if (!device->discov_active && !device->discov_timer) - device->discov_timer = g_timeout_add_seconds(DISCOVERY_TIMER, - start_discovery, - device); + device_set_paired(device, TRUE); - return 0; + g_free(device->authr); + device->authr = NULL; + bonding_request_free(bonding); + + return; + +failed: + + device_cancel_bonding(device, status); +} + +gboolean device_is_bonding(struct btd_device *device, const char *sender) +{ + struct bonding_req *bonding = device->bonding; + + if (!device->bonding) + return FALSE; + + if (!sender) + return TRUE; + + return g_str_equal(sender, dbus_message_get_sender(bonding->msg)); +} + +void device_cancel_bonding(struct btd_device *device, uint8_t status) +{ + struct bonding_req *bonding = device->bonding; + DBusMessage *reply; + + if (!bonding) + return; + + debug("%s: canceling bonding request", device->path); + + if (device->authr) + device_cancel_authentication(device); + + reply = new_authentication_return(bonding->msg, status); + g_dbus_send_message(bonding->conn, reply); + + if (device->bonding->io_id) + g_source_remove(device->bonding->io_id); + + bonding_request_free(bonding); +} + +static void pincode_cb(struct agent *agent, DBusError *err, const char *pincode, + void *data) +{ + struct authentication_req *auth = data; + struct btd_device *device = auth->device; + + /* No need to reply anything if the authentication already failed */ + if (!auth->cb) + return; + + ((agent_pincode_cb) auth->cb)(agent, err, pincode, device); + + auth->cb = NULL; +} + +static void confirm_cb(struct agent *agent, DBusError *err, void *data) +{ + struct authentication_req *auth = data; + struct btd_device *device = auth->device; + + /* No need to reply anything if the authentication already failed */ + if (!auth->cb) + return; + + ((agent_cb) auth->cb)(agent, err, device); + + auth->cb = NULL; +} + +static void passkey_cb(struct agent *agent, DBusError *err, uint32_t passkey, + void *data) +{ + struct authentication_req *auth = data; + struct btd_device *device = auth->device; + + /* No need to reply anything if the authentication already failed */ + if (!auth->cb) + return; + + ((agent_passkey_cb) auth->cb)(agent, err, passkey, device); + + auth->cb = NULL; +} + +int device_request_authentication(struct btd_device *device, auth_type_t type, + uint32_t passkey, void *cb) +{ + struct authentication_req *auth; + struct agent *agent; + int ret; + + debug("%s: requesting agent authentication", device->path); + + agent = device->agent; + + if (!agent) + agent = adapter_get_agent(device->adapter); + + if (!agent) { + error("No agent available for %u request", type); + return -EPERM; + } + + auth = g_new0(struct authentication_req, 1); + auth->agent = agent; + auth->device = device; + auth->cb = cb; + auth->type = type; + device->authr = auth; + + switch (type) { + case AUTH_TYPE_PINCODE: + ret = agent_request_pincode(agent, device, pincode_cb, + auth); + break; + case AUTH_TYPE_PASSKEY: + ret = agent_request_passkey(agent, device, passkey_cb, + auth); + break; + case AUTH_TYPE_CONFIRM: + ret = agent_request_confirmation(agent, device, passkey, + confirm_cb, auth); + break; + case AUTH_TYPE_NOTIFY: + ret = agent_display_passkey(agent, device, passkey); + break; + case AUTH_TYPE_AUTO: + ret = 0; + break; + default: + ret = -EINVAL; + } + + if (ret < 0) { + error("Failed requesting authentication"); + g_free(auth); + device->authr = NULL; + } + + return ret; +} + +static void cancel_authentication(struct authentication_req *auth) +{ + struct btd_device *device = auth->device; + struct agent *agent = auth->agent; + DBusError err; + + if (!auth->cb) + return; + + dbus_error_init(&err); + dbus_set_error_const(&err, "org.bluez.Error.Canceled", NULL); + + switch (auth->type) { + case AUTH_TYPE_PINCODE: + ((agent_pincode_cb) auth->cb)(agent, &err, NULL, device); + break; + case AUTH_TYPE_CONFIRM: + ((agent_cb) auth->cb)(agent, &err, device); + break; + case AUTH_TYPE_PASSKEY: + ((agent_passkey_cb) auth->cb)(agent, &err, 0, device); + break; + case AUTH_TYPE_NOTIFY: + case AUTH_TYPE_AUTO: + /* User Notify/Auto doesn't require any reply */ + break; + } + + dbus_error_free(&err); + auth->cb = NULL; +} + +void device_cancel_authentication(struct btd_device *device) +{ + struct authentication_req *auth = device->authr; + + if (!auth) + return; + + debug("%s: canceling authentication request", device->path); + + if (auth->agent) + agent_cancel(auth->agent); + + cancel_authentication(auth); + device->authr = NULL; + g_free(auth); +} + +gboolean device_is_authenticating(struct btd_device *device) +{ + return (device->authr != NULL); } void btd_device_add_uuid(struct btd_device *device, const char *uuid) |