blob: f87fca5fa58a3f568c9c1b8242bc21ad6af73b59 [file] [log] [blame]
/*
BlueZ - Bluetooth protocol stack for Linux
Copyright (C) 2000-2001 Qualcomm Incorporated
Written 2000,2001 by Maxim Krasnyansky <maxk@qualcomm.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation;
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY CLAIM,
OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER
RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
USE OR PERFORMANCE OF THIS SOFTWARE.
ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, COPYRIGHTS,
TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS SOFTWARE IS DISCLAIMED.
*/
/*
* $Id$
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <fcntl.h>
#include <syslog.h>
#include <errno.h>
#include <time.h>
#include <fcntl.h>
#include <time.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <asm/types.h>
#include <bluetooth.h>
#include <hci.h>
#include <hci_lib.h>
#include <glib.h>
#include "hcid.h"
#include "lib.h"
static GIOChannel *io_chan[HCI_MAX_DEV];
void save_link_keys(void)
{
int n, f;
syslog(LOG_INFO, "Saving link key database");
umask(0077);
if (!(f = open(hcid.key_file, O_WRONLY | O_CREAT | O_TRUNC, 0))) {
syslog(LOG_ERR, "Can't save key database %s. %s(%d)",
hcid.key_file, strerror(errno), errno);
return;
}
for (n = 0; n < hcid.key_num; n++) {
if (!hcid.link_key[n])
continue;
if (write_n(f, hcid.link_key[n], sizeof(struct link_key)) < 0)
break;
}
close(f);
}
void flush_link_keys(void)
{
int n;
syslog(LOG_INFO, "Flushing link key database");
for (n=0; n < hcid.key_num; n++) {
if (hcid.link_key[n]) {
free(hcid.link_key[n]);
hcid.link_key[n] = NULL;
}
}
}
int read_link_keys(void)
{
int f, n = 0;
if (!(f = open(hcid.key_file, O_RDONLY))) {
syslog(LOG_ERR, "Can't open key database %s. %s(%d)",
hcid.key_file, strerror(errno), errno);
return -1;
}
while (n < hcid.key_num) {
struct link_key *key;
int r;
key = malloc(sizeof(*key));
if (!key)
continue;
r = read_n(f, key, sizeof(*key));
if (r <= 0) {
free(key);
break;
}
hcid.link_key[n++] = key;
}
close(f);
return n;
}
int read_pin_code(void)
{
char buf[17];
FILE *f;
int len;
if (!(f = fopen(hcid.pin_file, "r"))) {
syslog(LOG_ERR, "Can't open PIN file %s. %s(%d)",
hcid.pin_file, strerror(errno), errno);
return -1;
}
if (fgets(buf, sizeof(buf), f)) {
strtok(buf, "\n\r");
len = strlen(buf);
memcpy(hcid.pin_code, buf, len);
hcid.pin_len = len;
} else {
syslog(LOG_ERR, "Can't read PIN file %s. %s(%d)",
hcid.pin_file, strerror(errno), errno);
len = -1;
}
fclose(f);
return len;
}
/*
PIN helper is an external app that asks user for a PIN. It can
implement its own PIN code generation policy and methods like
PIN look up in some database, etc.
HCId expects following output from PIN helper:
PIN:12345678 - PIN code
ERR - No PIN available
*/
static void call_pin_helper(int dev, struct hci_conn_info *ci)
{
pin_code_reply_cp pr;
char str[255], *pin, name[20];
bdaddr_t ba;
FILE *pipe;
int len;
/* Run PIN helper in the separate process */
switch (fork()) {
case 0:
break;
case -1:
syslog(LOG_ERR, "Can't fork PIN helper. %s(%d)",
strerror(errno), errno);
default:
return;
}
if (access(hcid.pin_helper, R_OK | X_OK)) {
syslog(LOG_ERR, "Can't exec PIN helper %s. %s(%d)",
hcid.pin_helper, strerror(errno), errno);
goto reject;
}
name[0] = 0;
//hci_remote_name(dev, &ci->bdaddr, sizeof(name), name, 0);
baswap(&ba, &ci->bdaddr);
sprintf(str, "%s %s %s \'%s\'", hcid.pin_helper,
ci->out ? "out" : "in",
batostr(&ba), name);
setenv("PATH", "/bin:/usr/bin:/usr/local/bin", 1);
pipe = popen(str, "r");
if (!pipe) {
syslog(LOG_ERR, "Can't exec PIN helper. %s(%d)", strerror(errno), errno);
goto reject;
}
pin = fgets(str, sizeof(str), pipe);
pclose(pipe);
if (!pin || strlen(pin) < 5)
goto reject;
strtok(pin, "\n\r");
if (strncmp("PIN:", pin, 4))
goto reject;
pin += 4;
len = strlen(pin);
memset(&pr, 0, sizeof(pr));
bacpy(&pr.bdaddr, &ci->bdaddr);
memcpy(pr.pin_code, pin, len);
pr.pin_len = len;
hci_send_cmd(dev, OGF_LINK_CTL, OCF_PIN_CODE_REPLY,
PIN_CODE_REPLY_CP_SIZE, &pr);
exit(0);
reject:
hci_send_cmd(dev, OGF_LINK_CTL, OCF_PIN_CODE_NEG_REPLY, 6, &ci->bdaddr);
exit(0);
}
static void link_key_request(int dev, bdaddr_t *sba, bdaddr_t *dba)
{
struct link_key *key = NULL;
int n;
/* Find the key */
for (n=0; n < hcid.key_num; n++) {
if (!hcid.link_key[n])
continue;
if (!bacmp(&hcid.link_key[n]->sba, sba) &&
!bacmp(&hcid.link_key[n]->dba, dba)) {
key = hcid.link_key[n];
break;
}
}
if (key) {
/* Link key found */
link_key_reply_cp lr;
memcpy(lr.link_key, key->key, 16);
bacpy(&lr.bdaddr, dba);
hci_send_cmd(dev, OGF_LINK_CTL, OCF_LINK_KEY_REPLY,
LINK_KEY_REPLY_CP_SIZE, &lr);
key->time = time(0);
} else {
/* Link key not found */
hci_send_cmd(dev, OGF_LINK_CTL, OCF_LINK_KEY_NEG_REPLY, 6, dba);
}
}
static void pin_code_request(int dev, bdaddr_t *ba)
{
struct hci_conn_info_req *cr;
struct hci_conn_info *ci;
cr = malloc(sizeof(*cr) + sizeof(*ci));
if (!cr)
return;
bacpy(&cr->bdaddr, ba);
cr->type = ACL_LINK;
if (ioctl(dev, HCIGETCONNINFO, (unsigned long) cr) < 0) {
syslog(LOG_ERR, "Can't get conn info %s(%d)",
strerror(errno), errno);
/* Reject PIN */
hci_send_cmd(dev, OGF_LINK_CTL, OCF_PIN_CODE_NEG_REPLY, 6, ba);
free(cr);
return;
}
ci = cr->conn_info;
if (hcid.security == HCID_SEC_AUTO) {
if (!ci->out) {
/* Incomming connection */
pin_code_reply_cp pr;
memset(&pr, 0, sizeof(pr));
bacpy(&pr.bdaddr, ba);
memcpy(pr.pin_code, hcid.pin_code, hcid.pin_len);
pr.pin_len = hcid.pin_len;
hci_send_cmd(dev, OGF_LINK_CTL, OCF_PIN_CODE_REPLY,
PIN_CODE_REPLY_CP_SIZE, &pr);
} else {
/* Outgoing connection */
/* Let PIN helper handle that */
call_pin_helper(dev, ci);
}
} else {
/* Let PIN helper handle that */
call_pin_helper(dev, ci);
}
free(cr);
}
static void link_key_notify(int dev, bdaddr_t *sba, void *ptr)
{
evt_link_key_notify *evt = ptr;
bdaddr_t *dba = &evt->bdaddr;
struct link_key *key;
time_t tm, td, ot;
int n, k = -1, ek = -1;
tm = time(0); ot = HCID_KEY_TTL;
/* Find an empty slot or the oldest key */
for (n=0; n < hcid.key_num; n++) {
key = hcid.link_key[n];
if (!key || (!bacmp(&key->sba, sba) && !bacmp(&key->dba, dba))) {
k = n;
break;
}
td = tm - key->time;
if (td > ot) {
ot = td;
ek = n;
}
}
if (k == -1 && ek != -1)
k = ek;
if (k != -1) {
char sa[40], da[40];
/* Update link key */
key = hcid.link_key[k];
if (!key && !(key = malloc(sizeof(*key)))) {
syslog(LOG_ERR, "Can't allocate link key memory. %s(%d)",
strerror(errno), errno);
return;
}
ba2str(sba, sa); ba2str(dba, da);
syslog(LOG_INFO, "Storing link key %s %s", sa, da);
bacpy(&key->sba, sba);
bacpy(&key->dba, dba);
memcpy(key->key, evt->link_key, 16);
key->type = evt->key_type;
key->time = tm;
hcid.link_key[k] = key;
} else
syslog(LOG_ERR, "No slot available for a link key.");
}
gboolean io_security_event(GIOChannel *chan, GIOCondition cond, gpointer data)
{
char buf[HCI_MAX_EVENT_SIZE], *ptr = buf;
struct hci_dev_info *di = (void *) data;
int len, type, dev;
hci_event_hdr *eh;
GIOError err;
if (cond & G_IO_NVAL) {
free(data);
return FALSE;
}
if (cond & (G_IO_HUP | G_IO_ERR)) {
g_io_channel_close(chan);
free(data);
return FALSE;
}
if ((err = g_io_channel_read(chan, buf, sizeof(buf), &len))) {
if (err == G_IO_ERROR_AGAIN)
return TRUE;
g_io_channel_close(chan);
return FALSE;
}
type = *ptr++;
if (type != HCI_EVENT_PKT)
return TRUE;
eh = (hci_event_hdr *) ptr;
ptr += HCI_EVENT_HDR_SIZE;
dev = g_io_channel_unix_get_fd(chan);
switch (eh->evt) {
case EVT_PIN_CODE_REQ:
pin_code_request(dev, (bdaddr_t *) ptr);
break;
case EVT_LINK_KEY_REQ:
link_key_request(dev, &di->bdaddr, (bdaddr_t *) ptr);
break;
case EVT_LINK_KEY_NOTIFY:
link_key_notify(dev, &di->bdaddr, ptr);
break;
}
return TRUE;
}
int init_security_data(void)
{
void *buf;
buf = calloc(hcid.key_num, sizeof(void*));
if (!buf) {
syslog(LOG_ERR, "Can't allocate link key database. %s(%d)",
strerror(errno), errno);
return -1;
}
hcid.link_key = buf;
read_link_keys();
/* Set local PIN code */
if (hcid.security == HCID_SEC_AUTO) {
if (read_pin_code() < 0) {
strcpy(hcid.pin_code, "bluez");
hcid.pin_len = 5;
}
}
return 0;
}
void start_security_manager(int hdev)
{
GIOChannel *chan = io_chan[hdev];
struct hci_dev_info *di;
struct hci_filter flt;
int dev;
if (chan)
return;
syslog(LOG_INFO, "Starting security manager %d", hdev);
if (!hcid.link_key && init_security_data())
return;
if ((dev = hci_open_dev(hdev)) < 0) {
syslog(LOG_ERR, "Can't open device hci%d. %s(%d)",
hdev, strerror(errno), errno);
return;
}
/* Set filter */
hci_filter_clear(&flt);
hci_filter_set_ptype(HCI_EVENT_PKT, &flt);
hci_filter_set_event(EVT_PIN_CODE_REQ, &flt);
hci_filter_set_event(EVT_LINK_KEY_REQ, &flt);
hci_filter_set_event(EVT_LINK_KEY_NOTIFY, &flt);
if (setsockopt(dev, SOL_HCI, HCI_FILTER, &flt, sizeof(flt)) < 0) {
syslog(LOG_ERR, "Can't set filter on hci%d. %s(%d)",
hdev, strerror(errno), errno);
close(dev);
return;
}
di = malloc(sizeof(*di));
if (!di) {
syslog(LOG_ERR, "Can't allocate device info buffer. %s(%d)",
strerror(errno), errno);
close(dev);
return;
}
di->dev_id = hdev;
if (ioctl(dev, HCIGETDEVINFO, (void *)di)) {
syslog(LOG_ERR, "Can't get device info. %s(%d)",
strerror(errno), errno);
close(dev);
return;
}
chan = g_io_channel_unix_new(dev);
g_io_add_watch(chan, G_IO_IN | G_IO_NVAL | G_IO_HUP | G_IO_ERR,
io_security_event, (void *) di);
io_chan[hdev] = chan;
}
void stop_security_manager(int hdev)
{
GIOChannel *chan = io_chan[hdev];
if (!chan)
return;
syslog(LOG_INFO, "Stoping security manager %d", hdev);
close(g_io_channel_unix_get_fd(chan));
io_chan[hdev] = NULL;
}