1/*
2 * Copyright (c) 2020 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/ioctl.h>
10
11#include <dev/usb/usb.h>
12#include <dev/usb/usbhid.h>
13
14#include <errno.h>
15#include <poll.h>
16#include <signal.h>
17#include <stdbool.h>
18#include <stdio.h>
19#include <stdlib.h>
20#include <string.h>
21#include <unistd.h>
22
23#include "fido.h"
24
25#define MAX_UHID	64
26
27struct hid_netbsd {
28	int             fd;
29	size_t          report_in_len;
30	size_t          report_out_len;
31	sigset_t        sigmask;
32	const sigset_t *sigmaskp;
33};
34
35/* Hack to make this work with newer kernels even if /usr/include is old.  */
36#if __NetBSD_Version__ < 901000000	/* 9.1 */
37#define	USB_HID_GET_RAW	_IOR('h', 1, int)
38#define	USB_HID_SET_RAW	_IOW('h', 2, int)
39#endif
40
41static bool
42is_fido(int fd)
43{
44	struct usb_ctl_report_desc	ucrd;
45	uint32_t			usage_page = 0;
46	int				raw = 1;
47
48	memset(&ucrd, 0, sizeof(ucrd));
49
50	if (ioctl(fd, IOCTL_REQ(USB_GET_REPORT_DESC), &ucrd) == -1) {
51		fido_log_error(errno, "%s: ioctl", __func__);
52		return (false);
53	}
54
55	if (ucrd.ucrd_size < 0 ||
56	    (size_t)ucrd.ucrd_size > sizeof(ucrd.ucrd_data) ||
57	    fido_hid_get_usage(ucrd.ucrd_data, (size_t)ucrd.ucrd_size,
58		&usage_page) < 0) {
59		fido_log_debug("%s: fido_hid_get_usage", __func__);
60		return (false);
61	}
62
63	if (usage_page != 0xf1d0)
64		return (false);
65
66	/*
67	 * This step is not strictly necessary -- NetBSD puts fido
68	 * devices into raw mode automatically by default, but in
69	 * principle that might change, and this serves as a test to
70	 * verify that we're running on a kernel with support for raw
71	 * mode at all so we don't get confused issuing writes that try
72	 * to set the report descriptor rather than transfer data on
73	 * the output interrupt pipe as we need.
74	 */
75	if (ioctl(fd, IOCTL_REQ(USB_HID_SET_RAW), &raw) == -1) {
76		fido_log_error(errno, "%s: unable to set raw", __func__);
77		return (false);
78	}
79
80	return (true);
81}
82
83static int
84copy_info(fido_dev_info_t *di, const char *path)
85{
86	int			fd = -1;
87	int			ok = -1;
88	struct usb_device_info	udi;
89
90	memset(di, 0, sizeof(*di));
91	memset(&udi, 0, sizeof(udi));
92
93	if ((fd = fido_hid_unix_open(path)) == -1 || is_fido(fd) == 0)
94		goto fail;
95
96	if (ioctl(fd, IOCTL_REQ(USB_GET_DEVICEINFO), &udi) == -1) {
97		fido_log_error(errno, "%s: ioctl", __func__);
98		goto fail;
99	}
100
101	if ((di->path = strdup(path)) == NULL ||
102	    (di->manufacturer = strdup(udi.udi_vendor)) == NULL ||
103	    (di->product = strdup(udi.udi_product)) == NULL)
104		goto fail;
105
106	di->vendor_id = (int16_t)udi.udi_vendorNo;
107	di->product_id = (int16_t)udi.udi_productNo;
108
109	ok = 0;
110fail:
111	if (fd != -1 && close(fd) == -1)
112		fido_log_error(errno, "%s: close", __func__);
113
114	if (ok < 0) {
115		free(di->path);
116		free(di->manufacturer);
117		free(di->product);
118		explicit_bzero(di, sizeof(*di));
119	}
120
121	return (ok);
122}
123
124int
125fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen)
126{
127	char	path[64];
128	size_t	i;
129
130	*olen = 0;
131
132	if (ilen == 0)
133		return (FIDO_OK); /* nothing to do */
134
135	if (devlist == NULL || olen == NULL)
136		return (FIDO_ERR_INVALID_ARGUMENT);
137
138	for (i = *olen = 0; i < MAX_UHID && *olen < ilen; i++) {
139		snprintf(path, sizeof(path), "/dev/uhid%zu", i);
140		if (copy_info(&devlist[*olen], path) == 0) {
141			devlist[*olen].io = (fido_dev_io_t) {
142				fido_hid_open,
143				fido_hid_close,
144				fido_hid_read,
145				fido_hid_write,
146			};
147			++(*olen);
148		}
149	}
150
151	return (FIDO_OK);
152}
153
154/*
155 * Workaround for NetBSD (as of 201910) bug that loses
156 * sync of DATA0/DATA1 sequence bit across uhid open/close.
157 * Send pings until we get a response - early pings with incorrect
158 * sequence bits will be ignored as duplicate packets by the device.
159 */
160static int
161terrible_ping_kludge(struct hid_netbsd *ctx)
162{
163	u_char data[256];
164	int i, n;
165	struct pollfd pfd;
166
167	if (sizeof(data) < ctx->report_out_len + 1)
168		return -1;
169	for (i = 0; i < 4; i++) {
170		memset(data, 0, sizeof(data));
171		/* broadcast channel ID */
172		data[1] = 0xff;
173		data[2] = 0xff;
174		data[3] = 0xff;
175		data[4] = 0xff;
176		/* Ping command */
177		data[5] = 0x81;
178		/* One byte ping only, Vasili */
179		data[6] = 0;
180		data[7] = 1;
181		fido_log_debug("%s: send ping %d", __func__, i);
182		if (fido_hid_write(ctx, data, ctx->report_out_len + 1) == -1)
183			return -1;
184		fido_log_debug("%s: wait reply", __func__);
185		memset(&pfd, 0, sizeof(pfd));
186		pfd.fd = ctx->fd;
187		pfd.events = POLLIN;
188		if ((n = poll(&pfd, 1, 100)) == -1) {
189			fido_log_error(errno, "%s: poll", __func__);
190			return -1;
191		} else if (n == 0) {
192			fido_log_debug("%s: timed out", __func__);
193			continue;
194		}
195		if (fido_hid_read(ctx, data, ctx->report_out_len, 250) == -1)
196			return -1;
197		/*
198		 * Ping isn't always supported on the broadcast channel,
199		 * so we might get an error, but we don't care - we're
200		 * synched now.
201		 */
202		fido_log_xxd(data, ctx->report_out_len, "%s: got reply",
203		    __func__);
204		return 0;
205	}
206	fido_log_debug("%s: no response", __func__);
207	return -1;
208}
209
210void *
211fido_hid_open(const char *path)
212{
213	struct hid_netbsd		*ctx;
214	struct usb_ctl_report_desc	 ucrd;
215	int				 r;
216
217	memset(&ucrd, 0, sizeof(ucrd));
218
219	if ((ctx = calloc(1, sizeof(*ctx))) == NULL ||
220	    (ctx->fd = fido_hid_unix_open(path)) == -1) {
221		free(ctx);
222		return (NULL);
223	}
224
225	if ((r = ioctl(ctx->fd, IOCTL_REQ(USB_GET_REPORT_DESC), &ucrd)) == -1 ||
226	    ucrd.ucrd_size < 0 ||
227	    (size_t)ucrd.ucrd_size > sizeof(ucrd.ucrd_data) ||
228	    fido_hid_get_report_len(ucrd.ucrd_data, (size_t)ucrd.ucrd_size,
229		&ctx->report_in_len, &ctx->report_out_len) < 0) {
230		if (r == -1)
231			fido_log_error(errno, "%s: ioctl", __func__);
232		fido_log_debug("%s: using default report sizes", __func__);
233		ctx->report_in_len = CTAP_MAX_REPORT_LEN;
234		ctx->report_out_len = CTAP_MAX_REPORT_LEN;
235	}
236
237	/*
238	 * NetBSD has a bug that causes it to lose
239	 * track of the DATA0/DATA1 sequence toggle across uhid device
240	 * open and close. This is a terrible hack to work around it.
241	 */
242	if (!is_fido(ctx->fd) || terrible_ping_kludge(ctx) != 0) {
243		fido_hid_close(ctx);
244		return NULL;
245	}
246
247	return (ctx);
248}
249
250void
251fido_hid_close(void *handle)
252{
253	struct hid_netbsd *ctx = handle;
254
255	if (close(ctx->fd) == -1)
256		fido_log_error(errno, "%s: close", __func__);
257
258	free(ctx);
259}
260
261int
262fido_hid_set_sigmask(void *handle, const fido_sigset_t *sigmask)
263{
264	struct hid_netbsd *ctx = handle;
265
266	ctx->sigmask = *sigmask;
267	ctx->sigmaskp = &ctx->sigmask;
268
269	return (FIDO_OK);
270}
271
272int
273fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms)
274{
275	struct hid_netbsd	*ctx = handle;
276	ssize_t			 r;
277
278	if (len != ctx->report_in_len) {
279		fido_log_debug("%s: len %zu", __func__, len);
280		return (-1);
281	}
282
283	if (fido_hid_unix_wait(ctx->fd, ms, ctx->sigmaskp) < 0) {
284		fido_log_debug("%s: fd not ready", __func__);
285		return (-1);
286	}
287
288	if ((r = read(ctx->fd, buf, len)) == -1) {
289		fido_log_error(errno, "%s: read", __func__);
290		return (-1);
291	}
292
293	if (r < 0 || (size_t)r != len) {
294		fido_log_error(errno, "%s: %zd != %zu", __func__, r, len);
295		return (-1);
296	}
297
298	return ((int)r);
299}
300
301int
302fido_hid_write(void *handle, const unsigned char *buf, size_t len)
303{
304	struct hid_netbsd	*ctx = handle;
305	ssize_t			 r;
306
307	if (len != ctx->report_out_len + 1) {
308		fido_log_debug("%s: len %zu", __func__, len);
309		return (-1);
310	}
311
312	if ((r = write(ctx->fd, buf + 1, len - 1)) == -1) {
313		fido_log_error(errno, "%s: write", __func__);
314		return (-1);
315	}
316
317	if (r < 0 || (size_t)r != len - 1) {
318		fido_log_error(errno, "%s: %zd != %zu", __func__, r, len - 1);
319		return (-1);
320	}
321
322	return ((int)len);
323}
324
325size_t
326fido_hid_report_in_len(void *handle)
327{
328	struct hid_netbsd *ctx = handle;
329
330	return (ctx->report_in_len);
331}
332
333size_t
334fido_hid_report_out_len(void *handle)
335{
336	struct hid_netbsd *ctx = handle;
337
338	return (ctx->report_out_len);
339}
340