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