diff options
author | Uwe Stuehler <uwe@cvs.openbsd.org> | 2008-11-24 23:34:43 +0000 |
---|---|---|
committer | Uwe Stuehler <uwe@cvs.openbsd.org> | 2008-11-24 23:34:43 +0000 |
commit | b76ea0f34f6d26d8711d402b22cc4aa21d8bac6c (patch) | |
tree | b8b93e3d3e731a3dd6da39f8b778856970f8b23f /usr.sbin/btd | |
parent | b6679679f13b731ba42b47d62e0c1c9c441021fe (diff) |
Bluetooth daemon and contrl utility, one for all, work in progress
Diffstat (limited to 'usr.sbin/btd')
-rw-r--r-- | usr.sbin/btd/Makefile | 14 | ||||
-rw-r--r-- | usr.sbin/btd/bt.c | 154 | ||||
-rw-r--r-- | usr.sbin/btd/btd.c | 243 | ||||
-rw-r--r-- | usr.sbin/btd/btd.h | 161 | ||||
-rw-r--r-- | usr.sbin/btd/conf.c | 123 | ||||
-rw-r--r-- | usr.sbin/btd/control.c | 208 | ||||
-rw-r--r-- | usr.sbin/btd/db.c | 203 | ||||
-rw-r--r-- | usr.sbin/btd/devinfo.c | 100 | ||||
-rw-r--r-- | usr.sbin/btd/hci.c | 498 | ||||
-rw-r--r-- | usr.sbin/btd/log.c | 175 | ||||
-rw-r--r-- | usr.sbin/btd/parse.y | 521 | ||||
-rw-r--r-- | usr.sbin/btd/sdp.c | 738 | ||||
-rw-r--r-- | usr.sbin/btd/util.c | 33 |
13 files changed, 3171 insertions, 0 deletions
diff --git a/usr.sbin/btd/Makefile b/usr.sbin/btd/Makefile new file mode 100644 index 00000000000..2fabc09fb19 --- /dev/null +++ b/usr.sbin/btd/Makefile @@ -0,0 +1,14 @@ +# $OpenBSD: Makefile,v 1.1 2008/11/24 23:34:41 uwe Exp $ + +PROG= btd +SRCS= bt.c btd.c conf.c control.c db.c devinfo.c \ + hci.c log.c sdp.c util.c +NOMAN= + +LDADD+= -levent -lbluetooth -lsdp -lusbhid +LDFLAGS+= -L/usr/local/lib +CPPFLAGS+= -I${.CURDIR} -I${.CURDIR}/../btctl -I/usr/local/include +COPTS+= -Wall -Werror +DEBUG= -g + +.include <bsd.prog.mk> diff --git a/usr.sbin/btd/bt.c b/usr.sbin/btd/bt.c new file mode 100644 index 00000000000..aac9cc66247 --- /dev/null +++ b/usr.sbin/btd/bt.c @@ -0,0 +1,154 @@ +/* $OpenBSD: bt.c,v 1.1 2008/11/24 23:34:41 uwe Exp $ */ + +/* + * Copyright (c) 2008 Uwe Stuehler <uwe@openbsd.org> + * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org> + * Copyright (c) 2004 Alexander Guy <alexander.guy@andern.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, 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. + */ + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/stat.h> + +#include <dev/bluetooth/btdev.h> + +#include <bluetooth.h> +#include <errno.h> +#include <event.h> +#include <fcntl.h> +#include <paths.h> +#include <signal.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <usbhid.h> + +#include "btd.h" + +void bt_sighdlr(int, short, void *); + +struct event ev_sigint; +struct event ev_sigterm; +struct event ev_siginfo; + +struct bufferevent *ev_priv; +void bt_priv_readcb(struct bufferevent *, void *); +void bt_priv_writecb(struct bufferevent *, void *); +void bt_priv_errorcb(struct bufferevent *, short, void *); + +void +bt_sighdlr(int sig, short ev, void *arg) +{ + switch (sig) { + case SIGINT: + case SIGTERM: + (void)event_loopexit(NULL); + break; + + case SIGINFO: + /* report status */ + break; + } +} + +pid_t +bt_main(int pipe_prnt[2], struct btd *env, struct passwd *pw) +{ + struct stat stb; + pid_t pid; + + switch (pid = fork()) { + case -1: + fatal("cannot fork"); + break; + case 0: + break; + default: + return pid; + } + + setproctitle("bt engine"); + + event_init(); + + hid_init(NULL); + + db_open(BTD_DB, &env->db); + + if (hci_init(env)) + fatalx("can't set up HCI listeners"); + + control_init(env); + + if (stat(pw->pw_dir, &stb) == -1) + fatal("stat"); + if (stb.st_uid != 0 || (stb.st_mode & (S_IWGRP|S_IWOTH)) != 0) + fatalx("bad privsep dir permissions"); + if (chroot(pw->pw_dir) == -1) + fatal("chroot"); + if (chdir("/") == -1) + fatal("chdir(\"/\")"); + + if (setgroups(1, &pw->pw_gid) || + setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) + fatal("can't drop privileges"); + + close(pipe_prnt[0]); + + if ((ev_priv = bufferevent_new(pipe_prnt[1], bt_priv_readcb, + bt_priv_writecb, bt_priv_errorcb, env)) == NULL) + fatalx("bufferevent_new ev_priv"); + + bufferevent_enable(ev_priv, EV_READ); + + signal_set(&ev_sigint, SIGINT, bt_sighdlr, NULL); + signal_set(&ev_sigterm, SIGTERM, bt_sighdlr, NULL); + signal_set(&ev_siginfo, SIGINFO, bt_sighdlr, NULL); + signal_add(&ev_sigint, NULL); + signal_add(&ev_sigterm, NULL); + signal_add(&ev_siginfo, NULL); + + signal(SIGPIPE, SIG_IGN); + signal(SIGHUP, SIG_IGN); + signal(SIGCHLD, SIG_DFL); + + log_info("bt engine ready"); + + (void)event_dispatch(); + + log_info("bt engine exiting"); + exit(0); +} + +void +bt_priv_readcb(struct bufferevent *ev, void *arg) +{ + log_debug(__func__); +} + +void +bt_priv_writecb(struct bufferevent *ev, void *arg) +{ + /* nothing to do */ + log_debug(__func__); +} + +void +bt_priv_errorcb(struct bufferevent *ev, short what, void *arg) +{ + log_warnx("priv pipe error, what=%#x", what); + exit(0); +} diff --git a/usr.sbin/btd/btd.c b/usr.sbin/btd/btd.c new file mode 100644 index 00000000000..f2c04fec282 --- /dev/null +++ b/usr.sbin/btd/btd.c @@ -0,0 +1,243 @@ +/* $OpenBSD: btd.c,v 1.1 2008/11/24 23:34:42 uwe Exp $ */ + +#include <sys/ioctl.h> +#include <sys/wait.h> + +#include <dev/bluetooth/btdev.h> + +#include <err.h> +#include <errno.h> +#include <event.h> +#include <fcntl.h> +#include <libgen.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "btd.h" + +void sighdlr(int, short, void *); +__dead void usage(void); +int check_child(pid_t, const char *); + +static const char *progname; +static int quit = 0; +static int reconfig = 0; +static int sigchld = 0; + +static struct event ev_sigchld; +static struct event ev_sighup; +static struct event ev_sigint; +static struct event ev_sigterm; +static struct bufferevent *ev_bt; + +static void readcb(struct bufferevent *, void *); +static void writecb(struct bufferevent *, void *); +static void errorcb(struct bufferevent *, short, void *); + +void +sighdlr(int sig, short what, void *arg) +{ + switch (sig) { + case SIGTERM: + case SIGINT: + quit = 1; + break; + case SIGCHLD: + sigchld = 1; + break; + case SIGHUP: + reconfig = 1; + break; + } +} + +__dead void +usage(void) +{ + fprintf(stderr, "usage: %s [-d]\n", progname); + exit(2); +} + +int +main(int argc, char *argv[]) +{ + struct btd env; + pid_t chld_pid, pid; + int pipe_chld[2]; + struct passwd *pw; + int ch; + + progname = basename(argv[0]); + bzero(&env, sizeof(env)); + TAILQ_INIT(&env.interfaces); + TAILQ_INIT(&env.devices); + + while ((ch = getopt(argc, argv, "d")) != -1) { + switch (ch) { + case 'd': + env.debug = 1; + break; + default: + usage(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + if (argc > 0) { + usage(); + /* NOTREACHED */ + } + + if (geteuid() != 0) + errx(1, "need root privileges"); + + if ((pw = getpwnam(BTD_USER)) == NULL) + errx(1, "unknown user %s", BTD_USER); + + endpwent(); + + log_init(env.debug); + + if (!env.debug && daemon(1, 0)) + fatal("daemon"); + + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe_chld) == -1) + fatal("socketpair"); + + /* fork child process */ + sigchld = 1; + chld_pid = bt_main(pipe_chld, &env, pw); + + setproctitle("[priv]"); + + /* only after daemon() */ + event_init(); + + signal_set(&ev_sigchld, SIGCHLD, sighdlr, &env); + signal_set(&ev_sighup, SIGHUP, sighdlr, &env); + signal_set(&ev_sigint, SIGINT, sighdlr, &env); + signal_set(&ev_sigterm, SIGTERM, sighdlr, &env); + signal_add(&ev_sigchld, NULL); + signal_add(&ev_sighup, NULL); + signal_add(&ev_sigint, NULL); + signal_add(&ev_sigterm, NULL); + + close(pipe_chld[1]); + + if ((ev_bt = bufferevent_new(pipe_chld[0], readcb, + writecb, errorcb, &env)) == NULL) + fatalx("bufferevent_new ev_bt"); + + bufferevent_enable(ev_bt, EV_READ); + + while (quit == 0) { + if (!event_loop(EVLOOP_ONCE)) + quit = 1; + + if (sigchld) { + if (check_child(chld_pid, "child")) { + quit = 1; + chld_pid = 0; + } + sigchld = 0; + } + } + + signal_del(&ev_sigchld); + + if (chld_pid) + kill(chld_pid, SIGTERM); + + do { + if ((pid = wait(NULL)) == -1 && + errno != EINTR && errno != ECHILD) + fatal("wait"); + } while (pid != -1 || (pid == -1 && errno == EINTR)); + + log_info("Terminating"); + control_cleanup(); + return 0; +} + +int +check_child(pid_t pid, const char *pname) +{ + int status, sig; + char *signame; + + if (waitpid(pid, &status, WNOHANG) > 0) { + if (WIFEXITED(status)) { + log_warnx("Lost child: %s exited", pname); + return (1); + } + if (WIFSIGNALED(status)) { + sig = WTERMSIG(status); + signame = strsignal(sig) ? strsignal(sig) : "unknown"; + log_warnx("Lost child: %s terminated; signal %d (%s)", + pname, sig, signame); + return (1); + } + } + + return (0); +} + +static void +readcb(struct bufferevent *ev, void *arg) +{ + log_warnx("readcb"); +} + +static void +writecb(struct bufferevent *ev, void *arg) +{ + /* nothing to do here */ + log_warnx("writecb"); +} + +static void +errorcb(struct bufferevent *ev, short what, void *arg) +{ + log_warnx("Pipe error"); + quit = 1; +} + +#ifdef notyet +void +btd_devctl(struct imsg *imsg) +{ + struct btdev_attach_args baa; + char buf[sizeof(int) + sizeof(bdaddr_t)]; + unsigned long cmd; + int res; + int fd; + + if (devinfo_load_attach_args(&baa, imsg->data, + imsg->hdr.len - IMSG_HEADER_SIZE)) + fatalx("invalid IMSG_ATTACH/DETACH received"); + + if ((fd = open(BTHUB_PATH, O_WRONLY, 0)) == -1) { + res = errno; + log_warn("can't open %s", BTHUB_PATH); + goto ret; + } + + cmd = imsg->hdr.type == IMSG_ATTACH ? BTDEV_ATTACH : BTDEV_DETACH; + if (ioctl(fd, cmd, &baa) == -1) + res = errno; + + res = 0; + (void)close(fd); +ret: + devinfo_unload_attach_args(&baa); + + memcpy(buf, &res, sizeof(res)); + memcpy((int *)buf + 1, &baa.bd_raddr, sizeof(bdaddr_t)); + /* send reply */ +} +#endif diff --git a/usr.sbin/btd/btd.h b/usr.sbin/btd/btd.h new file mode 100644 index 00000000000..1e88ff00546 --- /dev/null +++ b/usr.sbin/btd/btd.h @@ -0,0 +1,161 @@ +/* $OpenBSD: btd.h,v 1.1 2008/11/24 23:34:42 uwe Exp $ */ + +/* + * Copyright (c) 2008 Uwe Stuehler <uwe@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, 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. + */ + +#include <sys/types.h> +#include <sys/queue.h> + +#include <dev/bluetooth/btdev.h> +#include <netbt/bluetooth.h> + +#include <db.h> +#include <event.h> +#include <pwd.h> +#include <stdarg.h> + +#define DEFAULT_INTERFACE_NAME "OpenBSD" + +#define BTD_DB "/var/db/btd.db" +#define BTD_SOCKET "/var/run/btd.sock" +#define BTD_USER "_btd" + +/* XXX there is no need for more than one bthub device! */ +#define BTHUB_PATH "/dev/bthub0" + +#define READ_BUF_SIZE 8192 + +struct btd; + +struct bt_interface { + TAILQ_ENTRY(bt_interface) entry; + struct event ev; + struct btd *env; + const char *xname; + bdaddr_t addr; + const char *name; + int disabled; + int fd; +}; + +struct btd_db { + DB *dbh; +}; + +struct btd_hci { + struct bt_interface *inquiry_interface; + int inquiry_running; + time_t last_inquiry; /* start time */ +}; + +struct bt_devinfo { + /* XXX don't tie it to a kernel structure */ + struct btdev_attach_args baa; +}; + +struct bt_device { + TAILQ_ENTRY(bt_device) entry; + bdaddr_t addr; + uint16_t type; + uint8_t *pin; /* HCI_PIN_SIZE when not NULL */ + time_t last_seen; /* last response to an inquiry */ + int flags; + struct bt_devinfo info; +}; + +#define BTDF_VISIBLE 0x0001 /* device is discoverable */ +#define BTDF_ATTACH 0x0002 /* attempt to attach a driver */ + +struct btd { + int debug; + struct btd_db db; + struct btd_hci hci; + TAILQ_HEAD(interfaces, bt_interface) interfaces; + TAILQ_HEAD(devices, bt_device) devices; +}; + +/* ipc messages */ + +enum imsg_type { + IMSG_NONE, + IMSG_CONFIG_BEGIN, + IMSG_CONFIG_INTERFACE, + IMSG_CONFIG_DEVICE, + IMSG_CONFIG_COMMIT, + IMSG_CONFIG_ROLLBACK, + IMSG_ATTACH, + IMSG_DETACH +}; + +/* prototypes */ + +/* bt.c */ +pid_t bt_main(int[2], struct btd *, struct passwd *); + +/* conf.c */ +struct bt_device *conf_add_device(struct btd *, + const bdaddr_t *); +struct bt_device *conf_find_device(const struct btd *, + const bdaddr_t *); +struct bt_interface *conf_add_interface(struct btd *, + const bdaddr_t *); +struct bt_interface *conf_find_interface(const struct btd *, + const bdaddr_t *); +const uint8_t *conf_lookup_pin(const struct btd *, const bdaddr_t *); + +/* control.c */ +void control_init(struct btd *); +void control_cleanup(void); + +/* db.c */ +void db_open(const char *, struct btd_db *); +int db_put_link_key(struct btd_db *, const bdaddr_t *, const uint8_t *); +int db_get_link_key(struct btd_db *, const bdaddr_t *, uint8_t *); +int db_put_devinfo(struct btd_db *, const bdaddr_t *, + const struct bt_devinfo *); +int db_get_devinfo(struct btd_db *, const bdaddr_t *, struct bt_devinfo *); +void db_dump(struct btd_db *); + +/* devinfo.c */ +int devinfo_load(struct bt_devinfo *, void *, size_t); +void devinfo_unload(struct bt_devinfo *); +int devinfo_store(const struct bt_devinfo *, void **, size_t *); +int devinfo_load_attach_args(struct btdev_attach_args *, void *, size_t); +void devinfo_unload_attach_args(struct btdev_attach_args *); +void devinfo_dump(const struct bt_devinfo *); + +/* hci.c */ +int hci_init(struct btd *); +time_t hci_ping(struct btd *); +int hci_dispatch(int, struct btd *); + +/* log.c */ +extern int debug; +void log_init(int); +void vlog(int, const char *, va_list); +void log_warn(const char *, ...); +void log_warnx(const char *, ...); +void log_info(const char *, ...); +void log_debug(const char *, ...); +void log_packet(const bdaddr_t *, const bdaddr_t *, const char *, ...); +void fatal(const char *); +void fatalx(const char *); + +/* sdp.c */ +int sdp_query(struct btdev_attach_args *, bdaddr_t *, bdaddr_t *, const char *); + +/* util.c */ +time_t getmonotime(void); diff --git a/usr.sbin/btd/conf.c b/usr.sbin/btd/conf.c new file mode 100644 index 00000000000..7ed0f67f5a4 --- /dev/null +++ b/usr.sbin/btd/conf.c @@ -0,0 +1,123 @@ +#include <sys/types.h> + +#include <netbt/hci.h> + +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +#include "btd.h" + +struct bt_device * +conf_add_device(struct btd *conf, const bdaddr_t *addr) +{ + struct bt_device *btdev; + struct bt_device *defaults; + + assert(conf_find_device(conf, addr) == NULL); + + btdev = calloc(1, sizeof(*btdev)); + if (btdev == NULL) { + log_warn("conf_add_device"); + return NULL; + } + + bdaddr_copy(&btdev->addr, addr); + + defaults = bdaddr_any(addr) ? NULL : + conf_find_device(conf, BDADDR_ANY); + + if (defaults != NULL) { + btdev->type = defaults->type; + btdev->flags = defaults->flags; + + if (defaults->pin != NULL) { + if ((btdev->pin = malloc(HCI_KEY_SIZE)) == NULL) { + log_warn("conf_add_device malloc"); + TAILQ_REMOVE(&conf->devices, btdev, entry); + return NULL; + } + memcpy(btdev->pin, defaults->pin, HCI_KEY_SIZE); + } + } + + TAILQ_INSERT_TAIL(&conf->devices, btdev, entry); + + return btdev; +} + +struct bt_interface * +conf_add_interface(struct btd *conf, const bdaddr_t *addr) +{ + struct bt_interface *iface; + struct bt_interface *defaults; + + assert(conf_find_interface(conf, addr) == NULL); + + iface = calloc(1, sizeof(*iface)); + if (iface == NULL) { + log_warn("conf_add_interface"); + return NULL; + } + + bdaddr_copy(&iface->addr, addr); + iface->env = conf; + iface->fd = -1; + + defaults = bdaddr_any(addr) ? NULL : + conf_find_interface(conf, BDADDR_ANY); + + if (defaults != NULL) { + if (defaults->name != NULL && + (iface->name = strdup(defaults->name)) == NULL) { + log_warn("conf_add_interface strdup"); + TAILQ_REMOVE(&conf->interfaces, iface, entry); + free(iface); + return NULL; + } + + iface->disabled = defaults->disabled; + } + + TAILQ_INSERT_TAIL(&conf->interfaces, iface, entry); + + return iface; +} + +struct bt_device * +conf_find_device(const struct btd *conf, const bdaddr_t *addr) +{ + struct bt_device *btdev; + + TAILQ_FOREACH(btdev, &conf->devices, entry) { + if (bdaddr_same(&btdev->addr, addr)) + return btdev; + } + + return NULL; +} + +struct bt_interface * +conf_find_interface(const struct btd *conf, const bdaddr_t *addr) +{ + struct bt_interface *iface; + + TAILQ_FOREACH(iface, &conf->interfaces, entry) { + if (bdaddr_same(&iface->addr, addr)) + return iface; + } + + return NULL; +} + +const uint8_t * +conf_lookup_pin(const struct btd *conf, const bdaddr_t *addr) +{ + struct bt_device *btdev; + + if ((btdev = conf_find_device(conf, addr)) == NULL && + (btdev = conf_find_device(conf, BDADDR_ANY)) == NULL) + return NULL; + + return btdev->pin; +} diff --git a/usr.sbin/btd/control.c b/usr.sbin/btd/control.c new file mode 100644 index 00000000000..f81961e2b7f --- /dev/null +++ b/usr.sbin/btd/control.c @@ -0,0 +1,208 @@ +/* $OpenBSD: control.c,v 1.1 2008/11/24 23:34:42 uwe Exp $ */ + +/* + * Copyright (c) 2008 Uwe Stuehler <uwe@openbsd.org> + * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, 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. + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/un.h> + +#include <errno.h> +#include <event.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "btd.h" +#include "btctl.h" + +#define CONTROL_BACKLOG 5 + +void control_acceptcb(int, short, void *); +void control_readcb(struct bufferevent *, void *); +void control_errorcb(struct bufferevent *, short, void *); + +void socket_blockmode(int, int); + +struct control_connection { + TAILQ_ENTRY(control_connection) entry; + struct bufferevent *ev; + int fd; +}; + +static struct event ev_control; +static TAILQ_HEAD(, control_connection) connections = + TAILQ_HEAD_INITIALIZER(connections); + +void +control_init(struct btd *env) +{ + struct sockaddr_un sun; + mode_t old_umask; + int fd; + + if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) + fatal("control_init: socket"); + + sun.sun_family = AF_UNIX; + if (strlcpy(sun.sun_path, BTD_SOCKET, + sizeof(sun.sun_path)) >= sizeof(sun.sun_path)) + fatalx("control_init: socket name too long"); + + if (unlink(BTD_SOCKET) == -1) + if (errno != ENOENT) + fatal("control_init: unlink"); + + old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH); + if (bind(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1) + fatal("control_init: bind"); + (void)umask(old_umask); + + if (chmod(BTD_SOCKET, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) == -1) { + log_warn("control_init: chmod"); + close(fd); + (void)unlink(BTD_SOCKET); + exit(1); + } + + if (listen(fd, CONTROL_BACKLOG) == -1) + fatal("control_init: listen"); + + socket_blockmode(fd, 0); + + event_set(&ev_control, fd, EV_READ | EV_PERSIST, + control_acceptcb, env); + event_add(&ev_control, NULL); +} + +void +control_cleanup(void) +{ + if (unlink(BTD_SOCKET) == -1) + fatal("control_init: unlink"); +} + +void +control_acceptcb(int fd, short evflags, void *arg) +{ + struct control_connection *conn; + struct sockaddr_storage sa; + socklen_t salen = sizeof(sa); + int new_fd; + + if ((new_fd = accept(fd, (struct sockaddr *)&sa, &salen)) == -1) { + log_warn("control_eventcb: accept"); + return; + } + + if ((conn = calloc(1, sizeof(*conn))) == NULL) + fatal("control_eventcb: calloc"); + + conn->fd = new_fd; + + if ((conn->ev = bufferevent_new(new_fd, control_readcb, NULL, + control_errorcb, conn)) == NULL) + fatal("control_errorcb: bufferevent_new"); + + TAILQ_INSERT_TAIL(&connections, conn, entry); + bufferevent_enable(conn->ev, EV_READ); +} + +void +control_readcb(struct bufferevent *ev, void *arg) +{ + btctl_interface_stmt interface_stmt; + btctl_attach_stmt attach_stmt; + struct control_connection *conn = arg; + enum btctl_stmt_type stmt_type; + size_t stmt_size = 0; + int err; + + if (EVBUFFER_LENGTH(EVBUFFER_INPUT(ev)) < sizeof(stmt_type)) + return; + + memcpy(&stmt_type, EVBUFFER_DATA(EVBUFFER_INPUT(ev)), + sizeof(stmt_type)); + + switch (stmt_type) { + case BTCTL_CONFIG: + case BTCTL_COMMIT: + case BTCTL_ROLLBACK: + break; + case BTCTL_INTERFACE_STMT: + stmt_size += sizeof(btctl_interface_stmt); + break; + case BTCTL_ATTACH_STMT: + stmt_size += sizeof(btctl_attach_stmt); + break; + } + + if (EVBUFFER_LENGTH(EVBUFFER_INPUT(ev)) <= stmt_size) + return; + + bufferevent_read(ev, &stmt_type, sizeof(stmt_type)); + log_debug("stmt %#x size %lu", stmt_type, stmt_size); + + switch (stmt_type) { + case BTCTL_CONFIG: + case BTCTL_COMMIT: + case BTCTL_ROLLBACK: + break; + case BTCTL_INTERFACE_STMT: + bufferevent_read(ev, &interface_stmt, stmt_size); + break; + case BTCTL_ATTACH_STMT: + bufferevent_read(ev, &attach_stmt, stmt_size); + break; + default: + log_warnx("Invalid control packet of type %#x", stmt_type); + close(conn->fd); + return; + } + + err = 0; + bufferevent_write(ev, &err, sizeof(err)); +} + +void +control_errorcb(struct bufferevent *ev, short what, void *arg) +{ + struct control_connection *conn = arg; + + TAILQ_REMOVE(&connections, conn, entry); + bufferevent_free(conn->ev); + close(conn->fd); + free(conn); +} + +void +socket_blockmode(int fd, int block) +{ + int flags; + + if ((flags = fcntl(fd, F_GETFL, 0)) == -1) + fatal("fcntl F_GETFL"); + + if (block) + flags &= ~O_NONBLOCK; + else + flags |= O_NONBLOCK; + + if ((flags = fcntl(fd, F_SETFL, flags)) == -1) + fatal("fcntl F_SETFL"); +} diff --git a/usr.sbin/btd/db.c b/usr.sbin/btd/db.c new file mode 100644 index 00000000000..e147b072b8d --- /dev/null +++ b/usr.sbin/btd/db.c @@ -0,0 +1,203 @@ +#include <sys/stat.h> + +#include <netbt/hci.h> + +#include <assert.h> +#include <bluetooth.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> + +#include "btd.h" + +typedef enum { + DB_DEVINFO, + DB_LINK_KEY +} db_type; + +typedef struct { + bdaddr_t addr; + db_type type; +} db_key; + +int db_put(struct btd_db *, db_key *, const void *, size_t); +int db_get_raw(struct btd_db *, db_key *, void **, size_t *); +int db_get_exact(struct btd_db *, db_key *, void *, size_t); + +void +db_open(const char *file, struct btd_db *db) +{ + assert(db->dbh == NULL); + + db->dbh = dbopen(file, O_CREAT | O_EXLOCK | O_RDWR, + S_IRUSR | S_IWUSR, DB_HASH, NULL); + if (db->dbh == NULL) + fatal(file); +} + +int +db_put(struct btd_db *db, db_key *key, const void *data, size_t size) +{ + DB *dbh = db->dbh; + DBT dbk, dbd; + + memset(&dbk, 0, sizeof(dbk)); + dbk.size = sizeof(db_key); + dbk.data = (void *)key; + memset(&dbd, 0, sizeof(dbd)); + dbd.size = size; + dbd.data = (void*)data; + + if (dbh->put(dbh, &dbk, &dbd, 0)) { + log_warn("db_put"); + return -1; + } + + if (dbh->sync(dbh, 0)) { + log_warn("db_sync"); + return -1; + } + + return 0; +} + +int +db_get_raw(struct btd_db *db, db_key *key, void **data, size_t *size) +{ + DB *dbh = db->dbh; + DBT dbk, dbd; + int res; + + memset(&dbk, 0, sizeof(dbk)); + dbk.size = sizeof(db_key); + dbk.data = (void *)key; + memset(&dbd, 0, sizeof(dbd)); + + if ((res = dbh->get(dbh, &dbk, &dbd, 0)) != 0) { + if (res == -1) + log_warn("db_get"); + return res; + } + + *data = dbd.data; + *size = dbd.size; + return 0; +} + +int +db_get_exact(struct btd_db *db, db_key *key, void *data, size_t size) +{ + void *d; + size_t dlen; + int res; + + if ((res = db_get_raw(db, key, &d, &dlen)) == 0) { + if (dlen != size) { + log_warnx("data size mismatch for key type %#x" + " (%u != %u)", key->type, dlen, size); + return -1; + } + memcpy(data, d, size); + } + + return res; +} + +int +db_put_link_key(struct btd_db *db, const bdaddr_t *addr, + const uint8_t *link_key) +{ + db_key key; + + memset(&key, 0, sizeof(key)); + bdaddr_copy(&key.addr, addr); + key.type = DB_LINK_KEY; + + return db_put(db, &key, link_key, HCI_KEY_SIZE); +} + +int +db_get_link_key(struct btd_db *db, const bdaddr_t *addr, uint8_t *link_key) +{ + db_key key; + + memset(&key, 0, sizeof(key)); + bdaddr_copy(&key.addr, addr); + key.type = DB_LINK_KEY; + + return db_get_exact(db, &key, link_key, HCI_KEY_SIZE); +} + +int +db_put_devinfo(struct btd_db *db, const bdaddr_t *addr, + const struct bt_devinfo *info) +{ + db_key key; + void *data; + size_t size; + int res; + + memset(&key, 0, sizeof(key)); + bdaddr_copy(&key.addr, addr); + key.type = DB_DEVINFO; + + if (devinfo_store(info, &data, &size)) + return -1; + + res = db_put(db, &key, data, size); + free(data); + return res; +} + +int +db_get_devinfo(struct btd_db *db, const bdaddr_t *addr, + struct bt_devinfo *info) +{ + db_key key; + void *data; + size_t size; + int res; + + memset(&key, 0, sizeof(key)); + bdaddr_copy(&key.addr, addr); + key.type = DB_DEVINFO; + + if ((res = db_get_raw(db, &key, &data, &size)) == 0 && + devinfo_load(info, data, size)) + res = -1; + + return res; +} + +void +db_dump(struct btd_db *db) +{ + struct bt_devinfo info; + DB *dbh = db->dbh; + DBT dbk, dbd; + + if (dbh->seq(dbh, &dbk, &dbd, R_FIRST) != 0) + return; + do { + db_key *key = dbk.data; + + if (dbk.size != sizeof(db_key)) + fatalx("invalid db key"); + + switch (key->type) { + case DB_LINK_KEY: + log_info("%s link_key", bt_ntoa(&key->addr, NULL)); + break; + case DB_DEVINFO: + log_info("%s devinfo", bt_ntoa(&key->addr, NULL)); + if (db_get_devinfo(db, &key->addr, &info) == 0) { + devinfo_dump(&info); + devinfo_unload(&info); + } + break; + default: + fatalx("invalid db key type"); + } + } + while (dbh->seq(dbh, &dbk, &dbd, R_NEXT) == 0); +} diff --git a/usr.sbin/btd/devinfo.c b/usr.sbin/btd/devinfo.c new file mode 100644 index 00000000000..5471b2bbe4a --- /dev/null +++ b/usr.sbin/btd/devinfo.c @@ -0,0 +1,100 @@ +#include <bluetooth.h> +#include <stdlib.h> +#include <string.h> + +#include "btd.h" + +int +devinfo_store(const struct bt_devinfo *info, void **data, size_t *datalen) +{ + *datalen = sizeof(info->baa); + if (info->baa.bd_type == BTDEV_HID) + *datalen += info->baa.bd_hid.hid_dlen; + + if ((*data = malloc(*datalen)) == NULL) { + log_warn("devinfo_store"); + return -1; + } + + memcpy(*data, &info->baa, sizeof(info->baa)); + if (info->baa.bd_type == BTDEV_HID && + info->baa.bd_hid.hid_dlen > 0) + memcpy((uint8_t *)*data + sizeof(info->baa), + info->baa.bd_hid.hid_desc, + info->baa.bd_hid.hid_dlen); + + return 0; +} + +int +devinfo_load(struct bt_devinfo *info, void *data, size_t datalen) +{ + return devinfo_load_attach_args(&info->baa, data, datalen); +} + +void +devinfo_unload(struct bt_devinfo *info) +{ + return devinfo_unload_attach_args(&info->baa); +} + +int +devinfo_load_attach_args(struct btdev_attach_args *baa, void *data, + size_t datalen) +{ + if (datalen < sizeof(*baa)) { + log_warnx("devinfo data too short"); + memset(baa, 0, sizeof(*baa)); + return -1; + } + + memcpy(baa, data, sizeof(*baa)); + data = (struct btdev_attach_args *)data + 1; + datalen -= sizeof(*baa); + + if (baa->bd_type == BTDEV_HID) { + uint16_t dlen = baa->bd_hid.hid_dlen; + void *desc = NULL; + + if (datalen != dlen) { + log_warnx("bad devinfo data length (HID)"); + return -1; + } + + if (dlen > 0) { + if ((desc = malloc(dlen)) == NULL) { + log_warn("devinfo_load_attach_args"); + return -1; + } + memcpy(desc, data, dlen); + } + + baa->bd_hid.hid_desc = desc; + } else if (datalen > 0) { + log_warnx("devinfo data too long"); + return -1; + } + + return 0; +} + +void +devinfo_unload_attach_args(struct btdev_attach_args *baa) +{ + if (baa->bd_type == BTDEV_HID && baa->bd_hid.hid_desc != NULL) { + free(baa->bd_hid.hid_desc); + baa->bd_hid.hid_desc = NULL; + baa->bd_hid.hid_dlen = 0; + } +} + +void +devinfo_dump(const struct bt_devinfo *info) +{ + const struct btdev_attach_args *baa = &info->baa; + + log_info("laddr %s", bt_ntoa(&baa->bd_laddr, NULL)); + log_info("raddr %s", bt_ntoa(&baa->bd_raddr, NULL)); + log_info("type %#x", baa->bd_type); + log_info("mode %d", baa->bd_mode); +} diff --git a/usr.sbin/btd/hci.c b/usr.sbin/btd/hci.c new file mode 100644 index 00000000000..93d42318b9c --- /dev/null +++ b/usr.sbin/btd/hci.c @@ -0,0 +1,498 @@ +/* $OpenBSD: hci.c,v 1.1 2008/11/24 23:34:42 uwe Exp $ */ +/* $NetBSD: btconfig.c,v 1.13 2008/07/21 13:36:57 lukem Exp $ */ + +/*- + * Copyright (c) 2008 Uwe Stuehler <uwe@openbsd.org> + * Copyright (c) 2006 Itronix Inc. + * All rights reserved. + * + * Written by Iain Hibbert for Itronix Inc. + * + * 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. + * 3. The name of Itronix Inc. may not be used to endorse + * or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY ITRONIX INC. ``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 ITRONIX INC. 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. + */ + +#include <sys/ioctl.h> + +#include <assert.h> +#include <bluetooth.h> +#include <errno.h> +#include <event.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> + +#include "btd.h" + +int hci_init_config(struct bt_interface *); + +void hci_eventcb(int, short, void *); + +int hci_process_pin_code_req(struct bt_interface *, + struct sockaddr_bt *, const bdaddr_t *); +int hci_process_link_key_req(struct bt_interface *, + struct sockaddr_bt *, const bdaddr_t *); +int hci_process_link_key_notification(struct bt_interface *, struct sockaddr_bt *, + const hci_link_key_notification_ep *); +int hci_req(int, uint16_t, uint8_t , void *, size_t, void *, size_t); +int hci_send_cmd(int, struct sockaddr_bt *, uint16_t, size_t, void *); + +#define hci_write(iface, opcode, wbuf, wlen) \ + hci_req(iface->fd, opcode, 0, wbuf, wlen, NULL, 0) +#define hci_read(iface, opcode, rbuf, rlen) \ + hci_req(iface->fd, opcode, 0, NULL, 0, rbuf, rlen) +#define hci_cmd(iface, opcode, cbuf, clen) \ + hci_req(iface->fd, opcode, 0, cbuf, clen, NULL, 0) + +int +hci_init(struct btd *env) +{ + struct btreq btr; + struct bt_interface *defaults; + struct bt_interface *iface; + int hci; + + hci = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI); + if (hci == -1) + fatal("could not open raw HCI socket"); + + defaults = conf_find_interface(env, BDADDR_ANY); + iface = NULL; + + bzero(&btr, sizeof(btr)); + for (;;) { + int flags; + + if (ioctl(hci, SIOCNBTINFO, &btr) == -1) + break; + + /* + * Interfaces must be up, just to determine their + * bdaddr. Grrrr... + */ + if (!((flags = btr.btr_flags) & BTF_UP)) { + btr.btr_flags |= BTF_UP; + if (ioctl(hci, SIOCSBTFLAGS, &btr) == -1) { + log_warn("%s: SIOCSBTFLAGS", btr.btr_name); + continue; + } + if (ioctl(hci, SIOCGBTINFO, &btr) == -1) { + log_warn("%s: SIOCGBTINFO", btr.btr_name); + continue; + } + } + + if (!(btr.btr_flags & BTF_UP) || + bdaddr_any(&btr.btr_bdaddr)) { + log_warn("could not enable %s", btr.btr_name); + goto redisable; + } + + /* + * Discover device driver unit names for explicitly + * configured interfaces. + */ + iface = conf_find_interface(env, &btr.btr_bdaddr); + if (iface != NULL) { + assert(iface->xname == NULL); + iface->xname = strdup(btr.btr_name); + if (iface->xname == NULL) + fatal("hci_init strdup 1"); + goto redisable; + } + + /* + * Add interfaces not explicitly configured. + */ + iface = conf_add_interface(env, &btr.btr_bdaddr); + if (iface == NULL) + fatalx("hci_init add_interface"); + + iface->xname = strdup(btr.btr_name); + if (iface->xname == NULL) + fatal("hci_init strdup 2"); + + redisable: + /* See above. Grrrr... */ + if (!(flags & BTF_UP) && + (iface == NULL || iface->disabled)) { + btr.btr_flags &= ~BTF_UP; + if (ioctl(hci, SIOCSBTFLAGS, &btr) == -1) + fatal("hci_init SIOCSBTFLAGS"); + } + } + + close(hci); + + TAILQ_FOREACH(iface, &env->interfaces, entry) { + struct sockaddr_bt sa; + struct hci_filter filter; + + if (bdaddr_any(&iface->addr)) + continue; + + /* Disable interfaces not present in the system. */ + if (iface->xname == NULL) { + iface->disabled = 1; + log_info("interface disabled: %s (not present)", + bt_ntoa(&iface->addr, NULL)); + continue; + } + + /* Skip disabled interfaces. */ + if (iface->disabled) { + log_info("interface disabled: %s (%s)", + bt_ntoa(&iface->addr, NULL), iface->xname); + continue; + } + + log_info("listening on %s (%s)", + bt_ntoa(&iface->addr, NULL), iface->xname); + + iface->fd = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI); + if (iface->fd == -1) + fatal("socket"); + + bzero(&sa, sizeof(sa)); + sa.bt_len = sizeof(sa); + sa.bt_family = AF_BLUETOOTH; + bdaddr_copy(&sa.bt_bdaddr, &iface->addr); + if (bind(iface->fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) + fatal("bind"); + + if (connect(iface->fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) + fatal("connect"); + + if (hci_init_config(iface) == -1) + fatalx("hci_init_config"); + + bzero(&filter, sizeof(filter)); + hci_filter_set(HCI_EVENT_PIN_CODE_REQ, &filter); + hci_filter_set(HCI_EVENT_LINK_KEY_REQ, &filter); + hci_filter_set(HCI_EVENT_LINK_KEY_NOTIFICATION, &filter); + if (setsockopt(iface->fd, BTPROTO_HCI, SO_HCI_EVT_FILTER, + (const void *)&filter, sizeof(filter)) < 0) + fatal("setsockopt"); + + event_set(&iface->ev, iface->fd, EV_READ | EV_PERSIST, + hci_eventcb, iface); + event_add(&iface->ev, NULL); + } + + /* + * Run device discovery on the first available + * interface. This should be configurable. + */ + TAILQ_FOREACH(iface, &env->interfaces, entry) { + if (!bdaddr_any(&iface->addr) && !iface->disabled) { + env->hci.inquiry_interface = iface; + break; + } + } + + return 0; +} + +int +hci_init_config(struct bt_interface *iface) +{ + struct btreq btr; + struct btd *env = iface->env; + struct bt_interface *defaults; + const char *name; + uint8_t val; + + defaults = conf_find_interface(env, BDADDR_ANY); + + if (iface->name != NULL) + name = iface->name; + else if (defaults != NULL && defaults->name != NULL) + name = defaults->name; + else + name = NULL; + + if (name != NULL && hci_write(iface, HCI_CMD_WRITE_LOCAL_NAME, + (char *)name, HCI_UNIT_NAME_SIZE)) + return -1; + + if (hci_read(iface, HCI_CMD_READ_SCAN_ENABLE, &val, sizeof(val))) + return -1; + + val |= HCI_PAGE_SCAN_ENABLE; + val |= HCI_INQUIRY_SCAN_ENABLE; + + if (hci_write(iface, HCI_CMD_WRITE_SCAN_ENABLE, &val, sizeof(val))) + return -1; + + bdaddr_copy(&btr.btr_bdaddr, &iface->addr); + if (ioctl(iface->fd, SIOCGBTINFOA, &btr) < 0) { + log_warn("SIOCGBTINFOA"); + return -1; + } + + val = btr.btr_link_policy; + val |= HCI_LINK_POLICY_ENABLE_ROLE_SWITCH; + btr.btr_link_policy = val; + + if (ioctl(iface->fd, SIOCSBTPOLICY, &btr) < 0) { + log_warn("SIOCSBTPOLICY"); + return -1; + } + + return 0; +} + +void +hci_eventcb(int fd, short evflags, void *arg) +{ + char buf[HCI_EVENT_PKT_SIZE]; + hci_event_hdr_t *event = (hci_event_hdr_t *)buf; + struct bt_interface *iface = arg; + struct sockaddr_bt sa; + socklen_t size; + bdaddr_t *addr; + void *ep; + int n; + + if (iface == NULL) + fatal("HCI event on closed socket?"); + + size = sizeof(sa); + n = recvfrom(iface->fd, buf, sizeof(buf), 0, + (struct sockaddr *)&sa, &size); + if (n < 0) { + log_warn("could not receive from HCI socket"); + return; + } + + if (event->type != HCI_EVENT_PKT) { + log_packet(&sa.bt_bdaddr, &iface->addr, + "unexpected HCI packet, type=%#x", event->type); + return; + } + + addr = (bdaddr_t *)(event + 1); + ep = (bdaddr_t *)(event + 1); + + switch (event->event) { + case HCI_EVENT_PIN_CODE_REQ: + hci_process_pin_code_req(iface, &sa, addr); + break; + + case HCI_EVENT_LINK_KEY_REQ: + hci_process_link_key_req(iface, &sa, addr); + break; + + case HCI_EVENT_LINK_KEY_NOTIFICATION: + hci_process_link_key_notification(iface, &sa, ep); + break; + + default: + log_packet(&sa.bt_bdaddr, &iface->addr, + "unexpected HCI event, event=%#x", event->event); + break; + } +} + +int +hci_process_pin_code_req(struct bt_interface *iface, + struct sockaddr_bt *sa, const bdaddr_t *addr) +{ + const uint8_t *pin; + + pin = conf_lookup_pin(iface->env, addr); + + if (pin == NULL) { + log_info("%s: PIN code not found", bt_ntoa(addr, NULL)); + return hci_send_cmd(iface->fd, sa, HCI_CMD_PIN_CODE_NEG_REP, + sizeof(bdaddr_t), (void *)addr); + } else { + hci_pin_code_rep_cp cp; + int n; + + bdaddr_copy(&cp.bdaddr, addr); + memcpy(cp.pin, pin, HCI_PIN_SIZE); + + n = HCI_PIN_SIZE; + while (n > 0 && pin[n - 1] == 0) + n--; + cp.pin_size = n; + + log_info("%s: PIN code found", bt_ntoa(addr, NULL)); + return hci_send_cmd(iface->fd, sa, HCI_CMD_PIN_CODE_REP, + sizeof(cp), &cp); + } +} + +int +hci_process_link_key_req(struct bt_interface *iface, + struct sockaddr_bt *sa, const bdaddr_t *addr) +{ + hci_link_key_rep_cp cp; + + if (db_get_link_key(&iface->env->db, addr, cp.key)) { + log_info("%s: link key not found", bt_ntoa(addr, NULL)); + return hci_send_cmd(iface->fd, sa, HCI_CMD_LINK_KEY_NEG_REP, + sizeof(bdaddr_t), (void *)addr); + } + + log_info("%s: link key found", bt_ntoa(addr, NULL)); + bdaddr_copy(&cp.bdaddr, addr); + + return hci_send_cmd(iface->fd, sa, HCI_CMD_LINK_KEY_REP, + sizeof(cp), &cp); +} + +int +hci_process_link_key_notification(struct bt_interface *iface, + struct sockaddr_bt *sa, const hci_link_key_notification_ep *ep) +{ + const bdaddr_t *addr = &ep->bdaddr; + int res; + + if ((res = db_put_link_key(&iface->env->db, addr, ep->key)) == 0) + log_info("%s: link key stored", bt_ntoa(addr, NULL)); + else + log_info("%s: link key not stored", bt_ntoa(addr, NULL)); + + return res; +} + +/* + * Basic HCI cmd request function with argument return. + * + * Normally, this will return on COMMAND_STATUS or COMMAND_COMPLETE + * for the given opcode, but if event is given then it will ignore + * COMMAND_STATUS (unless error) and wait for the specified event. + * + * If rbuf/rlen is given, results will be copied into the result + * buffer for COMMAND_COMPLETE/event responses. + */ +int +hci_req(int hci, uint16_t opcode, uint8_t event, void *cbuf, size_t clen, + void *rbuf, size_t rlen) +{ + uint8_t msg[sizeof(hci_cmd_hdr_t) + HCI_CMD_PKT_SIZE]; + hci_event_hdr_t *ep; + hci_cmd_hdr_t *cp; + + cp = (hci_cmd_hdr_t *)msg; + cp->type = HCI_CMD_PKT; + cp->opcode = opcode = htole16(opcode); + cp->length = clen = MIN(clen, sizeof(msg) - sizeof(hci_cmd_hdr_t)); + + if (clen) + memcpy((cp + 1), cbuf, clen); + + if (send(hci, msg, sizeof(hci_cmd_hdr_t) + clen, 0) < 0) { + log_warn("HCI send"); + return -1; + } + + ep = (hci_event_hdr_t *)msg; + for(;;) { + if (recv(hci, msg, sizeof(msg), 0) < 0) { + if (errno == EAGAIN || errno == EINTR) + continue; + + log_warn("HCI recv"); + return -1; + } + + if (ep->event == HCI_EVENT_COMMAND_STATUS) { + hci_command_status_ep *cs; + + cs = (hci_command_status_ep *)(ep + 1); + if (cs->opcode != opcode) + continue; + + if (cs->status) { + log_warnx("HCI cmd (%4.4x) failed (status %d)", + opcode, cs->status); + return -1; + } + + if (event == 0) + break; + + continue; + } + + if (ep->event == HCI_EVENT_COMMAND_COMPL) { + hci_command_compl_ep *cc; + uint8_t *ptr; + + cc = (hci_command_compl_ep *)(ep + 1); + if (cc->opcode != opcode) + continue; + + if (rbuf == NULL) + break; + + ptr = (uint8_t *)(cc + 1); + if (*ptr) { + log_warn("HCI cmd (%4.4x) failed (status %d)", + opcode, *ptr); + return -1; + } + + memcpy(rbuf, ++ptr, rlen); + break; + } + + if (ep->event == event) { + if (rbuf == NULL) + break; + + memcpy(rbuf, (ep + 1), rlen); + break; + } + } + + return 0; +} + +/* Send HCI Command Packet to socket */ +int +hci_send_cmd(int sock, struct sockaddr_bt *sa, uint16_t opcode, size_t len, + void *buf) +{ + char msg[HCI_CMD_PKT_SIZE]; + hci_cmd_hdr_t *h = (hci_cmd_hdr_t *)msg; + + h->type = HCI_CMD_PKT; + h->opcode = htole16(opcode); + h->length = len; + + if (len > 0) + memcpy(msg + sizeof(hci_cmd_hdr_t), buf, len); + + if (sendto(sock, msg, sizeof(hci_cmd_hdr_t) + len, 0, + (struct sockaddr *)sa, sizeof(*sa)) == -1) { + log_warn("HCI send command"); + return -1; + } + + return 0; +} diff --git a/usr.sbin/btd/log.c b/usr.sbin/btd/log.c new file mode 100644 index 00000000000..8346fe08cb7 --- /dev/null +++ b/usr.sbin/btd/log.c @@ -0,0 +1,175 @@ +/* $OpenBSD: log.c,v 1.1 2008/11/24 23:34:42 uwe Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, 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. + */ + +#include <bluetooth.h> +#include <errno.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <time.h> + +#include "btd.h" + +int debug = 1; + +void logit(int, const char *, ...); + +void +log_init(int n_debug) +{ + extern char *__progname; + + debug = n_debug; + + if (!debug) + openlog(__progname, LOG_PID | LOG_NDELAY, LOG_DAEMON); + + tzset(); +} + +void +logit(int pri, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vlog(pri, fmt, ap); + va_end(ap); +} + +void +vlog(int pri, const char *fmt, va_list ap) +{ + char *nfmt; + + if (debug) { + /* best effort in out of mem situations */ + if (asprintf(&nfmt, "%s\n", fmt) == -1) { + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + } else { + vfprintf(stderr, nfmt, ap); + free(nfmt); + } + fflush(stderr); + } else + vsyslog(pri, fmt, ap); +} + + +void +log_warn(const char *emsg, ...) +{ + char *nfmt; + va_list ap; + + /* best effort to even work in out of memory situations */ + if (emsg == NULL) + logit(LOG_CRIT, "%s", strerror(errno)); + else { + va_start(ap, emsg); + + if (asprintf(&nfmt, "%s: %s", emsg, strerror(errno)) == -1) { + /* we tried it... */ + vlog(LOG_CRIT, emsg, ap); + logit(LOG_CRIT, "%s", strerror(errno)); + } else { + vlog(LOG_CRIT, nfmt, ap); + free(nfmt); + } + va_end(ap); + } +} + +void +log_warnx(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vlog(LOG_CRIT, emsg, ap); + va_end(ap); +} + +void +log_info(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vlog(LOG_INFO, emsg, ap); + va_end(ap); +} + +void +log_debug(const char *emsg, ...) +{ + va_list ap; + + if (debug) { + va_start(ap, emsg); + vlog(LOG_DEBUG, emsg, ap); + va_end(ap); + } +} + +void +log_packet(const bdaddr_t *srcn, const bdaddr_t *dstn, const char *emsg, ...) +{ + char srca[18], dsta[18]; + char *nfmt; + va_list ap; + + if (debug) { + bt_ntoa(srcn, srca); + bt_ntoa(dstn, dsta); + + va_start(ap, emsg); + if (asprintf(&nfmt, "%s > %s: %s", srca, dsta, emsg) == -1) + vlog(LOG_DEBUG, emsg, ap); + else { + vlog(LOG_DEBUG, nfmt, ap); + free(nfmt); + } + va_end(ap); + } +} + +void +fatal(const char *emsg) +{ + if (emsg == NULL) + logit(LOG_CRIT, "fatal: %s", strerror(errno)); + else + if (errno) + logit(LOG_CRIT, "fatal: %s: %s", + emsg, strerror(errno)); + else + logit(LOG_CRIT, "fatal: %s", emsg); + + exit(1); +} + +void +fatalx(const char *emsg) +{ + errno = 0; + fatal(emsg); +} diff --git a/usr.sbin/btd/parse.y b/usr.sbin/btd/parse.y new file mode 100644 index 00000000000..bef3126cc54 --- /dev/null +++ b/usr.sbin/btd/parse.y @@ -0,0 +1,521 @@ +/* $OpenBSD: parse.y,v 1.1 2008/11/24 23:34:42 uwe Exp $ */ + +/* + * Copyright (c) 2008 Uwe Stuehler <uwe@openbsd.org> + * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org> + * Copyright (c) 2001 Markus Friedl. All rights reserved. + * Copyright (c) 2001 Daniel Hartmeier. All rights reserved. + * Copyright (c) 2001 Theo de Raadt. All rights reserved. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, 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. + */ + +%{ +#include <sys/limits.h> + +#include <dev/bluetooth/btdev.h> + +#include <bluetooth.h> +#include <ctype.h> +#include <stdarg.h> +#include <stdio.h> +#include <syslog.h> + +#include "btd.h" + +TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files); + +static struct file { + TAILQ_ENTRY(file) entry; + FILE *stream; + char *name; + int lineno; + int errors; +} *file, *topfile; + +struct file *pushfile(const char *); +int popfile(void); +int yyparse(void); +int yylex(void); +int yyerror(const char *, ...); +int kw_cmp(const void *, const void *); +int lookup(char *); +int lgetc(int); +int lungetc(int); +int findeol(void); + +typedef struct { + union { + int64_t number; + char *string; + bdaddr_t bdaddr; + } v; + int lineno; +} YYSTYPE; + +struct btd *conf; + +%} + +%token INTERFACE NAME DISABLED +%token ATTACH TYPE HID HSET NONE HF PIN +%token ERROR +%token <v.string> STRING +%token <v.number> NUMBER +%type <v.bdaddr> address +%type <v.string> name_opt +%type <v.number> disabled_opt +%type <v.number> type_opt +%type <v.string> pin_opt +%% + +grammar : /* empty */ + | grammar '\n' + | grammar main '\n' + | grammar error '\n' + { + file->errors++; + } + ; + +main : INTERFACE address name_opt disabled_opt + { + struct bt_interface *iface; + + if (conf_find_interface(conf, &$2)) { + yyerror("interface %s is already defined", + bdaddr_any(&$2) ? "*" : bt_ntoa(&$2, NULL)); + YYERROR; + } + + iface = conf_add_interface(conf, &$2); + if (iface == NULL) { + yyerror("could not add interface"); + YYERROR; + } + + iface->name = $3; + iface->disabled = $4; + } + | ATTACH address type_opt pin_opt + { + struct bt_device *btdev; + + if (conf_find_device(conf, &$2)) { + yyerror("device %s is already defined", + bdaddr_any(&$2) ? "*" : bt_ntoa(&$2, NULL)); + YYERROR; + } + + btdev = conf_add_device(conf, &$2); + if (btdev == NULL) { + yyerror("could not add device"); + YYERROR; + } + + btdev->pin = $4; + btdev->flags |= BTDF_ATTACH; + btdev_set_type(btdev, $3); + } + ; + +name_opt + : /* empty */ + { $$ = NULL; } + | NAME STRING + { $$ = $2; } + ; + +disabled_opt + : /* empty */ + { $$ = 0; } + | DISABLED + { $$ = 1; } + ; + +type_opt + : /* empty */ + { $$ = BTDEV_NONE; } + | TYPE NONE + { $$ = BTDEV_NONE; } + | TYPE HID + { $$ = BTDEV_HID; } + | TYPE HSET + { $$ = BTDEV_HSET; } + | TYPE HF + { $$ = BTDEV_HF; } + ; + +pin_opt + : /* empty */ + { $$ = NULL; } + | PIN STRING + { + if (($$ = calloc(HCI_PIN_SIZE, sizeof(uint8_t))) == NULL) + fatal("pin_opt calloc"); + strlcpy($$, $2, HCI_PIN_SIZE); + free($2); + } + ; + +address : STRING + { + if (strcmp($1, "*")) { + bt_aton($1, &$$); + + if (bdaddr_any(&$$)) { + /* 0:0:0:0:0:0 could be misinterpreted */ + yyerror("invalid address '%s'", $1); + free($1); + YYERROR; + } + + free($1); + } else + bdaddr_copy(&$$, BDADDR_ANY); + } + ; + +%% + +struct keywords { + const char *k_name; + int k_val; +}; + +int +yyerror(const char *fmt, ...) +{ + va_list ap; + char *nfmt; + + file->errors++; + va_start(ap, fmt); + if (asprintf(&nfmt, "%s:%d: %s", file->name, yylval.lineno, fmt) == -1) + fatalx("yyerror asprintf"); + vlog(LOG_CRIT, nfmt, ap); + va_end(ap); + free(nfmt); + return (0); +} + +int +kw_cmp(const void *k, const void *e) +{ + return (strcmp(k, ((const struct keywords *)e)->k_name)); +} + +int +lookup(char *s) +{ + /* this has to be sorted always */ + static const struct keywords keywords[] = { + { "attach", ATTACH}, + { "disabled", DISABLED}, + { "hf", HF}, + { "hid", HID}, + { "hset", HSET}, + { "interface", INTERFACE}, + { "name", NAME}, + { "none", NONE}, + { "pin", PIN}, + { "type", TYPE} + }; + const struct keywords *p; + + p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]), + sizeof(keywords[0]), kw_cmp); + + if (p) + return (p->k_val); + else + return (STRING); +} + +#define MAXPUSHBACK 128 + +char *parsebuf; +int parseindex; +char pushback_buffer[MAXPUSHBACK]; +int pushback_index = 0; + +int +lgetc(int quotec) +{ + int c, next; + + if (parsebuf) { + /* Read character from the parsebuffer instead of input. */ + if (parseindex >= 0) { + c = parsebuf[parseindex++]; + if (c != '\0') + return (c); + parsebuf = NULL; + } else + parseindex++; + } + + if (pushback_index) + return (pushback_buffer[--pushback_index]); + + if (quotec) { + if ((c = getc(file->stream)) == EOF) { + yyerror("reached end of file while parsing " + "quoted string"); + if (file == topfile || popfile() == EOF) + return (EOF); + return (quotec); + } + return (c); + } + + while ((c = getc(file->stream)) == '\\') { + next = getc(file->stream); + if (next != '\n') { + c = next; + break; + } + yylval.lineno = file->lineno; + file->lineno++; + } + + while (c == EOF) { + if (file == topfile || popfile() == EOF) + return (EOF); + c = getc(file->stream); + } + return (c); +} + +int +lungetc(int c) +{ + if (c == EOF) + return (EOF); + if (parsebuf) { + parseindex--; + if (parseindex >= 0) + return (c); + } + if (pushback_index < MAXPUSHBACK-1) + return (pushback_buffer[pushback_index++] = c); + else + return (EOF); +} + +int +findeol(void) +{ + int c; + + parsebuf = NULL; + + /* skip to either EOF or the first real EOL */ + while (1) { + if (pushback_index) + c = pushback_buffer[--pushback_index]; + else + c = lgetc(0); + if (c == '\n') { + file->lineno++; + break; + } + if (c == EOF) + break; + } + return (ERROR); +} + +int +yylex(void) +{ + char buf[8096]; + char *p; + int quotec, next, c; + int token; + + p = buf; + while ((c = lgetc(0)) == ' ' || c == '\t') + ; /* nothing */ + + yylval.lineno = file->lineno; + if (c == '#') + while ((c = lgetc(0)) != '\n' && c != EOF) + ; /* nothing */ + + switch (c) { + case '\'': + case '"': + quotec = c; + while (1) { + if ((c = lgetc(quotec)) == EOF) + return (0); + if (c == '\n') { + file->lineno++; + continue; + } else if (c == '\\') { + if ((next = lgetc(quotec)) == EOF) + return (0); + if (next == quotec || c == ' ' || c == '\t') + c = next; + else if (next == '\n') + continue; + else + lungetc(next); + } else if (c == quotec) { + *p = '\0'; + break; + } + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return (findeol()); + } + *p++ = (char)c; + } + yylval.v.string = strdup(buf); + if (yylval.v.string == NULL) + fatal("yylex: strdup"); + return (STRING); + } + +#define allowed_to_end_number(x) \ + (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=') + + if (c == '-' || isdigit(c)) { + do { + *p++ = c; + if ((unsigned)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return (findeol()); + } + } while ((c = lgetc(0)) != EOF && isdigit(c)); + lungetc(c); + if (p == buf + 1 && buf[0] == '-') + goto nodigits; + if (c == EOF || allowed_to_end_number(c)) { + const char *errstr = NULL; + + *p = '\0'; + yylval.v.number = strtonum(buf, LLONG_MIN, + LLONG_MAX, &errstr); + if (errstr) { + yyerror("\"%s\" invalid number: %s", + buf, errstr); + return (findeol()); + } + return (NUMBER); + } else { +nodigits: + while (p > buf + 1) + lungetc(*--p); + c = *--p; + if (c == '-') + return (c); + } + } + +#define allowed_in_string(x) \ + (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \ + x != '{' && x != '}' && x != '<' && x != '>' && \ + x != '!' && x != '=' && x != '/' && x != '#' && \ + x != ',')) + + if (isalnum(c) || c == ':' || c == '_' || c == '*') { + do { + *p++ = c; + if ((unsigned)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return (findeol()); + } + } while ((c = lgetc(0)) != EOF && (allowed_in_string(c))); + lungetc(c); + *p = '\0'; + if ((token = lookup(buf)) == STRING) + if ((yylval.v.string = strdup(buf)) == NULL) + fatal("yylex: strdup"); + return (token); + } + if (c == '\n') { + yylval.lineno = file->lineno; + file->lineno++; + } + if (c == EOF) + return (0); + return (c); +} + +struct file * +pushfile(const char *name) +{ + struct file *nfile; + + if ((nfile = calloc(1, sizeof(struct file))) == NULL || + (nfile->name = strdup(name)) == NULL) { + log_warn("malloc"); + return (NULL); + } + if ((nfile->stream = fopen(nfile->name, "r")) == NULL) { + log_warn("%s", nfile->name); + free(nfile->name); + free(nfile); + return (NULL); + } + nfile->lineno = 1; + TAILQ_INSERT_TAIL(&files, nfile, entry); + return (nfile); +} + +int +popfile(void) +{ + struct file *prev; + + if ((prev = TAILQ_PREV(file, files, entry)) != NULL) + prev->errors += file->errors; + + TAILQ_REMOVE(&files, file, entry); + fclose(file->stream); + free(file->name); + free(file); + file = prev; + return (file ? 0 : EOF); +} + +int +parse_config(const char *filename, struct btd *xconf) +{ + int errors = 0; + + conf = xconf; + TAILQ_INIT(&conf->interfaces); + TAILQ_INIT(&conf->devices); + + if ((file = pushfile(filename)) == NULL) + return (-1); + + topfile = file; + + yyparse(); + errors = file->errors; + popfile(); + + if (!errors && conf_find_interface(conf, BDADDR_ANY) == NULL) + if (conf_add_interface(conf, BDADDR_ANY) == NULL) + fatalx("parse_config add_interface"); + + if (!errors && conf_find_device(conf, BDADDR_ANY) == NULL) + if (conf_add_device(conf, BDADDR_ANY) == NULL) + fatalx("parse_config add_device"); + + return (errors ? -1 : 0); +} diff --git a/usr.sbin/btd/sdp.c b/usr.sbin/btd/sdp.c new file mode 100644 index 00000000000..6ff2fbfd0d3 --- /dev/null +++ b/usr.sbin/btd/sdp.c @@ -0,0 +1,738 @@ +/* $OpenBSD: sdp.c,v 1.1 2008/11/24 23:34:42 uwe Exp $ */ +/* $NetBSD: sdp.c,v 1.5 2008/04/20 19:34:23 plunky Exp $ */ + +/*- + * Copyright (c) 2006 Itronix Inc. + * 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. + * 3. The name of Itronix Inc. may not be used to endorse + * or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY ITRONIX INC. ``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 ITRONIX INC. 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. + */ +/* + * Copyright (c) 2004 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. + */ + +#include <sys/types.h> + +#include <dev/bluetooth/btdev.h> +#include <dev/usb/usb.h> +#include <dev/usb/usbhid.h> + +#include <bluetooth.h> +#include <err.h> +#include <errno.h> +#include <sdp.h> +#include <stdlib.h> +#include <usbhid.h> + +#include "btd.h" + +static int hid_mode(uint8_t *, int32_t); + +static int32_t parse_l2cap_psm(sdp_attr_t *); +static int32_t parse_rfcomm_channel(sdp_attr_t *); +static int32_t parse_hid_descriptor(sdp_attr_t *); +static int32_t parse_boolean(sdp_attr_t *); + +static int config_hid(struct btdev_attach_args *); +static int config_hset(struct btdev_attach_args *); +static int config_hf(struct btdev_attach_args *); + +uint16_t hid_services[] = { + SDP_SERVICE_CLASS_HUMAN_INTERFACE_DEVICE +}; + +uint32_t hid_attrs[] = { + SDP_ATTR_RANGE( SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST, + SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST), + SDP_ATTR_RANGE( SDP_ATTR_ADDITIONAL_PROTOCOL_DESCRIPTOR_LISTS, + SDP_ATTR_ADDITIONAL_PROTOCOL_DESCRIPTOR_LISTS), + SDP_ATTR_RANGE( 0x0205, /* HIDReconnectInitiate */ + 0x0206), /* HIDDescriptorList */ + SDP_ATTR_RANGE( 0x0209, /* HIDBatteryPower */ + 0x0209), + SDP_ATTR_RANGE( 0x020d, /* HIDNormallyConnectable */ + 0x020d) +}; + +uint16_t hset_services[] = { + SDP_SERVICE_CLASS_HEADSET +}; + +uint32_t hset_attrs[] = { + SDP_ATTR_RANGE( SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST, + SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST), +}; + +uint16_t hf_services[] = { + SDP_SERVICE_CLASS_HANDSFREE_AUDIO_GATEWAY +}; + +uint32_t hf_attrs[] = { + SDP_ATTR_RANGE( SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST, + SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST), +}; + +#define NUM(v) (sizeof(v) / sizeof(v[0])) + +static struct { + const char *name; + int (*handler)(struct btdev_attach_args *); + const char *description; + uint16_t *services; + int nservices; + uint32_t *attrs; + int nattrs; +} cfgtype[] = { + { + "HID", config_hid, "Human Interface Device", + hid_services, NUM(hid_services), + hid_attrs, NUM(hid_attrs), + }, + { + "HSET", config_hset, "Headset", + hset_services, NUM(hset_services), + hset_attrs, NUM(hset_attrs), + }, + { + "HF", config_hf, "Handsfree", + hf_services, NUM(hf_services), + hf_attrs, NUM(hf_attrs), + }, +}; + +static sdp_attr_t values[8]; +static uint8_t buffer[NUM(values)][512]; + +int +sdp_query(struct btdev_attach_args *dict, + bdaddr_t *laddr, bdaddr_t *raddr, const char *service) +{ + void *ss; + int rv, i; + + for (i = 0 ; i < NUM(values) ; i++) { + values[i].flags = SDP_ATTR_INVALID; + values[i].attr = 0; + values[i].vlen = sizeof(buffer[i]); + values[i].value = buffer[i]; + } + + for (i = 0 ; i < NUM(cfgtype) ; i++) { + if (strcasecmp(service, cfgtype[i].name) == 0) { + ss = sdp_open(laddr, raddr); + + if (ss == NULL || (errno = sdp_error(ss)) != 0) + return -1; + + rv = sdp_search(ss, + cfgtype[i].nservices, cfgtype[i].services, + cfgtype[i].nattrs, cfgtype[i].attrs, + NUM(values), values); + + if (rv != 0) { + errno = sdp_error(ss); + return -1; + } + sdp_close(ss); + + rv = (*cfgtype[i].handler)(dict); + if (rv != 0) + return -1; + + return 0; + } + } + + fatalx("bad device type"); + /* NOTREACHED */ + return -1; +} + +/* + * Configure HID results + */ +static int +config_hid(struct btdev_attach_args *dict) +{ + int32_t control_psm, interrupt_psm, + reconnect_initiate, battery_power, + normally_connectable, hid_length; + uint8_t *hid_descriptor; + int i; + + control_psm = -1; + interrupt_psm = -1; + reconnect_initiate = -1; + normally_connectable = 0; + battery_power = 0; + hid_descriptor = NULL; + hid_length = -1; + + for (i = 0; i < NUM(values) ; i++) { + if (values[i].flags != SDP_ATTR_OK) + continue; + + switch (values[i].attr) { + case SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST: + control_psm = parse_l2cap_psm(&values[i]); + break; + + case SDP_ATTR_ADDITIONAL_PROTOCOL_DESCRIPTOR_LISTS: + interrupt_psm = parse_l2cap_psm(&values[i]); + break; + + case 0x0205: /* HIDReconnectInitiate */ + reconnect_initiate = parse_boolean(&values[i]); + break; + + case 0x0206: /* HIDDescriptorList */ + if (parse_hid_descriptor(&values[i]) == 0) { + hid_descriptor = values[i].value; + hid_length = values[i].vlen; + } + break; + + case 0x0209: /* HIDBatteryPower */ + battery_power = parse_boolean(&values[i]); + break; + + case 0x020d: /* HIDNormallyConnectable */ + normally_connectable = parse_boolean(&values[i]); + break; + } + } + + if (control_psm == -1 + || interrupt_psm == -1 + || reconnect_initiate == -1 + || hid_descriptor == NULL + || hid_length == -1) + return ENOATTR; + + dict->bd_type = BTDEV_HID; + dict->bd_hid.hid_ctl = control_psm; + dict->bd_hid.hid_int = interrupt_psm; + dict->bd_hid.hid_desc = hid_descriptor; + dict->bd_hid.hid_dlen = hid_length; + dict->bd_mode = hid_mode(hid_descriptor, hid_length); + + if (!reconnect_initiate) + dict->bd_hid.hid_flags |= BTHID_INITIATE; + + return 0; +} + +/* + * Configure HSET results + */ +static int +config_hset(struct btdev_attach_args *dict) +{ + uint32_t channel; + int i; + + channel = -1; + + for (i = 0; i < NUM(values) ; i++) { + if (values[i].flags != SDP_ATTR_OK) + continue; + + switch (values[i].attr) { + case SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST: + channel = parse_rfcomm_channel(&values[i]); + break; + } + } + + if (channel == -1) + return ENOATTR; + + dict->bd_type = BTDEV_HSET; + dict->bd_hset.hset_channel = channel; + + return 0; +} + +/* + * Configure HF results + */ +static int +config_hf(struct btdev_attach_args *dict) +{ + uint32_t channel; + int i; + + channel = -1; + + for (i = 0 ; i < NUM(values) ; i++) { + if (values[i].flags != SDP_ATTR_OK) + continue; + + switch (values[i].attr) { + case SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST: + channel = parse_rfcomm_channel(&values[i]); + break; + } + } + + if (channel == -1) + return ENOATTR; + + dict->bd_type = BTDEV_HF; + dict->bd_hf.hf_listen = 1; + dict->bd_hf.hf_channel = channel; + return 0; +} + +/* + * Parse [additional] protocol descriptor list for L2CAP PSM + * + * seq8 len8 2 + * seq8 len8 2 + * uuid16 value16 3 L2CAP + * uint16 value16 3 PSM + * seq8 len8 2 + * uuid16 value16 3 HID Protocol + * === + * 15 + */ + +static int32_t +parse_l2cap_psm(sdp_attr_t *a) +{ + uint8_t *ptr = a->value; + uint8_t *end = a->value + a->vlen; + int32_t type, len, uuid, psm; + + if (end - ptr < 15) + return (-1); + + if (a->attr == SDP_ATTR_ADDITIONAL_PROTOCOL_DESCRIPTOR_LISTS) { + SDP_GET8(type, ptr); + switch (type) { + case SDP_DATA_SEQ8: + SDP_GET8(len, ptr); + break; + + case SDP_DATA_SEQ16: + SDP_GET16(len, ptr); + break; + + case SDP_DATA_SEQ32: + SDP_GET32(len, ptr); + break; + + default: + return (-1); + } + if (ptr + len > end) + return (-1); + } + + SDP_GET8(type, ptr); + switch (type) { + case SDP_DATA_SEQ8: + SDP_GET8(len, ptr); + break; + + case SDP_DATA_SEQ16: + SDP_GET16(len, ptr); + break; + + case SDP_DATA_SEQ32: + SDP_GET32(len, ptr); + break; + + default: + return (-1); + } + if (ptr + len > end) + return (-1); + + /* Protocol */ + SDP_GET8(type, ptr); + switch (type) { + case SDP_DATA_SEQ8: + SDP_GET8(len, ptr); + break; + + case SDP_DATA_SEQ16: + SDP_GET16(len, ptr); + break; + + case SDP_DATA_SEQ32: + SDP_GET32(len, ptr); + break; + + default: + return (-1); + } + if (ptr + len > end) + return (-1); + + /* UUID */ + if (ptr + 3 > end) + return (-1); + SDP_GET8(type, ptr); + switch (type) { + case SDP_DATA_UUID16: + SDP_GET16(uuid, ptr); + if (uuid != SDP_UUID_PROTOCOL_L2CAP) + return (-1); + break; + + case SDP_DATA_UUID32: /* XXX FIXME can we have 32-bit UUID */ + case SDP_DATA_UUID128: /* XXX FIXME can we have 128-bit UUID */ + default: + return (-1); + } + + /* PSM */ + if (ptr + 3 > end) + return (-1); + SDP_GET8(type, ptr); + if (type != SDP_DATA_UINT16) + return (-1); + SDP_GET16(psm, ptr); + + return (psm); +} + +/* + * Parse HID descriptor string + * + * seq8 len8 2 + * seq8 len8 2 + * uint8 value8 2 + * str value 3 + * === + * 9 + */ + +static int32_t +parse_hid_descriptor(sdp_attr_t *a) +{ + uint8_t *ptr = a->value; + uint8_t *end = a->value + a->vlen; + int32_t type, len, descriptor_type; + + if (end - ptr < 9) + return (-1); + + SDP_GET8(type, ptr); + switch (type) { + case SDP_DATA_SEQ8: + SDP_GET8(len, ptr); + break; + + case SDP_DATA_SEQ16: + SDP_GET16(len, ptr); + break; + + case SDP_DATA_SEQ32: + SDP_GET32(len, ptr); + break; + + default: + return (-1); + } + if (ptr + len > end) + return (-1); + + while (ptr < end) { + /* Descriptor */ + SDP_GET8(type, ptr); + switch (type) { + case SDP_DATA_SEQ8: + if (ptr + 1 > end) + return (-1); + SDP_GET8(len, ptr); + break; + + case SDP_DATA_SEQ16: + if (ptr + 2 > end) + return (-1); + SDP_GET16(len, ptr); + break; + + case SDP_DATA_SEQ32: + if (ptr + 4 > end) + return (-1); + SDP_GET32(len, ptr); + break; + + default: + return (-1); + } + + /* Descripor type */ + if (ptr + 1 > end) + return (-1); + SDP_GET8(type, ptr); + if (type != SDP_DATA_UINT8 || ptr + 1 > end) + return (-1); + SDP_GET8(descriptor_type, ptr); + + /* Descriptor value */ + if (ptr + 1 > end) + return (-1); + SDP_GET8(type, ptr); + switch (type) { + case SDP_DATA_STR8: + if (ptr + 1 > end) + return (-1); + SDP_GET8(len, ptr); + break; + + case SDP_DATA_STR16: + if (ptr + 2 > end) + return (-1); + SDP_GET16(len, ptr); + break; + + case SDP_DATA_STR32: + if (ptr + 4 > end) + return (-1); + SDP_GET32(len, ptr); + break; + + default: + return (-1); + } + if (ptr + len > end) + return (-1); + + if (descriptor_type == UDESC_REPORT && len > 0) { + a->value = ptr; + a->vlen = len; + + return (0); + } + + ptr += len; + } + + return (-1); +} + +/* + * Parse boolean value + * + * bool8 int8 + */ + +static int32_t +parse_boolean(sdp_attr_t *a) +{ + if (a->vlen != 2 || a->value[0] != SDP_DATA_BOOL) + return (-1); + + return (a->value[1]); +} + +/* + * Parse protocol descriptor list for the RFCOMM channel + * + * seq8 len8 2 + * seq8 len8 2 + * uuid16 value16 3 L2CAP + * seq8 len8 2 + * uuid16 value16 3 RFCOMM + * uint8 value8 2 channel + * === + * 14 + */ + +static int32_t +parse_rfcomm_channel(sdp_attr_t *a) +{ + uint8_t *ptr = a->value; + uint8_t *end = a->value + a->vlen; + int32_t type, len, uuid, channel; + + if (end - ptr < 14) + return (-1); + + SDP_GET8(type, ptr); + switch (type) { + case SDP_DATA_SEQ8: + SDP_GET8(len, ptr); + break; + + case SDP_DATA_SEQ16: + SDP_GET16(len, ptr); + break; + + case SDP_DATA_SEQ32: + SDP_GET32(len, ptr); + break; + + default: + return (-1); + } + if (ptr + len > end) + return (-1); + + /* Protocol */ + SDP_GET8(type, ptr); + switch (type) { + case SDP_DATA_SEQ8: + SDP_GET8(len, ptr); + break; + + case SDP_DATA_SEQ16: + SDP_GET16(len, ptr); + break; + + case SDP_DATA_SEQ32: + SDP_GET32(len, ptr); + break; + + default: + return (-1); + } + if (ptr + len > end) + return (-1); + + /* UUID */ + if (ptr + 3 > end) + return (-1); + SDP_GET8(type, ptr); + switch (type) { + case SDP_DATA_UUID16: + SDP_GET16(uuid, ptr); + if (uuid != SDP_UUID_PROTOCOL_L2CAP) + return (-1); + break; + + case SDP_DATA_UUID32: /* XXX FIXME can we have 32-bit UUID */ + case SDP_DATA_UUID128: /* XXX FIXME can we have 128-bit UUID */ + default: + return (-1); + } + + /* Protocol */ + SDP_GET8(type, ptr); + switch (type) { + case SDP_DATA_SEQ8: + SDP_GET8(len, ptr); + break; + + case SDP_DATA_SEQ16: + SDP_GET16(len, ptr); + break; + + case SDP_DATA_SEQ32: + SDP_GET32(len, ptr); + break; + + default: + return (-1); + } + if (ptr + len > end) + return (-1); + + /* UUID */ + if (ptr + 3 > end) + return (-1); + SDP_GET8(type, ptr); + switch (type) { + case SDP_DATA_UUID16: + SDP_GET16(uuid, ptr); + if (uuid != SDP_UUID_PROTOCOL_RFCOMM) + return (-1); + break; + + case SDP_DATA_UUID32: /* XXX FIXME can we have 32-bit UUID */ + case SDP_DATA_UUID128: /* XXX FIXME can we have 128-bit UUID */ + default: + return (-1); + } + + /* channel */ + if (ptr + 2 > end) + return (-1); + + SDP_GET8(type, ptr); + if (type != SDP_DATA_UINT8) + return (-1); + + SDP_GET8(channel, ptr); + + return (channel); +} + +/* + * return appropriate mode for HID descriptor + */ +static int +hid_mode(uint8_t *desc, int32_t dlen) +{ + report_desc_t r; + hid_data_t d; + struct hid_item h; + int mode; + + mode = BTDEV_MODE_AUTH; /* default */ + + r = hid_use_report_desc(desc, dlen); + if (r == NULL) + err(EXIT_FAILURE, "hid_use_report_desc"); + + d = hid_start_parse(r, ~0, -1); + while (hid_get_item(d, &h) > 0) { + if (h.kind == hid_collection + && HID_PAGE(h.usage) == HUP_GENERIC_DESKTOP + && HID_USAGE(h.usage) == HUG_KEYBOARD) + mode = BTDEV_MODE_ENCRYPT; + } + + hid_end_parse(d); + hid_dispose_report_desc(r); + + return mode; +} diff --git a/usr.sbin/btd/util.c b/usr.sbin/btd/util.c new file mode 100644 index 00000000000..0117a3d27c3 --- /dev/null +++ b/usr.sbin/btd/util.c @@ -0,0 +1,33 @@ +/* $OpenBSD: util.c,v 1.1 2008/11/24 23:34:42 uwe Exp $ */ + +/* + * Copyright (c) 2004 Alexander Guy <alexander.guy@andern.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, 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. + */ + +#include <sys/time.h> +#include <limits.h> + +#include "btd.h" + +time_t +getmonotime(void) +{ + struct timespec ts; + + if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) + fatal("clock_gettime"); + + return (ts.tv_sec); +} |