diff options
Diffstat (limited to 'pand')
-rw-r--r-- | pand/Makefile.am | 22 | ||||
-rw-r--r-- | pand/bnep.c | 311 | ||||
-rw-r--r-- | pand/main.c | 798 | ||||
-rw-r--r-- | pand/pand.1 | 77 | ||||
-rw-r--r-- | pand/pand.h | 42 | ||||
-rw-r--r-- | pand/sdp.c | 247 |
6 files changed, 1497 insertions, 0 deletions
diff --git a/pand/Makefile.am b/pand/Makefile.am new file mode 100644 index 00000000..85d70542 --- /dev/null +++ b/pand/Makefile.am @@ -0,0 +1,22 @@ + +if PAND +bin_PROGRAMS = pand + +pand_SOURCES = main.c pand.h bnep.c sdp.c + +pand_LDADD = @BLUEZ_LIBS@ +endif + +AM_CFLAGS = @BLUEZ_CFLAGS@ + +INCLUDES = -I$(top_srcdir)/common + +if PAND +if MANPAGES +man_MANS = pand.1 +endif +endif + +EXTRA_DIST = pand.1 + +MAINTAINERCLEANFILES = Makefile.in diff --git a/pand/bnep.c b/pand/bnep.c new file mode 100644 index 00000000..604ed552 --- /dev/null +++ b/pand/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/pand/main.c b/pand/main.c new file mode 100644 index 00000000..ce3b8ebb --- /dev/null +++ b/pand/main.c @@ -0,0 +1,798 @@ +/* + * + * 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 "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/pand/pand.1 b/pand/pand.1 new file mode 100644 index 00000000..4603b8bf --- /dev/null +++ b/pand/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/pand/pand.h b/pand/pand.h new file mode 100644 index 00000000..3e91f82d --- /dev/null +++ b/pand/pand.h @@ -0,0 +1,42 @@ +/* + * + * 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 + * + */ + +/* BNEP functions */ +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); + +/* SDP functions */ +int bnep_sdp_register(bdaddr_t *device, uint16_t role); +void bnep_sdp_unregister(void); +int bnep_sdp_search(bdaddr_t *src, bdaddr_t *dst, uint16_t service); diff --git a/pand/sdp.c b/pand/sdp.c new file mode 100644 index 00000000..05e2e182 --- /dev/null +++ b/pand/sdp.c @@ -0,0 +1,247 @@ +/* + * + * 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 <string.h> +#include <syslog.h> +#include <sys/types.h> +#include <sys/socket.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/sdp.h> +#include <bluetooth/sdp_lib.h> +#include <bluetooth/bnep.h> + +#include "pand.h" + +static sdp_record_t *record; +static sdp_session_t *session; + +void bnep_sdp_unregister(void) +{ + if (record && sdp_record_unregister(session, record)) + syslog(LOG_ERR, "Service record unregistration failed."); + + sdp_close(session); +} + +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); +} + +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; +} |