1/*
2 * Copyright (c) 2019-2022 Yubico AB. All rights reserved.
3 * Use of this source code is governed by a BSD-style
4 * license that can be found in the LICENSE file.
5 * SPDX-License-Identifier: BSD-2-Clause
6 */
7
8#include <sys/types.h>
9#include <sys/file.h>
10#include <sys/ioctl.h>
11
12#include <linux/hidraw.h>
13#include <linux/input.h>
14
15#include <errno.h>
16#include <libudev.h>
17#include <time.h>
18#include <unistd.h>
19
20#include "fido.h"
21
22struct hid_linux {
23	int             fd;
24	size_t          report_in_len;
25	size_t          report_out_len;
26	sigset_t        sigmask;
27	const sigset_t *sigmaskp;
28};
29
30static int
31get_report_descriptor(int fd, struct hidraw_report_descriptor *hrd)
32{
33	int s = -1;
34
35	if (ioctl(fd, IOCTL_REQ(HIDIOCGRDESCSIZE), &s) == -1) {
36		fido_log_error(errno, "%s: ioctl HIDIOCGRDESCSIZE", __func__);
37		return (-1);
38	}
39
40	if (s < 0 || (unsigned)s > HID_MAX_DESCRIPTOR_SIZE) {
41		fido_log_debug("%s: HIDIOCGRDESCSIZE %d", __func__, s);
42		return (-1);
43	}
44
45	hrd->size = (unsigned)s;
46
47	if (ioctl(fd, IOCTL_REQ(HIDIOCGRDESC), hrd) == -1) {
48		fido_log_error(errno, "%s: ioctl HIDIOCGRDESC", __func__);
49		return (-1);
50	}
51
52	return (0);
53}
54
55static bool
56is_fido(const char *path)
57{
58	int				 fd = -1;
59	uint32_t			 usage_page = 0;
60	struct hidraw_report_descriptor	*hrd = NULL;
61
62	if ((hrd = calloc(1, sizeof(*hrd))) == NULL ||
63	    (fd = fido_hid_unix_open(path)) == -1)
64		goto out;
65	if (get_report_descriptor(fd, hrd) < 0 ||
66	    fido_hid_get_usage(hrd->value, hrd->size, &usage_page) < 0)
67		usage_page = 0;
68
69out:
70	free(hrd);
71
72	if (fd != -1 && close(fd) == -1)
73		fido_log_error(errno, "%s: close", __func__);
74
75	return (usage_page == 0xf1d0);
76}
77
78static int
79parse_uevent(const char *uevent, int *bus, int16_t *vendor_id,
80    int16_t *product_id)
81{
82	char			*cp;
83	char			*p;
84	char			*s;
85	int			 ok = -1;
86	short unsigned int	 x;
87	short unsigned int	 y;
88	short unsigned int	 z;
89
90	if ((s = cp = strdup(uevent)) == NULL)
91		return (-1);
92
93	while ((p = strsep(&cp, "\n")) != NULL && *p != '\0') {
94		if (strncmp(p, "HID_ID=", 7) == 0) {
95			if (sscanf(p + 7, "%hx:%hx:%hx", &x, &y, &z) == 3) {
96				*bus = (int)x;
97				*vendor_id = (int16_t)y;
98				*product_id = (int16_t)z;
99				ok = 0;
100				break;
101			}
102		}
103	}
104
105	free(s);
106
107	return (ok);
108}
109
110static char *
111get_parent_attr(struct udev_device *dev, const char *subsystem,
112    const char *devtype, const char *attr)
113{
114	struct udev_device	*parent;
115	const char		*value;
116
117	if ((parent = udev_device_get_parent_with_subsystem_devtype(dev,
118	    subsystem, devtype)) == NULL || (value =
119	    udev_device_get_sysattr_value(parent, attr)) == NULL)
120		return (NULL);
121
122	return (strdup(value));
123}
124
125static char *
126get_usb_attr(struct udev_device *dev, const char *attr)
127{
128	return (get_parent_attr(dev, "usb", "usb_device", attr));
129}
130
131static int
132copy_info(fido_dev_info_t *di, struct udev *udev,
133    struct udev_list_entry *udev_entry)
134{
135	const char		*name;
136	const char		*path;
137	char			*uevent = NULL;
138	struct udev_device	*dev = NULL;
139	int			 bus = 0;
140	int			 ok = -1;
141
142	memset(di, 0, sizeof(*di));
143
144	if ((name = udev_list_entry_get_name(udev_entry)) == NULL ||
145	    (dev = udev_device_new_from_syspath(udev, name)) == NULL ||
146	    (path = udev_device_get_devnode(dev)) == NULL ||
147	    is_fido(path) == 0)
148		goto fail;
149
150	if ((uevent = get_parent_attr(dev, "hid", NULL, "uevent")) == NULL ||
151	    parse_uevent(uevent, &bus, &di->vendor_id, &di->product_id) < 0) {
152		fido_log_debug("%s: uevent", __func__);
153		goto fail;
154	}
155
156#ifndef FIDO_HID_ANY
157	if (bus != BUS_USB) {
158		fido_log_debug("%s: bus", __func__);
159		goto fail;
160	}
161#endif
162
163	di->path = strdup(path);
164	if ((di->manufacturer = get_usb_attr(dev, "manufacturer")) == NULL)
165		di->manufacturer = strdup("");
166	if ((di->product = get_usb_attr(dev, "product")) == NULL)
167		di->product = strdup("");
168	if (di->path == NULL || di->manufacturer == NULL || di->product == NULL)
169		goto fail;
170
171	ok = 0;
172fail:
173	if (dev != NULL)
174		udev_device_unref(dev);
175
176	free(uevent);
177
178	if (ok < 0) {
179		free(di->path);
180		free(di->manufacturer);
181		free(di->product);
182		explicit_bzero(di, sizeof(*di));
183	}
184
185	return (ok);
186}
187
188int
189fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen)
190{
191	struct udev		*udev = NULL;
192	struct udev_enumerate	*udev_enum = NULL;
193	struct udev_list_entry	*udev_list;
194	struct udev_list_entry	*udev_entry;
195	int			 r = FIDO_ERR_INTERNAL;
196
197	*olen = 0;
198
199	if (ilen == 0)
200		return (FIDO_OK); /* nothing to do */
201
202	if (devlist == NULL)
203		return (FIDO_ERR_INVALID_ARGUMENT);
204
205	if ((udev = udev_new()) == NULL ||
206	    (udev_enum = udev_enumerate_new(udev)) == NULL)
207		goto fail;
208
209	if (udev_enumerate_add_match_subsystem(udev_enum, "hidraw") < 0 ||
210	    udev_enumerate_scan_devices(udev_enum) < 0)
211		goto fail;
212
213	if ((udev_list = udev_enumerate_get_list_entry(udev_enum)) == NULL) {
214		r = FIDO_OK; /* zero hidraw devices */
215		goto fail;
216	}
217
218	udev_list_entry_foreach(udev_entry, udev_list) {
219		if (copy_info(&devlist[*olen], udev, udev_entry) == 0) {
220			devlist[*olen].io = (fido_dev_io_t) {
221				fido_hid_open,
222				fido_hid_close,
223				fido_hid_read,
224				fido_hid_write,
225			};
226			if (++(*olen) == ilen)
227				break;
228		}
229	}
230
231	r = FIDO_OK;
232fail:
233	if (udev_enum != NULL)
234		udev_enumerate_unref(udev_enum);
235	if (udev != NULL)
236		udev_unref(udev);
237
238	return (r);
239}
240
241void *
242fido_hid_open(const char *path)
243{
244	struct hid_linux *ctx;
245	struct hidraw_report_descriptor *hrd;
246	struct timespec tv_pause;
247	long interval_ms, retries = 0;
248	bool looped;
249
250retry:
251	looped = false;
252
253	if ((ctx = calloc(1, sizeof(*ctx))) == NULL ||
254	    (ctx->fd = fido_hid_unix_open(path)) == -1) {
255		free(ctx);
256		return (NULL);
257	}
258
259	while (flock(ctx->fd, LOCK_EX|LOCK_NB) == -1) {
260		if (errno != EWOULDBLOCK) {
261			fido_log_error(errno, "%s: flock", __func__);
262			fido_hid_close(ctx);
263			return (NULL);
264		}
265		looped = true;
266		if (retries++ >= 20) {
267			fido_log_debug("%s: flock timeout", __func__);
268			fido_hid_close(ctx);
269			return (NULL);
270		}
271		interval_ms = retries * 100000000L;
272		tv_pause.tv_sec = interval_ms / 1000000000L;
273		tv_pause.tv_nsec = interval_ms % 1000000000L;
274		if (nanosleep(&tv_pause, NULL) == -1) {
275			fido_log_error(errno, "%s: nanosleep", __func__);
276			fido_hid_close(ctx);
277			return (NULL);
278		}
279	}
280
281	if (looped) {
282		fido_log_debug("%s: retrying", __func__);
283		fido_hid_close(ctx);
284		goto retry;
285	}
286
287	if ((hrd = calloc(1, sizeof(*hrd))) == NULL ||
288	    get_report_descriptor(ctx->fd, hrd) < 0 ||
289	    fido_hid_get_report_len(hrd->value, hrd->size, &ctx->report_in_len,
290	    &ctx->report_out_len) < 0 || ctx->report_in_len == 0 ||
291	    ctx->report_out_len == 0) {
292		fido_log_debug("%s: using default report sizes", __func__);
293		ctx->report_in_len = CTAP_MAX_REPORT_LEN;
294		ctx->report_out_len = CTAP_MAX_REPORT_LEN;
295	}
296
297	free(hrd);
298
299	return (ctx);
300}
301
302void
303fido_hid_close(void *handle)
304{
305	struct hid_linux *ctx = handle;
306
307	if (close(ctx->fd) == -1)
308		fido_log_error(errno, "%s: close", __func__);
309
310	free(ctx);
311}
312
313int
314fido_hid_set_sigmask(void *handle, const fido_sigset_t *sigmask)
315{
316	struct hid_linux *ctx = handle;
317
318	ctx->sigmask = *sigmask;
319	ctx->sigmaskp = &ctx->sigmask;
320
321	return (FIDO_OK);
322}
323
324int
325fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms)
326{
327	struct hid_linux	*ctx = handle;
328	ssize_t			 r;
329
330	if (len != ctx->report_in_len) {
331		fido_log_debug("%s: len %zu", __func__, len);
332		return (-1);
333	}
334
335	if (fido_hid_unix_wait(ctx->fd, ms, ctx->sigmaskp) < 0) {
336		fido_log_debug("%s: fd not ready", __func__);
337		return (-1);
338	}
339
340	if ((r = read(ctx->fd, buf, len)) == -1) {
341		fido_log_error(errno, "%s: read", __func__);
342		return (-1);
343	}
344
345	if (r < 0 || (size_t)r != len) {
346		fido_log_debug("%s: %zd != %zu", __func__, r, len);
347		return (-1);
348	}
349
350	return ((int)r);
351}
352
353int
354fido_hid_write(void *handle, const unsigned char *buf, size_t len)
355{
356	struct hid_linux	*ctx = handle;
357	ssize_t			 r;
358
359	if (len != ctx->report_out_len + 1) {
360		fido_log_debug("%s: len %zu", __func__, len);
361		return (-1);
362	}
363
364	if ((r = write(ctx->fd, buf, len)) == -1) {
365		fido_log_error(errno, "%s: write", __func__);
366		return (-1);
367	}
368
369	if (r < 0 || (size_t)r != len) {
370		fido_log_debug("%s: %zd != %zu", __func__, r, len);
371		return (-1);
372	}
373
374	return ((int)r);
375}
376
377size_t
378fido_hid_report_in_len(void *handle)
379{
380	struct hid_linux *ctx = handle;
381
382	return (ctx->report_in_len);
383}
384
385size_t
386fido_hid_report_out_len(void *handle)
387{
388	struct hid_linux *ctx = handle;
389
390	return (ctx->report_out_len);
391}
392