blob: 2d1d5686a2b108244464881373e5816d49cd92b2 [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 <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>
#include "glib-ectomy.h"
#include "hcid.h"
#include "lib.h"
struct hcid_opts hcid;
struct device_opts default_device;
struct device_opts *parser_device;
static struct device_list *device_list = NULL;
static GMainLoop *event_loop;
gboolean io_stack_event(GIOChannel *chan, GIOCondition cond, gpointer data);
gboolean io_security_event(GIOChannel *chan, GIOCondition cond, gpointer data);
static void usage(void)
{
printf("hcid - HCI daemon ver %s\n", VERSION);
printf("Usage: \n");
printf("\thcid [-n not_daemon] [-f config file]\n");
}
static inline void init_device_defaults(struct device_opts *device_opts)
{
memset(device_opts, 0, sizeof(*device_opts));
device_opts->scan = SCAN_PAGE | SCAN_INQUIRY;
}
struct device_opts *alloc_device_opts(char *ref)
{
struct device_list *device;
device = malloc(sizeof(struct device_list));
if (!device) {
syslog(LOG_INFO, "Can't allocate devlist opts buffer. %s(%d)",
strerror(errno), errno);
exit(1);
}
device->ref = ref;
device->next = device_list;
device_list = device;
init_device_defaults(&device->opts);
return &device->opts;
}
static void free_device_opts(void)
{
struct device_list *device, *next;
if (default_device.name) {
free(default_device.name);
default_device.name = NULL;
}
for (device = device_list; device; device = next) {
free(device->ref);
if (device->opts.name)
free(device->opts.name);
next = device->next;
free(device);
}
device_list = NULL;
}
static inline struct device_opts *find_device_opts(char *ref)
{
struct device_list *device;
for (device = device_list; device; device = device->next)
if (!strcmp(ref, device->ref))
return &device->opts;
return NULL;
}
static struct device_opts *get_device_opts(int sock, int hdev)
{
struct device_opts *device_opts = NULL;
struct hci_dev_info di;
/* First try to get BD_ADDR based settings ... */
di.dev_id = hdev;
if (!ioctl(sock, HCIGETDEVINFO, (void *) &di)) {
char addr[18];
ba2str(&di.bdaddr, addr);
device_opts = find_device_opts(addr);
}
/* ... then try HCI based settings ... */
if (!device_opts) {
char ref[8];
snprintf(ref, sizeof(ref) - 1, "hci%d", hdev);
device_opts = find_device_opts(ref);
}
/* ... and last use the default settings. */
if (!device_opts)
device_opts = &default_device;
return device_opts;
}
static void configure_device(int hdev)
{
struct device_opts *device_opts;
struct hci_dev_req dr;
int s;
/* Do configuration in the separate process */
switch (fork()) {
case 0:
break;
case -1:
syslog(LOG_ERR, "Fork failed. Can't init device hci%d. %s(%d)\n",
hdev, strerror(errno), errno);
default:
return;
}
set_title("hci%d config", hdev);
if ((s = hci_open_dev(hdev)) < 0) {
syslog(LOG_ERR, "Can't open device hci%d. %s(%d)\n", hdev, strerror(errno), errno);
exit(1);
}
dr.dev_id = hdev;
device_opts = get_device_opts(s, hdev);
/* Set scan mode */
dr.dev_opt = device_opts->scan;
if (ioctl(s, HCISETSCAN, (unsigned long) &dr) < 0) {
syslog(LOG_ERR, "Can't set scan mode on hci%d. %s(%d)\n",
hdev, strerror(errno), errno);
}
/* Set authentication */
if (device_opts->auth)
dr.dev_opt = AUTH_ENABLED;
else
dr.dev_opt = AUTH_DISABLED;
if (ioctl(s, HCISETAUTH, (unsigned long) &dr) < 0) {
syslog(LOG_ERR, "Can't set auth on hci%d. %s(%d)\n",
hdev, strerror(errno), errno);
}
/* Set encryption */
if (device_opts->encrypt)
dr.dev_opt = ENCRYPT_P2P;
else
dr.dev_opt = ENCRYPT_DISABLED;
if (ioctl(s, HCISETENCRYPT, (unsigned long) &dr) < 0) {
syslog(LOG_ERR, "Can't set encrypt on hci%d. %s(%d)\n",
hdev, strerror(errno), errno);
}
/* Set device class */
if (device_opts->class) {
uint32_t class = htobl(device_opts->class);
write_class_of_dev_cp cp;
memcpy(cp.dev_class, &class, 3);
hci_send_cmd(s, OGF_HOST_CTL, OCF_WRITE_CLASS_OF_DEV,
WRITE_CLASS_OF_DEV_CP_SIZE, (void *) &cp);
}
/* Set device name */
if (device_opts->name) {
change_local_name_cp cp;
expand_name(cp.name, device_opts->name, hdev);
hci_send_cmd(s, OGF_HOST_CTL, OCF_CHANGE_LOCAL_NAME,
CHANGE_LOCAL_NAME_CP_SIZE, (void *) &cp);
}
exit(0);
}
static void init_device(int hdev)
{
struct device_opts *device_opts;
struct hci_dev_req dr;
int s;
/* Do initialization in the separate process */
switch (fork()) {
case 0:
break;
case -1:
syslog(LOG_ERR, "Fork failed. Can't init device hci%d. %s(%d)\n",
hdev, strerror(errno), errno);
default:
return;
}
set_title("hci%d init", hdev);
if ((s = hci_open_dev(hdev)) < 0) {
syslog(LOG_ERR, "Can't open device hci%d. %s(%d)\n", hdev, strerror(errno), errno);
exit(1);
}
/* Start HCI device */
if (ioctl(s, HCIDEVUP, hdev) < 0 && errno != EALREADY) {
syslog(LOG_ERR, "Can't init device hci%d. %s(%d)\n", hdev,
strerror(errno), errno);
exit(1);
}
dr.dev_id = hdev;
device_opts = get_device_opts(s, hdev);
/* Set packet type */
if (device_opts->pkt_type) {
dr.dev_opt = device_opts->pkt_type;
if (ioctl(s, HCISETPTYPE, (unsigned long) &dr) < 0) {
syslog(LOG_ERR, "Can't set packet type on hci%d. %s(%d)\n",
hdev, strerror(errno), errno);
}
}
/* Set link mode */
if (device_opts->link_mode) {
dr.dev_opt = device_opts->link_mode;
if (ioctl(s, HCISETLINKMODE, (unsigned long) &dr) < 0) {
syslog(LOG_ERR, "Can't set link mode on hci%d. %s(%d)\n",
hdev, strerror(errno), errno);
}
}
/* Set link policy */
if (device_opts->link_policy) {
dr.dev_opt = device_opts->link_policy;
if (ioctl(s, HCISETLINKPOL, (unsigned long) &dr) < 0) {
syslog(LOG_ERR, "Can't set link policy on hci%d. %s(%d)\n",
hdev, strerror(errno), errno);
}
}
exit(0);
}
static void init_all_devices(int ctl)
{
struct hci_dev_list_req *dl;
struct hci_dev_req *dr;
int i;
if (!(dl = malloc(HCI_MAX_DEV * sizeof(struct hci_dev_req) + sizeof(uint16_t)))) {
syslog(LOG_INFO, "Can't allocate devlist buffer. %s(%d)",
strerror(errno), errno);
exit(1);
}
dl->dev_num = HCI_MAX_DEV;
dr = dl->dev_req;
if (ioctl(ctl, HCIGETDEVLIST, (void *) dl) < 0) {
syslog(LOG_INFO, "Can't get device list. %s(%d)",
strerror(errno), errno);
exit(1);
}
for (i = 0; i < dl->dev_num; i++, dr++) {
if (hcid.auto_init)
init_device(dr->dev_id);
if (hcid.auto_init && hci_test_bit(HCI_UP, &dr->dev_opt))
configure_device(dr->dev_id);
if (hcid.security && hci_test_bit(HCI_UP, &dr->dev_opt))
start_security_manager(dr->dev_id);
}
free(dl);
}
static void init_defaults(void)
{
hcid.auto_init = 0;
hcid.security = 0;
init_device_defaults(&default_device);
}
static void sig_usr1(int sig)
{
toggle_pairing(0);
}
static void sig_usr2(int sig)
{
toggle_pairing(1);
}
static void sig_term(int sig)
{
g_main_quit(event_loop);
}
static void sig_hup(int sig)
{
syslog(LOG_INFO, "Reloading config file");
init_defaults();
if (read_config(hcid.config_file) < 0)
syslog(LOG_ERR, "Config reload failed");
init_security_data();
init_all_devices(hcid.sock);
}
static inline void device_event(GIOChannel *chan, evt_stack_internal *si)
{
evt_si_device *sd = (void *) &si->data;
switch (sd->event) {
case HCI_DEV_REG:
syslog(LOG_INFO, "HCI dev %d registered", sd->dev_id);
if (hcid.auto_init)
init_device(sd->dev_id);
break;
case HCI_DEV_UNREG:
syslog(LOG_INFO, "HCI dev %d unregistered", sd->dev_id);
break;
case HCI_DEV_UP:
syslog(LOG_INFO, "HCI dev %d up", sd->dev_id);
if (hcid.auto_init)
configure_device(sd->dev_id);
if (hcid.security)
start_security_manager(sd->dev_id);
break;
case HCI_DEV_DOWN:
syslog(LOG_INFO, "HCI dev %d down", sd->dev_id);
if (hcid.security)
stop_security_manager(sd->dev_id);
break;
}
}
gboolean io_stack_event(GIOChannel *chan, GIOCondition cond, gpointer data)
{
unsigned char buf[HCI_MAX_FRAME_SIZE], *ptr;
evt_stack_internal *si;
hci_event_hdr *eh;
int type;
size_t len;
GIOError err;
ptr = buf;
if ((err = g_io_channel_read(chan, buf, sizeof(buf), &len))) {
if (err == G_IO_ERROR_AGAIN)
return TRUE;
syslog(LOG_ERR, "Read from control socket failed. %s(%d)",
strerror(errno), errno);
g_main_quit(event_loop);
return FALSE;
}
type = *ptr++;
if (type != HCI_EVENT_PKT)
return TRUE;
eh = (hci_event_hdr *) ptr;
if (eh->evt != EVT_STACK_INTERNAL)
return TRUE;
ptr += HCI_EVENT_HDR_SIZE;
si = (evt_stack_internal *) ptr;
switch (si->type) {
case EVT_SI_DEVICE:
device_event(chan, si);
break;
}
return TRUE;
}
extern int optind, opterr, optopt;
extern char *optarg;
int main(int argc, char *argv[], char *env[])
{
int daemon, dofork, opt, fd;
struct sockaddr_hci addr;
struct hci_filter flt;
struct sigaction sa;
GIOChannel *ctl_io;
daemon = 1; dofork = 1;
/* Default HCId settings */
hcid.config_file = HCID_CONFIG_FILE;
hcid.host_name = get_host_name();
hcid.security = HCID_SEC_AUTO;
hcid.pairing = HCID_PAIRING_MULTI;
hcid.pin_file = strdup(HCID_PIN_FILE);
hcid.pin_helper = strdup(HCID_PIN_HELPER);
hcid.key_file = strdup(HCID_KEY_FILE);
init_defaults();
while ((opt = getopt(argc, argv, "f:n")) != EOF) {
switch (opt) {
case 'n':
daemon = 0;
break;
case 'f':
hcid.config_file = strdup(optarg);
break;
default:
usage();
exit(1);
}
}
if (daemon) {
if (dofork && fork())
exit(0);
/* Direct stdin,stdout,stderr to '/dev/null' */
fd = open("/dev/null", O_RDWR);
dup2(fd, 0); dup2(fd, 1); dup2(fd, 2);
close(fd);
setsid();
chdir("/");
}
umask(0077);
init_title(argc, argv, env, "hcid: ");
set_title("initializing");
/* Start logging to syslog and stderr */
openlog("hcid", LOG_PID | LOG_NDELAY | LOG_PERROR, LOG_DAEMON);
syslog(LOG_INFO, "HCI daemon ver %s started", VERSION);
memset(&sa, 0, sizeof(sa));
sa.sa_flags = SA_NOCLDSTOP;
sa.sa_handler = sig_term;
sigaction(SIGTERM, &sa, NULL);
sigaction(SIGINT, &sa, NULL);
sa.sa_handler = sig_hup;
sigaction(SIGHUP, &sa, NULL);
sa.sa_handler = sig_usr1;
sigaction(SIGUSR1, &sa, NULL);
sa.sa_handler = sig_usr2;
sigaction(SIGUSR2, &sa, NULL);
sa.sa_handler = SIG_IGN;
sigaction(SIGCHLD, &sa, NULL);
sigaction(SIGPIPE, &sa, NULL);
/* Create and bind HCI socket */
if ((hcid.sock = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI)) < 0) {
syslog(LOG_ERR, "Can't open HCI socket. %s(%d)", strerror(errno), errno);
exit(1);
}
/* Set filter */
hci_filter_clear(&flt);
hci_filter_set_ptype(HCI_EVENT_PKT, &flt);
hci_filter_set_event(EVT_STACK_INTERNAL, &flt);
if (setsockopt(hcid.sock, SOL_HCI, HCI_FILTER, &flt, sizeof(flt)) < 0) {
syslog(LOG_ERR, "Can't set filter. %s(%d)", strerror(errno), errno);
exit(1);
}
addr.hci_family = AF_BLUETOOTH;
addr.hci_dev = HCI_DEV_NONE;
if (bind(hcid.sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
syslog(LOG_ERR, "Can't bind HCI socket. %s(%d)\n", strerror(errno), errno);
exit(1);
}
if (read_config(hcid.config_file) < 0)
syslog(LOG_ERR, "Config load failed");
#ifdef ENABLE_DBUS
if (hcid_dbus_init() == FALSE && hcid.dbus_pin_helper) {
syslog(LOG_ERR, "Unable to get on D-BUS");
exit(1);
}
#else
if (hcid.dbus_pin_helper) {
syslog(LOG_ERR, "D-BUS not configured in this build of hcid");
exit(1);
}
#endif
init_security_data();
/* Create event loop */
event_loop = g_main_new(FALSE);
/* Initialize already connected devices */
init_all_devices(hcid.sock);
set_title("processing events");
ctl_io = g_io_channel_unix_new(hcid.sock);
g_io_add_watch(ctl_io, G_IO_IN, io_stack_event, NULL);
/* Start event processor */
g_main_run(event_loop);
free_device_opts();
syslog(LOG_INFO, "Exit.");
return 0;
}