| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * |
| * BlueZ - Bluetooth protocol stack for Linux |
| * |
| * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org> |
| * |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <unistd.h> |
| #include <stdbool.h> |
| #include <errno.h> |
| |
| #include <glib.h> |
| #include <dbus/dbus.h> |
| |
| #include "bluetooth/bluetooth.h" |
| #include "bluetooth/sdp.h" |
| #include "bluetooth/uuid.h" |
| |
| #include "src/log.h" |
| #include "src/uuid-helper.h" |
| #include "btio/btio.h" |
| #include "src/adapter.h" |
| #include "src/device.h" |
| #include "src/profile.h" |
| |
| #include "sixaxis.h" |
| #include "device.h" |
| #include "server.h" |
| |
| struct confirm_data { |
| bdaddr_t dst; |
| GIOChannel *io; |
| }; |
| |
| static GSList *servers = NULL; |
| struct input_server { |
| bdaddr_t src; |
| GIOChannel *ctrl; |
| GIOChannel *intr; |
| struct confirm_data *confirm; |
| }; |
| |
| static int server_cmp(gconstpointer s, gconstpointer user_data) |
| { |
| const struct input_server *server = s; |
| const bdaddr_t *src = user_data; |
| |
| return bacmp(&server->src, src); |
| } |
| |
| struct sixaxis_data { |
| GIOChannel *chan; |
| uint16_t psm; |
| }; |
| |
| static void sixaxis_sdp_cb(struct btd_device *dev, int err, void *user_data) |
| { |
| struct sixaxis_data *data = user_data; |
| const bdaddr_t *src; |
| |
| DBG("err %d (%s)", err, strerror(-err)); |
| |
| if (err < 0) |
| goto fail; |
| |
| src = btd_adapter_get_address(device_get_adapter(dev)); |
| |
| if (input_device_set_channel(src, device_get_address(dev), data->psm, |
| data->chan) < 0) |
| goto fail; |
| |
| g_io_channel_unref(data->chan); |
| g_free(data); |
| |
| return; |
| |
| fail: |
| g_io_channel_shutdown(data->chan, TRUE, NULL); |
| g_io_channel_unref(data->chan); |
| g_free(data); |
| } |
| |
| static void sixaxis_browse_sdp(const bdaddr_t *src, const bdaddr_t *dst, |
| GIOChannel *chan, uint16_t psm) |
| { |
| struct btd_device *device; |
| struct sixaxis_data *data; |
| |
| device = btd_adapter_find_device(adapter_find(src), dst, BDADDR_BREDR); |
| if (!device) |
| return; |
| |
| data = g_new0(struct sixaxis_data, 1); |
| data->chan = g_io_channel_ref(chan); |
| data->psm = psm; |
| |
| if (psm == L2CAP_PSM_HIDP_CTRL) |
| device_discover_services(device); |
| |
| device_wait_for_svc_complete(device, sixaxis_sdp_cb, data); |
| } |
| |
| static bool dev_is_sixaxis(const bdaddr_t *src, const bdaddr_t *dst) |
| { |
| struct btd_device *device; |
| uint16_t vid, pid; |
| const struct cable_pairing *cp; |
| |
| device = btd_adapter_find_device(adapter_find(src), dst, BDADDR_BREDR); |
| if (!device) |
| return false; |
| |
| vid = btd_device_get_vendor(device); |
| pid = btd_device_get_product(device); |
| |
| cp = get_pairing(vid, pid, NULL); |
| if (cp && (cp->type == CABLE_PAIRING_SIXAXIS || |
| cp->type == CABLE_PAIRING_DS4)) |
| return true; |
| |
| return false; |
| } |
| |
| static void connect_event_cb(GIOChannel *chan, GError *err, gpointer data) |
| { |
| uint16_t psm; |
| bdaddr_t src, dst; |
| char address[18]; |
| GError *gerr = NULL; |
| int ret; |
| |
| if (err) { |
| error("%s", err->message); |
| return; |
| } |
| |
| bt_io_get(chan, &gerr, |
| BT_IO_OPT_SOURCE_BDADDR, &src, |
| BT_IO_OPT_DEST_BDADDR, &dst, |
| BT_IO_OPT_PSM, &psm, |
| BT_IO_OPT_INVALID); |
| if (gerr) { |
| error("%s", gerr->message); |
| g_error_free(gerr); |
| g_io_channel_shutdown(chan, TRUE, NULL); |
| return; |
| } |
| |
| ba2str(&dst, address); |
| DBG("Incoming connection from %s on PSM %d", address, psm); |
| |
| ret = input_device_set_channel(&src, &dst, psm, chan); |
| if (ret == 0) |
| return; |
| |
| if (ret == -ENOENT && dev_is_sixaxis(&src, &dst)) { |
| sixaxis_browse_sdp(&src, &dst, chan, psm); |
| return; |
| } |
| |
| error("Refusing input device connect: %s (%d)", strerror(-ret), -ret); |
| |
| /* Send unplug virtual cable to unknown devices */ |
| if (ret == -ENOENT && psm == L2CAP_PSM_HIDP_CTRL) { |
| unsigned char unplug = 0x15; |
| int sk = g_io_channel_unix_get_fd(chan); |
| if (write(sk, &unplug, sizeof(unplug)) < 0) |
| error("Unable to send virtual cable unplug"); |
| } |
| |
| g_io_channel_shutdown(chan, TRUE, NULL); |
| } |
| |
| static void auth_callback(DBusError *derr, void *user_data) |
| { |
| struct input_server *server = user_data; |
| struct confirm_data *confirm = server->confirm; |
| GError *err = NULL; |
| |
| if (derr) { |
| error("Access denied: %s", derr->message); |
| goto reject; |
| } |
| |
| if (!input_device_exists(&server->src, &confirm->dst) && |
| !dev_is_sixaxis(&server->src, &confirm->dst)) |
| return; |
| |
| if (!bt_io_accept(confirm->io, connect_event_cb, server, NULL, &err)) { |
| error("bt_io_accept: %s", err->message); |
| g_error_free(err); |
| goto reject; |
| } |
| |
| g_io_channel_unref(confirm->io); |
| g_free(server->confirm); |
| server->confirm = NULL; |
| |
| return; |
| |
| reject: |
| g_io_channel_shutdown(confirm->io, TRUE, NULL); |
| g_io_channel_unref(confirm->io); |
| server->confirm = NULL; |
| input_device_close_channels(&server->src, &confirm->dst); |
| g_free(confirm); |
| } |
| |
| static void confirm_event_cb(GIOChannel *chan, gpointer user_data) |
| { |
| struct input_server *server = user_data; |
| bdaddr_t src, dst; |
| GError *err = NULL; |
| char addr[18]; |
| guint ret; |
| |
| DBG(""); |
| |
| bt_io_get(chan, &err, |
| BT_IO_OPT_SOURCE_BDADDR, &src, |
| BT_IO_OPT_DEST_BDADDR, &dst, |
| BT_IO_OPT_INVALID); |
| if (err) { |
| error("%s", err->message); |
| g_error_free(err); |
| g_io_channel_shutdown(chan, TRUE, NULL); |
| return; |
| } |
| |
| ba2str(&dst, addr); |
| |
| if (server->confirm) { |
| error("Refusing connection from %s: setup in progress", addr); |
| goto drop; |
| } |
| |
| if (!input_device_exists(&src, &dst) && !dev_is_sixaxis(&src, &dst)) { |
| error("Refusing connection from %s: unknown device", addr); |
| goto drop; |
| } |
| |
| server->confirm = g_new0(struct confirm_data, 1); |
| server->confirm->io = g_io_channel_ref(chan); |
| bacpy(&server->confirm->dst, &dst); |
| |
| ret = btd_request_authorization(&src, &dst, HID_UUID, |
| auth_callback, server); |
| if (ret != 0) |
| return; |
| |
| error("input: authorization for device %s failed", addr); |
| |
| g_io_channel_unref(server->confirm->io); |
| g_free(server->confirm); |
| server->confirm = NULL; |
| |
| drop: |
| input_device_close_channels(&src, &dst); |
| g_io_channel_shutdown(chan, TRUE, NULL); |
| } |
| |
| static BtIOSecLevel get_necessary_sec_level(bool device_cable_pairing) |
| { |
| /* Use lower security to allow the cable paired devices to connect. */ |
| /* Unless classic bonded only mode is disabled, the security level */ |
| /* will be bumped again for non cable paired devices in */ |
| /* hidp_add_connection() */ |
| if (device_cable_pairing) |
| return BT_IO_SEC_LOW; |
| |
| return input_get_classic_bonded_only() ? BT_IO_SEC_MEDIUM : |
| BT_IO_SEC_LOW; |
| } |
| |
| int server_start(const bdaddr_t *src, bool device_sixaxis_cable_pairing) |
| { |
| struct input_server *server; |
| GError *err = NULL; |
| BtIOSecLevel sec_level; |
| |
| sec_level = get_necessary_sec_level(device_sixaxis_cable_pairing); |
| |
| server = g_new0(struct input_server, 1); |
| bacpy(&server->src, src); |
| |
| server->ctrl = bt_io_listen(connect_event_cb, NULL, |
| server, NULL, &err, |
| BT_IO_OPT_SOURCE_BDADDR, src, |
| BT_IO_OPT_PSM, L2CAP_PSM_HIDP_CTRL, |
| BT_IO_OPT_SEC_LEVEL, sec_level, |
| BT_IO_OPT_INVALID); |
| if (!server->ctrl) { |
| error("Failed to listen on control channel"); |
| g_error_free(err); |
| g_free(server); |
| return -1; |
| } |
| |
| server->intr = bt_io_listen(NULL, confirm_event_cb, |
| server, NULL, &err, |
| BT_IO_OPT_SOURCE_BDADDR, src, |
| BT_IO_OPT_PSM, L2CAP_PSM_HIDP_INTR, |
| BT_IO_OPT_SEC_LEVEL, sec_level, |
| BT_IO_OPT_INVALID); |
| if (!server->intr) { |
| error("Failed to listen on interrupt channel"); |
| g_io_channel_unref(server->ctrl); |
| g_error_free(err); |
| g_free(server); |
| return -1; |
| } |
| |
| servers = g_slist_append(servers, server); |
| |
| return 0; |
| } |
| |
| int server_set_cable_pairing(const bdaddr_t *src, bool device_cable_pairing) |
| { |
| struct input_server *server; |
| GSList *l; |
| BtIOSecLevel sec_level; |
| BtIOSecLevel new_sec_level; |
| GError *err = NULL; |
| |
| l = g_slist_find_custom(servers, src, server_cmp); |
| if (!l) |
| return -1; |
| |
| server = l->data; |
| |
| bt_io_get(server->ctrl, &err, BT_IO_OPT_SEC_LEVEL, &sec_level, |
| BT_IO_OPT_INVALID); |
| if (err) { |
| error("%s", err->message); |
| g_error_free(err); |
| return -1; |
| } |
| |
| new_sec_level = get_necessary_sec_level(device_cable_pairing); |
| if (sec_level == new_sec_level) |
| return -1; |
| |
| DBG("Applying the new security level to the listening input server"); |
| |
| if (!bt_io_set(server->ctrl, &err, BT_IO_OPT_SEC_LEVEL, new_sec_level, |
| BT_IO_OPT_INVALID)) { |
| error("bt_io_set(OPT_SEC_LEVEL): %s", err->message); |
| g_error_free(err); |
| return -1; |
| } |
| |
| if (!bt_io_set(server->intr, &err, BT_IO_OPT_SEC_LEVEL, new_sec_level, |
| BT_IO_OPT_INVALID)) { |
| error("bt_io_set(OPT_SEC_LEVEL): %s", err->message); |
| g_error_free(err); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| void server_stop(const bdaddr_t *src) |
| { |
| struct input_server *server; |
| GSList *l; |
| |
| l = g_slist_find_custom(servers, src, server_cmp); |
| if (!l) |
| return; |
| |
| server = l->data; |
| |
| g_io_channel_shutdown(server->intr, TRUE, NULL); |
| g_io_channel_unref(server->intr); |
| |
| g_io_channel_shutdown(server->ctrl, TRUE, NULL); |
| g_io_channel_unref(server->ctrl); |
| |
| servers = g_slist_remove(servers, server); |
| g_free(server); |
| } |