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