root/usr.sbin/bluetooth/hcsecd/parser.y
%{
/*-
 * parser.y
 *
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright (c) 2001-2002 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.5 2003/06/07 21:22:30 max Exp $
 */

#include <sys/fcntl.h>
#include <sys/queue.h>
#define L2CAP_SOCKET_CHECKED
#include <bluetooth.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include "hcsecd.h"

        int     yyparse  (void);
        int     yylex    (void);

static  void    free_key (link_key_p key);
static  int     hexa2int4(char *a);
static  int     hexa2int8(char *a);

extern  int                      yylineno;
static  LIST_HEAD(, link_key)    link_keys;
        char                    *config_file = "/etc/bluetooth/hcsecd.conf";

static  link_key_p               key = NULL;
%}

%union {
        char    *string;
}

%token <string> T_BDADDRSTRING T_HEXSTRING T_STRING
%token T_DEVICE T_BDADDR T_NAME T_KEY T_PIN T_NOKEY T_NOPIN T_JUNK

%%

config:         line
                | config line
                ;

line:           T_DEVICE
                        {
                        key = (link_key_p) malloc(sizeof(*key));
                        if (key == NULL) {
                                syslog(LOG_ERR, "Could not allocate new " \
                                                "config entry");
                                exit(1);
                        }

                        memset(key, 0, sizeof(*key));
                        }
                '{' options '}'
                        {
                        if (get_key(&key->bdaddr, 1) != NULL) {
                                syslog(LOG_ERR, "Ignoring duplicated entry " \
                                                "for bdaddr %s",
                                                bt_ntoa(&key->bdaddr, NULL));
                                free_key(key);
                        } else 
                                LIST_INSERT_HEAD(&link_keys, key, next);

                        key = NULL;
                        }
                ;

options:        option ';'
                | options option ';'
                ;

option:         bdaddr
                | name
                | key
                | pin
                ;

bdaddr:         T_BDADDR T_BDADDRSTRING
                        {
                        if (!bt_aton($2, &key->bdaddr)) {
                                syslog(LOG_ERR, "Cound not parse BD_ADDR " \
                                                "'%s'", $2);
                                exit(1);
                        }
                        }
                ;

name:           T_NAME T_STRING
                        {
                        if (key->name != NULL)
                                free(key->name);

                        key->name = strdup($2);
                        if (key->name == NULL) {
                                syslog(LOG_ERR, "Could not allocate new " \
                                                "device name");
                                exit(1);
                        }
                        }
                ;

key:            T_KEY T_HEXSTRING
                        {
                        int     i, len;

                        if (key->key != NULL)
                                free(key->key);

                        key->key = (uint8_t *) malloc(NG_HCI_KEY_SIZE);
                        if (key->key == NULL) {
                                syslog(LOG_ERR, "Could not allocate new " \
                                                "link key");
                                exit(1);
                        }

                        memset(key->key, 0, NG_HCI_KEY_SIZE);

                        len = strlen($2) / 2;
                        if (len > NG_HCI_KEY_SIZE)
                                len = NG_HCI_KEY_SIZE;

                        for (i = 0; i < len; i ++)
                                key->key[i] = hexa2int8((char *)($2) + 2*i);
                        }
                | T_KEY T_NOKEY
                        {
                        if (key->key != NULL)
                                free(key->key);

                        key->key = NULL;
                        }
                ;

pin:            T_PIN T_STRING
                        {
                        if (key->pin != NULL)
                                free(key->pin);

                        key->pin = strdup($2);
                        if (key->pin == NULL) {
                                syslog(LOG_ERR, "Could not allocate new " \
                                                "PIN code");
                                exit(1);
                        }
                        }
                | T_PIN T_NOPIN
                        {
                        if (key->pin != NULL)
                                free(key->pin);

                        key->pin = NULL;
                        }
                ;

%%

/* Display parser error message */
void
yyerror(char const *message)
{
        syslog(LOG_ERR, "%s in line %d", message, yylineno);
}

/* Re-read config file */
void
read_config_file(void)
{
        extern FILE     *yyin;

        if (config_file == NULL) {
                syslog(LOG_ERR, "Unknown config file name!");
                exit(1);
        }

        if ((yyin = fopen(config_file, "r")) == NULL) {
                syslog(LOG_ERR, "Could not open config file '%s'. %s (%d)",
                                config_file, strerror(errno), errno);
                exit(1);
        }

        clean_config();
        if (yyparse() < 0) {
                syslog(LOG_ERR, "Could not parse config file '%s'",config_file);
                exit(1);
        }

        fclose(yyin);
        yyin = NULL;

#if __config_debug__
        dump_config();
#endif
}

/* Clean config */
void
clean_config(void)
{
        link_key_p      key = NULL;

        while ((key = LIST_FIRST(&link_keys)) != NULL) {
                LIST_REMOVE(key, next);
                free_key(key);
        }
}

/* Find link key entry in the list. Return exact or default match */
link_key_p
get_key(bdaddr_p bdaddr, int exact_match)
{
        link_key_p      key = NULL, defkey = NULL;

        LIST_FOREACH(key, &link_keys, next) {
                if (memcmp(bdaddr, &key->bdaddr, sizeof(key->bdaddr)) == 0)
                        break;

                if (!exact_match)
                        if (memcmp(NG_HCI_BDADDR_ANY, &key->bdaddr,
                                        sizeof(key->bdaddr)) == 0)
                                defkey = key;
        }

        return ((key != NULL)? key : defkey);
}

