xref: /freebsd/usr.sbin/bluetooth/bthidd/parser.y (revision 4e8d558c)
1 %{
2 /*
3  * parser.y
4  */
5 
6 /*-
7  * SPDX-License-Identifier: BSD-2-Clause
8  *
9  * Copyright (c) 2006 Maksim Yevmenkin <m_evmenkin@yahoo.com>
10  * All rights reserved.
11  *
12  * Redistribution and use in source and binary forms, with or without
13  * modification, are permitted provided that the following conditions
14  * are met:
15  * 1. Redistributions of source code must retain the above copyright
16  *    notice, this list of conditions and the following disclaimer.
17  * 2. Redistributions in binary form must reproduce the above copyright
18  *    notice, this list of conditions and the following disclaimer in the
19  *    documentation and/or other materials provided with the distribution.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  *
33  * $Id: parser.y,v 1.7 2006/09/07 21:06:53 max Exp $
34  * $FreeBSD$
35  */
36 
37 #include <sys/queue.h>
38 #define L2CAP_SOCKET_CHECKED
39 #include <bluetooth.h>
40 #include <dev/usb/usb.h>
41 #include <dev/usb/usbhid.h>
42 #include <errno.h>
43 #include <limits.h>
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <string.h>
47 #include <unistd.h>
48 #include <usbhid.h>
49 
50 #ifndef BTHIDCONTROL
51 #include <stdarg.h>
52 #include <syslog.h>
53 #define	SYSLOG		syslog
54 #define	LOGCRIT		LOG_CRIT
55 #define	LOGERR		LOG_ERR
56 #define	LOGWARNING	LOG_WARNING
57 #define	EOL
58 #else
59 #define	SYSLOG		fprintf
60 #define	LOGCRIT		stderr
61 #define	LOGERR		stderr
62 #define	LOGWARNING	stderr
63 #define	EOL	"\n"
64 #endif /* ndef BTHIDCONTROL */
65 
66 #define	NAMELESS_DEVICE	"No Name"
67 
68 #include "bthid_config.h"
69 
70 	int	yylex		(void);
71 	void	yyerror		(char const *);
72 static	int32_t	check_hid_device(hid_device_p hid_device);
73 static	void	free_hid_device	(hid_device_p hid_device);
74 
75 extern	FILE			*yyin;
76 extern	int			 yylineno;
77 	char const		*config_file = BTHIDD_CONFFILE;
78 	char const		*hids_file   = BTHIDD_HIDSFILE;
79 
80 static	char			 buffer[1024];
81 static	int32_t			 hid_descriptor_size;
82 static	hid_device_t		*hid_device = NULL;
83 static	LIST_HEAD(, hid_device)	 hid_devices;
84 
85 %}
86 
87 %union {
88 	bdaddr_t	bdaddr;
89 	int32_t		num;
90 	char		*string;
91 }
92 
93 %token <bdaddr> T_BDADDRSTRING
94 %token <num>	T_HEXBYTE
95 %token <num>	T_HEXWORD
96 %token <string>	T_STRING
97 %token T_NAME
98 %token T_DEVICE T_BDADDR T_VENDOR_ID T_PRODUCT_ID T_VERSION T_CONTROL_PSM
99 %token T_INTERRUPT_PSM T_RECONNECT_INITIATE T_BATTERY_POWER
100 %token T_NORMALLY_CONNECTABLE T_HID_DESCRIPTOR
101 %token T_TRUE T_FALSE T_ERROR
102 
103 %%
104 
105 config:		line
106 		| config line
107 		;
108 
109 line:		T_DEVICE
110 			{
111 			hid_device = (hid_device_t *) calloc(1, sizeof(*hid_device));
112 			if (hid_device == NULL) {
113 				SYSLOG(LOGCRIT, "Could not allocate new " \
114 						"config entry" EOL);
115 				YYABORT;
116 			}
117 
118 			hid_device->new_device = 1;
119 			}
120 		'{' options '}'
121 			{
122 			if (check_hid_device(hid_device))
123 				LIST_INSERT_HEAD(&hid_devices,hid_device,next);
124 			else
125 				free_hid_device(hid_device);
126 
127 			hid_device = NULL;
128 			}
129 		;
130 
131 options:	option ';'
132 		| options option ';'
133 		;
134 
135 option:		bdaddr
136 		| name
137 		| vendor_id
138 		| product_id
139 		| version
140 		| control_psm
141 		| interrupt_psm
142 		| reconnect_initiate
143 		| battery_power
144 		| normally_connectable
145 		| hid_descriptor
146 		| parser_error
147 		;
148 
149 bdaddr:		T_BDADDR T_BDADDRSTRING
150 			{
151 			memcpy(&hid_device->bdaddr, &$2, sizeof(hid_device->bdaddr));
152 			}
153 		;
154 
155 name:		T_NAME T_STRING
156 			{
157 			if (hid_device->name != NULL) {
158                                 free(hid_device->name);
159                                 hid_device->name = NULL;
160 			}
161 
162 			if (strcmp($2, NAMELESS_DEVICE)) {
163 				hid_device->name = strdup($2);
164 				if (hid_device->name == NULL) {
165 					SYSLOG(LOGCRIT, "Could not allocate new " \
166 							"device name" EOL);
167 					YYABORT;
168 				}
169 			}
170 			}
171 		;
172 
173 vendor_id:	T_VENDOR_ID T_HEXWORD
174 			{
175 			hid_device->vendor_id = $2;
176 			}
177 		;
178 
179 product_id:	T_PRODUCT_ID T_HEXWORD
180 			{
181 			hid_device->product_id = $2;
182 			}
183 		;
184 
185 version:	T_VERSION T_HEXWORD
186 			{
187 			hid_device->version = $2;
188 			}
189 		;
190 
191 control_psm:	T_CONTROL_PSM T_HEXBYTE
192 			{
193 			hid_device->control_psm = $2;
194 			}
195 		;
196 
197 interrupt_psm:	T_INTERRUPT_PSM T_HEXBYTE
198 			{
199 			hid_device->interrupt_psm = $2;
200 			}
201 		;
202 
203 reconnect_initiate: T_RECONNECT_INITIATE T_TRUE
204 			{
205 			hid_device->reconnect_initiate = 1;
206 			}
207 		| T_RECONNECT_INITIATE T_FALSE
208 			{
209 			hid_device->reconnect_initiate = 0;
210 			}
211 		;
212 
213 battery_power:	T_BATTERY_POWER T_TRUE
214 			{
215 			hid_device->battery_power = 1;
216 			}
217 		| T_BATTERY_POWER T_FALSE
218 			{
219 			hid_device->battery_power = 0;
220 			}
221 		;
222 
223 normally_connectable: T_NORMALLY_CONNECTABLE T_TRUE
224 			{
225 			hid_device->normally_connectable = 1;
226 			}
227 		| T_NORMALLY_CONNECTABLE T_FALSE
228 			{
229 			hid_device->normally_connectable = 0;
230 			}
231 		;
232 
233 hid_descriptor:	T_HID_DESCRIPTOR
234 			{
235 			hid_descriptor_size = 0;
236 			}
237 		'{' hid_descriptor_bytes '}'
238 			{
239 			if (hid_device->desc != NULL)
240 				hid_dispose_report_desc(hid_device->desc);
241 
242 			hid_device->desc = hid_use_report_desc((unsigned char *) buffer, hid_descriptor_size);
243 			if (hid_device->desc == NULL) {
244 				SYSLOG(LOGCRIT, "Could not use HID descriptor" EOL);
245 				YYABORT;
246 			}
247 			}
248 		;
249 
250 hid_descriptor_bytes: hid_descriptor_byte
251 		| hid_descriptor_bytes hid_descriptor_byte
252 		;
253 
254 hid_descriptor_byte: T_HEXBYTE
255 			{
256 			if (hid_descriptor_size >= (int32_t) sizeof(buffer)) {
257 				SYSLOG(LOGCRIT, "HID descriptor is too big" EOL);
258 				YYABORT;
259 			}
260 
261 			buffer[hid_descriptor_size ++] = $1;
262 			}
263 		;
264 
265 parser_error:	T_ERROR
266 			{
267 				YYABORT;
268 			}
269 
270 %%
271 
272 /* Display parser error message */
273 void
274 yyerror(char const *message)
275 {
276 	SYSLOG(LOGERR, "%s in line %d" EOL, message, yylineno);
277 }
278 
279 /* Re-read config file */
280 int32_t
281 read_config_file(void)
282 {
283 	int32_t	e;
284 
285 	if (config_file == NULL) {
286 		SYSLOG(LOGERR, "Unknown config file name!" EOL);
287 		return (-1);
288 	}
289 
290 	if ((yyin = fopen(config_file, "r")) == NULL) {
291 		SYSLOG(LOGERR, "Could not open config file '%s'. %s (%d)" EOL,
292 				config_file, strerror(errno), errno);
293 		return (-1);
294 	}
295 
296 	clean_config();
297 	if (yyparse() < 0) {
298 		SYSLOG(LOGERR, "Could not parse config file '%s'" EOL,
299 				config_file);
300 		e = -1;
301 	} else
302 		e = 0;
303 
304 	fclose(yyin);
305 	yyin = NULL;
306 
307 	return (e);
308 }
309 
310 /* Clean config */
311 void
312 clean_config(void)
313 {
314 	while (!LIST_EMPTY(&hid_devices)) {
315 		hid_device_p	d = LIST_FIRST(&hid_devices);
316 
317 		LIST_REMOVE(d, next);
318 		free_hid_device(d);
319 	}
320 }
321 
322 /* Lookup config entry */
323 hid_device_p
324 get_hid_device(bdaddr_p bdaddr)
325 {
326 	hid_device_p	d;
327 
328 	LIST_FOREACH(d, &hid_devices, next)
329 		if (memcmp(&d->bdaddr, bdaddr, sizeof(bdaddr_t)) == 0)
330 			break;
331 
332 	return (d);
333 }
334 
335 /* Get next config entry */
336 hid_device_p
337 get_next_hid_device(hid_device_p d)
338 {
339 	return ((d == NULL)? LIST_FIRST(&hid_devices) : LIST_NEXT(d, next));
340 }
341 
342 /* Print config entry */
343 void
344 print_hid_device(hid_device_p d, FILE *f)
345 {
346 	/* XXX FIXME hack! */
347 	struct report_desc {
348 		unsigned int	size;
349 		unsigned char	data[1];
350 	};
351 	/* XXX FIXME hack! */
352 
353 	struct report_desc	*desc = (struct report_desc *) d->desc;
354 	uint32_t		 i;
355 
356 	fprintf(f,
357 "device {\n"					\
358 "	bdaddr			%s;\n"		\
359 "	name			\"%s\";\n"	\
360 "	vendor_id		0x%04x;\n"	\
361 "	product_id		0x%04x;\n"	\
362 "	version			0x%04x;\n"	\
363 "	control_psm		0x%x;\n"	\
364 "	interrupt_psm		0x%x;\n"	\
365 "	reconnect_initiate	%s;\n"		\
366 "	battery_power		%s;\n"		\
367 "	normally_connectable	%s;\n"		\
368 "	hid_descriptor		{",
369 		bt_ntoa(&d->bdaddr, NULL),
370 		(d->name != NULL)? d->name : NAMELESS_DEVICE,
371 		d->vendor_id, d->product_id, d->version,
372 		d->control_psm, d->interrupt_psm,
373                 d->reconnect_initiate? "true" : "false",
374                 d->battery_power? "true" : "false",
375                 d->normally_connectable? "true" : "false");
376 
377 	for (i = 0; i < desc->size; i ++) {
378 			if ((i % 8) == 0)
379 				fprintf(f, "\n		");
380 
381 			fprintf(f, "0x%2.2x ", desc->data[i]);
382 	}
383 
384 	fprintf(f,
385 "\n"		\
386 "	};\n"	\
387 "}\n");
388 }
389 
390 /* Check config entry */
391 static int32_t
392 check_hid_device(hid_device_p d)
393 {
394 	hid_data_t	hd;
395 	hid_item_t	hi;
396 	int32_t		page, mdepth;
397 
398 	if (get_hid_device(&d->bdaddr) != NULL) {
399 		SYSLOG(LOGERR, "Ignoring duplicated entry for bdaddr %s" EOL,
400 				bt_ntoa(&d->bdaddr, NULL));
401 		return (0);
402 	}
403 
404 	if (d->control_psm == 0) {
405 		SYSLOG(LOGERR, "Ignoring entry with invalid control PSM" EOL);
406 		return (0);
407 	}
408 
409 	if (d->interrupt_psm == 0) {
410 		SYSLOG(LOGERR, "Ignoring entry with invalid interrupt PSM" EOL);
411 		return (0);
412 	}
413 
414 	if (d->desc == NULL) {
415 		SYSLOG(LOGERR, "Ignoring entry without HID descriptor" EOL);
416 		return (0);
417 	}
418 
419 	mdepth = 0;
420 
421 	/* XXX somehow need to make sure descriptor is valid */
422 	for (hd = hid_start_parse(d->desc, ~0, -1); hid_get_item(hd, &hi) > 0; ) {
423 		switch (hi.kind) {
424 		case hid_collection:
425 			if (mdepth != 0)
426 				mdepth++;
427 			else if (hi.collection == 1 &&
428 			     hi.usage ==
429 			      HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_MOUSE))
430 				mdepth++;
431 			break;
432 		case hid_endcollection:
433 			if (mdepth != 0)
434 				mdepth--;
435 			break;
436 		case hid_output:
437 		case hid_feature:
438 			break;
439 
440 		case hid_input:
441 			/* Check if the device may send keystrokes */
442 			page = HID_PAGE(hi.usage);
443 			if (page == HUP_KEYBOARD)
444 				d->keyboard = 1;
445 			if (page == HUP_CONSUMER &&
446 			    (hi.flags & (HIO_CONST|HIO_RELATIVE)) == 0)
447 				d->has_cons = 1;
448 			/* Check if the device may send relative motion events */
449 			if (mdepth == 0)
450 				break;
451 			if (hi.usage ==
452 			     HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X) &&
453 			    (hi.flags & (HIO_CONST|HIO_RELATIVE)) == HIO_RELATIVE)
454 				d->mouse = 1;
455 			if (hi.usage ==
456 			     HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y) &&
457 			    (hi.flags & (HIO_CONST|HIO_RELATIVE)) == HIO_RELATIVE)
458 				d->mouse = 1;
459 			if (hi.usage ==
460 			     HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_WHEEL) &&
461 			    (hi.flags & (HIO_CONST|HIO_RELATIVE)) == HIO_RELATIVE)
462 				d->has_wheel = 1;
463 			if (hi.usage ==
464 			    HID_USAGE2(HUP_CONSUMER, HUC_AC_PAN) &&
465 			    (hi.flags & (HIO_CONST|HIO_RELATIVE)) == HIO_RELATIVE)
466 				d->has_hwheel = 1;
467 			break;
468 		}
469 	}
470 	hid_end_parse(hd);
471 
472 	return (1);
473 }
474 
475 /* Free config entry */
476 static void
477 free_hid_device(hid_device_p d)
478 {
479 	if (d->desc != NULL)
480 		hid_dispose_report_desc(d->desc);
481 
482 	free(d->name);
483 	memset(d, 0, sizeof(*d));
484 	free(d);
485 }
486 
487 /* Re-read hids file */
488 int32_t
489 read_hids_file(void)
490 {
491 	FILE		*f;
492 	hid_device_t	*d;
493 	char		*line;
494 	bdaddr_t	 bdaddr;
495 	int32_t		 lineno;
496 
497 	if (hids_file == NULL) {
498 		SYSLOG(LOGERR, "Unknown HIDs file name!" EOL);
499 		return (-1);
500 	}
501 
502 	if ((f = fopen(hids_file, "r")) == NULL) {
503 		if (errno == ENOENT)
504 			return (0);
505 
506 		SYSLOG(LOGERR, "Could not open HIDs file '%s'. %s (%d)" EOL,
507 			hids_file, strerror(errno), errno);
508 		return (-1);
509 	}
510 
511 	for (lineno = 1; fgets(buffer, sizeof(buffer), f) != NULL; lineno ++) {
512 		if ((line = strtok(buffer, "\r\n\t ")) == NULL)
513 			continue; /* ignore empty lines */
514 
515 		if (!bt_aton(line, &bdaddr)) {
516 			SYSLOG(LOGWARNING, "Ignoring unparseable BD_ADDR in " \
517 				"%s:%d" EOL, hids_file, lineno);
518 			continue;
519 		}
520 
521 		if ((d = get_hid_device(&bdaddr)) != NULL)
522 			d->new_device = 0;
523 	}
524 
525 	fclose(f);
526 
527 	return (0);
528 }
529 
530 /* Write hids file */
531 int32_t
532 write_hids_file(void)
533 {
534 	char		 path[PATH_MAX];
535 	FILE		*f;
536 	hid_device_t	*d;
537 
538 	if (hids_file == NULL) {
539 		SYSLOG(LOGERR, "Unknown HIDs file name!" EOL);
540 		return (-1);
541 	}
542 
543 	snprintf(path, sizeof(path), "%s.new", hids_file);
544 
545 	if ((f = fopen(path, "w")) == NULL) {
546 		SYSLOG(LOGERR, "Could not open HIDs file '%s'. %s (%d)" EOL,
547 			path, strerror(errno), errno);
548 		return (-1);
549 	}
550 
551 	LIST_FOREACH(d, &hid_devices, next)
552 		if (!d->new_device)
553 			fprintf(f, "%s\n", bt_ntoa(&d->bdaddr, NULL));
554 
555 	fclose(f);
556 
557 	if (rename(path, hids_file) < 0) {
558 		SYSLOG(LOGERR, "Could not rename new HIDs file '%s' to '%s'. " \
559 			"%s (%d)" EOL, path, hids_file, strerror(errno), errno);
560 		unlink(path);
561 		return (-1);
562 	}
563 
564 	return (0);
565 }
566 
567