xref: /freebsd/usr.sbin/bluetooth/bthidd/parser.y (revision 56e53cb8ef000c3ef72337a4095987a932cdedef)
1 %{
2 /*
3  * parser.y
4  */
5 
6 /*-
7  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
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 #include "bthid_config.h"
67 
68 	int	yylex		(void);
69 	void	yyerror		(char const *);
70 static	int32_t	check_hid_device(hid_device_p hid_device);
71 static	void	free_hid_device	(hid_device_p hid_device);
72 
73 extern	FILE			*yyin;
74 extern	int			 yylineno;
75 	char const		*config_file = BTHIDD_CONFFILE;
76 	char const		*hids_file   = BTHIDD_HIDSFILE;
77 
78 static	char			 buffer[1024];
79 static	int32_t			 hid_descriptor_size;
80 static	hid_device_t		*hid_device = NULL;
81 static	LIST_HEAD(, hid_device)	 hid_devices;
82 
83 %}
84 
85 %union {
86 	bdaddr_t	bdaddr;
87 	int32_t		num;
88 }
89 
90 %token <bdaddr> T_BDADDRSTRING
91 %token <num>	T_HEXBYTE
92 %token <num>	T_HEXWORD
93 %token T_DEVICE T_BDADDR T_VENDOR_ID T_PRODUCT_ID T_VERSION T_CONTROL_PSM
94 %token T_INTERRUPT_PSM T_RECONNECT_INITIATE T_BATTERY_POWER
95 %token T_NORMALLY_CONNECTABLE T_HID_DESCRIPTOR
96 %token T_TRUE T_FALSE T_ERROR
97 
98 %%
99 
100 config:		line
101 		| config line
102 		;
103 
104 line:		T_DEVICE
105 			{
106 			hid_device = (hid_device_t *) calloc(1, sizeof(*hid_device));
107 			if (hid_device == NULL) {
108 				SYSLOG(LOGCRIT, "Could not allocate new " \
109 						"config entry" EOL);
110 				YYABORT;
111 			}
112 
113 			hid_device->new_device = 1;
114 			}
115 		'{' options '}'
116 			{
117 			if (check_hid_device(hid_device))
118 				LIST_INSERT_HEAD(&hid_devices,hid_device,next);
119 			else
120 				free_hid_device(hid_device);
121 
122 			hid_device = NULL;
123 			}
124 		;
125 
126 options:	option ';'
127 		| options option ';'
128 		;
129 
130 option:		bdaddr
131 		| vendor_id
132 		| product_id
133 		| version
134 		| control_psm
135 		| interrupt_psm
136 		| reconnect_initiate
137 		| battery_power
138 		| normally_connectable
139 		| hid_descriptor
140 		| parser_error
141 		;
142 
143 bdaddr:		T_BDADDR T_BDADDRSTRING
144 			{
145 			memcpy(&hid_device->bdaddr, &$2, sizeof(hid_device->bdaddr));
146 			}
147 		;
148 
149 vendor_id:	T_VENDOR_ID T_HEXWORD
150 			{
151 			hid_device->vendor_id = $2;
152 			}
153 		;
154 
155 product_id:	T_PRODUCT_ID T_HEXWORD
156 			{
157 			hid_device->product_id = $2;
158 			}
159 		;
160 
161 version:	T_VERSION T_HEXWORD
162 			{
163 			hid_device->version = $2;
164 			}
165 		;
166 
167 control_psm:	T_CONTROL_PSM T_HEXBYTE
168 			{
169 			hid_device->control_psm = $2;
170 			}
171 		;
172 
173 interrupt_psm:	T_INTERRUPT_PSM T_HEXBYTE
174 			{
175 			hid_device->interrupt_psm = $2;
176 			}
177 		;
178 
179 reconnect_initiate: T_RECONNECT_INITIATE T_TRUE
180 			{
181 			hid_device->reconnect_initiate = 1;
182 			}
183 		| T_RECONNECT_INITIATE T_FALSE
184 			{
185 			hid_device->reconnect_initiate = 0;
186 			}
187 		;
188 
189 battery_power:	T_BATTERY_POWER T_TRUE
190 			{
191 			hid_device->battery_power = 1;
192 			}
193 		| T_BATTERY_POWER T_FALSE
194 			{
195 			hid_device->battery_power = 0;
196 			}
197 		;
198 
199 normally_connectable: T_NORMALLY_CONNECTABLE T_TRUE
200 			{
201 			hid_device->normally_connectable = 1;
202 			}
203 		| T_NORMALLY_CONNECTABLE T_FALSE
204 			{
205 			hid_device->normally_connectable = 0;
206 			}
207 		;
208 
209 hid_descriptor:	T_HID_DESCRIPTOR
210 			{
211 			hid_descriptor_size = 0;
212 			}
213 		'{' hid_descriptor_bytes '}'
214 			{
215 			if (hid_device->desc != NULL)
216 				hid_dispose_report_desc(hid_device->desc);
217 
218 			hid_device->desc = hid_use_report_desc((unsigned char *) buffer, hid_descriptor_size);
219 			if (hid_device->desc == NULL) {
220 				SYSLOG(LOGCRIT, "Could not use HID descriptor" EOL);
221 				YYABORT;
222 			}
223 			}
224 		;
225 
226 hid_descriptor_bytes: hid_descriptor_byte
227 		| hid_descriptor_bytes hid_descriptor_byte
228 		;
229 
230 hid_descriptor_byte: T_HEXBYTE
231 			{
232 			if (hid_descriptor_size >= (int32_t) sizeof(buffer)) {
233 				SYSLOG(LOGCRIT, "HID descriptor is too big" EOL);
234 				YYABORT;
235 			}
236 
237 			buffer[hid_descriptor_size ++] = $1;
238 			}
239 		;
240 
241 parser_error:	T_ERROR
242 			{
243 				YYABORT;
244 			}
245 
246 %%
247 
248 /* Display parser error message */
249 void
250 yyerror(char const *message)
251 {
252 	SYSLOG(LOGERR, "%s in line %d" EOL, message, yylineno);
253 }
254 
255 /* Re-read config file */
256 int32_t
257 read_config_file(void)
258 {
259 	int32_t	e;
260 
261 	if (config_file == NULL) {
262 		SYSLOG(LOGERR, "Unknown config file name!" EOL);
263 		return (-1);
264 	}
265 
266 	if ((yyin = fopen(config_file, "r")) == NULL) {
267 		SYSLOG(LOGERR, "Could not open config file '%s'. %s (%d)" EOL,
268 				config_file, strerror(errno), errno);
269 		return (-1);
270 	}
271 
272 	clean_config();
273 	if (yyparse() < 0) {
274 		SYSLOG(LOGERR, "Could not parse config file '%s'" EOL,
275 				config_file);
276 		e = -1;
277 	} else
278 		e = 0;
279 
280 	fclose(yyin);
281 	yyin = NULL;
282 
283 	return (e);
284 }
285 
286 /* Clean config */
287 void
288 clean_config(void)
289 {
290 	while (!LIST_EMPTY(&hid_devices)) {
291 		hid_device_p	d = LIST_FIRST(&hid_devices);
292 
293 		LIST_REMOVE(d, next);
294 		free_hid_device(d);
295 	}
296 }
297 
298 /* Lookup config entry */
299 hid_device_p
300 get_hid_device(bdaddr_p bdaddr)
301 {
302 	hid_device_p	d;
303 
304 	LIST_FOREACH(d, &hid_devices, next)
305 		if (memcmp(&d->bdaddr, bdaddr, sizeof(bdaddr_t)) == 0)
306 			break;
307 
308 	return (d);
309 }
310 
311 /* Get next config entry */
312 hid_device_p
313 get_next_hid_device(hid_device_p d)
314 {
315 	return ((d == NULL)? LIST_FIRST(&hid_devices) : LIST_NEXT(d, next));
316 }
317 
318 /* Print config entry */
319 void
320 print_hid_device(hid_device_p d, FILE *f)
321 {
322 	/* XXX FIXME hack! */
323 	struct report_desc {
324 		unsigned int	size;
325 		unsigned char	data[1];
326 	};
327 	/* XXX FIXME hack! */
328 
329 	struct report_desc	*desc = (struct report_desc *) d->desc;
330 	uint32_t		 i;
331 
332 	fprintf(f,
333 "device {\n"					\
334 "	bdaddr			%s;\n"		\
335 "	vendor_id		0x%04x;\n"	\
336 "	product_id		0x%04x;\n"	\
337 "	version			0x%04x;\n"	\
338 "	control_psm		0x%x;\n"	\
339 "	interrupt_psm		0x%x;\n"	\
340 "	reconnect_initiate	%s;\n"		\
341 "	battery_power		%s;\n"		\
342 "	normally_connectable	%s;\n"		\
343 "	hid_descriptor		{",
344 		bt_ntoa(&d->bdaddr, NULL),
345 		d->vendor_id, d->product_id, d->version,
346 		d->control_psm, d->interrupt_psm,
347                 d->reconnect_initiate? "true" : "false",
348                 d->battery_power? "true" : "false",
349                 d->normally_connectable? "true" : "false");
350 
351 	for (i = 0; i < desc->size; i ++) {
352 			if ((i % 8) == 0)
353 				fprintf(f, "\n		");
354 
355 			fprintf(f, "0x%2.2x ", desc->data[i]);
356 	}
357 
358 	fprintf(f,
359 "\n"		\
360 "	};\n"	\
361 "}\n");
362 }
363 
364 /* Check config entry */
365 static int32_t
366 check_hid_device(hid_device_p d)
367 {
368 	hid_data_t	hd;
369 	hid_item_t	hi;
370 	int32_t		page;
371 
372 	if (get_hid_device(&d->bdaddr) != NULL) {
373 		SYSLOG(LOGERR, "Ignoring duplicated entry for bdaddr %s" EOL,
374 				bt_ntoa(&d->bdaddr, NULL));
375 		return (0);
376 	}
377 
378 	if (d->control_psm == 0) {
379 		SYSLOG(LOGERR, "Ignoring entry with invalid control PSM" EOL);
380 		return (0);
381 	}
382 
383 	if (d->interrupt_psm == 0) {
384 		SYSLOG(LOGERR, "Ignoring entry with invalid interrupt PSM" EOL);
385 		return (0);
386 	}
387 
388 	if (d->desc == NULL) {
389 		SYSLOG(LOGERR, "Ignoring entry without HID descriptor" EOL);
390 		return (0);
391 	}
392 
393 	/* XXX somehow need to make sure descriptor is valid */
394 	for (hd = hid_start_parse(d->desc, ~0, -1); hid_get_item(hd, &hi) > 0; ) {
395 		switch (hi.kind) {
396 		case hid_collection:
397 		case hid_endcollection:
398 		case hid_output:
399 		case hid_feature:
400 			break;
401 
402 		case hid_input:
403 			/* Check if the device may send keystrokes */
404 			page = HID_PAGE(hi.usage);
405 			if (page == HUP_KEYBOARD)
406 				d->keyboard = 1;
407 			break;
408 		}
409 	}
410 	hid_end_parse(hd);
411 
412 	return (1);
413 }
414 
415 /* Free config entry */
416 static void
417 free_hid_device(hid_device_p d)
418 {
419 	if (d->desc != NULL)
420 		hid_dispose_report_desc(d->desc);
421 
422 	memset(d, 0, sizeof(*d));
423 	free(d);
424 }
425 
426 /* Re-read hids file */
427 int32_t
428 read_hids_file(void)
429 {
430 	FILE		*f;
431 	hid_device_t	*d;
432 	char		*line;
433 	bdaddr_t	 bdaddr;
434 	int32_t		 lineno;
435 
436 	if (hids_file == NULL) {
437 		SYSLOG(LOGERR, "Unknown HIDs file name!" EOL);
438 		return (-1);
439 	}
440 
441 	if ((f = fopen(hids_file, "r")) == NULL) {
442 		if (errno == ENOENT)
443 			return (0);
444 
445 		SYSLOG(LOGERR, "Could not open HIDs file '%s'. %s (%d)" EOL,
446 			hids_file, strerror(errno), errno);
447 		return (-1);
448 	}
449 
450 	for (lineno = 1; fgets(buffer, sizeof(buffer), f) != NULL; lineno ++) {
451 		if ((line = strtok(buffer, "\r\n\t ")) == NULL)
452 			continue; /* ignore empty lines */
453 
454 		if (!bt_aton(line, &bdaddr)) {
455 			SYSLOG(LOGWARNING, "Ignoring unparseable BD_ADDR in " \
456 				"%s:%d" EOL, hids_file, lineno);
457 			continue;
458 		}
459 
460 		if ((d = get_hid_device(&bdaddr)) != NULL)
461 			d->new_device = 0;
462 	}
463 
464 	fclose(f);
465 
466 	return (0);
467 }
468 
469 /* Write hids file */
470 int32_t
471 write_hids_file(void)
472 {
473 	char		 path[PATH_MAX];
474 	FILE		*f;
475 	hid_device_t	*d;
476 
477 	if (hids_file == NULL) {
478 		SYSLOG(LOGERR, "Unknown HIDs file name!" EOL);
479 		return (-1);
480 	}
481 
482 	snprintf(path, sizeof(path), "%s.new", hids_file);
483 
484 	if ((f = fopen(path, "w")) == NULL) {
485 		SYSLOG(LOGERR, "Could not open HIDs file '%s'. %s (%d)" EOL,
486 			path, strerror(errno), errno);
487 		return (-1);
488 	}
489 
490 	LIST_FOREACH(d, &hid_devices, next)
491 		if (!d->new_device)
492 			fprintf(f, "%s\n", bt_ntoa(&d->bdaddr, NULL));
493 
494 	fclose(f);
495 
496 	if (rename(path, hids_file) < 0) {
497 		SYSLOG(LOGERR, "Could not rename new HIDs file '%s' to '%s'. " \
498 			"%s (%d)" EOL, path, hids_file, strerror(errno), errno);
499 		unlink(path);
500 		return (-1);
501 	}
502 
503 	return (0);
504 }
505 
506