diff options
Diffstat (limited to 'hcid/security.c')
| -rw-r--r-- | hcid/security.c | 477 | 
1 files changed, 477 insertions, 0 deletions
| diff --git a/hcid/security.c b/hcid/security.c new file mode 100644 index 00000000..bca27805 --- /dev/null +++ b/hcid/security.c @@ -0,0 +1,477 @@ +/*  +	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; + +	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; +	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 = time(0); +	int n, k = -1; + +	/* Find a slot */ +	for (n=0; n < hcid.key_num; n++) { +		key = hcid.link_key[n]; +		if (!key || (!bacmp(&key->sba, sba) && !bacmp(&key->dba, dba)) || +				(tm - key->time) > HCID_KEY_TTL) { +			k = n; +			break; +		} +	} + +	if (k != -1) { +		/* Update link key */ +		key = hcid.link_key[k]; +		if (!key && !(key = malloc(sizeof(*key)))) +			return; + +		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; +	} +} + +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; +} | 
