/* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2000-2001 Qualcomm Incorporated * Copyright (C) 2002-2003 Maxim Krasnyansky * Copyright (C) 2002-2004 Marcel Holtmann * * * 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$ */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "glib-ectomy.h" #include "hcid.h" #include "lib.h" static GIOChannel *io_chan[HCI_MAX_DEV]; static int pairing; void toggle_pairing(int enable) { if (enable) pairing = hcid.pairing; else pairing = 0; syslog(LOG_INFO, "Pairing %s", pairing ? "enabled" : "disabled"); } /* Link Key handling */ /* This function is not reentrable */ static struct link_key *__get_link_key(int f, bdaddr_t *sba, bdaddr_t *dba) { static struct link_key k; struct link_key *key = NULL; int r; while ((r = read_n(f, &k, sizeof(k)))) { if (r < 0) { syslog(LOG_ERR, "Link key database read failed. %s(%d)", strerror(errno), errno); break; } if (!bacmp(&k.sba, sba) && !bacmp(&k.dba, dba)) { key = &k; break; } } return key; } static struct link_key *get_link_key(bdaddr_t *sba, bdaddr_t *dba) { struct link_key *key = NULL; int f; f = open(hcid.key_file, O_RDONLY); if (f >= 0) key = __get_link_key(f, sba, dba); else if (errno != ENOENT) syslog(LOG_ERR, "Link key database open failed. %s(%d)", strerror(errno), errno); close(f); return key; } static void link_key_request(int dev, bdaddr_t *sba, bdaddr_t *dba) { struct link_key *key = get_link_key(sba, dba); char sa[18], da[18]; ba2str(sba, sa); ba2str(dba, da); syslog(LOG_INFO, "link_key_request (sba=%s, dba=%s)\n", sa, da); 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 save_link_key(struct link_key *key) { struct link_key *exist; char sa[18], da[18]; int f, err; f = open(hcid.key_file, O_RDWR | O_CREAT, 0); if (f < 0) { syslog(LOG_ERR, "Link key database open failed. %s(%d)", strerror(errno), errno); return; } /* Check if key already exist */ exist = __get_link_key(f, &key->sba, &key->dba); err = 0; if (exist) { off_t o = lseek(f, 0, SEEK_CUR); err = lseek(f, o - sizeof(*key), SEEK_SET); } else err = fcntl(f, F_SETFL, O_APPEND); if (err < 0) { syslog(LOG_ERR, "Link key database seek failed. %s(%d)", strerror(errno), errno); goto failed; } if (write_n(f, key, sizeof(*key)) < 0) { syslog(LOG_ERR, "Link key database write failed. %s(%d)", strerror(errno), errno); } ba2str(&key->sba, sa); ba2str(&key->dba, da); syslog(LOG_INFO, "%s link key %s %s", exist ? "Replacing" : "Saving", sa, da); failed: close(f); } 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; char sa[18]; ba2str(sba, sa); syslog(LOG_INFO, "link_key_notify (sba=%s)\n", sa); memcpy(key.key, evt->link_key, 16); bacpy(&key.sba, sba); bacpy(&key.dba, dba); key.type = evt->key_type; key.time = time(0); save_link_key(&key); } /* PIN code handling */ 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; struct sigaction sa; char addr[18], str[255], *pin, name[20]; FILE *pipe; int ret, 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); ba2str(&ci->bdaddr, addr); sprintf(str, "%s %s %s \'%s\'", hcid.pin_helper, ci->out ? "out" : "in", addr, name); setenv("PATH", "/bin:/usr/bin:/usr/local/bin", 1); memset(&sa, 0, sizeof(sa)); sa.sa_flags = SA_NOCLDSTOP; sa.sa_handler = SIG_DFL; sigaction(SIGCHLD, &sa, NULL); 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); ret = pclose(pipe); if (!pin || strlen(pin) < 5) goto nopin; strtok(pin, "\n\r"); if (strncmp("PIN:", pin, 4)) goto nopin; 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); nopin: if (!pin || strncmp("ERR", pin, 3)) syslog(LOG_ERR, "PIN helper exited abnormally with code %d", ret); reject: hci_send_cmd(dev, OGF_LINK_CTL, OCF_PIN_CODE_NEG_REPLY, 6, &ci->bdaddr); exit(0); } static void request_pin(int dev, struct hci_conn_info *ci) { #ifdef ENABLE_DBUS if (hcid.dbus_pin_helper) { hcid_dbus_request_pin(dev, ci); return; } #endif call_pin_helper(dev, ci); } static void pin_code_request(int dev, bdaddr_t *sba, bdaddr_t *dba) { struct hci_conn_info_req *cr; struct hci_conn_info *ci; char sa[18], da[18]; ba2str(sba, sa); ba2str(dba, da); syslog(LOG_INFO, "pin_code_request (sba=%s, dba=%s)\n", sa, da); cr = malloc(sizeof(*cr) + sizeof(*ci)); if (!cr) return; bacpy(&cr->bdaddr, dba); 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); goto reject; } ci = cr->conn_info; if (pairing == HCID_PAIRING_ONCE) { struct link_key *key = get_link_key(sba, dba); if (key) { ba2str(dba, da); syslog(LOG_WARNING, "PIN code request for already paired device %s", da); goto reject; } } else if (pairing == HCID_PAIRING_NONE) goto reject; if (hcid.security == HCID_SEC_AUTO) { if (!ci->out) { /* Incomming connection */ pin_code_reply_cp pr; memset(&pr, 0, sizeof(pr)); bacpy(&pr.bdaddr, dba); 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 */ request_pin(dev, ci); } } else { /* Let PIN helper handle that */ request_pin(dev, ci); } free(cr); return; reject: hci_send_cmd(dev, OGF_LINK_CTL, OCF_PIN_CODE_NEG_REPLY, 6, dba); free(cr); return; } gboolean io_security_event(GIOChannel *chan, GIOCondition cond, gpointer data) { unsigned char buf[HCI_MAX_EVENT_SIZE], *ptr = buf; struct hci_dev_info *di = (void *) data; int type, dev; size_t len; hci_event_hdr *eh; GIOError err; if (cond & (G_IO_NVAL | 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); free(data); 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); ioctl(dev, HCIGETDEVINFO, (void *) di); if (hci_test_bit(HCI_SECMGR, &di->flags)) return TRUE; switch (eh->evt) { case EVT_PIN_CODE_REQ: pin_code_request(dev, &di->bdaddr, (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; } 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 ((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); /* this is a bit sneaky. closing the fd will cause the event loop to call us right back with G_IO_NVAL set, at which point we will see it and clean things up */ close(g_io_channel_unix_get_fd(chan)); io_chan[hdev] = NULL; } void init_security_data(void) { /* Set local PIN code */ if (read_pin_code() < 0) { strcpy(hcid.pin_code, "BlueZ"); hcid.pin_len = 5; } pairing = hcid.pairing; return; }