1ba9bdd8bSchristos /*
2*2d40c451Schristos * Copyright (c) 2019-2022 Yubico AB. All rights reserved.
3ba9bdd8bSchristos * Use of this source code is governed by a BSD-style
4ba9bdd8bSchristos * license that can be found in the LICENSE file.
5*2d40c451Schristos * SPDX-License-Identifier: BSD-2-Clause
6ba9bdd8bSchristos */
7ba9bdd8bSchristos
8ba9bdd8bSchristos #include <sys/types.h>
9ede6d7f8Schristos #include <sys/file.h>
10ba9bdd8bSchristos #include <sys/ioctl.h>
11ede6d7f8Schristos
12ba9bdd8bSchristos #include <linux/hidraw.h>
131fc1e710Schristos #include <linux/input.h>
14ba9bdd8bSchristos
151fc1e710Schristos #include <errno.h>
16ba9bdd8bSchristos #include <libudev.h>
17ede6d7f8Schristos #include <time.h>
18ba9bdd8bSchristos #include <unistd.h>
19ba9bdd8bSchristos
20ba9bdd8bSchristos #include "fido.h"
21ba9bdd8bSchristos
221fc1e710Schristos struct hid_linux {
231fc1e710Schristos int fd;
241fc1e710Schristos size_t report_in_len;
251fc1e710Schristos size_t report_out_len;
2695dbdf32Schristos sigset_t sigmask;
2795dbdf32Schristos const sigset_t *sigmaskp;
281fc1e710Schristos };
29ba9bdd8bSchristos
30ba9bdd8bSchristos static int
get_report_descriptor(int fd,struct hidraw_report_descriptor * hrd)311fc1e710Schristos get_report_descriptor(int fd, struct hidraw_report_descriptor *hrd)
321fc1e710Schristos {
331fc1e710Schristos int s = -1;
341fc1e710Schristos
3595dbdf32Schristos if (ioctl(fd, IOCTL_REQ(HIDIOCGRDESCSIZE), &s) == -1) {
3695dbdf32Schristos fido_log_error(errno, "%s: ioctl HIDIOCGRDESCSIZE", __func__);
3795dbdf32Schristos return (-1);
3895dbdf32Schristos }
3995dbdf32Schristos
4095dbdf32Schristos if (s < 0 || (unsigned)s > HID_MAX_DESCRIPTOR_SIZE) {
4195dbdf32Schristos fido_log_debug("%s: HIDIOCGRDESCSIZE %d", __func__, s);
421fc1e710Schristos return (-1);
43ba9bdd8bSchristos }
44ba9bdd8bSchristos
451fc1e710Schristos hrd->size = (unsigned)s;
46ba9bdd8bSchristos
4795dbdf32Schristos if (ioctl(fd, IOCTL_REQ(HIDIOCGRDESC), hrd) == -1) {
4895dbdf32Schristos fido_log_error(errno, "%s: ioctl HIDIOCGRDESC", __func__);
491fc1e710Schristos return (-1);
50ba9bdd8bSchristos }
51ba9bdd8bSchristos
521fc1e710Schristos return (0);
53ba9bdd8bSchristos }
54ba9bdd8bSchristos
55ba9bdd8bSchristos static bool
is_fido(const char * path)56ba9bdd8bSchristos is_fido(const char *path)
57ba9bdd8bSchristos {
58*2d40c451Schristos int fd = -1;
59ba9bdd8bSchristos uint32_t usage_page = 0;
60*2d40c451Schristos struct hidraw_report_descriptor *hrd = NULL;
61ba9bdd8bSchristos
62*2d40c451Schristos if ((hrd = calloc(1, sizeof(*hrd))) == NULL ||
63*2d40c451Schristos (fd = fido_hid_unix_open(path)) == -1)
64*2d40c451Schristos goto out;
65*2d40c451Schristos if (get_report_descriptor(fd, hrd) < 0 ||
66*2d40c451Schristos fido_hid_get_usage(hrd->value, hrd->size, &usage_page) < 0)
6795dbdf32Schristos usage_page = 0;
681fc1e710Schristos
69*2d40c451Schristos out:
70*2d40c451Schristos free(hrd);
71*2d40c451Schristos
72*2d40c451Schristos if (fd != -1 && close(fd) == -1)
7395dbdf32Schristos fido_log_error(errno, "%s: close", __func__);
741fc1e710Schristos
75ba9bdd8bSchristos return (usage_page == 0xf1d0);
76ba9bdd8bSchristos }
77ba9bdd8bSchristos
78ba9bdd8bSchristos static int
parse_uevent(const char * uevent,int * bus,int16_t * vendor_id,int16_t * product_id)791fc1e710Schristos parse_uevent(const char *uevent, int *bus, int16_t *vendor_id,
801fc1e710Schristos int16_t *product_id)
81ba9bdd8bSchristos {
82ba9bdd8bSchristos char *cp;
83ba9bdd8bSchristos char *p;
84ba9bdd8bSchristos char *s;
85ba9bdd8bSchristos int ok = -1;
86ba9bdd8bSchristos short unsigned int x;
87ba9bdd8bSchristos short unsigned int y;
881fc1e710Schristos short unsigned int z;
89ba9bdd8bSchristos
90ba9bdd8bSchristos if ((s = cp = strdup(uevent)) == NULL)
91ba9bdd8bSchristos return (-1);
92ba9bdd8bSchristos
931fc1e710Schristos while ((p = strsep(&cp, "\n")) != NULL && *p != '\0') {
94ba9bdd8bSchristos if (strncmp(p, "HID_ID=", 7) == 0) {
951fc1e710Schristos if (sscanf(p + 7, "%hx:%hx:%hx", &x, &y, &z) == 3) {
961fc1e710Schristos *bus = (int)x;
971fc1e710Schristos *vendor_id = (int16_t)y;
981fc1e710Schristos *product_id = (int16_t)z;
99ba9bdd8bSchristos ok = 0;
100ba9bdd8bSchristos break;
101ba9bdd8bSchristos }
102ba9bdd8bSchristos }
1031fc1e710Schristos }
104ba9bdd8bSchristos
105ba9bdd8bSchristos free(s);
106ba9bdd8bSchristos
107ba9bdd8bSchristos return (ok);
108ba9bdd8bSchristos }
109ba9bdd8bSchristos
1101fc1e710Schristos static char *
get_parent_attr(struct udev_device * dev,const char * subsystem,const char * devtype,const char * attr)1111fc1e710Schristos get_parent_attr(struct udev_device *dev, const char *subsystem,
1121fc1e710Schristos const char *devtype, const char *attr)
1131fc1e710Schristos {
1141fc1e710Schristos struct udev_device *parent;
1151fc1e710Schristos const char *value;
1161fc1e710Schristos
1171fc1e710Schristos if ((parent = udev_device_get_parent_with_subsystem_devtype(dev,
1181fc1e710Schristos subsystem, devtype)) == NULL || (value =
1191fc1e710Schristos udev_device_get_sysattr_value(parent, attr)) == NULL)
1201fc1e710Schristos return (NULL);
1211fc1e710Schristos
1221fc1e710Schristos return (strdup(value));
1231fc1e710Schristos }
1241fc1e710Schristos
1251fc1e710Schristos static char *
get_usb_attr(struct udev_device * dev,const char * attr)1261fc1e710Schristos get_usb_attr(struct udev_device *dev, const char *attr)
1271fc1e710Schristos {
1281fc1e710Schristos return (get_parent_attr(dev, "usb", "usb_device", attr));
1291fc1e710Schristos }
1301fc1e710Schristos
131ba9bdd8bSchristos static int
copy_info(fido_dev_info_t * di,struct udev * udev,struct udev_list_entry * udev_entry)132ba9bdd8bSchristos copy_info(fido_dev_info_t *di, struct udev *udev,
133ba9bdd8bSchristos struct udev_list_entry *udev_entry)
134ba9bdd8bSchristos {
135ba9bdd8bSchristos const char *name;
136ba9bdd8bSchristos const char *path;
1371fc1e710Schristos char *uevent = NULL;
138ba9bdd8bSchristos struct udev_device *dev = NULL;
1391fc1e710Schristos int bus = 0;
140ba9bdd8bSchristos int ok = -1;
141ba9bdd8bSchristos
142ba9bdd8bSchristos memset(di, 0, sizeof(*di));
143ba9bdd8bSchristos
144ba9bdd8bSchristos if ((name = udev_list_entry_get_name(udev_entry)) == NULL ||
145ba9bdd8bSchristos (dev = udev_device_new_from_syspath(udev, name)) == NULL ||
146ba9bdd8bSchristos (path = udev_device_get_devnode(dev)) == NULL ||
147ba9bdd8bSchristos is_fido(path) == 0)
148ba9bdd8bSchristos goto fail;
149ba9bdd8bSchristos
1501fc1e710Schristos if ((uevent = get_parent_attr(dev, "hid", NULL, "uevent")) == NULL ||
1511fc1e710Schristos parse_uevent(uevent, &bus, &di->vendor_id, &di->product_id) < 0) {
1521fc1e710Schristos fido_log_debug("%s: uevent", __func__);
153ba9bdd8bSchristos goto fail;
1541fc1e710Schristos }
155ba9bdd8bSchristos
1561fc1e710Schristos #ifndef FIDO_HID_ANY
1571fc1e710Schristos if (bus != BUS_USB) {
1581fc1e710Schristos fido_log_debug("%s: bus", __func__);
159ba9bdd8bSchristos goto fail;
1601fc1e710Schristos }
1611fc1e710Schristos #endif
162ba9bdd8bSchristos
163ba9bdd8bSchristos di->path = strdup(path);
164ede6d7f8Schristos if ((di->manufacturer = get_usb_attr(dev, "manufacturer")) == NULL)
165*2d40c451Schristos di->manufacturer = strdup("");
166ede6d7f8Schristos if ((di->product = get_usb_attr(dev, "product")) == NULL)
167*2d40c451Schristos di->product = strdup("");
1681fc1e710Schristos if (di->path == NULL || di->manufacturer == NULL || di->product == NULL)
169ba9bdd8bSchristos goto fail;
170ba9bdd8bSchristos
171ba9bdd8bSchristos ok = 0;
172ba9bdd8bSchristos fail:
173ba9bdd8bSchristos if (dev != NULL)
174ba9bdd8bSchristos udev_device_unref(dev);
175ba9bdd8bSchristos
1761fc1e710Schristos free(uevent);
1771fc1e710Schristos
178ba9bdd8bSchristos if (ok < 0) {
179ba9bdd8bSchristos free(di->path);
180ba9bdd8bSchristos free(di->manufacturer);
181ba9bdd8bSchristos free(di->product);
182ba9bdd8bSchristos explicit_bzero(di, sizeof(*di));
183ba9bdd8bSchristos }
184ba9bdd8bSchristos
185ba9bdd8bSchristos return (ok);
186ba9bdd8bSchristos }
187ba9bdd8bSchristos
188ba9bdd8bSchristos int
fido_hid_manifest(fido_dev_info_t * devlist,size_t ilen,size_t * olen)189ba9bdd8bSchristos fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen)
190ba9bdd8bSchristos {
191ba9bdd8bSchristos struct udev *udev = NULL;
192ba9bdd8bSchristos struct udev_enumerate *udev_enum = NULL;
193ba9bdd8bSchristos struct udev_list_entry *udev_list;
194ba9bdd8bSchristos struct udev_list_entry *udev_entry;
195ba9bdd8bSchristos int r = FIDO_ERR_INTERNAL;
196ba9bdd8bSchristos
197ba9bdd8bSchristos *olen = 0;
198ba9bdd8bSchristos
199ba9bdd8bSchristos if (ilen == 0)
200ba9bdd8bSchristos return (FIDO_OK); /* nothing to do */
201ba9bdd8bSchristos
202ba9bdd8bSchristos if (devlist == NULL)
203ba9bdd8bSchristos return (FIDO_ERR_INVALID_ARGUMENT);
204ba9bdd8bSchristos
205ba9bdd8bSchristos if ((udev = udev_new()) == NULL ||
206ba9bdd8bSchristos (udev_enum = udev_enumerate_new(udev)) == NULL)
207ba9bdd8bSchristos goto fail;
208ba9bdd8bSchristos
209ba9bdd8bSchristos if (udev_enumerate_add_match_subsystem(udev_enum, "hidraw") < 0 ||
2101fc1e710Schristos udev_enumerate_scan_devices(udev_enum) < 0)
211ba9bdd8bSchristos goto fail;
212ba9bdd8bSchristos
2131fc1e710Schristos if ((udev_list = udev_enumerate_get_list_entry(udev_enum)) == NULL) {
2141fc1e710Schristos r = FIDO_OK; /* zero hidraw devices */
2151fc1e710Schristos goto fail;
2161fc1e710Schristos }
2171fc1e710Schristos
218ba9bdd8bSchristos udev_list_entry_foreach(udev_entry, udev_list) {
219ba9bdd8bSchristos if (copy_info(&devlist[*olen], udev, udev_entry) == 0) {
220ba9bdd8bSchristos devlist[*olen].io = (fido_dev_io_t) {
221ba9bdd8bSchristos fido_hid_open,
222ba9bdd8bSchristos fido_hid_close,
223ba9bdd8bSchristos fido_hid_read,
224ba9bdd8bSchristos fido_hid_write,
225ba9bdd8bSchristos };
226ba9bdd8bSchristos if (++(*olen) == ilen)
227ba9bdd8bSchristos break;
228ba9bdd8bSchristos }
229ba9bdd8bSchristos }
230ba9bdd8bSchristos
231ba9bdd8bSchristos r = FIDO_OK;
232ba9bdd8bSchristos fail:
233ba9bdd8bSchristos if (udev_enum != NULL)
234ba9bdd8bSchristos udev_enumerate_unref(udev_enum);
235ba9bdd8bSchristos if (udev != NULL)
236ba9bdd8bSchristos udev_unref(udev);
237ba9bdd8bSchristos
238ba9bdd8bSchristos return (r);
239ba9bdd8bSchristos }
240ba9bdd8bSchristos
241ba9bdd8bSchristos void *
fido_hid_open(const char * path)242ba9bdd8bSchristos fido_hid_open(const char *path)
243ba9bdd8bSchristos {
2441fc1e710Schristos struct hid_linux *ctx;
245*2d40c451Schristos struct hidraw_report_descriptor *hrd;
246ede6d7f8Schristos struct timespec tv_pause;
247ede6d7f8Schristos long interval_ms, retries = 0;
248*2d40c451Schristos bool looped;
249*2d40c451Schristos
250*2d40c451Schristos retry:
251*2d40c451Schristos looped = false;
252ba9bdd8bSchristos
253ede6d7f8Schristos if ((ctx = calloc(1, sizeof(*ctx))) == NULL ||
254ede6d7f8Schristos (ctx->fd = fido_hid_unix_open(path)) == -1) {
2551fc1e710Schristos free(ctx);
256ba9bdd8bSchristos return (NULL);
257ba9bdd8bSchristos }
258ba9bdd8bSchristos
259ede6d7f8Schristos while (flock(ctx->fd, LOCK_EX|LOCK_NB) == -1) {
260ede6d7f8Schristos if (errno != EWOULDBLOCK) {
261ede6d7f8Schristos fido_log_error(errno, "%s: flock", __func__);
262ede6d7f8Schristos fido_hid_close(ctx);
263ede6d7f8Schristos return (NULL);
264ede6d7f8Schristos }
265*2d40c451Schristos looped = true;
266*2d40c451Schristos if (retries++ >= 20) {
267ede6d7f8Schristos fido_log_debug("%s: flock timeout", __func__);
268ede6d7f8Schristos fido_hid_close(ctx);
269ede6d7f8Schristos return (NULL);
270ede6d7f8Schristos }
271ede6d7f8Schristos interval_ms = retries * 100000000L;
272ede6d7f8Schristos tv_pause.tv_sec = interval_ms / 1000000000L;
273ede6d7f8Schristos tv_pause.tv_nsec = interval_ms % 1000000000L;
274ede6d7f8Schristos if (nanosleep(&tv_pause, NULL) == -1) {
275ede6d7f8Schristos fido_log_error(errno, "%s: nanosleep", __func__);
276ede6d7f8Schristos fido_hid_close(ctx);
277ede6d7f8Schristos return (NULL);
278ede6d7f8Schristos }
279ede6d7f8Schristos }
280ede6d7f8Schristos
281*2d40c451Schristos if (looped) {
282*2d40c451Schristos fido_log_debug("%s: retrying", __func__);
283*2d40c451Schristos fido_hid_close(ctx);
284*2d40c451Schristos goto retry;
285*2d40c451Schristos }
286*2d40c451Schristos
287*2d40c451Schristos if ((hrd = calloc(1, sizeof(*hrd))) == NULL ||
288*2d40c451Schristos get_report_descriptor(ctx->fd, hrd) < 0 ||
289*2d40c451Schristos fido_hid_get_report_len(hrd->value, hrd->size, &ctx->report_in_len,
29095dbdf32Schristos &ctx->report_out_len) < 0 || ctx->report_in_len == 0 ||
29195dbdf32Schristos ctx->report_out_len == 0) {
2921fc1e710Schristos fido_log_debug("%s: using default report sizes", __func__);
2931fc1e710Schristos ctx->report_in_len = CTAP_MAX_REPORT_LEN;
2941fc1e710Schristos ctx->report_out_len = CTAP_MAX_REPORT_LEN;
2951fc1e710Schristos }
2961fc1e710Schristos
297*2d40c451Schristos free(hrd);
298*2d40c451Schristos
2991fc1e710Schristos return (ctx);
300ba9bdd8bSchristos }
301ba9bdd8bSchristos
302ba9bdd8bSchristos void
fido_hid_close(void * handle)303ba9bdd8bSchristos fido_hid_close(void *handle)
304ba9bdd8bSchristos {
3051fc1e710Schristos struct hid_linux *ctx = handle;
306ba9bdd8bSchristos
30795dbdf32Schristos if (close(ctx->fd) == -1)
30895dbdf32Schristos fido_log_error(errno, "%s: close", __func__);
30995dbdf32Schristos
3101fc1e710Schristos free(ctx);
3111fc1e710Schristos }
3121fc1e710Schristos
31395dbdf32Schristos int
fido_hid_set_sigmask(void * handle,const fido_sigset_t * sigmask)31495dbdf32Schristos fido_hid_set_sigmask(void *handle, const fido_sigset_t *sigmask)
3151fc1e710Schristos {
31695dbdf32Schristos struct hid_linux *ctx = handle;
3171fc1e710Schristos
31895dbdf32Schristos ctx->sigmask = *sigmask;
31995dbdf32Schristos ctx->sigmaskp = &ctx->sigmask;
3201fc1e710Schristos
32195dbdf32Schristos return (FIDO_OK);
322ba9bdd8bSchristos }
323ba9bdd8bSchristos
324ba9bdd8bSchristos int
fido_hid_read(void * handle,unsigned char * buf,size_t len,int ms)325ba9bdd8bSchristos fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms)
326ba9bdd8bSchristos {
3271fc1e710Schristos struct hid_linux *ctx = handle;
328ba9bdd8bSchristos ssize_t r;
329ba9bdd8bSchristos
3301fc1e710Schristos if (len != ctx->report_in_len) {
3311fc1e710Schristos fido_log_debug("%s: len %zu", __func__, len);
332ba9bdd8bSchristos return (-1);
333ba9bdd8bSchristos }
334ba9bdd8bSchristos
33595dbdf32Schristos if (fido_hid_unix_wait(ctx->fd, ms, ctx->sigmaskp) < 0) {
3361fc1e710Schristos fido_log_debug("%s: fd not ready", __func__);
337ba9bdd8bSchristos return (-1);
3381fc1e710Schristos }
339ba9bdd8bSchristos
34095dbdf32Schristos if ((r = read(ctx->fd, buf, len)) == -1) {
34195dbdf32Schristos fido_log_error(errno, "%s: read", __func__);
34295dbdf32Schristos return (-1);
34395dbdf32Schristos }
34495dbdf32Schristos
34595dbdf32Schristos if (r < 0 || (size_t)r != len) {
34695dbdf32Schristos fido_log_debug("%s: %zd != %zu", __func__, r, len);
3471fc1e710Schristos return (-1);
3481fc1e710Schristos }
3491fc1e710Schristos
3501fc1e710Schristos return ((int)r);
351ba9bdd8bSchristos }
352ba9bdd8bSchristos
353ba9bdd8bSchristos int
fido_hid_write(void * handle,const unsigned char * buf,size_t len)354ba9bdd8bSchristos fido_hid_write(void *handle, const unsigned char *buf, size_t len)
355ba9bdd8bSchristos {
3561fc1e710Schristos struct hid_linux *ctx = handle;
357ba9bdd8bSchristos ssize_t r;
358ba9bdd8bSchristos
3591fc1e710Schristos if (len != ctx->report_out_len + 1) {
3601fc1e710Schristos fido_log_debug("%s: len %zu", __func__, len);
361ba9bdd8bSchristos return (-1);
362ba9bdd8bSchristos }
363ba9bdd8bSchristos
36495dbdf32Schristos if ((r = write(ctx->fd, buf, len)) == -1) {
36595dbdf32Schristos fido_log_error(errno, "%s: write", __func__);
36695dbdf32Schristos return (-1);
36795dbdf32Schristos }
36895dbdf32Schristos
36995dbdf32Schristos if (r < 0 || (size_t)r != len) {
37095dbdf32Schristos fido_log_debug("%s: %zd != %zu", __func__, r, len);
371ba9bdd8bSchristos return (-1);
372ba9bdd8bSchristos }
373ba9bdd8bSchristos
3741fc1e710Schristos return ((int)r);
3751fc1e710Schristos }
3761fc1e710Schristos
3771fc1e710Schristos size_t
fido_hid_report_in_len(void * handle)3781fc1e710Schristos fido_hid_report_in_len(void *handle)
3791fc1e710Schristos {
3801fc1e710Schristos struct hid_linux *ctx = handle;
3811fc1e710Schristos
3821fc1e710Schristos return (ctx->report_in_len);
3831fc1e710Schristos }
3841fc1e710Schristos
3851fc1e710Schristos size_t
fido_hid_report_out_len(void * handle)3861fc1e710Schristos fido_hid_report_out_len(void *handle)
3871fc1e710Schristos {
3881fc1e710Schristos struct hid_linux *ctx = handle;
3891fc1e710Schristos
3901fc1e710Schristos return (ctx->report_out_len);
391ba9bdd8bSchristos }
392