#if __config_debug__
/* Dump config */
void
dump_config(void)
{
        link_key_p      key = NULL;
        char            buffer[64];

        LIST_FOREACH(key, &link_keys, next) {
                if (key->key != NULL)
                        snprintf(buffer, sizeof(buffer),
"0x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
                                key->key[0], key->key[1], key->key[2],
                                key->key[3], key->key[4], key->key[5],
                                key->key[6], key->key[7], key->key[8],
                                key->key[9], key->key[10], key->key[11],
                                key->key[12], key->key[13], key->key[14],
                                key->key[15]);

                syslog(LOG_DEBUG, 
"device %s " \
"bdaddr %s " \
"pin %s " \
"key %s",
                        (key->name != NULL)? key->name : "noname",
                        bt_ntoa(&key->bdaddr, NULL),
                        (key->pin != NULL)? key->pin : "nopin",
                        (key->key != NULL)? buffer : "nokey");
        }
}
#endif

/* Read keys file */
int
read_keys_file(void)
{
        FILE            *f = NULL;
        link_key_t      *key = NULL;
        char             buf[HCSECD_BUFFER_SIZE], *p = NULL, *cp = NULL;
        bdaddr_t         bdaddr;
        int              i, len;

        if ((f = fopen(HCSECD_KEYSFILE, "r")) == NULL) {
                if (errno == ENOENT)
                        return (0);

                syslog(LOG_ERR, "Could not open keys file %s. %s (%d)\n",
                                HCSECD_KEYSFILE, strerror(errno), errno);

                return (-1);
        }

        while ((p = fgets(buf, sizeof(buf), f)) != NULL) {
                if (*p == '#')
                        continue;
                if ((cp = strpbrk(p, " ")) == NULL)
                        continue;

                *cp++ = '\0';

                if (!bt_aton(p, &bdaddr))
                        continue;

                if ((key = get_key(&bdaddr, 1)) == NULL)
                        continue;

                if (key->key == NULL) {
                        key->key = (uint8_t *) malloc(NG_HCI_KEY_SIZE);
                        if (key->key == NULL) {
                                syslog(LOG_ERR, "Could not allocate link key");
                                exit(1);
                        }
                }

                memset(key->key, 0, NG_HCI_KEY_SIZE);

                len = strlen(cp) / 2;
                if (len > NG_HCI_KEY_SIZE)
                        len = NG_HCI_KEY_SIZE;

                for (i = 0; i < len; i ++)
                        key->key[i] = hexa2int8(cp + 2*i);

                syslog(LOG_DEBUG, "Restored link key for the entry, " \
                                "remote bdaddr %s, name '%s'",
                                bt_ntoa(&key->bdaddr, NULL),
                                (key->name != NULL)? key->name : "No name");
        }

        fclose(f);

        return (0);
}

/* Dump keys file */
int
dump_keys_file(void)
{
        link_key_p      key = NULL;
        char            tmp[PATH_MAX], buf[HCSECD_BUFFER_SIZE];
        int             f;

        snprintf(tmp, sizeof(tmp), "%s.tmp", HCSECD_KEYSFILE);
        if ((f = open(tmp, O_RDWR|O_CREAT|O_TRUNC|O_EXCL, 0600)) < 0) {
                syslog(LOG_ERR, "Could not create temp keys file %s. %s (%d)\n",
                                tmp, strerror(errno), errno);
                return (-1);
        }

        LIST_FOREACH(key, &link_keys, next) {
                if (key->key == NULL)
                        continue;

                snprintf(buf, sizeof(buf),
"%s %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n",
                        bt_ntoa(&key->bdaddr, NULL),
                        key->key[0],  key->key[1],  key->key[2],  key->key[3],
                        key->key[4],  key->key[5],  key->key[6],  key->key[7],
                        key->key[8],  key->key[9],  key->key[10], key->key[11],
                        key->key[12], key->key[13], key->key[14], key->key[15]);

                if (write(f, buf, strlen(buf)) < 0) {
                        syslog(LOG_ERR, "Could not write temp keys file. " \
                                        "%s (%d)\n", strerror(errno), errno);
                        break;
                }
        }

        close(f);

        if (rename(tmp, HCSECD_KEYSFILE) < 0) {
                syslog(LOG_ERR, "Could not rename(%s, %s). %s (%d)\n",
                                tmp, HCSECD_KEYSFILE, strerror(errno), errno);
                unlink(tmp);
                return (-1);
        }

        return (0);
}

/* Free key entry */
static void
free_key(link_key_p key)
{
        if (key->name != NULL)
                free(key->name);
        if (key->key != NULL)
                free(key->key);
        if (key->pin != NULL)
                free(key->pin);

        memset(key, 0, sizeof(*key));
        free(key);
}

/* Convert hex ASCII to int4 */
static int
hexa2int4(char *a)
{
        if ('0' <= *a && *a <= '9')
                return (*a - '0');

        if ('A' <= *a && *a <= 'F')
                return (*a - 'A' + 0xa);

        if ('a' <= *a && *a <= 'f')
                return (*a - 'a' + 0xa);

        syslog(LOG_ERR, "Invalid hex character: '%c' (%#x)", *a, *a);
        exit(1);
}

/* Convert hex ASCII to int8 */
static int
hexa2int8(char *a)
{
        return ((hexa2int4(a) << 4) | hexa2int4(a + 1));
}