freebsd-dev/usr.sbin/bluetooth/bthidd/parser.y
Maksim Yevmenkin 06912ebab3 Do not mark Bluetooth HID device as a "potential keyboard" if its descriptor
has items with CONSUMER page. For now only check for items with KEYBOARD page.
This should prevent bthidd(8) from allocating vkbd(4) keyboard for Microsoft
Bluetooth Explorer mouse.

Reported by:	Eric Anderson
MFC after:	3 days
2006-11-27 22:52:11 +00:00

476 lines
9.8 KiB
Plaintext

%{
/*
* parser.y
*/
/*-
* Copyright (c) 2006 Maksim Yevmenkin <m_evmenkin@yahoo.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $Id: parser.y,v 1.7 2006/09/07 21:06:53 max Exp $
* $FreeBSD$
*/
#include <sys/queue.h>
#include <bluetooth.h>
#include <dev/usb/usb.h>
#include <dev/usb/usbhid.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <usbhid.h>
#ifndef BTHIDCONTROL
#include <stdarg.h>
#include <syslog.h>
#define SYSLOG syslog
#define LOGCRIT LOG_CRIT
#define LOGERR LOG_ERR
#define LOGWARNING LOG_WARNING
#define EOL
#else
#define SYSLOG fprintf
#define LOGCRIT stderr
#define LOGERR stderr
#define LOGWARNING stderr
#define EOL "\n"
#endif /* ndef BTHIDCONTROL */
#include "bthid_config.h"
int yyparse (void);
int yylex (void);
void yyerror (char const *);
static int32_t check_hid_device(hid_device_p hid_device);
static void free_hid_device (hid_device_p hid_device);
extern FILE *yyin;
extern int yylineno;
char const *config_file = BTHIDD_CONFFILE;
char const *hids_file = BTHIDD_HIDSFILE;
static char buffer[1024];
static int32_t hid_descriptor_size;
static hid_device_t *hid_device = NULL;
static LIST_HEAD(, hid_device) hid_devices;
%}
%union {
bdaddr_t bdaddr;
int32_t num;
}
%token <bdaddr> T_BDADDRSTRING
%token <num> T_HEXBYTE
%token T_DEVICE T_BDADDR T_CONTROL_PSM T_INTERRUPT_PSM T_RECONNECT_INITIATE
%token T_BATTERY_POWER T_NORMALLY_CONNECTABLE T_HID_DESCRIPTOR
%token T_TRUE T_FALSE T_ERROR
%%
config: line
| config line
;
line: T_DEVICE
{
hid_device = (hid_device_t *) calloc(1, sizeof(*hid_device));
if (hid_device == NULL) {
SYSLOG(LOGCRIT, "Could not allocate new " \
"config entry" EOL);
YYABORT;
}
hid_device->new_device = 1;
}
'{' options '}'
{
if (check_hid_device(hid_device))
LIST_INSERT_HEAD(&hid_devices,hid_device,next);
else
free_hid_device(hid_device);
hid_device = NULL;
}
;
options: option ';'
| options option ';'
;
option: bdaddr
| control_psm
| interrupt_psm
| reconnect_initiate
| battery_power
| normally_connectable
| hid_descriptor
| parser_error
;
bdaddr: T_BDADDR T_BDADDRSTRING
{
memcpy(&hid_device->bdaddr, &$2, sizeof(hid_device->bdaddr));
}
;
control_psm: T_CONTROL_PSM T_HEXBYTE
{
hid_device->control_psm = $2;
}
;
interrupt_psm: T_INTERRUPT_PSM T_HEXBYTE
{
hid_device->interrupt_psm = $2;
}
;
reconnect_initiate: T_RECONNECT_INITIATE T_TRUE
{
hid_device->reconnect_initiate = 1;
}
| T_RECONNECT_INITIATE T_FALSE
{
hid_device->reconnect_initiate = 0;
}
;
battery_power: T_BATTERY_POWER T_TRUE
{
hid_device->battery_power = 1;
}
| T_BATTERY_POWER T_FALSE
{
hid_device->battery_power = 0;
}
;
normally_connectable: T_NORMALLY_CONNECTABLE T_TRUE
{
hid_device->normally_connectable = 1;
}
| T_NORMALLY_CONNECTABLE T_FALSE
{
hid_device->normally_connectable = 0;
}
;
hid_descriptor: T_HID_DESCRIPTOR
{
hid_descriptor_size = 0;
}
'{' hid_descriptor_bytes '}'
{
if (hid_device->desc != NULL)
hid_dispose_report_desc(hid_device->desc);
hid_device->desc = hid_use_report_desc((unsigned char *) buffer, hid_descriptor_size);
if (hid_device->desc == NULL) {
SYSLOG(LOGCRIT, "Could not use HID descriptor" EOL);
YYABORT;
}
}
;
hid_descriptor_bytes: hid_descriptor_byte
| hid_descriptor_bytes hid_descriptor_byte
;
hid_descriptor_byte: T_HEXBYTE
{
if (hid_descriptor_size >= (int32_t) sizeof(buffer)) {
SYSLOG(LOGCRIT, "HID descriptor is too big" EOL);
YYABORT;
}
buffer[hid_descriptor_size ++] = $1;
}
;
parser_error: T_ERROR
{
YYABORT;
}
%%
/* Display parser error message */
void
yyerror(char const *message)
{
SYSLOG(LOGERR, "%s in line %d" EOL, message, yylineno);
}
/* Re-read config file */
int32_t
read_config_file(void)
{
int32_t e;
if (config_file == NULL) {
SYSLOG(LOGERR, "Unknown config file name!" EOL);
return (-1);
}
if ((yyin = fopen(config_file, "r")) == NULL) {
SYSLOG(LOGERR, "Could not open config file '%s'. %s (%d)" EOL,
config_file, strerror(errno), errno);
return (-1);
}
clean_config();
if (yyparse() < 0) {
SYSLOG(LOGERR, "Could not parse config file '%s'" EOL,
config_file);
e = -1;
} else
e = 0;
fclose(yyin);
yyin = NULL;
return (e);
}
/* Clean config */
void
clean_config(void)
{
while (!LIST_EMPTY(&hid_devices)) {
hid_device_p d = LIST_FIRST(&hid_devices);
LIST_REMOVE(d, next);
free_hid_device(d);
}
}
/* Lookup config entry */
hid_device_p
get_hid_device(bdaddr_p bdaddr)
{
hid_device_p d;
LIST_FOREACH(d, &hid_devices, next)
if (memcmp(&d->bdaddr, bdaddr, sizeof(bdaddr_t)) == 0)
break;
return (d);
}
/* Get next config entry */
hid_device_p
get_next_hid_device(hid_device_p d)
{
return ((d == NULL)? LIST_FIRST(&hid_devices) : LIST_NEXT(d, next));
}
/* Print config entry */
void
print_hid_device(hid_device_p d, FILE *f)
{
/* XXX FIXME hack! */
struct report_desc {
unsigned int size;
unsigned char data[1];
};
/* XXX FIXME hack! */
struct report_desc *desc = (struct report_desc *) d->desc;
uint32_t i;
fprintf(f,
"device {\n" \
" bdaddr %s;\n" \
" control_psm 0x%x;\n" \
" interrupt_psm 0x%x;\n" \
" reconnect_initiate %s;\n" \
" battery_power %s;\n" \
" normally_connectable %s;\n" \
" hid_descriptor {",
bt_ntoa(&d->bdaddr, NULL),
d->control_psm, d->interrupt_psm,
d->reconnect_initiate? "true" : "false",
d->battery_power? "true" : "false",
d->normally_connectable? "true" : "false");
for (i = 0; i < desc->size; i ++) {
if ((i % 8) == 0)
fprintf(f, "\n ");
fprintf(f, "0x%2.2x ", desc->data[i]);
}
fprintf(f,
"\n" \
" };\n" \
"}\n");
}
/* Check config entry */
static int32_t
check_hid_device(hid_device_p d)
{
hid_data_t hd;
hid_item_t hi;
int32_t page;
if (get_hid_device(&d->bdaddr) != NULL) {
SYSLOG(LOGERR, "Ignoring duplicated entry for bdaddr %s" EOL,
bt_ntoa(&d->bdaddr, NULL));
return (0);
}
if (d->control_psm == 0) {
SYSLOG(LOGERR, "Ignoring entry with invalid control PSM" EOL);
return (0);
}
if (d->interrupt_psm == 0) {
SYSLOG(LOGERR, "Ignoring entry with invalid interrupt PSM" EOL);
return (0);
}
if (d->desc == NULL) {
SYSLOG(LOGERR, "Ignoring entry without HID descriptor" EOL);
return (0);
}
/* XXX somehow need to make sure descriptor is valid */
for (hd = hid_start_parse(d->desc, ~0, -1); hid_get_item(hd, &hi) > 0; ) {
switch (hi.kind) {
case hid_collection:
case hid_endcollection:
case hid_output:
case hid_feature:
break;
case hid_input:
/* Check if the device may send keystrokes */
page = HID_PAGE(hi.usage);
if (page == HUP_KEYBOARD)
d->keyboard = 1;
break;
}
}
hid_end_parse(hd);
return (1);
}
/* Free config entry */
static void
free_hid_device(hid_device_p d)
{
if (d->desc != NULL)
hid_dispose_report_desc(d->desc);
memset(d, 0, sizeof(*d));
free(d);
}
/* Re-read hids file */
int32_t
read_hids_file(void)
{
FILE *f;
hid_device_t *d;
char *line;
bdaddr_t bdaddr;
int32_t lineno;
if (hids_file == NULL) {
SYSLOG(LOGERR, "Unknown HIDs file name!" EOL);
return (-1);
}
if ((f = fopen(hids_file, "r")) == NULL) {
if (errno == ENOENT)
return (0);
SYSLOG(LOGERR, "Could not open HIDs file '%s'. %s (%d)" EOL,
hids_file, strerror(errno), errno);
return (-1);
}
for (lineno = 1; fgets(buffer, sizeof(buffer), f) != NULL; lineno ++) {
if ((line = strtok(buffer, "\r\n\t ")) == NULL)
continue; /* ignore empty lines */
if (!bt_aton(line, &bdaddr)) {
SYSLOG(LOGWARNING, "Ignoring unparseable BD_ADDR in " \
"%s:%d" EOL, hids_file, lineno);
continue;
}
if ((d = get_hid_device(&bdaddr)) != NULL)
d->new_device = 0;
}
fclose(f);
return (0);
}
/* Write hids file */
int32_t
write_hids_file(void)
{
char path[PATH_MAX];
FILE *f;
hid_device_t *d;
if (hids_file == NULL) {
SYSLOG(LOGERR, "Unknown HIDs file name!" EOL);
return (-1);
}
snprintf(path, sizeof(path), "%s.new", hids_file);
if ((f = fopen(path, "w")) == NULL) {
SYSLOG(LOGERR, "Could not open HIDs file '%s'. %s (%d)" EOL,
path, strerror(errno), errno);
return (-1);
}
LIST_FOREACH(d, &hid_devices, next)
if (!d->new_device)
fprintf(f, "%s\n", bt_ntoa(&d->bdaddr, NULL));
fclose(f);
if (rename(path, hids_file) < 0) {
SYSLOG(LOGERR, "Could not rename new HIDs file '%s' to '%s'. " \
"%s (%d)" EOL, path, hids_file, strerror(errno), errno);
unlink(path);
return (-1);
}
return (0);
}