/*-
 * hcsecd.c
 *
 * 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: hcsecd.c,v 1.6 2003/08/18 19:19:55 max Exp $
 */

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

static int	done = 0;

static int process_pin_code_request_event
	(int sock, struct sockaddr_hci *addr, bdaddr_p bdaddr);
static int process_link_key_request_event
	(int sock, struct sockaddr_hci *addr, bdaddr_p bdaddr);
static int send_pin_code_reply
	(int sock, struct sockaddr_hci *addr, bdaddr_p bdaddr, char const *pin);
static int send_link_key_reply
	(int sock, struct sockaddr_hci *addr, bdaddr_p bdaddr, uint8_t *key);
static int process_link_key_notification_event
	(int sock, struct sockaddr_hci *addr, ng_hci_link_key_notification_ep *ep);
static void sighup
	(int s);
static void sigint
	(int s);
static void usage
	(void);

/* Main */
int
main(int argc, char *argv[])
{
	int					 n, detach, sock;
	socklen_t				 size;
	struct sigaction			 sa;
	struct sockaddr_hci			 addr;
	struct ng_btsocket_hci_raw_filter	 filter;
	char					 buffer[HCSECD_BUFFER_SIZE];
	ng_hci_event_pkt_t			*event = NULL;

	detach = 1;

	while ((n = getopt(argc, argv, "df:h")) != -1) {
		switch (n) {
		case 'd':
			detach = 0;
			break;

		case 'f':
			config_file = optarg;
			break;

		case 'h':
		default:
			usage();
			/* NOT REACHED */
		}
	}

	if (config_file == NULL)
		usage();
		/* NOT REACHED */

	if (getuid() != 0)
		errx(1, "** ERROR: You should run %s as privileged user!",
			HCSECD_IDENT);

	/* Set signal handlers */
	memset(&sa, 0, sizeof(sa));
	sa.sa_handler = sigint;
	sa.sa_flags = SA_NOCLDWAIT;
	if (sigaction(SIGINT, &sa, NULL) < 0)
		err(1, "Could not sigaction(SIGINT)");
	if (sigaction(SIGTERM, &sa, NULL) < 0)
		err(1, "Could not sigaction(SIGINT)");

	memset(&sa, 0, sizeof(sa));
	sa.sa_handler = sighup;
	if (sigaction(SIGHUP, &sa, NULL) < 0)
		err(1, "Could not sigaction(SIGHUP)");

	/* Open socket and set filter */
	sock = socket(PF_BLUETOOTH, SOCK_RAW, BLUETOOTH_PROTO_HCI);
	if (sock < 0)
		err(1, "Could not create HCI socket");

	memset(&filter, 0, sizeof(filter));
	bit_set(filter.event_mask, NG_HCI_EVENT_PIN_CODE_REQ - 1);
	bit_set(filter.event_mask, NG_HCI_EVENT_LINK_KEY_REQ - 1);
	bit_set(filter.event_mask, NG_HCI_EVENT_LINK_KEY_NOTIFICATION - 1);

	if (setsockopt(sock, SOL_HCI_RAW, SO_HCI_RAW_FILTER,
			(void * const) &filter, sizeof(filter)) < 0)
		err(1, "Could not set HCI socket filter");

	if (detach && daemon(0, 0) < 0)
		err(1, "Could not daemon()ize");

	openlog(HCSECD_IDENT, LOG_NDELAY|LOG_PERROR|LOG_PID, LOG_DAEMON);

	read_config_file();
	read_keys_file();

	if (detach) {
		FILE	*pid = NULL;

		if ((pid = fopen(HCSECD_PIDFILE, "w")) == NULL) {
			syslog(LOG_ERR, "Could not create PID file %s. %s (%d)",
					HCSECD_PIDFILE, strerror(errno), errno);
			exit(1);
		}

		fprintf(pid, "%d", getpid());
		fclose(pid);
	}

	event = (ng_hci_event_pkt_t *) buffer;
	while (!done) {
		size = sizeof(addr);
		n = recvfrom(sock, buffer, sizeof(buffer), 0,
				(struct sockaddr *) &addr, &size);
		if (n < 0) {
			if (errno == EINTR)
				continue;

			syslog(LOG_ERR, "Could not receive from HCI socket. " \
					"%s (%d)", strerror(errno), errno);
			exit(1);
		}

		if (event->type != NG_HCI_EVENT_PKT) {
			syslog(LOG_ERR, "Received unexpected HCI packet, " \
					"type=%#x", event->type);
			continue;
		}

		switch (event->event) {
		case NG_HCI_EVENT_PIN_CODE_REQ:
			process_pin_code_request_event(sock, &addr,
							(bdaddr_p)(event + 1));
			break;

		case NG_HCI_EVENT_LINK_KEY_REQ:
			process_link_key_request_event(sock, &addr,
							(bdaddr_p)(event + 1));
			break;

		case NG_HCI_EVENT_LINK_KEY_NOTIFICATION:
			process_link_key_notification_event(sock, &addr,
				(ng_hci_link_key_notification_ep *)(event + 1));
			break;

		default:
			syslog(LOG_ERR, "Received unexpected HCI event, " \
					"event=%#x", event->event);
			break;
		}
	}

	if (detach)
		if (remove(HCSECD_PIDFILE) < 0)
			syslog(LOG_ERR, "Could not remove PID file %s. %s (%d)",
					HCSECD_PIDFILE, strerror(errno), errno);

	dump_keys_file();
	clean_config();
	closelog();
	close(sock);

	return (0);
}

