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; +} | 
