diff options
author | Marcel Holtmann <marcel@holtmann.org> | 2008-08-04 20:58:34 +0200 |
---|---|---|
committer | Marcel Holtmann <marcel@holtmann.org> | 2008-08-04 20:58:34 +0200 |
commit | 40372f9dc3ab115870a10343124216a041e55d17 (patch) | |
tree | ef5bfa80e3f560cf6856fd7a0d5c27c75c2cefe0 /compat | |
parent | 295eb0c879802eacff46cf3c962646363d81f6d3 (diff) |
Move hidd pand and dund into compat directory
Diffstat (limited to 'compat')
-rw-r--r-- | compat/Makefile.am | 47 | ||||
-rw-r--r-- | compat/bnep.c | 311 | ||||
-rw-r--r-- | compat/dun.c | 331 | ||||
-rw-r--r-- | compat/dund.1 | 72 | ||||
-rw-r--r-- | compat/dund.c | 637 | ||||
-rw-r--r-- | compat/dund.h | 40 | ||||
-rw-r--r-- | compat/fakehid.c | 669 | ||||
-rw-r--r-- | compat/fakehid.txt | 134 | ||||
-rw-r--r-- | compat/hidd.1 | 41 | ||||
-rw-r--r-- | compat/hidd.c | 861 | ||||
-rw-r--r-- | compat/hidd.h | 30 | ||||
-rw-r--r-- | compat/lib.h | 86 | ||||
-rw-r--r-- | compat/msdun.c | 151 | ||||
-rw-r--r-- | compat/pand.1 | 77 | ||||
-rw-r--r-- | compat/pand.c | 800 | ||||
-rw-r--r-- | compat/pand.h | 36 | ||||
-rw-r--r-- | compat/sdp.c | 718 | ||||
-rw-r--r-- | compat/sdp.h | 39 |
18 files changed, 5080 insertions, 0 deletions
diff --git a/compat/Makefile.am b/compat/Makefile.am new file mode 100644 index 00000000..4054b3e7 --- /dev/null +++ b/compat/Makefile.am @@ -0,0 +1,47 @@ + +bin_PROGRAMS = +man_MANS = + +if HIDD +bin_PROGRAMS += hidd + +hidd_SOURCES = hidd.c hidd.h sdp.c fakehid.c + +hidd_LDADD = $(top_builddir)/common/libhelper.a @BLUEZ_LIBS@ -lm + +if MANPAGES +man_MANS += hidd.1 +endif +endif + +if PAND +bin_PROGRAMS += pand + +pand_SOURCES = pand.c pand.h bnep.c sdp.c + +pand_LDADD = $(top_builddir)/common/libhelper.a @BLUEZ_LIBS@ +endif + +if MANPAGES +man_MANS += pand.1 +endif + +if DUND +bin_PROGRAMS += dund + +dund_SOURCES = dund.c dund.h lib.h sdp.c dun.c msdun.c + +dund_LDADD = $(top_builddir)/common/libhelper.a @BLUEZ_LIBS@ +endif + +if MANPAGES +man_MANS += dund.1 +endif + +AM_CFLAGS = @BLUEZ_CFLAGS@ + +INCLUDES = -I$(top_srcdir)/common + +EXTRA_DIST = fakehid.txt hidd.1 pand.1 dund.1 + +MAINTAINERCLEANFILES = Makefile.in diff --git a/compat/bnep.c b/compat/bnep.c new file mode 100644 index 00000000..604ed552 --- /dev/null +++ b/compat/bnep.c @@ -0,0 +1,311 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com> + * Copyright (C) 2002-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <sys/socket.h> +#include <sys/ioctl.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/l2cap.h> +#include <bluetooth/bnep.h> + +#include <netinet/in.h> + +#include "pand.h" + +static int ctl; + +/* Compatibility with old ioctls */ +#define OLD_BNEPCONADD 1 +#define OLD_BNEPCONDEL 2 +#define OLD_BNEPGETCONLIST 3 +#define OLD_BNEPGETCONINFO 4 + +static unsigned long bnepconnadd; +static unsigned long bnepconndel; +static unsigned long bnepgetconnlist; +static unsigned long bnepgetconninfo; + +static struct { + char *str; + uint16_t uuid; +} __svc[] = { + { "PANU", BNEP_SVC_PANU }, + { "NAP", BNEP_SVC_NAP }, + { "GN", BNEP_SVC_GN }, + { NULL } +}; + +int bnep_str2svc(char *svc, uint16_t *uuid) +{ + int i; + for (i = 0; __svc[i].str; i++) + if (!strcasecmp(svc, __svc[i].str)) { + *uuid = __svc[i].uuid; + return 0; + } + return -1; +} + +char *bnep_svc2str(uint16_t uuid) +{ + int i; + for (i = 0; __svc[i].str; i++) + if (__svc[i].uuid == uuid) + return __svc[i].str; + return NULL; +} + +int bnep_init(void) +{ + ctl = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_BNEP); + if (ctl < 0) { + perror("Failed to open control socket"); + return 1; + } + + /* Temporary ioctl compatibility hack */ + { + struct bnep_connlist_req req; + struct bnep_conninfo ci[1]; + + req.cnum = 1; + req.ci = ci; + + if (!ioctl(ctl, BNEPGETCONNLIST, &req)) { + /* New ioctls */ + bnepconnadd = BNEPCONNADD; + bnepconndel = BNEPCONNDEL; + bnepgetconnlist = BNEPGETCONNLIST; + bnepgetconninfo = BNEPGETCONNINFO; + } else { + /* Old ioctls */ + bnepconnadd = OLD_BNEPCONADD; + bnepconndel = OLD_BNEPCONDEL; + bnepgetconnlist = OLD_BNEPGETCONLIST; + bnepgetconninfo = OLD_BNEPGETCONINFO; + } + } + + return 0; +} + +int bnep_cleanup(void) +{ + close(ctl); + return 0; +} + +int bnep_show_connections(void) +{ + struct bnep_connlist_req req; + struct bnep_conninfo ci[48]; + int i; + + req.cnum = 48; + req.ci = ci; + if (ioctl(ctl, bnepgetconnlist, &req)) { + perror("Failed to get connection list"); + return -1; + } + + for (i=0; i < req.cnum; i++) { + printf("%s %s %s\n", ci[i].device, + batostr((bdaddr_t *) ci[i].dst), + bnep_svc2str(ci[i].role)); + } + return 0; +} + +int bnep_kill_connection(uint8_t *dst) +{ + struct bnep_conndel_req req; + + memcpy(req.dst, dst, ETH_ALEN); + req.flags = 0; + if (ioctl(ctl, bnepconndel, &req)) { + perror("Failed to kill connection"); + return -1; + } + return 0; +} + +int bnep_kill_all_connections(void) +{ + struct bnep_connlist_req req; + struct bnep_conninfo ci[48]; + int i; + + req.cnum = 48; + req.ci = ci; + if (ioctl(ctl, bnepgetconnlist, &req)) { + perror("Failed to get connection list"); + return -1; + } + + for (i=0; i < req.cnum; i++) { + struct bnep_conndel_req req; + memcpy(req.dst, ci[i].dst, ETH_ALEN); + req.flags = 0; + ioctl(ctl, bnepconndel, &req); + } + return 0; +} + +static int bnep_connadd(int sk, uint16_t role, char *dev) +{ + struct bnep_connadd_req req; + + strncpy(req.device, dev, 16); + req.device[15] = '\0'; + req.sock = sk; + req.role = role; + if (ioctl(ctl, bnepconnadd, &req)) + return -1; + strncpy(dev, req.device, 16); + return 0; +} + +struct __service_16 { + uint16_t dst; + uint16_t src; +} __attribute__ ((packed)); + +struct __service_32 { + uint16_t unused1; + uint16_t dst; + uint16_t unused2; + uint16_t src; +} __attribute__ ((packed)); + +struct __service_128 { + uint16_t unused1; + uint16_t dst; + uint16_t unused2[8]; + uint16_t src; + uint16_t unused3[7]; +} __attribute__ ((packed)); + +int bnep_accept_connection(int sk, uint16_t role, char *dev) +{ + struct bnep_setup_conn_req *req; + struct bnep_control_rsp *rsp; + unsigned char pkt[BNEP_MTU]; + int r; + + r = recv(sk, pkt, BNEP_MTU, 0); + if (r <= 0) + return -1; + + errno = EPROTO; + + if (r < sizeof(*req)) + return -1; + + req = (void *) pkt; + if (req->type != BNEP_CONTROL || req->ctrl != BNEP_SETUP_CONN_REQ) + return -1; + + /* FIXME: Check role UUIDs */ + + rsp = (void *) pkt; + rsp->type = BNEP_CONTROL; + rsp->ctrl = BNEP_SETUP_CONN_RSP; + rsp->resp = htons(BNEP_SUCCESS); + if (send(sk, rsp, sizeof(*rsp), 0) < 0) + return -1; + + return bnep_connadd(sk, role, dev); +} + +/* Create BNEP connection + * sk - Connect L2CAP socket + * role - Local role + * service - Remote service + * dev - Network device (contains actual dev name on return) + */ +int bnep_create_connection(int sk, uint16_t role, uint16_t svc, char *dev) +{ + struct bnep_setup_conn_req *req; + struct bnep_control_rsp *rsp; + struct __service_16 *s; + unsigned char pkt[BNEP_MTU]; + int r; + + /* Send request */ + req = (void *) pkt; + req->type = BNEP_CONTROL; + req->ctrl = BNEP_SETUP_CONN_REQ; + req->uuid_size = 2; /* 16bit UUID */ + s = (void *) req->service; + s->dst = htons(svc); + s->src = htons(role); + + if (send(sk, pkt, sizeof(*req) + sizeof(*s), 0) < 0) + return -1; + +receive: + /* Get response */ + r = recv(sk, pkt, BNEP_MTU, 0); + if (r <= 0) + return -1; + + errno = EPROTO; + + if (r < sizeof(*rsp)) + return -1; + + rsp = (void *) pkt; + if (rsp->type != BNEP_CONTROL) + return -1; + + if (rsp->ctrl != BNEP_SETUP_CONN_RSP) + goto receive; + + r = ntohs(rsp->resp); + + switch (r) { + case BNEP_SUCCESS: + break; + + case BNEP_CONN_INVALID_DST: + case BNEP_CONN_INVALID_SRC: + case BNEP_CONN_INVALID_SVC: + errno = EPROTO; + return -1; + + case BNEP_CONN_NOT_ALLOWED: + errno = EACCES; + return -1; + } + + return bnep_connadd(sk, role, dev); +} diff --git a/compat/dun.c b/compat/dun.c new file mode 100644 index 00000000..de437cf4 --- /dev/null +++ b/compat/dun.c @@ -0,0 +1,331 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com> + * Copyright (C) 2002-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <errno.h> +#include <ctype.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> +#include <syslog.h> +#include <dirent.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <sys/param.h> +#include <sys/ioctl.h> +#include <sys/socket.h> + +#include <netinet/in.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/rfcomm.h> + +#include "dund.h" +#include "lib.h" + +#define PROC_BASE "/proc" + +static int for_each_port(int (*func)(struct rfcomm_dev_info *, unsigned long), unsigned long arg) +{ + struct rfcomm_dev_list_req *dl; + struct rfcomm_dev_info *di; + long r = 0; + int sk, i; + + sk = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_RFCOMM); + if (sk < 0 ) { + perror("Can't open RFCOMM control socket"); + exit(1); + } + + dl = malloc(sizeof(*dl) + RFCOMM_MAX_DEV * sizeof(*di)); + if (!dl) { + perror("Can't allocate request memory"); + close(sk); + exit(1); + } + + dl->dev_num = RFCOMM_MAX_DEV; + di = dl->dev_info; + + if (ioctl(sk, RFCOMMGETDEVLIST, (void *) dl) < 0) { + perror("Can't get device list"); + exit(1); + } + + for (i = 0; i < dl->dev_num; i++) { + r = func(di + i, arg); + if (r) break; + } + + close(sk); + return r; +} + +static int uses_rfcomm(char *path, char *dev) +{ + struct dirent *de; + DIR *dir; + + dir = opendir(path); + if (!dir) + return 0; + + if (chdir(path) < 0) + return 0; + + while ((de = readdir(dir)) != NULL) { + char link[PATH_MAX + 1]; + int len = readlink(de->d_name, link, sizeof(link)); + if (len > 0) { + link[len] = 0; + if (strstr(link, dev)) + return 1; + } + } + + closedir(dir); + + return 0; +} + +static int find_pppd(int id, pid_t *pid) +{ + struct dirent *de; + char path[PATH_MAX + 1]; + char dev[10]; + int empty = 1; + DIR *dir; + + dir = opendir(PROC_BASE); + if (!dir) { + perror(PROC_BASE); + return -1; + } + + sprintf(dev, "rfcomm%d", id); + + *pid = 0; + while ((de = readdir(dir)) != NULL) { + empty = 0; + if (isdigit(de->d_name[0])) { + sprintf(path, "%s/%s/fd", PROC_BASE, de->d_name); + if (uses_rfcomm(path, dev)) { + *pid = atoi(de->d_name); + break; + } + } + } + closedir(dir); + + if (empty) + fprintf(stderr, "%s is empty (not mounted ?)\n", PROC_BASE); + + return *pid != 0; +} + +static int dun_exec(char *tty, char *prog, char **args) +{ + int pid = fork(); + int fd; + + switch (pid) { + case -1: + return -1; + + case 0: + break; + + default: + return pid; + } + + setsid(); + + /* Close all FDs */ + for (fd = 3; fd < 20; fd++) + close(fd); + + execvp(prog, args); + + syslog(LOG_ERR, "Error while executing %s", prog); + + exit(1); +} + +static int dun_create_tty(int sk, char *tty, int size) +{ + struct sockaddr_rc sa; + struct stat st; + socklen_t alen; + int id, try = 30; + + struct rfcomm_dev_req req = { + flags: (1 << RFCOMM_REUSE_DLC) | (1 << RFCOMM_RELEASE_ONHUP), + dev_id: -1 + }; + + alen = sizeof(sa); + if (getpeername(sk, (struct sockaddr *) &sa, &alen) < 0) + return -1; + bacpy(&req.dst, &sa.rc_bdaddr); + + alen = sizeof(sa); + if (getsockname(sk, (struct sockaddr *) &sa, &alen) < 0) + return -1; + bacpy(&req.src, &sa.rc_bdaddr); + req.channel = sa.rc_channel; + + id = ioctl(sk, RFCOMMCREATEDEV, &req); + if (id < 0) + return id; + + snprintf(tty, size, "/dev/rfcomm%d", id); + while (stat(tty, &st) < 0) { + snprintf(tty, size, "/dev/bluetooth/rfcomm/%d", id); + if (stat(tty, &st) < 0) { + snprintf(tty, size, "/dev/rfcomm%d", id); + if (try--) { + usleep(100 * 1000); + continue; + } + + memset(&req, 0, sizeof(req)); + req.dev_id = id; + ioctl(sk, RFCOMMRELEASEDEV, &req); + + return -1; + } + } + + return id; +} + +int dun_init(void) +{ + return 0; +} + +int dun_cleanup(void) +{ + return 0; +} + +static int show_conn(struct rfcomm_dev_info *di, unsigned long arg) +{ + pid_t pid; + + if (di->state == BT_CONNECTED && + (di->flags & (1<<RFCOMM_REUSE_DLC)) && + (di->flags & (1<<RFCOMM_TTY_ATTACHED)) && + (di->flags & (1<<RFCOMM_RELEASE_ONHUP))) { + + if (find_pppd(di->id, &pid)) { + char dst[18]; + ba2str(&di->dst, dst); + + printf("rfcomm%d: %s channel %d pppd pid %d\n", + di->id, dst, di->channel, pid); + } + } + return 0; +} + +static int kill_conn(struct rfcomm_dev_info *di, unsigned long arg) +{ + bdaddr_t *dst = (bdaddr_t *) arg; + pid_t pid; + + if (di->state == BT_CONNECTED && + (di->flags & (1<<RFCOMM_REUSE_DLC)) && + (di->flags & (1<<RFCOMM_TTY_ATTACHED)) && + (di->flags & (1<<RFCOMM_RELEASE_ONHUP))) { + + if (dst && bacmp(&di->dst, dst)) + return 0; + + if (find_pppd(di->id, &pid)) { + if (kill(pid, SIGINT) < 0) + perror("Kill"); + + if (!dst) + return 0; + return 1; + } + } + return 0; +} + +int dun_show_connections(void) +{ + for_each_port(show_conn, 0); + return 0; +} + +int dun_kill_connection(uint8_t *dst) +{ + for_each_port(kill_conn, (unsigned long) dst); + return 0; +} + +int dun_kill_all_connections(void) +{ + for_each_port(kill_conn, 0); + return 0; +} + +int dun_open_connection(int sk, char *pppd, char **args, int wait) +{ + char tty[100]; + int pid; + + if (dun_create_tty(sk, tty, sizeof(tty) - 1) < 0) { + syslog(LOG_ERR, "RFCOMM TTY creation failed. %s(%d)", strerror(errno), errno); + return -1; + } + + args[0] = "pppd"; + args[1] = tty; + args[2] = "nodetach"; + + pid = dun_exec(tty, pppd, args); + if (pid < 0) { + syslog(LOG_ERR, "Exec failed. %s(%d)", strerror(errno), errno); + return -1; + } + + if (wait) { + int status; + waitpid(pid, &status, 0); + /* FIXME: Check for waitpid errors */ + } + + return 0; +} diff --git a/compat/dund.1 b/compat/dund.1 new file mode 100644 index 00000000..09fb7f75 --- /dev/null +++ b/compat/dund.1 @@ -0,0 +1,72 @@ +.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.29. +.TH BlueZ "1" "February 2003" "DUN daemon" "User Commands" +.SH NAME +dund \- BlueZ Bluetooth dial-up networking daemon +.SH DESCRIPTION +DUN daemon +.SH SYNOPSIS +dund <options> [pppd options] +.SH OPTIONS +.TP +\fB\-\-show\fR \fB\-\-list\fR \fB\-l\fR +Show active DUN connections +.TP +\fB\-\-listen\fR \fB\-s\fR +Listen for DUN connections +.TP +\fB\-\-dialup\fR \fB\-u\fR +Listen for dialup/telephone connections +.TP +\fB\-\-connect\fR \fB\-c\fR <bdaddr> +Create DUN connection +.TP +\fB\-\-mrouter\fR \fB\-m\fR <bdaddr> +Create mRouter connection +.TP +\fB\-\-search\fR \fB\-Q[duration]\fR +Search and connect +.TP +\fB\-\-kill\fR \fB\-k\fR <bdaddr> +Kill DUN connection +.TP +\fB\-\-killall\fR \fB\-K\fR +Kill all DUN connections +.TP +\fB\-\-channel\fR \fB\-C\fR <channel> +RFCOMM channel +.TP +\fB\-\-device\fR \fB\-i\fR <bdaddr> +Source bdaddr +.TP +\fB\-\-nosdp\fR \fB\-D\fR +Disable SDP +.TP +\fB\-\-auth\fR \fB\-A\fR +Enable authentification +.TP +\fB\-\-encrypt\fR \fB\-E\fR +Enable encryption +.TP +\fB\-\-secure\fR \fB\-S\fR +Secure connection +.TP +\fB\-\-master\fR \fB\-M\fR +Become the master of a piconet +.TP +\fB\-\-nodetach\fR \fB\-n\fR +Do not become a daemon +.TP +\fB\-\-persist\fR \fB\-p[interval]\fR +Persist mode +.TP +\fB\-\-pppd\fR \fB\-d\fR <pppd> +Location of the PPP daemon (pppd) +.TP +\fB\-\-msdun\fR \fB\-X\fR [timeo] +Enable Microsoft dialup networking support +.TP +\fB\-\-activesync\fR \fB\-a\fR +Enable Microsoft ActiveSync networking +.TP +\fB\-\-cache\fR \fB\-C\fR [valid] +Enable address cache diff --git a/compat/dund.c b/compat/dund.c new file mode 100644 index 00000000..dea945d2 --- /dev/null +++ b/compat/dund.c @@ -0,0 +1,637 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com> + * Copyright (C) 2002-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <signal.h> +#include <getopt.h> + +#include <sys/socket.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/hci.h> +#include <bluetooth/hci_lib.h> +#include <bluetooth/rfcomm.h> +#include <bluetooth/hidp.h> + +#include "sdp.h" +#include "dund.h" +#include "lib.h" + +volatile sig_atomic_t __io_canceled; + +/* MS dialup networking support (i.e. CLIENT / CLIENTSERVER thing) */ +static int msdun = 0; + +static char *pppd = "/usr/sbin/pppd"; +static char *pppd_opts[DUN_MAX_PPP_OPTS] = + { + /* First 3 are reserved */ + "", "", "", + "noauth", + "noipdefault", + NULL + }; + +static int detach = 1; +static int persist; +static int use_sdp = 1; +static int auth; +static int encrypt; +static int secure; +static int master; +static int type = LANACCESS; +static int search_duration = 10; +static uint use_cache; + +static int channel; + +static struct { + uint valid; + char dst[40]; + bdaddr_t bdaddr; + int channel; +} cache; + +static bdaddr_t src_addr = *BDADDR_ANY; +static int src_dev = -1; + +volatile int terminate; + +enum { + NONE, + SHOW, + LISTEN, + CONNECT, + KILL +} modes; + +static int create_connection(char *dst, bdaddr_t *bdaddr, int mrouter); + +static int do_listen(void) +{ + struct sockaddr_rc sa; + int sk, lm; + + if (type == MROUTER) { + if (!cache.valid) + return -1; + + if (create_connection(cache.dst, &cache.bdaddr, type) < 0) { + syslog(LOG_ERR, "Cannot connect to mRouter device. %s(%d)", + strerror(errno), errno); + return -1; + } + } + + if (!channel) + channel = DUN_DEFAULT_CHANNEL; + + if (use_sdp) + dun_sdp_register(&src_addr, channel, type); + + if (type == MROUTER) + syslog(LOG_INFO, "Waiting for mRouter callback on channel %d", channel); + + /* Create RFCOMM socket */ + sk = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); + if (sk < 0) { + syslog(LOG_ERR, "Cannot create RFCOMM socket. %s(%d)", + strerror(errno), errno); + return -1; + } + + sa.rc_family = AF_BLUETOOTH; + sa.rc_channel = channel; + sa.rc_bdaddr = src_addr; + + if (bind(sk, (struct sockaddr *) &sa, sizeof(sa))) { + syslog(LOG_ERR, "Bind failed. %s(%d)", strerror(errno), errno); + return -1; + } + + /* Set link mode */ + lm = 0; + if (master) + lm |= RFCOMM_LM_MASTER; + if (auth) + lm |= RFCOMM_LM_AUTH; + if (encrypt) + lm |= RFCOMM_LM_ENCRYPT; + if (secure) + lm |= RFCOMM_LM_SECURE; + + if (lm && setsockopt(sk, SOL_RFCOMM, RFCOMM_LM, &lm, sizeof(lm)) < 0) { + syslog(LOG_ERR, "Failed to set link mode. %s(%d)", strerror(errno), errno); + return -1; + } + + listen(sk, 10); + + while (!terminate) { + socklen_t alen = sizeof(sa); + int nsk; + char ba[40]; + char ch[10]; + + nsk = accept(sk, (struct sockaddr *) &sa, &alen); + if (nsk < 0) { + syslog(LOG_ERR, "Accept failed. %s(%d)", strerror(errno), errno); + continue; + } + + switch (fork()) { + case 0: + break; + case -1: + syslog(LOG_ERR, "Fork failed. %s(%d)", strerror(errno), errno); + default: + close(nsk); + if (type == MROUTER) { + close(sk); + terminate = 1; + } + continue; + } + + close(sk); + + if (msdun && ms_dun(nsk, 1, msdun) < 0) { + syslog(LOG_ERR, "MSDUN failed. %s(%d)", strerror(errno), errno); + exit(0); + } + + ba2str(&sa.rc_bdaddr, ba); + sprintf(ch, "%d", channel); + + /* Setup environment */ + setenv("DUN_BDADDR", ba, 1); + setenv("DUN_CHANNEL", ch, 1); + + if (!dun_open_connection(nsk, pppd, pppd_opts, 0)) + syslog(LOG_INFO, "New connection from %s", ba); + + close(nsk); + exit(0); + } + + if (use_sdp) + dun_sdp_unregister(); + return 0; +} + +/* Connect and initiate RFCOMM session + * Returns: + * -1 - critical error (exit persist mode) + * 1 - non critical error + * 0 - success + */ +static int create_connection(char *dst, bdaddr_t *bdaddr, int mrouter) +{ + struct sockaddr_rc sa; + int sk, err = 0, ch; + + if (use_cache && cache.valid && cache.channel) { + /* Use cached channel */ + ch = cache.channel; + + } else if (!channel) { + syslog(LOG_INFO, "Searching for %s on %s", mrouter ? "SP" : "LAP", dst); + + if (dun_sdp_search(&src_addr, bdaddr, &ch, mrouter) <= 0) + return 0; + } else + ch = channel; + + syslog(LOG_INFO, "Connecting to %s channel %d", dst, ch); + + sk = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); + if (sk < 0) { + syslog(LOG_ERR, "Cannot create RFCOMM socket. %s(%d)", + strerror(errno), errno); + return -1; + } + + sa.rc_family = AF_BLUETOOTH; + sa.rc_channel = 0; + sa.rc_bdaddr = src_addr; + + if (bind(sk, (struct sockaddr *) &sa, sizeof(sa))) + syslog(LOG_ERR, "Bind failed. %s(%d)", + strerror(errno), errno); + + sa.rc_channel = ch; + sa.rc_bdaddr = *bdaddr; + + if (!connect(sk, (struct sockaddr *) &sa, sizeof(sa)) ) { + if (mrouter) { + sleep(1); + close(sk); + return 0; + } + + syslog(LOG_INFO, "Connection established"); + + if (msdun && ms_dun(sk, 0, msdun) < 0) { + syslog(LOG_ERR, "MSDUN failed. %s(%d)", strerror(errno), errno); + err = 1; + goto out; + } + + if (!dun_open_connection(sk, pppd, pppd_opts, (persist > 0))) + err = 0; + else + err = 1; + } else { + syslog(LOG_ERR, "Connect to %s failed. %s(%d)", + dst, strerror(errno), errno); + err = 1; + } + +out: + if (use_cache) { + if (!err) { + /* Succesesful connection, validate cache */ + strcpy(cache.dst, dst); + bacpy(&cache.bdaddr, bdaddr); + cache.channel = ch; + cache.valid = use_cache; + } else { + cache.channel = 0; + cache.valid--; + } + } + + close(sk); + return err; +} + +/* Search and connect + * Returns: + * -1 - critical error (exit persist mode) + * 1 - non critical error + * 0 - success + */ +static int do_connect(void) +{ + inquiry_info *ii; + int reconnect = 0; + int i, n, r = 0; + + do { + if (reconnect) + sleep(persist); + reconnect = 1; + + if (cache.valid) { + /* Use cached bdaddr */ + r = create_connection(cache.dst, &cache.bdaddr, 0); + if (r < 0) { + terminate = 1; + break; + } + continue; + } + + syslog(LOG_INFO, "Inquiring"); + + /* FIXME: Should we use non general LAP here ? */ + + ii = NULL; + n = hci_inquiry(src_dev, search_duration, 0, NULL, &ii, 0); + if (n < 0) { + syslog(LOG_ERR, "Inquiry failed. %s(%d)", strerror(errno), errno); + continue; + } + + for (i = 0; i < n; i++) { + char dst[40]; + ba2str(&ii[i].bdaddr, dst); + + r = create_connection(dst, &ii[i].bdaddr, 0); + if (r < 0) { + terminate = 1; + break; + } + } + bt_free(ii); + } while (!terminate && persist); + + return r; +} + +static void do_show(void) +{ + dun_show_connections(); +} + +static void do_kill(char *dst) +{ + if (dst) { + bdaddr_t ba; + str2ba(dst, &ba); + dun_kill_connection((void *) &ba); + } else + dun_kill_all_connections(); +} + +void sig_hup(int sig) +{ + return; +} + +void sig_term(int sig) +{ + io_cancel(); + terminate = 1; +} + +static struct option main_lopts[] = { + { "help", 0, 0, 'h' }, + { "listen", 0, 0, 's' }, + { "connect", 1, 0, 'c' }, + { "search", 2, 0, 'Q' }, + { "kill", 1, 0, 'k' }, + { "killall", 0, 0, 'K' }, + { "channel", 1, 0, 'P' }, + { "device", 1, 0, 'i' }, + { "nosdp", 0, 0, 'D' }, + { "list", 0, 0, 'l' }, + { "show", 0, 0, 'l' }, + { "nodetach", 0, 0, 'n' }, + { "persist", 2, 0, 'p' }, + { "auth", 0, 0, 'A' }, + { "encrypt", 0, 0, 'E' }, + { "secure", 0, 0, 'S' }, + { "master", 0, 0, 'M' }, + { "cache", 0, 0, 'C' }, + { "pppd", 1, 0, 'd' }, + { "msdun", 2, 0, 'X' }, + { "activesync", 0, 0, 'a' }, + { "mrouter", 1, 0, 'm' }, + { "dialup", 0, 0, 'u' }, + { 0, 0, 0, 0 } +}; + +static char main_sopts[] = "hsc:k:Kr:i:lnp::DQ::AESMP:C::P:Xam:u"; + +static char main_help[] = + "Bluetooth LAP (LAN Access over PPP) daemon version " VERSION " \n" + "Usage:\n" + "\tdund <options> [pppd options]\n" + "Options:\n" + "\t--show --list -l Show active LAP connections\n" + "\t--listen -s Listen for LAP connections\n" + "\t--dialup -u Pretend to be a dialup/telephone\n" + "\t--connect -c <bdaddr> Create LAP connection\n" + "\t--mrouter -m <bdaddr> Create mRouter connection\n" + "\t--search -Q[duration] Search and connect\n" + "\t--kill -k <bdaddr> Kill LAP connection\n" + "\t--killall -K Kill all LAP connections\n" + "\t--channel -P <channel> RFCOMM channel\n" + "\t--device -i <bdaddr> Source bdaddr\n" + "\t--nosdp -D Disable SDP\n" + "\t--auth -A Enable authentication\n" + "\t--encrypt -E Enable encryption\n" + "\t--secure -S Secure connection\n" + "\t--master -M Become the master of a piconet\n" + "\t--nodetach -n Do not become a daemon\n" + "\t--persist -p[interval] Persist mode\n" + "\t--pppd -d <pppd> Location of the PPP daemon (pppd)\n" + "\t--msdun -X[timeo] Enable Microsoft dialup networking support\n" + "\t--activesync -a Enable Microsoft ActiveSync networking\n" + "\t--cache -C[valid] Enable address cache\n"; + +int main(int argc, char *argv[]) +{ + char *dst = NULL, *src = NULL; + struct sigaction sa; + int mode = NONE; + int opt; + + while ((opt=getopt_long(argc, argv, main_sopts, main_lopts, NULL)) != -1) { + switch(opt) { + case 'l': + mode = SHOW; + detach = 0; + break; + + case 's': + mode = LISTEN; + type = LANACCESS; + break; + + case 'c': + mode = CONNECT; + dst = strdup(optarg); + break; + + case 'Q': + mode = CONNECT; + dst = NULL; + if (optarg) + search_duration = atoi(optarg); + break; + + case 'k': + mode = KILL; + detach = 0; + dst = strdup(optarg); + break; + + case 'K': + mode = KILL; + detach = 0; + dst = NULL; + break; + + case 'P': + channel = atoi(optarg); + break; + + case 'i': + src = strdup(optarg); + break; + + case 'D': + use_sdp = 0; + break; + + case 'A': + auth = 1; + break; + + case 'E': + encrypt = 1; + break; + + case 'S': + secure = 1; + break; + + case 'M': + master = 1; + break; + + case 'n': + detach = 0; + break; + + case 'p': + if (optarg) + persist = atoi(optarg); + else + persist = 5; + break; + + case 'C': + if (optarg) + use_cache = atoi(optarg); + else + use_cache = 2; + break; + + case 'd': + pppd = strdup(optarg); + break; + + case 'X': + if (optarg) + msdun = atoi(optarg); + else + msdun = 10; + break; + + case 'a': + msdun = 10; + type = ACTIVESYNC; + break; + + case 'm': + mode = LISTEN; + dst = strdup(optarg); + type = MROUTER; + break; + + case 'u': + mode = LISTEN; + type = DIALUP; + break; + + case 'h': + default: + printf(main_help); + exit(0); + } + } + + argc -= optind; + argv += optind; + + /* The rest is pppd options */ + if (argc > 0) { + for (opt = 3; argc && opt < DUN_MAX_PPP_OPTS; argc--, opt++) + pppd_opts[opt] = *argv++; + pppd_opts[opt] = NULL; + } + + io_init(); + + if (dun_init()) + return -1; + + /* Check non daemon modes first */ + switch (mode) { + case SHOW: + do_show(); + return 0; + + case KILL: + do_kill(dst); + return 0; + + case NONE: + printf(main_help); + return 0; + } + + /* Initialize signals */ + memset(&sa, 0, sizeof(sa)); + sa.sa_flags = SA_NOCLDSTOP; + sa.sa_handler = SIG_IGN; + sigaction(SIGCHLD, &sa, NULL); + sigaction(SIGPIPE, &sa, NULL); + + sa.sa_handler = sig_term; + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + + sa.sa_handler = sig_hup; + sigaction(SIGHUP, &sa, NULL); + + if (detach && daemon(0, 0)) { + perror("Can't start daemon"); + exit(1); + } + + openlog("dund", LOG_PID | LOG_NDELAY | LOG_PERROR, LOG_DAEMON); + syslog(LOG_INFO, "Bluetooth DUN daemon version %s", VERSION); + + if (src) { + src_dev = hci_devid(src); + if (src_dev < 0 || hci_devba(src_dev, &src_addr) < 0) { + syslog(LOG_ERR, "Invalid source. %s(%d)", strerror(errno), errno); + return -1; + } + } + + if (dst) { + strncpy(cache.dst, dst, sizeof(cache.dst) - 1); + str2ba(dst, &cache.bdaddr); + + /* Disable cache invalidation */ + use_cache = cache.valid = ~0; + } + + switch (mode) { + case CONNECT: + do_connect(); + break; + + case LISTEN: + do_listen(); + break; + } + + return 0; +} diff --git a/compat/dund.h b/compat/dund.h new file mode 100644 index 00000000..c3308e1e --- /dev/null +++ b/compat/dund.h @@ -0,0 +1,40 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com> + * Copyright (C) 2002-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#define DUN_CONFIG_DIR "/etc/bluetooth/dun" + +#define DUN_DEFAULT_CHANNEL 1 + +#define DUN_MAX_PPP_OPTS 40 + +int dun_init(void); +int dun_cleanup(void); + +int dun_show_connections(void); +int dun_kill_connection(uint8_t *dst); +int dun_kill_all_connections(void); + +int dun_open_connection(int sk, char *pppd, char **pppd_opts, int wait); + +int ms_dun(int fd, int server, int timeo); diff --git a/compat/fakehid.c b/compat/fakehid.c new file mode 100644 index 00000000..feefe2a6 --- /dev/null +++ b/compat/fakehid.c @@ -0,0 +1,669 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2003-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#define _GNU_SOURCE +#include <stdio.h> +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> +#include <signal.h> +#include <sys/poll.h> +#include <sys/ioctl.h> +#include <sys/socket.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/rfcomm.h> +#include <bluetooth/hidp.h> + +#include "hidd.h" +#include "uinput.h" + +#include <math.h> + +#ifdef NEED_PPOLL +#include "ppoll.h" +#endif + +static volatile sig_atomic_t __io_canceled = 0; + +static void sig_hup(int sig) +{ +} + +static void sig_term(int sig) +{ + __io_canceled = 1; +} + +static void send_event(int fd, uint16_t type, uint16_t code, int32_t value) +{ + struct uinput_event event; + int len; + + if (fd <= fileno(stderr)) + return; + + memset(&event, 0, sizeof(event)); + event.type = type; + event.code = code; + event.value = value; + + len = write(fd, &event, sizeof(event)); +} + +static int uinput_create(char *name, int keyboard, int mouse) +{ + struct uinput_dev dev; + int fd, aux; + + fd = open("/dev/uinput", O_RDWR); + if (fd < 0) { + fd = open("/dev/input/uinput", O_RDWR); + if (fd < 0) { + fd = open("/dev/misc/uinput", O_RDWR); + if (fd < 0) { + fprintf(stderr, "Can't open input device: %s (%d)\n", + strerror(errno), errno); + return -1; + } + } + } + + memset(&dev, 0, sizeof(dev)); + + if (name) + strncpy(dev.name, name, UINPUT_MAX_NAME_SIZE); + + dev.id.bustype = BUS_BLUETOOTH; + dev.id.vendor = 0x0000; + dev.id.product = 0x0000; + dev.id.version = 0x0000; + + if (write(fd, &dev, sizeof(dev)) < 0) { + fprintf(stderr, "Can't write device information: %s (%d)\n", + strerror(errno), errno); + close(fd); + return -1; + } + + if (mouse) { + ioctl(fd, UI_SET_EVBIT, EV_REL); + + for (aux = REL_X; aux <= REL_MISC; aux++) + ioctl(fd, UI_SET_RELBIT, aux); + } + + if (keyboard) { + ioctl(fd, UI_SET_EVBIT, EV_KEY); + ioctl(fd, UI_SET_EVBIT, EV_LED); + ioctl(fd, UI_SET_EVBIT, EV_REP); + + for (aux = KEY_RESERVED; aux <= KEY_UNKNOWN; aux++) + ioctl(fd, UI_SET_KEYBIT, aux); + + //for (aux = LED_NUML; aux <= LED_MISC; aux++) + // ioctl(fd, UI_SET_LEDBIT, aux); + } + + if (mouse) { + ioctl(fd, UI_SET_EVBIT, EV_KEY); + + for (aux = BTN_LEFT; aux <= BTN_BACK; aux++) + ioctl(fd, UI_SET_KEYBIT, aux); + } + + ioctl(fd, UI_DEV_CREATE); + + return fd; +} + +static int rfcomm_connect(const bdaddr_t *src, const bdaddr_t *dst, uint8_t channel) +{ + struct sockaddr_rc addr; + int sk; + + sk = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); + if (sk < 0) { + fprintf(stderr, "Can't create socket: %s (%d)\n", + strerror(errno), errno); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.rc_family = AF_BLUETOOTH; + bacpy(&addr.rc_bdaddr, src); + + if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + fprintf(stderr, "Can't bind socket: %s (%d)\n", + strerror(errno), errno); + close(sk); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.rc_family = AF_BLUETOOTH; + bacpy(&addr.rc_bdaddr, dst); + addr.rc_channel = channel; + + if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + fprintf(stderr, "Can't connect: %s (%d)\n", + strerror(errno), errno); + close(sk); + return -1; + } + + return sk; +} + +static void func(int fd) +{ +} + +static void back(int fd) +{ +} + +static void next(int fd) +{ +} + +static void button(int fd, unsigned int button, int is_press) +{ + switch (button) { + case 1: + send_event(fd, EV_KEY, BTN_LEFT, is_press); + break; + case 3: + send_event(fd, EV_KEY, BTN_RIGHT, is_press); + break; + } + + send_event(fd, EV_SYN, SYN_REPORT, 0); +} + +static void move(int fd, unsigned int direction) +{ + double angle; + int32_t x, y; + + angle = (direction * 22.5) * 3.1415926 / 180; + x = (int) (sin(angle) * 8); + y = (int) (cos(angle) * -8); + + send_event(fd, EV_REL, REL_X, x); + send_event(fd, EV_REL, REL_Y, y); + + send_event(fd, EV_SYN, SYN_REPORT, 0); +} + +static inline void epox_decode(int fd, unsigned char event) +{ + switch (event) { + case 48: + func(fd); break; + case 55: + back(fd); break; + case 56: + next(fd); break; + case 53: + button(fd, 1, 1); break; + case 121: + button(fd, 1, 0); break; + case 113: + break; + case 54: + button(fd, 3, 1); break; + case 120: + button(fd, 3, 0); break; + case 112: + break; + case 51: + move(fd, 0); break; + case 97: + move(fd, 1); break; + case 65: + move(fd, 2); break; + case 98: + move(fd, 3); break; + case 50: + move(fd, 4); break; + case 99: + move(fd, 5); break; + case 67: + move(fd, 6); break; + case 101: + move(fd, 7); break; + case 52: + move(fd, 8); break; + case 100: + move(fd, 9); break; + case 66: + move(fd, 10); break; + case 102: + move(fd, 11); break; + case 49: + move(fd, 12); break; + case 103: + move(fd, 13); break; + case 57: + move(fd, 14); break; + case 104: + move(fd, 15); break; + case 69: + break; + default: + printf("Unknown event code %d\n", event); + break; + } +} + +int epox_presenter(const bdaddr_t *src, const bdaddr_t *dst, uint8_t channel) +{ + unsigned char buf[16]; + struct sigaction sa; + struct pollfd p; + sigset_t sigs; + char addr[18]; + int i, fd, sk, len; + + sk = rfcomm_connect(src, dst, channel); + if (sk < 0) + return -1; + + fd = uinput_create("Bluetooth Presenter", 0, 1); + if (fd < 0) { + close(sk); + return -1; + } + + ba2str(dst, addr); + + printf("Connected to %s on channel %d\n", addr, channel); + printf("Press CTRL-C for hangup\n"); + + memset(&sa, 0, sizeof(sa)); + sa.sa_flags = SA_NOCLDSTOP; + sa.sa_handler = SIG_IGN; + sigaction(SIGCHLD, &sa, NULL); + sigaction(SIGPIPE, &sa, NULL); + + sa.sa_handler = sig_term; + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + + sa.sa_handler = sig_hup; + sigaction(SIGHUP, &sa, NULL); + + sigfillset(&sigs); + sigdelset(&sigs, SIGCHLD); + sigdelset(&sigs, SIGPIPE); + sigdelset(&sigs, SIGTERM); + sigdelset(&sigs, SIGINT); + sigdelset(&sigs, SIGHUP); + + p.fd = sk; + p.events = POLLIN | POLLERR | POLLHUP; + + while (!__io_canceled) { + p.revents = 0; + if (ppoll(&p, 1, NULL, &sigs) < 1) + continue; + + len = read(sk, buf, sizeof(buf)); + if (len < 0) + break; + + for (i = 0; i < len; i++) + epox_decode(fd, buf[i]); + } + + printf("Disconnected\n"); + + ioctl(fd, UI_DEV_DESTROY); + + close(fd); + close(sk); + + return 0; +} + +int headset_presenter(const bdaddr_t *src, const bdaddr_t *dst, uint8_t channel) +{ + printf("Not implemented\n"); + return -1; +} + +/* The strange meta key close to Ctrl has been assigned to Esc, + Fn key to CtrlR and the left space to Alt*/ + +static unsigned char jthree_keycodes[63] = { + KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, + KEY_Q, KEY_W, KEY_E, KEY_R, KEY_T, + KEY_A, KEY_S, KEY_D, KEY_F, KEY_G, + KEY_Z, KEY_X, KEY_C, KEY_V, KEY_B, + KEY_LEFTALT, KEY_TAB, KEY_CAPSLOCK, KEY_ESC, + KEY_7, KEY_8, KEY_9, KEY_0, KEY_MINUS, KEY_EQUAL, KEY_BACKSPACE, + KEY_Y, KEY_U, KEY_I, KEY_O, KEY_P, KEY_LEFTBRACE, KEY_RIGHTBRACE, + KEY_H, KEY_J, KEY_K, KEY_L, KEY_SEMICOLON, KEY_APOSTROPHE, KEY_ENTER, + KEY_N, KEY_M, KEY_COMMA, KEY_DOT, KEY_SLASH, KEY_UP, + KEY_SPACE, KEY_COMPOSE, KEY_LEFT, KEY_DOWN, KEY_RIGHT, + KEY_LEFTCTRL, KEY_RIGHTSHIFT, KEY_LEFTSHIFT, KEY_DELETE, KEY_RIGHTCTRL, KEY_RIGHTALT, +}; + +static inline void jthree_decode(int fd, unsigned char event) +{ + if (event > 63) + send_event(fd, EV_KEY, jthree_keycodes[event & 0x3f], 0); + else + send_event(fd, EV_KEY, jthree_keycodes[event - 1], 1); +} + +int jthree_keyboard(const bdaddr_t *src, const bdaddr_t *dst, uint8_t channel) +{ + unsigned char buf[16]; + struct sigaction sa; + struct pollfd p; + sigset_t sigs; + char addr[18]; + int i, fd, sk, len; + + sk = rfcomm_connect(src, dst, channel); + if (sk < 0) + return -1; + + fd = uinput_create("J-Three Keyboard", 1, 0); + if (fd < 0) { + close(sk); + return -1; + } + + ba2str(dst, addr); + + printf("Connected to %s on channel %d\n", addr, channel); + printf("Press CTRL-C for hangup\n"); + + memset(&sa, 0, sizeof(sa)); + sa.sa_flags = SA_NOCLDSTOP; + sa.sa_handler = SIG_IGN; + sigaction(SIGCHLD, &sa, NULL); + sigaction(SIGPIPE, &sa, NULL); + + sa.sa_handler = sig_term; + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + + sa.sa_handler = sig_hup; + sigaction(SIGHUP, &sa, NULL); + + sigfillset(&sigs); + sigdelset(&sigs, SIGCHLD); + sigdelset(&sigs, SIGPIPE); + sigdelset(&sigs, SIGTERM); + sigdelset(&sigs, SIGINT); + sigdelset(&sigs, SIGHUP); + + p.fd = sk; + p.events = POLLIN | POLLERR | POLLHUP; + + while (!__io_canceled) { + p.revents = 0; + if (ppoll(&p, 1, NULL, &sigs) < 1) + continue; + + len = read(sk, buf, sizeof(buf)); + if (len < 0) + break; + + for (i = 0; i < len; i++) + jthree_decode(fd, buf[i]); + } + + printf("Disconnected\n"); + + ioctl(fd, UI_DEV_DESTROY); + + close(fd); + close(sk); + + return 0; +} + +static const int celluon_xlate_num[10] = { + KEY_0, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9 +}; + +static const int celluon_xlate_char[26] = { + KEY_A, KEY_B, KEY_C, KEY_D, KEY_E, KEY_F, KEY_G, KEY_H, KEY_I, KEY_J, + KEY_K, KEY_L, KEY_M, KEY_N, KEY_O, KEY_P, KEY_Q, KEY_R, KEY_S, KEY_T, + KEY_U, KEY_V, KEY_W, KEY_X, KEY_Y, KEY_Z +}; + +static int celluon_xlate(int c) +{ + if (c >= '0' && c <= '9') + return celluon_xlate_num[c - '0']; + + if (c >= 'A' && c <= 'Z') + return celluon_xlate_char[c - 'A']; + + switch (c) { + case 0x08: + return KEY_BACKSPACE; + case 0x09: + return KEY_TAB; + case 0x0d: + return KEY_ENTER; + case 0x11: + return KEY_LEFTCTRL; + case 0x14: + return KEY_CAPSLOCK; + case 0x20: + return KEY_SPACE; + case 0x25: + return KEY_LEFT; + case 0x26: + return KEY_UP; + case 0x27: + return KEY_RIGHT; + case 0x28: + return KEY_DOWN; + case 0x2e: + return KEY_DELETE; + case 0x5b: + return KEY_MENU; + case 0xa1: + return KEY_RIGHTSHIFT; + case 0xa0: + return KEY_LEFTSHIFT; + case 0xba: + return KEY_SEMICOLON; + case 0xbd: + return KEY_MINUS; + case 0xbc: + return KEY_COMMA; + case 0xbb: + return KEY_EQUAL; + case 0xbe: + return KEY_DOT; + case 0xbf: + return KEY_SLASH; + case 0xc0: + return KEY_GRAVE; + case 0xdb: + return KEY_LEFTBRACE; + case 0xdc: + return KEY_BACKSLASH; + case 0xdd: + return KEY_RIGHTBRACE; + case 0xde: + return KEY_APOSTROPHE; + case 0xff03: + return KEY_HOMEPAGE; + case 0xff04: + return KEY_TIME; + case 0xff06: + return KEY_OPEN; + case 0xff07: + return KEY_LIST; + case 0xff08: + return KEY_MAIL; + case 0xff30: + return KEY_CALC; + case 0xff1a: /* Map FN to ALT */ + return KEY_LEFTALT; + case 0xff2f: + return KEY_INFO; + default: + printf("Unknown key %x\n", c); + return c; + } +} + +struct celluon_state { + int len; /* Expected length of current packet */ + int count; /* Number of bytes received */ + int action; + int key; +}; + +static void celluon_decode(int fd, struct celluon_state *s, uint8_t c) +{ + if (s->count < 2 && c != 0xa5) { + /* Lost Sync */ + s->count = 0; + return; + } + + switch (s->count) { + case 0: + /* New packet - Reset state */ + s->len = 30; + s->key = 0; + break; + case 1: + break; + case 6: + s->action = c; + break; + case 28: + s->key = c; + if (c == 0xff) + s->len = 31; + break; + case 29: + case 30: + if (s->count == s->len - 1) { + /* TODO: Verify checksum */ + if (s->action < 2) { + send_event(fd, EV_KEY, celluon_xlate(s->key), + s->action); + } + s->count = -1; + } else { + s->key = (s->key << 8) | c; + } + break; + } + + s->count++; + + return; +} + +int celluon_keyboard(const bdaddr_t *src, const bdaddr_t *dst, uint8_t channel) +{ + unsigned char buf[16]; + struct sigaction sa; + struct pollfd p; + sigset_t sigs; + char addr[18]; + int i, fd, sk, len; + struct celluon_state s; + + sk = rfcomm_connect(src, dst, channel); + if (sk < 0) + return -1; + + fd = uinput_create("Celluon Keyboard", 1, 0); + if (fd < 0) { + close(sk); + return -1; + } + + ba2str(dst, addr); + + printf("Connected to %s on channel %d\n", addr, channel); + printf("Press CTRL-C for hangup\n"); + + memset(&sa, 0, sizeof(sa)); + sa.sa_flags = SA_NOCLDSTOP; + sa.sa_handler = SIG_IGN; + sigaction(SIGCHLD, &sa, NULL); + sigaction(SIGPIPE, &sa, NULL); + + sa.sa_handler = sig_term; + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + + sa.sa_handler = sig_hup; + sigaction(SIGHUP, &sa, NULL); + + sigfillset(&sigs); + sigdelset(&sigs, SIGCHLD); + sigdelset(&sigs, SIGPIPE); + sigdelset(&sigs, SIGTERM); + sigdelset(&sigs, SIGINT); + sigdelset(&sigs, SIGHUP); + + p.fd = sk; + p.events = POLLIN | POLLERR | POLLHUP; + + memset(&s, 0, sizeof(s)); + + while (!__io_canceled) { + p.revents = 0; + if (ppoll(&p, 1, NULL, &sigs) < 1) + continue; + + len = read(sk, buf, sizeof(buf)); + if (len < 0) + break; + + for (i = 0; i < len; i++) + celluon_decode(fd, &s, buf[i]); + } + + printf("Disconnected\n"); + + ioctl(fd, UI_DEV_DESTROY); + + close(fd); + close(sk); + + return 0; +} diff --git a/compat/fakehid.txt b/compat/fakehid.txt new file mode 100644 index 00000000..000d0ee2 --- /dev/null +++ b/compat/fakehid.txt @@ -0,0 +1,134 @@ +EPox Presenter +============== + +# hcitool inq +Inquiring ... + 00:04:61:aa:bb:cc clock offset: 0x1ded class: 0x004000 + +# hcitool info 00:04:61:aa:bb:cc +Requesting information ... + BD Address: 00:04:61:aa:bb:cc + OUI Company: EPOX Computer Co., Ltd. (00-04-61) + Device Name: EPox BT-PM01B aabbcc + LMP Version: 1.1 (0x1) LMP Subversion: 0xf78 + Manufacturer: Cambridge Silicon Radio (10) + Features: 0xff 0xff 0x0f 0x00 0x00 0x00 0x00 0x00 + <3-slot packets> <5-slot packets> <encryption> <slot offset> + <timing accuracy> <role switch> <hold mode> <sniff mode> + <park state> <RSSI> <channel quality> <SCO link> <HV2 packets> + <HV3 packets> <u-law log> <A-law log> <CVSD> <paging scheme> + <power control> <transparent SCO> + +# sdptool records --raw 00:04:61:aa:bb:cc +Sequence + Attribute 0x0000 - ServiceRecordHandle + UINT32 0x00010000 + Attribute 0x0001 - ServiceClassIDList + Sequence + UUID16 0x1101 - SerialPort + Attribute 0x0004 - ProtocolDescriptorList + Sequence + Sequence + UUID16 0x0100 - L2CAP + Sequence + UUID16 0x0003 - RFCOMM + UINT8 0x01 + Attribute 0x0100 + String Cable Replacement + + +J-Three Keyboard +================ + +# hcitool inq +Inquiring ... + 00:0A:3A:aa:bb:cc clock offset: 0x3039 class: 0x001f00 + +# hcitool info 00:0A:3A:aa:bb:cc +Password: +Requesting information ... + BD Address: 00:0A:3A:aa:bb:cc + OUI Company: J-THREE INTERNATIONAL Holding Co., Ltd. (00-0A-3A) + Device Name: KEYBOARD + LMP Version: 1.1 (0x1) LMP Subversion: 0x2c2 + Manufacturer: Cambridge Silicon Radio (10) + Features: 0xbc 0x06 0x07 0x00 0x00 0x00 0x00 0x00 + <encryption> <slot offset> <timing accuracy> <role switch> + <sniff mode> <RSSI> <channel quality> <CVSD> <paging scheme> + <power control> + +# sdptool records --raw 00:0A:3A:aa:bb:cc +Sequence + Attribute 0x0000 - ServiceRecordHandle + UINT32 0x00010000 + Attribute 0x0001 - ServiceClassIDList + Sequence + UUID16 0x1101 - SerialPort + Attribute 0x0004 - ProtocolDescriptorList + Sequence + Sequence + UUID16 0x0100 - L2CAP + Sequence + UUID16 0x0003 - RFCOMM + UINT8 0x01 + Attribute 0x0006 - LanguageBaseAttributeIDList + Sequence + UINT16 0x656e + UINT16 0x006a + UINT16 0x0100 + Attribute 0x0100 + String SPP slave + + +Celluon Laserkey Keyboard +========================= + +# hcitool inq +Inquiring ... + 00:0B:24:aa:bb:cc clock offset: 0x3ab6 class: 0x400210 + +# hcitool info 00:0B:24:aa:bb:cc +Requesting information ... + BD Address: 00:0B:24:aa:bb:cc + OUI Company: AirLogic (00-0B-24) + Device Name: CL800BT + LMP Version: 1.1 (0x1) LMP Subversion: 0x291 + Manufacturer: Cambridge Silicon Radio (10) + Features: 0xff 0xff 0x0f 0x00 0x00 0x00 0x00 0x00 + <3-slot packets> <5-slot packets> <encryption> <slot offset> + <timing accuracy> <role switch> <hold mode> <sniff mode> + <park state> <RSSI> <channel quality> <SCO link> <HV2 packets> + <HV3 packets> <u-law log> <A-law log> <CVSD> <paging scheme> + <power control> <transparent SCO> + +# sdptool records --raw 00:0B:24:aa:bb:cc +Sequence + Attribute 0x0000 - ServiceRecordHandle + UINT32 0x00010000 + Attribute 0x0001 - ServiceClassIDList + Sequence + UUID16 0x1101 - SerialPort + Attribute 0x0004 - ProtocolDescriptorList + Sequence + Sequence + UUID16 0x0100 - L2CAP + Sequence + UUID16 0x0003 - RFCOMM + UINT8 0x01 + Attribute 0x0100 + String Serial Port + +Packet format is as follows (all fields little-endian): + 0 uint16 magic # 0x5a5a + 2 uint32 unknown # ??? + 6 uint8 action # 0 = keyup, 1 = keydown, 2 = repeat + # 3, 4, 5, 6 = ??? (Mouse mode) + 7 uint8 unknown[9] # ??? + 16 uint8 action2 # ??? same as action + 17 uint16 x # Horizontal coordinate + 19 uint16 y # Vertical coordinate + 21 uint16 time # Some sort of timestamp + 23 uint8 unknown[5] # ??? + 28 uint8 key[] # single byte keycode or 0xff byte + # follwed by special keycode byte. + Each packet followed by a checksum byte. diff --git a/compat/hidd.1 b/compat/hidd.1 new file mode 100644 index 00000000..b186ac24 --- /dev/null +++ b/compat/hidd.1 @@ -0,0 +1,41 @@ +.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.33. +.TH HIDD "1" "May 2004" "hidd - Bluetooth HID daemon" "User Commands" +.SH NAME +hidd \- Bluetooth HID daemon +.SH DESCRIPTION +hidd - Bluetooth HID daemon +.SS "Usage:" +.IP +hidd [options] [commands] +.SH OPTIONS +.TP +\fB\-i\fR <hciX|bdaddr> +Local HCI device or BD Address +.TP +\fB\-t\fR <timeout> +Set idle timeout (in minutes) +.TP +\fB\-n\fR, \fB\-\-nodaemon\fR +Don't fork daemon to background +.TP +\fB\-h\fR, \fB\-\-help\fR +Display help +.SS "Commands:" +.TP +\fB\-\-server\fR +Start HID server +.TP +\fB\-\-search\fR +Search for HID devices +.TP +\fB\-\-connect\fR <bdaddr> +Connect remote HID device +.TP +\fB\-\-kill\fR <bdaddr> +Terminate HID connection +.TP +\fB\-\-killall\fR +Terminate all connections +.TP +\fB\-\-show\fR +List current HID connections diff --git a/compat/hidd.c b/compat/hidd.c new file mode 100644 index 00000000..6a0058c4 --- /dev/null +++ b/compat/hidd.c @@ -0,0 +1,861 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2003-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#define _GNU_SOURCE +#include <stdio.h> +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <signal.h> +#include <getopt.h> +#include <sys/poll.h> +#include <sys/ioctl.h> +#include <sys/socket.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/hci.h> +#include <bluetooth/hci_lib.h> +#include <bluetooth/l2cap.h> +#include <bluetooth/sdp.h> +#include <bluetooth/hidp.h> + +#include "sdp.h" +#include "hidd.h" + +#ifdef NEED_PPOLL +#include "ppoll.h" +#endif + +enum { + NONE, + SHOW, + SERVER, + SEARCH, + CONNECT, + KILL +}; + +static volatile sig_atomic_t __io_canceled = 0; + +static void sig_hup(int sig) +{ +} + +static void sig_term(int sig) +{ + __io_canceled = 1; +} + +static int l2cap_connect(bdaddr_t *src, bdaddr_t *dst, unsigned short psm) +{ + struct sockaddr_l2 addr; + struct l2cap_options opts; + int sk; + + if ((sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP)) < 0) + return -1; + + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + bacpy(&addr.l2_bdaddr, src); + + if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + close(sk); + return -1; + } + + memset(&opts, 0, sizeof(opts)); + opts.imtu = HIDP_DEFAULT_MTU; + opts.omtu = HIDP_DEFAULT_MTU; + opts.flush_to = 0xffff; + + setsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &opts, sizeof(opts)); + + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + bacpy(&addr.l2_bdaddr, dst); + addr.l2_psm = htobs(psm); + + if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + close(sk); + return -1; + } + + return sk; +} + +static int l2cap_listen(const bdaddr_t *bdaddr, unsigned short psm, int lm, int backlog) +{ + struct sockaddr_l2 addr; + struct l2cap_options opts; + int sk; + + if ((sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP)) < 0) + return -1; + + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + bacpy(&addr.l2_bdaddr, bdaddr); + addr.l2_psm = htobs(psm); + + if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + close(sk); + return -1; + } + + setsockopt(sk, SOL_L2CAP, L2CAP_LM, &lm, sizeof(lm)); + + memset(&opts, 0, sizeof(opts)); + opts.imtu = HIDP_DEFAULT_MTU; + opts.omtu = HIDP_DEFAULT_MTU; + opts.flush_to = 0xffff; + + setsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &opts, sizeof(opts)); + + if (listen(sk, backlog) < 0) { + close(sk); + return -1; + } + + return sk; +} + +static int l2cap_accept(int sk, bdaddr_t *bdaddr) +{ + struct sockaddr_l2 addr; + socklen_t addrlen; + int nsk; + + memset(&addr, 0, sizeof(addr)); + addrlen = sizeof(addr); + + if ((nsk = accept(sk, (struct sockaddr *) &addr, &addrlen)) < 0) + return -1; + + if (bdaddr) + bacpy(bdaddr, &addr.l2_bdaddr); + + return nsk; +} + +static int request_authentication(bdaddr_t *src, bdaddr_t *dst) +{ + struct hci_conn_info_req *cr; + char addr[18]; + int err, dd, dev_id; + + ba2str(src, addr); + dev_id = hci_devid(addr); + if (dev_id < 0) + return dev_id; + + dd = hci_open_dev(dev_id); + if (dd < 0) + return dd; + + cr = malloc(sizeof(*cr) + sizeof(struct hci_conn_info)); + if (!cr) + return -ENOMEM; + + bacpy(&cr->bdaddr, dst); + cr->type = ACL_LINK; + err = ioctl(dd, HCIGETCONNINFO, (unsigned long) cr); + if (err < 0) { + free(cr); + hci_close_dev(dd); + return err; + } + + err = hci_authenticate_link(dd, htobs(cr->conn_info->handle), 25000); + + free(cr); + hci_close_dev(dd); + + return err; +} + +static int request_encryption(bdaddr_t *src, bdaddr_t *dst) +{ + struct hci_conn_info_req *cr; + char addr[18]; + int err, dd, dev_id; + + ba2str(src, addr); + dev_id = hci_devid(addr); + if (dev_id < 0) + return dev_id; + + dd = hci_open_dev(dev_id); + if (dd < 0) + return dd; + + cr = malloc(sizeof(*cr) + sizeof(struct hci_conn_info)); + if (!cr) + return -ENOMEM; + + bacpy(&cr->bdaddr, dst); + cr->type = ACL_LINK; + err = ioctl(dd, HCIGETCONNINFO, (unsigned long) cr); + if (err < 0) { + free(cr); + hci_close_dev(dd); + return err; + } + + err = hci_encrypt_link(dd, htobs(cr->conn_info->handle), 1, 25000); + + free(cr); + hci_close_dev(dd); + + return err; +} + +static void enable_sixaxis(int csk) +{ + const unsigned char buf[] = { + 0x53 /*HIDP_TRANS_SET_REPORT | HIDP_DATA_RTYPE_FEATURE*/, + 0xf4, 0x42, 0x03, 0x00, 0x00 }; + int err; + + err = write(csk, buf, sizeof(buf)); +} + +static int create_device(int ctl, int csk, int isk, uint8_t subclass, int nosdp, int nocheck, int bootonly, int encrypt, int timeout) +{ + struct hidp_connadd_req req; + struct sockaddr_l2 addr; + socklen_t addrlen; + bdaddr_t src, dst; + char bda[18]; + int err; + + memset(&addr, 0, sizeof(addr)); + addrlen = sizeof(addr); + + if (getsockname(csk, (struct sockaddr *) &addr, &addrlen) < 0) + return -1; + + bacpy(&src, &addr.l2_bdaddr); + + memset(&addr, 0, sizeof(addr)); + addrlen = sizeof(addr); + + if (getpeername(csk, (struct sockaddr *) &addr, &addrlen) < 0) + return -1; + + bacpy(&dst, &addr.l2_bdaddr); + + memset(&req, 0, sizeof(req)); + req.ctrl_sock = csk; + req.intr_sock = isk; + req.flags = 0; + req.idle_to = timeout * 60; + + err = get_stored_device_info(&src, &dst, &req); + if (!err) + goto create; + + if (!nocheck) { + ba2str(&dst, bda); + syslog(LOG_ERR, "Rejected connection from unknown device %s", bda); + /* Return no error to avoid run_server() complaining too */ + return 0; + } + + if (!nosdp) { + err = get_sdp_device_info(&src, &dst, &req); + if (err < 0) + goto error; + } else { + struct l2cap_conninfo conn; + socklen_t size; + uint8_t class[3]; + + memset(&conn, 0, sizeof(conn)); + size = sizeof(conn); + if (getsockopt(csk, SOL_L2CAP, L2CAP_CONNINFO, &conn, &size) < 0) + memset(class, 0, 3); + else + memcpy(class, conn.dev_class, 3); + + if (class[1] == 0x25 && (class[2] == 0x00 || class[2] == 0x01)) + req.subclass = class[0]; + else + req.subclass = 0xc0; + } + +create: + if (subclass != 0x00) + req.subclass = subclass; + + ba2str(&dst, bda); + syslog(LOG_INFO, "New HID device %s (%s)", bda, req.name); + + if (encrypt && (req.subclass & 0x40)) { + err = request_authentication(&src, &dst); + if (err < 0) { + syslog(LOG_ERR, "Authentication for %s failed", bda); + goto error; + } + + err = request_encryption(&src, &dst); + if (err < 0) + syslog(LOG_ERR, "Encryption for %s failed", bda); + } + + if (bootonly) { + req.rd_size = 0; + req.flags |= (1 << HIDP_BOOT_PROTOCOL_MODE); + } + + if (req.vendor == 0x054c && req.product == 0x0268) + enable_sixaxis(csk); + + err = ioctl(ctl, HIDPCONNADD, &req); + +error: + if (req.rd_data) + free(req.rd_data); + + return err; +} + +static void run_server(int ctl, int csk, int isk, uint8_t subclass, int nosdp, int nocheck, int bootonly, int encrypt, int timeout) +{ + struct pollfd p[2]; + sigset_t sigs; + short events; + int err, ncsk, nisk; + + sigfillset(&sigs); + sigdelset(&sigs, SIGCHLD); + sigdelset(&sigs, SIGPIPE); + sigdelset(&sigs, SIGTERM); + sigdelset(&sigs, SIGINT); + sigdelset(&sigs, SIGHUP); + + p[0].fd = csk; + p[0].events = POLLIN | POLLERR | POLLHUP; + + p[1].fd = isk; + p[1].events = POLLIN | POLLERR | POLLHUP; + + while (!__io_canceled) { + p[0].revents = 0; + p[1].revents = 0; + + if (ppoll(p, 2, NULL, &sigs) < 1) + continue; + + events = p[0].revents | p[1].revents; + + if (events & POLLIN) { + ncsk = l2cap_accept(csk, NULL); + nisk = l2cap_accept(isk, NULL); + + err = create_device(ctl, ncsk, nisk, subclass, nosdp, nocheck, bootonly, encrypt, timeout); + if (err < 0) + syslog(LOG_ERR, "HID create error %d (%s)", + errno, strerror(errno)); + + close(nisk); + sleep(1); + close(ncsk); + } + } +} + +static char *hidp_state[] = { + "unknown", + "connected", + "open", + "bound", + "listening", + "connecting", + "connecting", + "config", + "disconnecting", + "closed" +}; + +static char *hidp_flagstostr(uint32_t flags) +{ + static char str[100]; + str[0] = 0; + + strcat(str, "["); + + if (flags & (1 << HIDP_BOOT_PROTOCOL_MODE)) + strcat(str, "boot-protocol"); + + strcat(str, "]"); + + return str; +} + +static void do_show(int ctl) +{ + struct hidp_connlist_req req; + struct hidp_conninfo ci[16]; + char addr[18]; + int i; + + req.cnum = 16; + req.ci = ci; + + if (ioctl(ctl, HIDPGETCONNLIST, &req) < 0) { + perror("Can't get connection list"); + close(ctl); + exit(1); + } + + for (i = 0; i < req.cnum; i++) { + ba2str(&ci[i].bdaddr, addr); + printf("%s %s [%04x:%04x] %s %s\n", addr, ci[i].name, + ci[i].vendor, ci[i].product, hidp_state[ci[i].state], + ci[i].flags ? hidp_flagstostr(ci[i].flags) : ""); + } +} + +static void do_connect(int ctl, bdaddr_t *src, bdaddr_t *dst, uint8_t subclass, int fakehid, int bootonly, int encrypt, int timeout) +{ + struct hidp_connadd_req req; + uint16_t uuid = HID_SVCLASS_ID; + uint8_t channel = 0; + char name[256]; + int csk, isk, err; + + memset(&req, 0, sizeof(req)); + + err = get_sdp_device_info(src, dst, &req); + if (err < 0 && fakehid) + err = get_alternate_device_info(src, dst, + &uuid, &channel, name, sizeof(name) - 1); + + if (err < 0) { + perror("Can't get device information"); + close(ctl); + exit(1); + } + + switch (uuid) { + case HID_SVCLASS_ID: + goto connect; + + case SERIAL_PORT_SVCLASS_ID: + if (subclass == 0x40 || !strcmp(name, "Cable Replacement")) { + if (epox_presenter(src, dst, channel) < 0) { + close(ctl); + exit(1); + } + break; + } + if (subclass == 0x1f || !strcmp(name, "SPP slave")) { + if (jthree_keyboard(src, dst, channel) < 0) { + close(ctl); + exit(1); + } + break; + } + if (subclass == 0x02 || !strcmp(name, "Serial Port")) { + if (celluon_keyboard(src, dst, channel) < 0) { + close(ctl); + exit(1); + } + break; + } + break; + + case HEADSET_SVCLASS_ID: + case HANDSFREE_SVCLASS_ID: + if (headset_presenter(src, dst, channel) < 0) { + close(ctl); + exit(1); + } + break; + } + + return; + +connect: + csk = l2cap_connect(src, dst, L2CAP_PSM_HIDP_CTRL); + if (csk < 0) { + perror("Can't create HID control channel"); + close(ctl); + exit(1); + } + + isk = l2cap_connect(src, dst, L2CAP_PSM_HIDP_INTR); + if (isk < 0) { + perror("Can't create HID interrupt channel"); + close(csk); + close(ctl); + exit(1); + } + + err = create_device(ctl, csk, isk, subclass, 1, 1, bootonly, encrypt, timeout); + if (err < 0) { + fprintf(stderr, "HID create error %d (%s)\n", + errno, strerror(errno)); + close(isk); + sleep(1); + close(csk); + close(ctl); + exit(1); + } +} + +static void do_search(int ctl, bdaddr_t *bdaddr, uint8_t subclass, int fakehid, int bootonly, int encrypt, int timeout) +{ + inquiry_info *info = NULL; + bdaddr_t src, dst; + int i, dev_id, num_rsp, length, flags; + char addr[18]; + uint8_t class[3]; + + ba2str(bdaddr, addr); + dev_id = hci_devid(addr); + if (dev_id < 0) { + dev_id = hci_get_route(NULL); + hci_devba(dev_id, &src); + } else + bacpy(&src, bdaddr); + + length = 8; /* ~10 seconds */ + num_rsp = 0; + flags = IREQ_CACHE_FLUSH; + + printf("Searching ...\n"); + + num_rsp = hci_inquiry(dev_id, length, num_rsp, NULL, &info, flags); + + for (i = 0; i < num_rsp; i++) { + memcpy(class, (info+i)->dev_class, 3); + if (class[1] == 0x25 && (class[2] == 0x00 || class[2] == 0x01)) { + bacpy(&dst, &(info+i)->bdaddr); + ba2str(&dst, addr); + + printf("\tConnecting to device %s\n", addr); + do_connect(ctl, &src, &dst, subclass, fakehid, bootonly, encrypt, timeout); + } + } + + if (!fakehid) + goto done; + + for (i = 0; i < num_rsp; i++) { + memcpy(class, (info+i)->dev_class, 3); + if ((class[0] == 0x00 && class[2] == 0x00 && + (class[1] == 0x40 || class[1] == 0x1f)) || + (class[0] == 0x10 && class[1] == 0x02 && class[2] == 0x40)) { + bacpy(&dst, &(info+i)->bdaddr); + ba2str(&dst, addr); + + printf("\tConnecting to device %s\n", addr); + do_connect(ctl, &src, &dst, subclass, 1, bootonly, 0, timeout); + } + } + +done: + bt_free(info); + + if (!num_rsp) { + fprintf(stderr, "\tNo devices in range or visible\n"); + close(ctl); + exit(1); + } +} + +static void do_kill(int ctl, bdaddr_t *bdaddr, uint32_t flags) +{ + struct hidp_conndel_req req; + struct hidp_connlist_req cl; + struct hidp_conninfo ci[16]; + int i; + + if (!bacmp(bdaddr, BDADDR_ALL)) { + cl.cnum = 16; + cl.ci = ci; + + if (ioctl(ctl, HIDPGETCONNLIST, &cl) < 0) { + perror("Can't get connection list"); + close(ctl); + exit(1); + } + + for (i = 0; i < cl.cnum; i++) { + bacpy(&req.bdaddr, &ci[i].bdaddr); + req.flags = flags; + + if (ioctl(ctl, HIDPCONNDEL, &req) < 0) { + perror("Can't release connection"); + close(ctl); + exit(1); + } + } + + } else { + bacpy(&req.bdaddr, bdaddr); + req.flags = flags; + + if (ioctl(ctl, HIDPCONNDEL, &req) < 0) { + perror("Can't release connection"); + close(ctl); + exit(1); + } + } +} + +static void usage(void) +{ + printf("hidd - Bluetooth HID daemon version %s\n\n", VERSION); + + printf("Usage:\n" + "\thidd [options] [commands]\n" + "\n"); + + printf("Options:\n" + "\t-i <hciX|bdaddr> Local HCI device or BD Address\n" + "\t-t <timeout> Set idle timeout (in minutes)\n" + "\t-b <subclass> Overwrite the boot mode subclass\n" + "\t-n, --nodaemon Don't fork daemon to background\n" + "\t-h, --help Display help\n" + "\n"); + + printf("Commands:\n" + "\t--server Start HID server\n" + "\t--search Search for HID devices\n" + "\t--connect <bdaddr> Connect remote HID device\n" + "\t--unplug <bdaddr> Unplug the HID connection\n" + "\t--kill <bdaddr> Terminate HID connection\n" + "\t--killall Terminate all connections\n" + "\t--show List current HID connections\n" + "\n"); +} + +static struct option main_options[] = { + { "help", 0, 0, 'h' }, + { "nodaemon", 0, 0, 'n' }, + { "subclass", 1, 0, 'b' }, + { "timeout", 1, 0, 't' }, + { "device", 1, 0, 'i' }, + { "master", 0, 0, 'M' }, + { "encrypt", 0, 0, 'E' }, + { "nosdp", 0, 0, 'D' }, + { "nocheck", 0, 0, 'Z' }, + { "bootonly", 0, 0, 'B' }, + { "hidonly", 0, 0, 'H' }, + { "show", 0, 0, 'l' }, + { "list", 0, 0, 'l' }, + { "server", 0, 0, 'd' }, + { "listen", 0, 0, 'd' }, + { "search", 0, 0, 's' }, + { "create", 1, 0, 'c' }, + { "connect", 1, 0, 'c' }, + { "disconnect", 1, 0, 'k' }, + { "terminate", 1, 0, 'k' }, + { "release", 1, 0, 'k' }, + { "kill", 1, 0, 'k' }, + { "killall", 0, 0, 'K' }, + { "unplug", 1, 0, 'u' }, + { 0, 0, 0, 0 } +}; + +int main(int argc, char *argv[]) +{ + struct sigaction sa; + bdaddr_t bdaddr, dev; + uint32_t flags = 0; + uint8_t subclass = 0x00; + char addr[18]; + int log_option = LOG_NDELAY | LOG_PID; + int opt, ctl, csk, isk; + int mode = SHOW, detach = 1, nosdp = 0, nocheck = 0, bootonly = 0; + int fakehid = 1, encrypt = 0, timeout = 30, lm = 0; + + bacpy(&bdaddr, BDADDR_ANY); + + while ((opt = getopt_long(argc, argv, "+i:nt:b:MEDZBHldsc:k:Ku:h", main_options, NULL)) != -1) { + switch(opt) { + case 'i': + if (!strncasecmp(optarg, "hci", 3)) + hci_devba(atoi(optarg + 3), &bdaddr); + else + str2ba(optarg, &bdaddr); + break; + case 'n': + detach = 0; + break; + case 't': + timeout = atoi(optarg); + break; + case 'b': + if (!strncasecmp(optarg, "0x", 2)) + subclass = (uint8_t) strtol(optarg, NULL, 16); + else + subclass = atoi(optarg); + break; + case 'M': + lm |= L2CAP_LM_MASTER; + break; + case 'E': + encrypt = 1; + break; + case 'D': + nosdp = 1; + break; + case 'Z': + nocheck = 1; + break; + case 'B': + bootonly = 1; + break; + case 'H': + fakehid = 0; + break; + case 'l': + mode = SHOW; + break; + case 'd': + mode = SERVER; + break; + case 's': + mode = SEARCH; + break; + case 'c': + str2ba(optarg, &dev); + mode = CONNECT; + break; + case 'k': + str2ba(optarg, &dev); + mode = KILL; + break; + case 'K': + bacpy(&dev, BDADDR_ALL); + mode = KILL; + break; + case 'u': + str2ba(optarg, &dev); + flags = (1 << HIDP_VIRTUAL_CABLE_UNPLUG); + mode = KILL; + break; + case 'h': + usage(); + exit(0); + default: + exit(0); + } + } + + ba2str(&bdaddr, addr); + + ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HIDP); + if (ctl < 0) { + perror("Can't open HIDP control socket"); + exit(1); + } + + switch (mode) { + case SERVER: + csk = l2cap_listen(&bdaddr, L2CAP_PSM_HIDP_CTRL, lm, 10); + if (csk < 0) { + perror("Can't listen on HID control channel"); + close(ctl); + exit(1); + } + + isk = l2cap_listen(&bdaddr, L2CAP_PSM_HIDP_INTR, lm, 10); + if (isk < 0) { + perror("Can't listen on HID interrupt channel"); + close(ctl); + close(csk); + exit(1); + } + break; + + case SEARCH: + do_search(ctl, &bdaddr, subclass, fakehid, bootonly, encrypt, timeout); + close(ctl); + exit(0); + + case CONNECT: + do_connect(ctl, &bdaddr, &dev, subclass, fakehid, bootonly, encrypt, timeout); + close(ctl); + exit(0); + + case KILL: + do_kill(ctl, &dev, flags); + close(ctl); + exit(0); + + default: + do_show(ctl); + close(ctl); + exit(0); + } + + if (detach) { + if (daemon(0, 0)) { + perror("Can't start daemon"); + exit(1); + } + } else + log_option |= LOG_PERROR; + + openlog("hidd", log_option, LOG_DAEMON); + + if (bacmp(&bdaddr, BDADDR_ANY)) + syslog(LOG_INFO, "Bluetooth HID daemon (%s)", addr); + else + syslog(LOG_INFO, "Bluetooth HID daemon"); + + 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_IGN; + sigaction(SIGCHLD, &sa, NULL); + sigaction(SIGPIPE, &sa, NULL); + + run_server(ctl, csk, isk, subclass, nosdp, nocheck, bootonly, encrypt, timeout); + + syslog(LOG_INFO, "Exit"); + + close(csk); + close(isk); + close(ctl); + + return 0; +} diff --git a/compat/hidd.h b/compat/hidd.h new file mode 100644 index 00000000..ba94bcf4 --- /dev/null +++ b/compat/hidd.h @@ -0,0 +1,30 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2003-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#define L2CAP_PSM_HIDP_CTRL 0x11 +#define L2CAP_PSM_HIDP_INTR 0x13 + +int epox_presenter(const bdaddr_t *src, const bdaddr_t *dst, uint8_t channel); +int headset_presenter(const bdaddr_t *src, const bdaddr_t *dst, uint8_t channel); +int jthree_keyboard(const bdaddr_t *src, const bdaddr_t *dst, uint8_t channel); +int celluon_keyboard(const bdaddr_t *src, const bdaddr_t *dst, uint8_t channel); diff --git a/compat/lib.h b/compat/lib.h new file mode 100644 index 00000000..b070ee40 --- /dev/null +++ b/compat/lib.h @@ -0,0 +1,86 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com> + * Copyright (C) 2002-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <sys/types.h> +#include <errno.h> +#include <signal.h> + +#ifndef min +#define min(a,b) ( (a)<(b) ? (a):(b) ) +#endif + +/* IO cancelation */ +extern volatile sig_atomic_t __io_canceled; + +static inline void io_init(void) +{ + __io_canceled = 0; +} + +static inline void io_cancel(void) +{ + __io_canceled = 1; +} + +/* Read exactly len bytes (Signal safe)*/ +static inline int read_n(int fd, char *buf, int len) +{ + register int t = 0, w; + + while (!__io_canceled && len > 0) { + if ((w = read(fd, buf, len)) < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + return -1; + } + if (!w) + return 0; + len -= w; + buf += w; + t += w; + } + + return t; +} + +/* Write exactly len bytes (Signal safe)*/ +static inline int write_n(int fd, char *buf, int len) +{ + register int t = 0, w; + + while (!__io_canceled && len > 0) { + if ((w = write(fd, buf, len)) < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + return -1; + } + if (!w) + return 0; + len -= w; + buf += w; + t += w; + } + + return t; +} diff --git a/compat/msdun.c b/compat/msdun.c new file mode 100644 index 00000000..1ef559f1 --- /dev/null +++ b/compat/msdun.c @@ -0,0 +1,151 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com> + * Copyright (C) 2002-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdint.h> +#include <stdlib.h> +#include <syslog.h> +#include <setjmp.h> +#include <string.h> + +#include "lib.h" + +#define MS_PPP 2 +#define MS_SUCCESS 1 +#define MS_FAILED -1 +#define MS_TIMEOUT -2 + +static sigjmp_buf jmp; +static int retry; +static int timeout; + +static void sig_alarm(int sig) +{ + siglongjmp(jmp, MS_TIMEOUT); +} + +static int w4_str(int fd, char *str) +{ + char buf[40]; + int r, len = 0; + + while (1) { + r = read(fd, buf + len, sizeof(buf) - len - 1); + if (r < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + break; + } + if (!r) + break; + + len += r; + + if (len < strlen(str)) + continue; + buf[len] = 0; + + if (strstr(buf, str)) + return MS_SUCCESS; + + /* Detect PPP */ + if (strchr(buf, '~')) + return MS_PPP; + } + return MS_FAILED; +} + +static int ms_server(int fd) +{ + switch (w4_str(fd, "CLIENT")) { + case MS_SUCCESS: + write_n(fd, "CLIENTSERVER", 12); + case MS_PPP: + return MS_SUCCESS; + default: + return MS_FAILED; + } +} + +static int ms_client(int fd) +{ + write_n(fd, "CLIENT", 6); + return w4_str(fd, "CLIENTSERVER"); +} + +int ms_dun(int fd, int server, int timeo) +{ + sig_t osig; + + retry = 4; + timeout = timeo; + + if (!server) + timeout /= retry; + + osig = signal(SIGALRM, sig_alarm); + + while (1) { + int r = sigsetjmp(jmp, 1); + if (r) { + if (r == MS_TIMEOUT && !server && --retry) + continue; + + alarm(0); + signal(SIGALRM, osig); + + switch (r) { + case MS_SUCCESS: + case MS_PPP: + errno = 0; + return 0; + + case MS_FAILED: + errno = EPROTO; + break; + + case MS_TIMEOUT: + errno = ETIMEDOUT; + break; + } + return -1; + } + + alarm(timeout); + + if (server) + r = ms_server(fd); + else + r = ms_client(fd); + + siglongjmp(jmp, r); + } +} diff --git a/compat/pand.1 b/compat/pand.1 new file mode 100644 index 00000000..4603b8bf --- /dev/null +++ b/compat/pand.1 @@ -0,0 +1,77 @@ +.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.29. +.TH BlueZ "1" "February 2003" "PAN daemon" "User Commands" +.SH NAME +pand \- BlueZ Bluetooth PAN daemon +.SH DESCRIPTION +The pand PAN daemon allows your computer to connect to ethernet +networks using Bluetooth. +.SH SYNPOSIS +pand <options> +.SH OPTIONS +.TP +\fB\-\-show\fR \fB\-\-list\fR \fB\-l\fR +Show active PAN connections +.TP +\fB\-\-listen\fR \fB\-s\fR +Listen for PAN connections +.TP +\fB\-\-connect\fR \fB\-c\fR <bdaddr> +Create PAN connection +.TP +\fB\-\-search\fR \fB\-Q[duration]\fR +Search and connect +.TP +\fB\-\-kill\fR \fB\-k\fR <bdaddr> +Kill PAN connection +.TP +\fB\-\-killall\fR \fB\-K\fR +Kill all PAN connections +.TP +\fB\-\-role\fR \fB\-r\fR <role> +Local PAN role (PANU, NAP, GN) +.TP +\fB\-\-service\fR \fB\-d\fR <role> +Remote PAN service (PANU, NAP, GN) +.TP +\fB\-\-ethernet\fR \fB\-e\fR <name> +Network interface name +.TP +\fB\-\-device\fR \fB\-i\fR <bdaddr> +Source bdaddr +.TP +\fB\-\-nosdp\fR \fB\-D\fR +Disable SDP +.TP +\fB\-\-encrypt\fR \fB\-E\fR +Enable encryption +.TP +\fB\-\-secure\fR \fB\-S\fR +Secure connection +.TP +\fB\-\-master\fR \fB\-M\fR +Become the master of a piconet +.TP +\fB\-\-nodetach\fR \fB\-n\fR +Do not become a daemon +.TP +\fB\-\-persist\fR \fB\-p[interval]\fR +Persist mode +.TP +\fB\-\-cache\fR \fB\-C[valid]\fR +Cache addresses +.TP +\fB\-\-pidfile\fR \fB\-P <pidfile>\fR +Create PID file +.TP +\fB\-\-devup\fR \fB\-u <script>\fR +Script to run when interface comes up +.TP +\fB\-\-devdown\fR \fB\-o <script>\fR +Script to run when interface comes down +.TP +\fB\-\-autozap\fR \fB\-z\fR +Disconnect automatically on exit + +.SH SCRIPTS +The devup/devdown script will be called with bluetooth device as first argument +and bluetooth destination address as second argument. diff --git a/compat/pand.c b/compat/pand.c new file mode 100644 index 00000000..ed80c7a5 --- /dev/null +++ b/compat/pand.c @@ -0,0 +1,800 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com> + * Copyright (C) 2002-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#define _GNU_SOURCE +#include <stdio.h> +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <signal.h> +#include <getopt.h> +#include <sys/poll.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/socket.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/hci.h> +#include <bluetooth/hci_lib.h> +#include <bluetooth/l2cap.h> +#include <bluetooth/bnep.h> +#include <bluetooth/hidp.h> + +#include "sdp.h" +#include "pand.h" + +#ifdef NEED_PPOLL +#include "ppoll.h" +#endif + +static uint16_t role = BNEP_SVC_PANU; /* Local role (ie service) */ +static uint16_t service = BNEP_SVC_NAP; /* Remote service */ + +static int detach = 1; +static int persist; +static int use_sdp = 1; +static int use_cache; +static int link_mode = 0; +static int cleanup; +static int search_duration = 10; + +static struct { + int valid; + char dst[40]; + bdaddr_t bdaddr; +} cache; + +static char netdev[16] = "bnep%d"; +static char *pidfile = NULL; +static char *devupcmd = NULL; +static char *devdowncmd = NULL; + +static bdaddr_t src_addr = *BDADDR_ANY; +static int src_dev = -1; + +static volatile int terminate; + +static void do_kill(char *dst); + +enum { + NONE, + SHOW, + LISTEN, + CONNECT, + KILL +} modes; + +struct script_arg { + char dev[20]; + char dst[20]; + int sk; + int nsk; +}; + +static void run_script(char *script, char *dev, char *dst, int sk, int nsk) +{ + char *argv[4]; + struct sigaction sa; + + if (!script) + return; + + if (access(script, R_OK | X_OK)) + return; + + if (fork()) + return; + + if (sk >= 0) + close(sk); + + if (nsk >= 0) + close(nsk); + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = SIG_DFL; + sigaction(SIGCHLD, &sa, NULL); + sigaction(SIGPIPE, &sa, NULL); + + argv[0] = script; + argv[1] = dev; + argv[2] = dst; + argv[3] = NULL; + + execv(script, argv); + + exit(1); +} + +/* Wait for disconnect or error condition on the socket */ +static int w4_hup(int sk, struct script_arg *down_cmd) +{ + struct pollfd pf; + sigset_t sigs; + int n; + + sigfillset(&sigs); + sigdelset(&sigs, SIGCHLD); + sigdelset(&sigs, SIGPIPE); + sigdelset(&sigs, SIGTERM); + sigdelset(&sigs, SIGINT); + sigdelset(&sigs, SIGHUP); + + while (!terminate) { + pf.fd = sk; + pf.events = POLLERR | POLLHUP; + + n = ppoll(&pf, 1, NULL, &sigs); + + if (n < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + + syslog(LOG_ERR, "Poll failed. %s(%d)", + strerror(errno), errno); + + return 1; + } + + if (n) { + int err = 0; + socklen_t olen = sizeof(err); + + getsockopt(sk, SOL_SOCKET, SO_ERROR, &err, &olen); + + syslog(LOG_INFO, "%s disconnected%s%s", netdev, + err ? " : " : "", err ? strerror(err) : ""); + + if (down_cmd) + run_script(devdowncmd, + down_cmd->dev, down_cmd->dst, + down_cmd->sk, down_cmd->nsk); + + close(sk); + + return 0; + } + } + + return 0; +} + +static int do_listen(void) +{ + struct l2cap_options l2o; + struct sockaddr_l2 l2a; + socklen_t olen; + int sk, lm; + + if (use_sdp) + bnep_sdp_register(&src_addr, role); + + /* Create L2CAP socket and bind it to PSM BNEP */ + sk = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); + if (sk < 0) { + syslog(LOG_ERR, "Cannot create L2CAP socket. %s(%d)", + strerror(errno), errno); + return -1; + } + + memset(&l2a, 0, sizeof(l2a)); + l2a.l2_family = AF_BLUETOOTH; + bacpy(&l2a.l2_bdaddr, &src_addr); + l2a.l2_psm = htobs(BNEP_PSM); + + if (bind(sk, (struct sockaddr *) &l2a, sizeof(l2a))) { + syslog(LOG_ERR, "Bind failed. %s(%d)", + strerror(errno), errno); + return -1; + } + + /* Setup L2CAP options according to BNEP spec */ + memset(&l2o, 0, sizeof(l2o)); + olen = sizeof(l2o); + if (getsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &l2o, &olen) < 0) { + syslog(LOG_ERR, "Failed to get L2CAP options. %s(%d)", + strerror(errno), errno); + return -1; + } + + l2o.imtu = l2o.omtu = BNEP_MTU; + if (setsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &l2o, sizeof(l2o)) < 0) { + syslog(LOG_ERR, "Failed to set L2CAP options. %s(%d)", + strerror(errno), errno); + return -1; + } + + /* Set link mode */ + lm = link_mode; + if (lm && setsockopt(sk, SOL_L2CAP, L2CAP_LM, &lm, sizeof(lm)) < 0) { + syslog(LOG_ERR, "Failed to set link mode. %s(%d)", + strerror(errno), errno); + return -1; + } + + listen(sk, 10); + + while (!terminate) { + socklen_t alen = sizeof(l2a); + char devname[16]; + int nsk; + + nsk = accept(sk, (struct sockaddr *) &l2a, &alen); + if (nsk < 0) { + syslog(LOG_ERR, "Accept failed. %s(%d)", + strerror(errno), errno); + continue; + } + + switch (fork()) { + case 0: + break; + case -1: + syslog(LOG_ERR, "Fork failed. %s(%d)", + strerror(errno), errno); + default: + close(nsk); + continue; + } + + strncpy(devname, netdev, 16); + devname[15] = '\0'; + + if (!bnep_accept_connection(nsk, role, devname)) { + char str[40]; + struct script_arg down_cmd; + + ba2str(&l2a.l2_bdaddr, str); + + syslog(LOG_INFO, "New connection from %s at %s", + str, devname); + + run_script(devupcmd, devname, str, sk, nsk); + + memset(&down_cmd, 0, sizeof(struct script_arg)); + strncpy(down_cmd.dev, devname, strlen(devname) + 1); + strncpy(down_cmd.dst, str, strlen(str) + 1); + down_cmd.sk = sk; + down_cmd.nsk = nsk; + w4_hup(nsk, &down_cmd); + } else { + syslog(LOG_ERR, "Connection failed. %s(%d)", + strerror(errno), errno); + } + + close(nsk); + exit(0); + } + + if (use_sdp) + bnep_sdp_unregister(); + + return 0; +} + +/* Connect and initiate BNEP session + * Returns: + * -1 - critical error (exit persist mode) + * 1 - non critical error + * 0 - success + */ +static int create_connection(char *dst, bdaddr_t *bdaddr) +{ + struct l2cap_options l2o; + struct sockaddr_l2 l2a; + socklen_t olen; + int sk, r = 0; + struct script_arg down_cmd; + + syslog(LOG_INFO, "Connecting to %s", dst); + + sk = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); + if (sk < 0) { + syslog(LOG_ERR, "Cannot create L2CAP socket. %s(%d)", + strerror(errno), errno); + return -1; + } + + /* Setup L2CAP options according to BNEP spec */ + memset(&l2o, 0, sizeof(l2o)); + olen = sizeof(l2o); + getsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &l2o, &olen); + l2o.imtu = l2o.omtu = BNEP_MTU; + setsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &l2o, sizeof(l2o)); + + memset(&l2a, 0, sizeof(l2a)); + l2a.l2_family = AF_BLUETOOTH; + bacpy(&l2a.l2_bdaddr, &src_addr); + + if (bind(sk, (struct sockaddr *) &l2a, sizeof(l2a))) + syslog(LOG_ERR, "Bind failed. %s(%d)", + strerror(errno), errno); + + memset(&l2a, 0, sizeof(l2a)); + l2a.l2_family = AF_BLUETOOTH; + bacpy(&l2a.l2_bdaddr, bdaddr); + l2a.l2_psm = htobs(BNEP_PSM); + + if (!connect(sk, (struct sockaddr *) &l2a, sizeof(l2a)) && + !bnep_create_connection(sk, role, service, netdev)) { + + syslog(LOG_INFO, "%s connected", netdev); + + run_script(devupcmd, netdev, dst, sk, -1); + + if (persist || devdowncmd) { + memset(&down_cmd, 0, sizeof(struct script_arg)); + strncpy(down_cmd.dev, netdev, strlen(netdev) + 1); + strncpy(down_cmd.dst, dst, strlen(dst) + 1); + down_cmd.sk = sk; + down_cmd.nsk = -1; + w4_hup(sk, &down_cmd); + + if (terminate && cleanup) { + syslog(LOG_INFO, "Disconnecting from %s.", dst); + do_kill(dst); + } + } + + r = 0; + } else { + syslog(LOG_ERR, "Connect to %s failed. %s(%d)", + dst, strerror(errno), errno); + r = 1; + } + + close(sk); + + if (use_cache) { + if (!r) { + /* Succesesful connection, validate cache */ + strcpy(cache.dst, dst); + bacpy(&cache.bdaddr, bdaddr); + cache.valid = use_cache; + } else + cache.valid--; + } + + return r; +} + +/* Search and connect + * Returns: + * -1 - critical error (exit persist mode) + * 1 - non critical error + * 0 - success + */ +static int do_connect(void) +{ + inquiry_info *ii; + int reconnect = 0; + int i, n, r = 0; + + do { + if (reconnect) + sleep(persist); + reconnect = 1; + + if (cache.valid > 0) { + /* Use cached bdaddr */ + r = create_connection(cache.dst, &cache.bdaddr); + if (r < 0) { + terminate = 1; + break; + } + continue; + } + + syslog(LOG_INFO, "Inquiring"); + + /* FIXME: Should we use non general LAP here ? */ + + ii = NULL; + n = hci_inquiry(src_dev, search_duration, 0, NULL, &ii, 0); + if (n < 0) { + syslog(LOG_ERR, "Inquiry failed. %s(%d)", + strerror(errno), errno); + continue; + } + + for (i = 0; i < n; i++) { + char dst[40]; + ba2str(&ii[i].bdaddr, dst); + + if (use_sdp) { + syslog(LOG_INFO, "Searching for %s on %s", + bnep_svc2str(service), dst); + + if (bnep_sdp_search(&src_addr, &ii[i].bdaddr, service) <= 0) + continue; + } + + r = create_connection(dst, &ii[i].bdaddr); + if (r < 0) { + terminate = 1; + break; + } + } + bt_free(ii); + } while (!terminate && persist); + + return r; +} + +static void do_show(void) +{ + bnep_show_connections(); +} + +static void do_kill(char *dst) +{ + if (dst) + bnep_kill_connection((void *) strtoba(dst)); + else + bnep_kill_all_connections(); +} + +static void sig_hup(int sig) +{ + return; +} + +static void sig_term(int sig) +{ + terminate = 1; +} + +static int write_pidfile(void) +{ + int fd; + FILE *f; + pid_t pid; + + do { + fd = open(pidfile, O_WRONLY|O_TRUNC|O_CREAT|O_EXCL, 0644); + if (fd == -1) { + /* Try to open the file for read. */ + fd = open(pidfile, O_RDONLY); + if (fd < 0) { + syslog(LOG_ERR, "Could not read old pidfile: %s(%d)", + strerror(errno), errno); + return -1; + } + + /* We're already running; send a SIGHUP (we presume that they + * are calling ifup for a reason, so they probably want to + * rescan) and then exit cleanly and let things go on in the + * background. Muck with the filename so that we don't go + * deleting the pid file for the already-running instance. + */ + f = fdopen(fd, "r"); + if (!f) { + syslog(LOG_ERR, "Could not fdopen old pidfile: %s(%d)", + strerror(errno), errno); + close(fd); + return -1; + } + + pid = 0; + if (fscanf(f, "%d", &pid) != 1) + pid = 0; + fclose(f); + + if (pid) { + /* Try to kill it. */ + if (kill(pid, SIGHUP) == -1) { + /* No such pid; remove the bogus pid file. */ + syslog(LOG_INFO, "Removing stale pidfile"); + unlink(pidfile); + fd = -1; + } else { + /* Got it. Don't mess with the pid file on + * our way out. */ + syslog(LOG_INFO, "Signalling existing process %d and exiting\n", pid); + pidfile = NULL; + return -1; + } + } + } + } while(fd == -1); + + f = fdopen(fd, "w"); + if (!f) { + syslog(LOG_ERR, "Could not fdopen new pidfile: %s(%d)", + strerror(errno), errno); + close(fd); + unlink(pidfile); + return -1; + } + + fprintf(f, "%d\n", getpid()); + fclose(f); + + return 0; +} + +static struct option main_lopts[] = { + { "help", 0, 0, 'h' }, + { "listen", 0, 0, 's' }, + { "connect", 1, 0, 'c' }, + { "search", 2, 0, 'Q' }, + { "kill", 1, 0, 'k' }, + { "killall", 0, 0, 'K' }, + { "role", 1, 0, 'r' }, + { "service", 1, 0, 'd' }, + { "ethernet", 1, 0, 'e' }, + { "device", 1, 0, 'i' }, + { "nosdp", 0, 0, 'D' }, + { "list", 0, 0, 'l' }, + { "show", 0, 0, 'l' }, + { "nodetach", 0, 0, 'n' }, + { "persist", 2, 0, 'p' }, + { "auth", 0, 0, 'A' }, + { "encrypt", 0, 0, 'E' }, + { "secure", 0, 0, 'S' }, + { "master", 0, 0, 'M' }, + { "cache", 0, 0, 'C' }, + { "pidfile", 1, 0, 'P' }, + { "devup", 1, 0, 'u' }, + { "devdown", 1, 0, 'o' }, + { "autozap", 0, 0, 'z' }, + { 0, 0, 0, 0 } +}; + +static char main_sopts[] = "hsc:k:Kr:d:e:i:lnp::DQ::AESMC::P:u:o:z"; + +static char main_help[] = + "Bluetooth PAN daemon version " VERSION " \n" + "Usage:\n" + "\tpand <options>\n" + "Options:\n" + "\t--show --list -l Show active PAN connections\n" + "\t--listen -s Listen for PAN connections\n" + "\t--connect -c <bdaddr> Create PAN connection\n" + "\t--autozap -z Disconnect automatically on exit\n" + "\t--search -Q[duration] Search and connect\n" + "\t--kill -k <bdaddr> Kill PAN connection\n" + "\t--killall -K Kill all PAN connections\n" + "\t--role -r <role> Local PAN role (PANU, NAP, GN)\n" + "\t--service -d <role> Remote PAN service (PANU, NAP, GN)\n" + "\t--ethernet -e <name> Network interface name\n" + "\t--device -i <bdaddr> Source bdaddr\n" + "\t--nosdp -D Disable SDP\n" + "\t--auth -A Enable authentication\n" + "\t--encrypt -E Enable encryption\n" + "\t--secure -S Secure connection\n" + "\t--master -M Become the master of a piconet\n" + "\t--nodetach -n Do not become a daemon\n" + "\t--persist -p[interval] Persist mode\n" + "\t--cache -C[valid] Cache addresses\n" + "\t--pidfile -P <pidfile> Create PID file\n" + "\t--devup -u <script> Script to run when interface comes up\n" + "\t--devdown -o <script> Script to run when interface comes down\n"; + +int main(int argc, char *argv[]) +{ + char *dst = NULL, *src = NULL; + struct sigaction sa; + int mode = NONE; + int opt; + + while ((opt=getopt_long(argc, argv, main_sopts, main_lopts, NULL)) != -1) { + switch(opt) { + case 'l': + mode = SHOW; + detach = 0; + break; + + case 's': + mode = LISTEN; + break; + + case 'c': + mode = CONNECT; + dst = strdup(optarg); + break; + + case 'Q': + mode = CONNECT; + if (optarg) + search_duration = atoi(optarg); + break; + + case 'k': + mode = KILL; + detach = 0; + dst = strdup(optarg); + break; + + case 'K': + mode = KILL; + detach = 0; + break; + + case 'i': + src = strdup(optarg); + break; + + case 'r': + bnep_str2svc(optarg, &role); + break; + + case 'd': + bnep_str2svc(optarg, &service); + break; + + case 'D': + use_sdp = 0; + break; + + case 'A': + link_mode |= L2CAP_LM_AUTH; + break; + + case 'E': + link_mode |= L2CAP_LM_ENCRYPT; + break; + + case 'S': + link_mode |= L2CAP_LM_SECURE; + break; + + case 'M': + link_mode |= L2CAP_LM_MASTER; + break; + + case 'e': + strncpy(netdev, optarg, 16); + netdev[15] = '\0'; + break; + + case 'n': + detach = 0; + break; + + case 'p': + if (optarg) + persist = atoi(optarg); + else + persist = 5; + break; + + case 'C': + if (optarg) + use_cache = atoi(optarg); + else + use_cache = 2; + break; + + case 'P': + pidfile = strdup(optarg); + break; + + case 'u': + devupcmd = strdup(optarg); + break; + + case 'o': + devdowncmd = strdup(optarg); + break; + + case 'z': + cleanup = 1; + break; + + case 'h': + default: + printf(main_help); + exit(0); + } + } + + argc -= optind; + argv += optind; + optind = 0; + + if (bnep_init()) + return -1; + + /* Check non daemon modes first */ + switch (mode) { + case SHOW: + do_show(); + return 0; + + case KILL: + do_kill(dst); + return 0; + + case NONE: + printf(main_help); + return 0; + } + + /* Initialize signals */ + memset(&sa, 0, sizeof(sa)); + sa.sa_flags = SA_NOCLDSTOP; + sa.sa_handler = SIG_IGN; + sigaction(SIGCHLD, &sa, NULL); + sigaction(SIGPIPE, &sa, NULL); + + sa.sa_handler = sig_hup; + sigaction(SIGHUP, &sa, NULL); + + sa.sa_handler = sig_term; + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + + if (detach && daemon(0, 0)) { + perror("Can't start daemon"); + exit(1); + } + + openlog("pand", LOG_PID | LOG_NDELAY | LOG_PERROR, LOG_DAEMON); + syslog(LOG_INFO, "Bluetooth PAN daemon version %s", VERSION); + + if (src) { + src_dev = hci_devid(src); + if (src_dev < 0 || hci_devba(src_dev, &src_addr) < 0) { + syslog(LOG_ERR, "Invalid source. %s(%d)", + strerror(errno), errno); + return -1; + } + } + + if (pidfile && write_pidfile()) + return -1; + + if (dst) { + /* Disable cache invalidation */ + use_cache = 0; + + strncpy(cache.dst, dst, sizeof(cache.dst) - 1); + str2ba(dst, &cache.bdaddr); + cache.valid = 1; + free(dst); + } + + switch (mode) { + case CONNECT: + do_connect(); + break; + + case LISTEN: + do_listen(); + break; + } + + if (pidfile) + unlink(pidfile); + + return 0; +} diff --git a/compat/pand.h b/compat/pand.h new file mode 100644 index 00000000..90f26ed6 --- /dev/null +++ b/compat/pand.h @@ -0,0 +1,36 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com> + * Copyright (C) 2002-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +int bnep_init(void); +int bnep_cleanup(void); + +int bnep_str2svc(char *svc, uint16_t *uuid); +char *bnep_svc2str(uint16_t uuid); + +int bnep_show_connections(void); +int bnep_kill_connection(uint8_t *dst); +int bnep_kill_all_connections(void); + +int bnep_accept_connection(int sk, uint16_t role, char *dev); +int bnep_create_connection(int sk, uint16_t role, uint16_t svc, char *dev); diff --git a/compat/sdp.c b/compat/sdp.c new file mode 100644 index 00000000..a08ace90 --- /dev/null +++ b/compat/sdp.c @@ -0,0 +1,718 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2003-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <sys/stat.h> +#include <sys/socket.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/l2cap.h> +#include <bluetooth/sdp.h> +#include <bluetooth/sdp_lib.h> +#include <bluetooth/hidp.h> +#include <bluetooth/bnep.h> + +#include "textfile.h" +#include "sdp.h" + +static sdp_record_t *record = NULL; +static sdp_session_t *session = NULL; + +static void add_lang_attr(sdp_record_t *r) +{ + sdp_lang_attr_t base_lang; + sdp_list_t *langs = 0; + + /* UTF-8 MIBenum (http://www.iana.org/assignments/character-sets) */ + base_lang.code_ISO639 = (0x65 << 8) | 0x6e; + base_lang.encoding = 106; + base_lang.base_offset = SDP_PRIMARY_LANG_BASE; + langs = sdp_list_append(0, &base_lang); + sdp_set_lang_attr(r, langs); + sdp_list_free(langs, 0); +} + +static void epox_endian_quirk(unsigned char *data, int size) +{ + /* USAGE_PAGE (Keyboard) 05 07 + * USAGE_MINIMUM (0) 19 00 + * USAGE_MAXIMUM (65280) 2A 00 FF <= must be FF 00 + * LOGICAL_MINIMUM (0) 15 00 + * LOGICAL_MAXIMUM (65280) 26 00 FF <= must be FF 00 + */ + unsigned char pattern[] = { 0x05, 0x07, 0x19, 0x00, 0x2a, 0x00, 0xff, + 0x15, 0x00, 0x26, 0x00, 0xff }; + int i; + + if (!data) + return; + + for (i = 0; i < size - sizeof(pattern); i++) { + if (!memcmp(data + i, pattern, sizeof(pattern))) { + data[i + 5] = 0xff; + data[i + 6] = 0x00; + data[i + 10] = 0xff; + data[i + 11] = 0x00; + } + } +} + +static int store_device_info(const bdaddr_t *src, const bdaddr_t *dst, struct hidp_connadd_req *req) +{ + char filename[PATH_MAX + 1], addr[18], *str, *desc; + int i, err, size; + + ba2str(src, addr); + create_name(filename, PATH_MAX, STORAGEDIR, addr, "hidd"); + + size = 15 + 3 + 3 + 5 + (req->rd_size * 2) + 1 + 9 + strlen(req->name) + 2; + str = malloc(size); + if (!str) + return -ENOMEM; + + desc = malloc((req->rd_size * 2) + 1); + if (!desc) { + free(str); + return -ENOMEM; + } + + memset(desc, 0, (req->rd_size * 2) + 1); + for (i = 0; i < req->rd_size; i++) + sprintf(desc + (i * 2), "%2.2X", req->rd_data[i]); + + snprintf(str, size - 1, "%04X:%04X:%04X %02X %02X %04X %s %08X %s", + req->vendor, req->product, req->version, + req->subclass, req->country, req->parser, desc, + req->flags, req->name); + + free(desc); + + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + ba2str(dst, addr); + err = textfile_put(filename, addr, str); + + free(str); + + return err; +} + +int get_stored_device_info(const bdaddr_t *src, const bdaddr_t *dst, struct hidp_connadd_req *req) +{ + char filename[PATH_MAX + 1], addr[18], tmp[3], *str, *desc; + unsigned int vendor, product, version, subclass, country, parser, pos; + int i; + + desc = malloc(4096); + if (!desc) + return -ENOMEM; + + memset(desc, 0, 4096); + + ba2str(src, addr); + create_name(filename, PATH_MAX, STORAGEDIR, addr, "hidd"); + + ba2str(dst, addr); + str = textfile_get(filename, addr); + if (!str) { + free(desc); + return -EIO; + } + + sscanf(str, "%04X:%04X:%04X %02X %02X %04X %4095s %08X %n", + &vendor, &product, &version, &subclass, &country, + &parser, desc, &req->flags, &pos); + + free(str); + + req->vendor = vendor; + req->product = product; + req->version = version; + req->subclass = subclass; + req->country = country; + req->parser = parser; + + snprintf(req->name, 128, str + pos); + + req->rd_size = strlen(desc) / 2; + req->rd_data = malloc(req->rd_size); + if (!req->rd_data) { + free(desc); + return -ENOMEM; + } + + memset(tmp, 0, sizeof(tmp)); + for (i = 0; i < req->rd_size; i++) { + memcpy(tmp, desc + (i * 2), 2); + req->rd_data[i] = (uint8_t) strtol(tmp, NULL, 16); + } + + free(desc); + + return 0; +} + +int get_sdp_device_info(const bdaddr_t *src, const bdaddr_t *dst, struct hidp_connadd_req *req) +{ + struct sockaddr_l2 addr; + socklen_t addrlen; + bdaddr_t bdaddr; + uint32_t range = 0x0000ffff; + sdp_session_t *s; + sdp_list_t *search, *attrid, *pnp_rsp, *hid_rsp; + sdp_record_t *rec; + sdp_data_t *pdlist, *pdlist2; + uuid_t svclass; + int err; + + s = sdp_connect(src, dst, SDP_RETRY_IF_BUSY | SDP_WAIT_ON_CLOSE); + if (!s) + return -1; + + sdp_uuid16_create(&svclass, PNP_INFO_SVCLASS_ID); + search = sdp_list_append(NULL, &svclass); + attrid = sdp_list_append(NULL, &range); + + err = sdp_service_search_attr_req(s, search, + SDP_ATTR_REQ_RANGE, attrid, &pnp_rsp); + + sdp_list_free(search, NULL); + sdp_list_free(attrid, NULL); + + sdp_uuid16_create(&svclass, HID_SVCLASS_ID); + search = sdp_list_append(NULL, &svclass); + attrid = sdp_list_append(NULL, &range); + + err = sdp_service_search_attr_req(s, search, + SDP_ATTR_REQ_RANGE, attrid, &hid_rsp); + + sdp_list_free(search, NULL); + sdp_list_free(attrid, NULL); + + memset(&addr, 0, sizeof(addr)); + addrlen = sizeof(addr); + + if (getsockname(s->sock, (struct sockaddr *) &addr, &addrlen) < 0) + bacpy(&bdaddr, src); + else + bacpy(&bdaddr, &addr.l2_bdaddr); + + sdp_close(s); + + if (err || !hid_rsp) + return -1; + + if (pnp_rsp) { + rec = (sdp_record_t *) pnp_rsp->data; + + pdlist = sdp_data_get(rec, 0x0201); + req->vendor = pdlist ? pdlist->val.uint16 : 0x0000; + + pdlist = sdp_data_get(rec, 0x0202); + req->product = pdlist ? pdlist->val.uint16 : 0x0000; + + pdlist = sdp_data_get(rec, 0x0203); + req->version = pdlist ? pdlist->val.uint16 : 0x0000; + + sdp_record_free(rec); + } + + rec = (sdp_record_t *) hid_rsp->data; + + pdlist = sdp_data_get(rec, 0x0101); + pdlist2 = sdp_data_get(rec, 0x0102); + if (pdlist) { + if (pdlist2) { + if (strncmp(pdlist->val.str, pdlist2->val.str, 5)) { + strncpy(req->name, pdlist2->val.str, sizeof(req->name) - 1); + strcat(req->name, " "); + } + strncat(req->name, pdlist->val.str, + sizeof(req->name) - strlen(req->name)); + } else + strncpy(req->name, pdlist->val.str, sizeof(req->name)); + } else { + pdlist2 = sdp_data_get(rec, 0x0100); + if (pdlist2) + strncpy(req->name, pdlist2->val.str, sizeof(req->name)); + } + + pdlist = sdp_data_get(rec, 0x0201); + req->parser = pdlist ? pdlist->val.uint16 : 0x0100; + + pdlist = sdp_data_get(rec, 0x0202); + req->subclass = pdlist ? pdlist->val.uint8 : 0; + + pdlist = sdp_data_get(rec, 0x0203); + req->country = pdlist ? pdlist->val.uint8 : 0; + + pdlist = sdp_data_get(rec, 0x0206); + if (pdlist) { + pdlist = pdlist->val.dataseq; + pdlist = pdlist->val.dataseq; + pdlist = pdlist->next; + + req->rd_data = malloc(pdlist->unitSize); + if (req->rd_data) { + memcpy(req->rd_data, (unsigned char *) pdlist->val.str, pdlist->unitSize); + req->rd_size = pdlist->unitSize; + epox_endian_quirk(req->rd_data, req->rd_size); + } + } + + sdp_record_free(rec); + + if (bacmp(&bdaddr, BDADDR_ANY)) + store_device_info(&bdaddr, dst, req); + + return 0; +} + +int get_alternate_device_info(const bdaddr_t *src, const bdaddr_t *dst, uint16_t *uuid, uint8_t *channel, char *name, size_t len) +{ + uint16_t attr1 = SDP_ATTR_PROTO_DESC_LIST; + uint16_t attr2 = SDP_ATTR_SVCNAME_PRIMARY; + sdp_session_t *s; + sdp_list_t *search, *attrid, *rsp; + uuid_t svclass; + int err; + + s = sdp_connect(src, dst, SDP_RETRY_IF_BUSY | SDP_WAIT_ON_CLOSE); + if (!s) + return -1; + + sdp_uuid16_create(&svclass, HEADSET_SVCLASS_ID); + search = sdp_list_append(NULL, &svclass); + attrid = sdp_list_append(NULL, &attr1); + attrid = sdp_list_append(attrid, &attr2); + + err = sdp_service_search_attr_req(s, search, + SDP_ATTR_REQ_INDIVIDUAL, attrid, &rsp); + + sdp_list_free(search, NULL); + sdp_list_free(attrid, NULL); + + if (err <= 0) { + sdp_uuid16_create(&svclass, SERIAL_PORT_SVCLASS_ID); + search = sdp_list_append(NULL, &svclass); + attrid = sdp_list_append(NULL, &attr1); + attrid = sdp_list_append(attrid, &attr2); + + err = sdp_service_search_attr_req(s, search, + SDP_ATTR_REQ_INDIVIDUAL, attrid, &rsp); + + sdp_list_free(search, NULL); + sdp_list_free(attrid, NULL); + + if (err < 0) { + sdp_close(s); + return err; + } + + if (uuid) + *uuid = SERIAL_PORT_SVCLASS_ID; + } else { + if (uuid) + *uuid = HEADSET_SVCLASS_ID; + } + + sdp_close(s); + + for (; rsp; rsp = rsp->next) { + sdp_record_t *rec = (sdp_record_t *) rsp->data; + sdp_list_t *protos; + + sdp_get_service_name(rec, name, len); + + if (!sdp_get_access_protos(rec, &protos)) { + uint8_t ch = sdp_get_proto_port(protos, RFCOMM_UUID); + if (ch > 0) { + if (channel) + *channel = ch; + return 0; + } + } + + sdp_record_free(rec); + } + + return -EIO; +} + +void bnep_sdp_unregister(void) +{ + if (record && sdp_record_unregister(session, record)) + syslog(LOG_ERR, "Service record unregistration failed."); + + sdp_close(session); +} + +int bnep_sdp_register(bdaddr_t *device, uint16_t role) +{ + sdp_list_t *svclass, *pfseq, *apseq, *root, *aproto; + uuid_t root_uuid, pan, l2cap, bnep; + sdp_profile_desc_t profile[1]; + sdp_list_t *proto[2]; + sdp_data_t *v, *p; + uint16_t psm = 15, version = 0x0100; + uint16_t security_desc = 0; + uint16_t net_access_type = 0xfffe; + uint32_t max_net_access_rate = 0; + char *name = "BlueZ PAN"; + char *desc = "BlueZ PAN Service"; + int status; + + session = sdp_connect(BDADDR_ANY, BDADDR_LOCAL, 0); + if (!session) { + syslog(LOG_ERR, "Failed to connect to the local SDP server. %s(%d)", + strerror(errno), errno); + return -1; + } + + record = sdp_record_alloc(); + if (!record) { + syslog(LOG_ERR, "Failed to allocate service record %s(%d)", + strerror(errno), errno); + sdp_close(session); + return -1; + } + + sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); + root = sdp_list_append(NULL, &root_uuid); + sdp_set_browse_groups(record, root); + sdp_list_free(root, 0); + + sdp_uuid16_create(&l2cap, L2CAP_UUID); + proto[0] = sdp_list_append(NULL, &l2cap); + p = sdp_data_alloc(SDP_UINT16, &psm); + proto[0] = sdp_list_append(proto[0], p); + apseq = sdp_list_append(NULL, proto[0]); + + sdp_uuid16_create(&bnep, BNEP_UUID); + proto[1] = sdp_list_append(NULL, &bnep); + v = sdp_data_alloc(SDP_UINT16, &version); + proto[1] = sdp_list_append(proto[1], v); + + /* Supported protocols */ + { + uint16_t ptype[4] = { + 0x0800, /* IPv4 */ + 0x0806, /* ARP */ + }; + sdp_data_t *head, *pseq; + int p; + + for (p = 0, head = NULL; p < 2; p++) { + sdp_data_t *data = sdp_data_alloc(SDP_UINT16, &ptype[p]); + if (head) + sdp_seq_append(head, data); + else + head = data; + } + pseq = sdp_data_alloc(SDP_SEQ16, head); + proto[1] = sdp_list_append(proto[1], pseq); + } + + apseq = sdp_list_append(apseq, proto[1]); + + aproto = sdp_list_append(NULL, apseq); + sdp_set_access_protos(record, aproto); + + add_lang_attr(record); + + sdp_list_free(proto[0], NULL); + sdp_list_free(proto[1], NULL); + sdp_list_free(apseq, NULL); + sdp_list_free(aproto, NULL); + sdp_data_free(p); + sdp_data_free(v); + sdp_attr_add_new(record, SDP_ATTR_SECURITY_DESC, SDP_UINT16, &security_desc); + + switch (role) { + case BNEP_SVC_NAP: + sdp_uuid16_create(&pan, NAP_SVCLASS_ID); + svclass = sdp_list_append(NULL, &pan); + sdp_set_service_classes(record, svclass); + + sdp_uuid16_create(&profile[0].uuid, NAP_PROFILE_ID); + profile[0].version = 0x0100; + pfseq = sdp_list_append(NULL, &profile[0]); + sdp_set_profile_descs(record, pfseq); + + sdp_set_info_attr(record, "Network Access Point", name, desc); + + sdp_attr_add_new(record, SDP_ATTR_NET_ACCESS_TYPE, SDP_UINT16, &net_access_type); + sdp_attr_add_new(record, SDP_ATTR_MAX_NET_ACCESSRATE, SDP_UINT32, &max_net_access_rate); + break; + + case BNEP_SVC_GN: + sdp_uuid16_create(&pan, GN_SVCLASS_ID); + svclass = sdp_list_append(NULL, &pan); + sdp_set_service_classes(record, svclass); + + sdp_uuid16_create(&profile[0].uuid, GN_PROFILE_ID); + profile[0].version = 0x0100; + pfseq = sdp_list_append(NULL, &profile[0]); + sdp_set_profile_descs(record, pfseq); + + sdp_set_info_attr(record, "Group Network Service", name, desc); + break; + + case BNEP_SVC_PANU: + sdp_uuid16_create(&pan, PANU_SVCLASS_ID); + svclass = sdp_list_append(NULL, &pan); + sdp_set_service_classes(record, svclass); + sdp_list_free(svclass, 0); + + sdp_uuid16_create(&profile[0].uuid, PANU_PROFILE_ID); + profile[0].version = 0x0100; + pfseq = sdp_list_append(NULL, &profile[0]); + sdp_set_profile_descs(record, pfseq); + sdp_list_free(pfseq, 0); + + sdp_set_info_attr(record, "PAN User", name, desc); + break; + } + + status = sdp_device_record_register(session, device, record, 0); + if (status) { + syslog(LOG_ERR, "SDP registration failed."); + sdp_record_free(record); record = NULL; + sdp_close(session); + return -1; + } + + return 0; +} + +/* Search for PAN service. + * Returns 1 if service is found and 0 otherwise. */ +int bnep_sdp_search(bdaddr_t *src, bdaddr_t *dst, uint16_t service) +{ + sdp_list_t *srch, *rsp = NULL; + sdp_session_t *s; + uuid_t svclass; + int err; + + switch (service) { + case BNEP_SVC_PANU: + sdp_uuid16_create(&svclass, PANU_SVCLASS_ID); + break; + case BNEP_SVC_NAP: + sdp_uuid16_create(&svclass, NAP_SVCLASS_ID); + break; + case BNEP_SVC_GN: + sdp_uuid16_create(&svclass, GN_SVCLASS_ID); + break; + } + + srch = sdp_list_append(NULL, &svclass); + + s = sdp_connect(src, dst, 0); + if (!s) { + syslog(LOG_ERR, "Failed to connect to the SDP server. %s(%d)", + strerror(errno), errno); + return 0; + } + + err = sdp_service_search_req(s, srch, 1, &rsp); + sdp_close(s); + + /* Assume that search is successeful + * if at least one record is found */ + if (!err && sdp_list_len(rsp)) + return 1; + + return 0; +} + +static unsigned char async_uuid[] = { 0x03, 0x50, 0x27, 0x8F, 0x3D, 0xCA, 0x4E, 0x62, + 0x83, 0x1D, 0xA4, 0x11, 0x65, 0xFF, 0x90, 0x6C }; + +void dun_sdp_unregister(void) +{ + if (record && sdp_record_unregister(session, record)) + syslog(LOG_ERR, "Service record unregistration failed."); + sdp_close(session); +} + +int dun_sdp_register(bdaddr_t *device, uint8_t channel, int type) +{ + sdp_list_t *svclass, *pfseq, *apseq, *root, *aproto; + uuid_t root_uuid, l2cap, rfcomm, dun; + sdp_profile_desc_t profile[1]; + sdp_list_t *proto[2]; + int status; + + session = sdp_connect(BDADDR_ANY, BDADDR_LOCAL, 0); + if (!session) { + syslog(LOG_ERR, "Failed to connect to the local SDP server. %s(%d)", + strerror(errno), errno); + return -1; + } + + record = sdp_record_alloc(); + if (!record) { + syslog(LOG_ERR, "Failed to alloc service record"); + return -1; + } + + sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); + root = sdp_list_append(NULL, &root_uuid); + sdp_set_browse_groups(record, root); + + sdp_uuid16_create(&l2cap, L2CAP_UUID); + proto[0] = sdp_list_append(NULL, &l2cap); + apseq = sdp_list_append(NULL, proto[0]); + + sdp_uuid16_create(&rfcomm, RFCOMM_UUID); + proto[1] = sdp_list_append(NULL, &rfcomm); + proto[1] = sdp_list_append(proto[1], sdp_data_alloc(SDP_UINT8, &channel)); + apseq = sdp_list_append(apseq, proto[1]); + + aproto = sdp_list_append(NULL, apseq); + sdp_set_access_protos(record, aproto); + + switch (type) { + case MROUTER: + sdp_uuid16_create(&dun, SERIAL_PORT_SVCLASS_ID); + break; + case ACTIVESYNC: + sdp_uuid128_create(&dun, (void *) async_uuid); + break; + case DIALUP: + sdp_uuid16_create(&dun, DIALUP_NET_SVCLASS_ID); + break; + default: + sdp_uuid16_create(&dun, LAN_ACCESS_SVCLASS_ID); + break; + } + + svclass = sdp_list_append(NULL, &dun); + sdp_set_service_classes(record, svclass); + + switch (type) { + case LANACCESS: + sdp_uuid16_create(&profile[0].uuid, LAN_ACCESS_PROFILE_ID); + profile[0].version = 0x0100; + pfseq = sdp_list_append(NULL, &profile[0]); + sdp_set_profile_descs(record, pfseq); + break; + case DIALUP: + sdp_uuid16_create(&profile[0].uuid, DIALUP_NET_PROFILE_ID); + profile[0].version = 0x0100; + pfseq = sdp_list_append(NULL, &profile[0]); + sdp_set_profile_descs(record, pfseq); + break; + } + + switch (type) { + case MROUTER: + sdp_set_info_attr(record, "mRouter", NULL, NULL); + break; + case ACTIVESYNC: + sdp_set_info_attr(record, "ActiveSync", NULL, NULL); + break; + case DIALUP: + sdp_set_info_attr(record, "Dialup Networking", NULL, NULL); + break; + default: + sdp_set_info_attr(record, "LAN Access Point", NULL, NULL); + break; + } + + status = sdp_device_record_register(session, device, record, 0); + if (status) { + syslog(LOG_ERR, "SDP registration failed."); + sdp_record_free(record); + record = NULL; + return -1; + } + return 0; +} + +int dun_sdp_search(bdaddr_t *src, bdaddr_t *dst, int *channel, int type) +{ + sdp_session_t *s; + sdp_list_t *srch, *attrs, *rsp; + uuid_t svclass; + uint16_t attr; + int err; + + s = sdp_connect(src, dst, 0); + if (!s) { + syslog(LOG_ERR, "Failed to connect to the SDP server. %s(%d)", + strerror(errno), errno); + return -1; + } + + switch (type) { + case MROUTER: + sdp_uuid16_create(&svclass, SERIAL_PORT_SVCLASS_ID); + break; + case ACTIVESYNC: + sdp_uuid128_create(&svclass, (void *) async_uuid); + break; + case DIALUP: + sdp_uuid16_create(&svclass, DIALUP_NET_SVCLASS_ID); + break; + default: + sdp_uuid16_create(&svclass, LAN_ACCESS_SVCLASS_ID); + break; + } + + srch = sdp_list_append(NULL, &svclass); + + attr = SDP_ATTR_PROTO_DESC_LIST; + attrs = sdp_list_append(NULL, &attr); + + err = sdp_service_search_attr_req(s, srch, SDP_ATTR_REQ_INDIVIDUAL, attrs, &rsp); + + sdp_close(s); + + if (err) + return 0; + + for(; rsp; rsp = rsp->next) { + sdp_record_t *rec = (sdp_record_t *) rsp->data; + sdp_list_t *protos; + + if (!sdp_get_access_protos(rec, &protos)) { + int ch = sdp_get_proto_port(protos, RFCOMM_UUID); + if (ch > 0) { + *channel = ch; + return 1; + } + } + } + + return 0; +} diff --git a/compat/sdp.h b/compat/sdp.h new file mode 100644 index 00000000..7ff75d68 --- /dev/null +++ b/compat/sdp.h @@ -0,0 +1,39 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2003-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#define LANACCESS 0 +#define MROUTER 1 +#define ACTIVESYNC 2 +#define DIALUP 3 + +int get_stored_device_info(const bdaddr_t *src, const bdaddr_t *dst, struct hidp_connadd_req *req); +int get_sdp_device_info(const bdaddr_t *src, const bdaddr_t *dst, struct hidp_connadd_req *req); +int get_alternate_device_info(const bdaddr_t *src, const bdaddr_t *dst, uint16_t *uuid, uint8_t *channel, char *name, size_t len); + +int bnep_sdp_search(bdaddr_t *src, bdaddr_t *dst, uint16_t service); +int bnep_sdp_register(bdaddr_t *device, uint16_t role); +void bnep_sdp_unregister(void); + +int dun_sdp_search(bdaddr_t *src, bdaddr_t *dst, int *channel, int type); +int dun_sdp_register(bdaddr_t *device, uint8_t channel, int type); +void dun_sdp_unregister(void); |