/* Process PIN_Code_Request event */
static int
process_pin_code_request_event(int sock, struct sockaddr_hci *addr,
		bdaddr_p bdaddr)
{
	link_key_p	key = NULL;

	syslog(LOG_DEBUG, "Got PIN_Code_Request event from '%s', " \
			"remote bdaddr %s", addr->hci_node,
			bt_ntoa(bdaddr, NULL));

	if ((key = get_key(bdaddr, 0)) != NULL) {
		syslog(LOG_DEBUG, "Found matching entry, " \
				"remote bdaddr %s, name '%s', PIN code %s",
				bt_ntoa(&key->bdaddr, NULL),
				(key->name != NULL)? key->name : "No name",
				(key->pin != NULL)? "exists" : "doesn't exist");

		return (send_pin_code_reply(sock, addr, bdaddr, key->pin));
	}

	syslog(LOG_DEBUG, "Could not PIN code for remote bdaddr %s",
			bt_ntoa(bdaddr, NULL));

	return (send_pin_code_reply(sock, addr, bdaddr, NULL));
}

/* Process Link_Key_Request event */
static int
process_link_key_request_event(int sock, struct sockaddr_hci *addr,
		bdaddr_p bdaddr)
{
	link_key_p	key = NULL;

	syslog(LOG_DEBUG, "Got Link_Key_Request event from '%s', " \
			"remote bdaddr %s", addr->hci_node,
			bt_ntoa(bdaddr, NULL));

	if ((key = get_key(bdaddr, 0)) != NULL) {
		syslog(LOG_DEBUG, "Found matching entry, " \
				"remote bdaddr %s, name '%s', link key %s",
				bt_ntoa(&key->bdaddr, NULL),
				(key->name != NULL)? key->name : "No name",
				(key->key != NULL)? "exists" : "doesn't exist");

		return (send_link_key_reply(sock, addr, bdaddr, key->key));
	}

	syslog(LOG_DEBUG, "Could not find link key for remote bdaddr %s",
			bt_ntoa(bdaddr, NULL));

	return (send_link_key_reply(sock, addr, bdaddr, NULL));
}

/* Send PIN_Code_[Negative]_Reply */
static int
send_pin_code_reply(int sock, struct sockaddr_hci *addr, 
		bdaddr_p bdaddr, char const *pin)
{
	uint8_t			 buffer[HCSECD_BUFFER_SIZE];
	ng_hci_cmd_pkt_t	*cmd = NULL;

	memset(buffer, 0, sizeof(buffer));

	cmd = (ng_hci_cmd_pkt_t *) buffer;
	cmd->type = NG_HCI_CMD_PKT;

	if (pin != NULL) {
		ng_hci_pin_code_rep_cp	*cp = NULL;

		cmd->opcode = htole16(NG_HCI_OPCODE(NG_HCI_OGF_LINK_CONTROL,
						NG_HCI_OCF_PIN_CODE_REP));
		cmd->length = sizeof(*cp);

		cp = (ng_hci_pin_code_rep_cp *)(cmd + 1);
		memcpy(&cp->bdaddr, bdaddr, sizeof(cp->bdaddr));
		strncpy((char *) cp->pin, pin, sizeof(cp->pin));
		cp->pin_size = strlen((char const *) cp->pin);

		syslog(LOG_DEBUG, "Sending PIN_Code_Reply to '%s' " \
				"for remote bdaddr %s",
				addr->hci_node, bt_ntoa(bdaddr, NULL));
	} else {
		ng_hci_pin_code_neg_rep_cp	*cp = NULL;

		cmd->opcode = htole16(NG_HCI_OPCODE(NG_HCI_OGF_LINK_CONTROL,
						NG_HCI_OCF_PIN_CODE_NEG_REP));
		cmd->length = sizeof(*cp);

		cp = (ng_hci_pin_code_neg_rep_cp *)(cmd + 1);
		memcpy(&cp->bdaddr, bdaddr, sizeof(cp->bdaddr));

		syslog(LOG_DEBUG, "Sending PIN_Code_Negative_Reply to '%s' " \
				"for remote bdaddr %s",
				addr->hci_node, bt_ntoa(bdaddr, NULL));
	}

again:
	if (sendto(sock, buffer, sizeof(*cmd) + cmd->length, 0,
			(struct sockaddr *) addr, sizeof(*addr)) < 0) {
		if (errno == EINTR)
			goto again;

		syslog(LOG_ERR, "Could not send PIN code reply to '%s' " \
				"for remote bdaddr %s. %s (%d)",
				addr->hci_node, bt_ntoa(bdaddr, NULL),
				strerror(errno), errno);
		return (-1);
	}

	return (0);
}

