diff options
Diffstat (limited to 'input/device.c')
| -rw-r--r-- | input/device.c | 220 | 
1 files changed, 195 insertions, 25 deletions
diff --git a/input/device.c b/input/device.c index 44bec0d6..0283918b 100644 --- a/input/device.c +++ b/input/device.c @@ -34,21 +34,26 @@  #include <sys/socket.h>  #include <bluetooth/bluetooth.h> +#include <bluetooth/hci.h> +#include <bluetooth/hci_lib.h>  #include <bluetooth/hidp.h>  #include <bluetooth/l2cap.h>  #include <bluetooth/rfcomm.h>  #include <bluetooth/sdp.h> +#include <bluetooth/sdp_lib.h>  #include <glib.h>  #include <dbus/dbus.h>  #include <gdbus.h>  #include "logging.h" +#include "textfile.h"  #include "uinput.h" +#include "../src/storage.h" +  #include "device.h"  #include "error.h" -#include "storage.h"  #include "fakehid.h"  #include "glib-helper.h" @@ -78,6 +83,7 @@ struct input_device {  	char			*path;  	bdaddr_t		src;  	bdaddr_t		dst; +	uint32_t		handle;  	char			*name;  	GSList			*connections;  }; @@ -466,13 +472,168 @@ static int fake_hid_disconnect(struct input_conn *iconn)  	return fhid->disconnect(iconn->fake);  } -static int hidp_connadd(bdaddr_t *src, bdaddr_t *dst, -		int ctrl_sk, int intr_sk, int timeout, const char *name) +static int encrypt_link(const char *src_addr, const char *dst_addr) +{ +	char filename[PATH_MAX + 1]; +	struct hci_conn_info_req *cr; +	int dd, err, dev_id; +	char *str; + +	create_name(filename, PATH_MAX, STORAGEDIR, src_addr, "linkkeys"); + +	str = textfile_get(filename, dst_addr); +	if (!str) { +		error("Encryption link key not found"); +		return -ENOKEY; +	} + +	free(str); + +	cr = g_try_malloc0(sizeof(*cr) + sizeof(struct hci_conn_info)); +	if (!cr) +		return -ENOMEM; + +	dev_id = hci_devid(src_addr); +	if (dev_id < 0) { +		g_free(cr); +		return -errno; +	} + +	dd = hci_open_dev(dev_id); +	if (dd < 0) { +		g_free(cr); +		return -errno; +	} + +	str2ba(dst_addr, &cr->bdaddr); +	cr->type = ACL_LINK; + +	if (ioctl(dd, HCIGETCONNINFO, (unsigned long) cr) < 0) +		goto fail; + +	if (cr->conn_info->link_mode & HCI_LM_ENCRYPT) { +		/* Already encrypted */ +		goto done; +	} + +	if (hci_authenticate_link(dd, htobs(cr->conn_info->handle), 1000) < 0) { +		error("Link authentication failed: %s (%d)", +						strerror(errno), errno); +		goto fail; +	} + +	if (hci_encrypt_link(dd, htobs(cr->conn_info->handle), 1, 1000) < 0) { +		error("Link encryption failed: %s (%d)", +						strerror(errno), errno); +		goto fail; +	} + +done: +	g_free(cr); + +	hci_close_dev(dd); + +	return 0; + +fail: +	g_free(cr); + +	err = errno; +	hci_close_dev(dd); + +	return -err; +} + +static void epox_endian_quirk(unsigned char *data, int size) +{ +	/* USAGE_PAGE (Keyboard)	05 07 +	 * USAGE_MINIMUM (0)		19 00 +	 * USAGE_MAXIMUM (65280)	2A 00 FF   <= must be FF 00 +	 * LOGICAL_MINIMUM (0)		15 00 +	 * LOGICAL_MAXIMUM (65280)	26 00 FF   <= must be FF 00 +	 */ +	unsigned char pattern[] = { 0x05, 0x07, 0x19, 0x00, 0x2a, 0x00, 0xff, +						0x15, 0x00, 0x26, 0x00, 0xff }; +	int i; + +	if (!data) +		return; + +	for (i = 0; i < size - sizeof(pattern); i++) { +		if (!memcmp(data + i, pattern, sizeof(pattern))) { +			data[i + 5] = 0xff; +			data[i + 6] = 0x00; +			data[i + 10] = 0xff; +			data[i + 11] = 0x00; +		} +	} +} + +static void extract_hid_record(sdp_record_t *rec, struct hidp_connadd_req *req) +{ +	sdp_data_t *pdlist, *pdlist2; +	uint8_t attr_val; + +	pdlist = sdp_data_get(rec, 0x0101); +	pdlist2 = sdp_data_get(rec, 0x0102); +	if (pdlist) { +		if (pdlist2) { +			if (strncmp(pdlist->val.str, pdlist2->val.str, 5)) { +				strncpy(req->name, pdlist2->val.str, 127); +				strcat(req->name, " "); +			} +			strncat(req->name, pdlist->val.str, 127 - strlen(req->name)); +		} else +			strncpy(req->name, pdlist->val.str, 127); +	} else { +		pdlist2 = sdp_data_get(rec, 0x0100); +		if (pdlist2) +			strncpy(req->name, pdlist2->val.str, 127); +	} + +	pdlist = sdp_data_get(rec, SDP_ATTR_HID_PARSER_VERSION); +	req->parser = pdlist ? pdlist->val.uint16 : 0x0100; + +	pdlist = sdp_data_get(rec, SDP_ATTR_HID_DEVICE_SUBCLASS); +	req->subclass = pdlist ? pdlist->val.uint8 : 0; + +	pdlist = sdp_data_get(rec, SDP_ATTR_HID_COUNTRY_CODE); +	req->country = pdlist ? pdlist->val.uint8 : 0; + +	pdlist = sdp_data_get(rec, SDP_ATTR_HID_VIRTUAL_CABLE); +	attr_val = pdlist ? pdlist->val.uint8 : 0; +	if (attr_val) +		req->flags |= (1 << HIDP_VIRTUAL_CABLE_UNPLUG); + +	pdlist = sdp_data_get(rec, SDP_ATTR_HID_BOOT_DEVICE); +	attr_val = pdlist ? pdlist->val.uint8 : 0; +	if (attr_val) +		req->flags |= (1 << HIDP_BOOT_PROTOCOL_MODE); + +	pdlist = sdp_data_get(rec, SDP_ATTR_HID_DESCRIPTOR_LIST); +	if (pdlist) { +		pdlist = pdlist->val.dataseq; +		pdlist = pdlist->val.dataseq; +		pdlist = pdlist->next; + +		req->rd_data = g_try_malloc0(pdlist->unitSize); +		if (req->rd_data) { +			memcpy(req->rd_data, (unsigned char *) pdlist->val.str, +								pdlist->unitSize); +			req->rd_size = pdlist->unitSize; +			epox_endian_quirk(req->rd_data, req->rd_size); +		} +	} +} + +static int hidp_connadd(const bdaddr_t *src, const bdaddr_t *dst, int ctrl_sk, +		int intr_sk, int timeout, const char *name, const uint32_t handle)  {  	struct hidp_connadd_req req;  	struct fake_hid *fake_hid;  	struct fake_input *fake; -	char addr[18]; +	sdp_record_t *rec; +	char src_addr[18], dst_addr[18];  	int ctl, err;  	memset(&req, 0, sizeof(req)); @@ -481,12 +642,19 @@ static int hidp_connadd(bdaddr_t *src, bdaddr_t *dst,  	req.flags     = 0;  	req.idle_to   = timeout; -	err = get_stored_device_info(src, dst, &req); -	if (err < 0) { -		error("Rejected connection from unknown device %s", addr); +	ba2str(src, src_addr); +	ba2str(dst, dst_addr); + +	rec = fetch_record(src_addr, dst_addr, handle); +	if (!rec) { +		error("Rejected connection from unknown device %s", dst_addr); +		err = -EPERM;  		goto cleanup;  	} +	extract_hid_record(rec, &req); +	sdp_record_free(rec); +  	fake_hid = get_fake_hid(req.vendor, req.product);  	if (fake_hid) {  		fake = g_new0(struct fake_input, 1); @@ -502,10 +670,8 @@ static int hidp_connadd(bdaddr_t *src, bdaddr_t *dst,  		return -errno;  	} -	ba2str(dst, addr); -  	if (req.subclass & 0x40) { -		err = encrypt_link(src, dst); +		err = encrypt_link(src_addr, dst_addr);  		if (err < 0 && err != -EACCES)  			goto cleanup;  	} @@ -524,7 +690,7 @@ static int hidp_connadd(bdaddr_t *src, bdaddr_t *dst,  		goto cleanup;  	} -	info("New input device %s (%s)", addr, req.name); +	info("New input device %s (%s)", dst_addr, req.name);  cleanup:  	if (req.rd_data) @@ -548,7 +714,7 @@ static void interrupt_connect_cb(GIOChannel *chan, int err, const bdaddr_t *src,  	iconn->intr_sk = g_io_channel_unix_get_fd(chan);  	err = hidp_connadd(&idev->src, &idev->dst,  				iconn->ctrl_sk, iconn->intr_sk, -					iconn->timeout, idev->name); +				iconn->timeout, idev->name, idev->handle);  	if (err < 0)  		goto failed; @@ -860,17 +1026,23 @@ static GDBusSignalTable device_signals[] = {  };  static struct input_device *input_device_new(DBusConnection *conn, -					const char *path, bdaddr_t *src, -					bdaddr_t *dst) +					const char *path, const bdaddr_t *src, +					const bdaddr_t *dst, const uint32_t handle)  {  	struct input_device *idev; +	char name[249], src_addr[18], dst_addr[18];  	idev = g_new0(struct input_device, 1);  	bacpy(&idev->src, src);  	bacpy(&idev->dst, dst);  	idev->path = g_strdup(path); -	read_device_name(src, dst, &idev->name);  	idev->conn = dbus_connection_ref(conn); +	idev->handle = handle; + +	ba2str(src, src_addr); +	ba2str(dst, dst_addr); +	if (read_device_name(src_addr, dst_addr, name) == 0) +		idev->name = g_strdup(name);  	if (g_dbus_register_interface(conn, idev->path, INPUT_DEVICE_INTERFACE,  					device_methods, device_signals, NULL, @@ -881,8 +1053,8 @@ static struct input_device *input_device_new(DBusConnection *conn,  		return NULL;  	} -	info("Registered interface %s on path %s", INPUT_DEVICE_INTERFACE, -		idev->path); +	info("Registered interface %s on path %s", +			INPUT_DEVICE_INTERFACE, idev->path);  	return idev;  } @@ -905,15 +1077,15 @@ static struct input_conn *input_conn_new(struct input_device *idev,  }  int input_device_register(DBusConnection *conn, const char *path, -			bdaddr_t *src, bdaddr_t *dst, const char *uuid, -			int timeout) +			const bdaddr_t *src, const bdaddr_t *dst, +			const char *uuid, uint32_t handle, int timeout)  {  	struct input_device *idev;  	struct input_conn *iconn;  	idev = find_device_by_path(devices, path);  	if (!idev) { -		idev = input_device_new(conn, path, src, dst); +		idev = input_device_new(conn, path, src, dst, handle);  		if (!idev)  			return -EINVAL;  		devices = g_slist_append(devices, idev); @@ -936,7 +1108,7 @@ int fake_input_register(DBusConnection *conn, const char *path, bdaddr_t *src,  	idev = find_device_by_path(devices, path);  	if (!idev) { -		idev = input_device_new(conn, path, src, dst); +		idev = input_device_new(conn, path, src, dst, 0);  		if (!idev)  			return -EINVAL;  		devices = g_slist_append(devices, idev); @@ -989,8 +1161,6 @@ int input_device_unregister(const char *path, const char *uuid)  		return -EBUSY;  	} -	del_stored_device_info(&idev->src, &idev->dst); -  	idev->connections = g_slist_remove(idev->connections, iconn);  	input_conn_free(iconn);  	if (idev->connections) @@ -1050,7 +1220,7 @@ int input_device_close_channels(const bdaddr_t *src, const bdaddr_t *dst)  	return 0;  } -int input_device_connadd(bdaddr_t *src, bdaddr_t *dst) +int input_device_connadd(const bdaddr_t *src, const bdaddr_t *dst)  {  	struct input_device *idev;  	struct input_conn *iconn; @@ -1065,7 +1235,7 @@ int input_device_connadd(bdaddr_t *src, bdaddr_t *dst)  		return -ENOENT;  	err = hidp_connadd(src, dst, iconn->ctrl_sk, iconn->intr_sk, -				iconn->timeout, idev->name); +				iconn->timeout, idev->name, idev->handle);  	if (err < 0)  		goto error;  | 
