summaryrefslogtreecommitdiff
path: root/compat/pand.c
diff options
context:
space:
mode:
Diffstat (limited to 'compat/pand.c')
-rw-r--r--compat/pand.c811
1 files changed, 811 insertions, 0 deletions
diff --git a/compat/pand.c b/compat/pand.c
new file mode 100644
index 0000000..b82650e
--- /dev/null
+++ b/compat/pand.c
@@ -0,0 +1,811 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com>
+ * Copyright (C) 2002-2010 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) {
+ bdaddr_t *ba = strtoba(dst);
+ bnep_kill_connection((void *) ba);
+ free(ba);
+ } 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 const char *main_sopts = "hsc:k:Kr:d:e:i:lnp::DQ::AESMC::P:u:o:z";
+
+static const char *main_help =
+ "Bluetooth PAN daemon version %s\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, VERSION);
+ exit(0);
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+ optind = 0;
+
+ if (bnep_init()) {
+ free(dst);
+ return -1;
+ }
+
+ /* Check non daemon modes first */
+ switch (mode) {
+ case SHOW:
+ do_show();
+ free(dst);
+ return 0;
+
+ case KILL:
+ do_kill(dst);
+ free(dst);
+ return 0;
+
+ case NONE:
+ printf(main_help, VERSION);
+ free(dst);
+ 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);
+ free(dst);
+ return -1;
+ }
+ }
+
+ if (pidfile && write_pidfile()) {
+ free(dst);
+ 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;
+}