/* Send Link_Key_[Negative]_Reply */
static int
send_link_key_reply(int sock, struct sockaddr_hci *addr, 
		bdaddr_p bdaddr, uint8_t *key)
{
	uint8_t			 buffer[HCSECD_BUFFER_SIZE];
	ng_hci_cmd_pkt_t	*cmd = NULL;

	memset(buffer, 0, sizeof(buffer));

	cmd = (ng_hci_cmd_pkt_t *) buffer;
	cmd->type = NG_HCI_CMD_PKT;

	if (key != NULL) {
		ng_hci_link_key_rep_cp	*cp = NULL;

		cmd->opcode = htole16(NG_HCI_OPCODE(NG_HCI_OGF_LINK_CONTROL,
						NG_HCI_OCF_LINK_KEY_REP));
		cmd->length = sizeof(*cp);

		cp = (ng_hci_link_key_rep_cp *)(cmd + 1);
		memcpy(&cp->bdaddr, bdaddr, sizeof(cp->bdaddr));
		memcpy(&cp->key, key, sizeof(cp->key));

		syslog(LOG_DEBUG, "Sending Link_Key_Reply to '%s' " \
				"for remote bdaddr %s",
				addr->hci_node, bt_ntoa(bdaddr, NULL));
	} else {
		ng_hci_link_key_neg_rep_cp	*cp = NULL;

		cmd->opcode = htole16(NG_HCI_OPCODE(NG_HCI_OGF_LINK_CONTROL,
						NG_HCI_OCF_LINK_KEY_NEG_REP));
		cmd->length = sizeof(*cp);

		cp = (ng_hci_link_key_neg_rep_cp *)(cmd + 1);
		memcpy(&cp->bdaddr, bdaddr, sizeof(cp->bdaddr));

		syslog(LOG_DEBUG, "Sending Link_Key_Negative_Reply to '%s' " \
				"for remote bdaddr %s",
				addr->hci_node, bt_ntoa(bdaddr, NULL));
	}

again:
	if (sendto(sock, buffer, sizeof(*cmd) + cmd->length, 0,
			(struct sockaddr *) addr, sizeof(*addr)) < 0) {
		if (errno == EINTR)
			goto again;

		syslog(LOG_ERR, "Could not send link key reply to '%s' " \
				"for remote bdaddr %s. %s (%d)",
				addr->hci_node, bt_ntoa(bdaddr, NULL),
				strerror(errno), errno);
		return (-1);
	}

	return (0);
}

/* Process Link_Key_Notification event */
static int
process_link_key_notification_event(int sock, struct sockaddr_hci *addr,
		ng_hci_link_key_notification_ep *ep)
{
	link_key_p	key = NULL;

	syslog(LOG_DEBUG, "Got Link_Key_Notification event from '%s', " \
			"remote bdaddr %s", addr->hci_node,
			bt_ntoa(&ep->bdaddr, NULL));

	if ((key = get_key(&ep->bdaddr, 1)) == NULL) {
		syslog(LOG_ERR, "Could not find entry for remote bdaddr %s",
				bt_ntoa(&ep->bdaddr, NULL));
		return (-1);
	}

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

	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);
		}
	}

	memcpy(key->key, &ep->key, NG_HCI_KEY_SIZE);

	return (0);
}

/* Signal handlers */
static void
sighup(int s)
{
	syslog(LOG_DEBUG, "Got SIGHUP (%d)", s);

	dump_keys_file();
	read_config_file();
	read_keys_file();
}

static void
sigint(int s)
{
	syslog(LOG_DEBUG, "Got signal %d, total number of signals %d",
			s, ++ done);
}

/* Display usage and exit */
static void
usage(void)
{
	fprintf(stderr,
"Usage: %s [-d] -f config_file [-h]\n" \
"Where:\n" \
"\t-d              do not detach from terminal\n" \
"\t-f config_file  use <config_file>\n" \
"\t-h              display this message\n", HCSECD_IDENT);

	exit(255);
}

