diff options
Diffstat (limited to 'compat/pand.c')
-rw-r--r-- | compat/pand.c | 800 |
1 files changed, 800 insertions, 0 deletions
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; +} |