/*
 * Bluetooth Presense Daemon
 *
 * Copyright (C) 2003 Digium, Inc.
 *
 * Mark Spencer <markster@digium.com>
 *
 * Distributed under the terms of the GNU General Public License
 * Version 2 or later at your option.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <fcntl.h>
#include <errno.h>
#include <ctype.h>
#include <time.h>

#include <fcntl.h>
#include <getopt.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <asm/types.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>


#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>

#include "btpp.h"
#include "btpd.h"
#include "md5.h"
#include "aes.h"

static int dev_id = -1;
static int dd;
static int debug = 6;
static time_t start;
static int discover = 1;
static struct btp_watch *pendingsetup;

/* Try to connect up to 5 times */
#define KEEPALIVES 5

#define MAXRETRIES 5
#define MAXRETRYTIME	5

#define STATE_IDLE			0
#define STATE_TRYCONNECT	1
#define STATE_CONNWAIT		2
#define STATE_LINKQUALITY	3

#define LQ_OPCODE (htobs(cmd_opcode_pack(OGF_STATUS_PARAM, OCF_READ_LINK_QUALITY)))
#define SETUP_OPCODE (htobs(cmd_opcode_pack(OGF_LINK_CTL, OCF_CREATE_CONN)))

static struct btp_center {
	struct sockaddr_in sin;
	aes_encrypt_ctx cx;
	struct btp_center *next;
	int seqno;
	char username[80];
} *centers;

static struct btp_packet {
	struct btp_packet *next;
	int retrans;
	time_t retranstime;
	struct sockaddr_in sin;
	char data[0];
};

static struct btp_watch {
	bdaddr_t bdaddr;
	char id[18];
	int opcode;
	int quality;
	int keepalive;
	int state;
	int handle;
	time_t retry;
	time_t lastsetup;
	time_t lastsuccess;
	struct btp_watch *next;
} *watches;

static struct btp_watch *bt_lookup(bdaddr_t *bdaddr)
{
	struct btp_watch *w;
	w = watches;
	while(w) {
		if (!bacmp(&w->bdaddr, bdaddr))
			break;
		w = w->next;
	}
	return w;
}

static struct btp_watch *bt_lookuph(uint16_t handle)
{
	struct btp_watch *w;
	w = watches;
	while(w) {
		if (w->handle == handle)
			break;
		w = w->next;
	}
	return w;
}

static void bt_disconnect(struct btp_watch *w)
{
	printf("Disconnected from '%s'\n", w->id);
	w->handle = -1;
	w->state = STATE_IDLE;
	time(&w->retry);
	if (pendingsetup == w)
		pendingsetup = NULL;
}

static void bt_link_quality(int fd, struct btp_watch *w, uint8_t lq)
{
	struct btp_report br;
	w->quality = lq;
	/* Try again in another 3 seconds */
	w->state = STATE_CONNWAIT;
	time(&w->retry);
	w->retry += 3;
	/* Reset keepalives */
	if (w->keepalive >= 0)
		w->keepalive = KEEPALIVES;
	memset(&br, 0, sizeof(br));
	strncpy(br.id, w->id, sizeof(br.id) - 1);
	br.level = w->quality;
	write(fd, &br, sizeof(br));
}

static int bt_finished(void)
{
	/* Close device */
	hci_close_dev(dd);
	return 0;
}

static void md5sum(char *sum, const char *data, int datalen, char *extra)
{
	struct MD5Context md5;
	MD5Init(&md5);
	MD5Update(&md5, data, datalen);
	if (extra)
		MD5Update(&md5, extra, strlen(extra));
	MD5Final(sum, &md5);
}

static void destroy_watch(struct btp_watch *w)
{
	struct btp_watch *prev=NULL, *cur;
	printf("Dropping '%s' from watch list!\n", w->id);
	cur = watches;
	while(cur) {
		if (cur == w) {
			if (prev)
				prev->next = cur->next;
			else
				watches = cur->next;
			free(w);
			return;
		}
		prev = cur;
		cur = cur->next;
	}
}

static void add_extra(char *desc, int lineno)
{
	bdaddr_t tmp;
	struct btp_watch *be;
	be = malloc(sizeof(struct btp_watch));
	if (be) {
		memset(be, 0, sizeof(struct btp_watch));
		str2ba(desc, &be->bdaddr);
		baswap(&tmp, &be->bdaddr);
		strcpy(be->id, batostr(&tmp));
		be->next = watches;
		be->keepalive = -1;
		be->handle = -1;
		watches = be;
	} else
		fprintf(stderr, "Out of memory at line '%d'\n", lineno);
}

static void add_watch(char *desc, int lineno)
{
	bdaddr_t tmp;
	struct btp_watch *be;
	be = malloc(sizeof(struct btp_watch));
	if (be) {
		memset(be, 0, sizeof(struct btp_watch));
		str2ba(desc, &be->bdaddr);
		baswap(&tmp, &be->bdaddr);
		strcpy(be->id, batostr(&tmp));
		be->next = watches;
		be->keepalive = 5;
		be->handle = -1;
		watches = be;
	} else
		fprintf(stderr, "Out of memory at line '%d'\n", lineno);
}

static void add_center(char *desc, int lineno)
{
	char *host;
	char *secret;
	char *port;
	int portno = BTP_PORT;
	struct hostent *hp;
	struct btp_center *bc;
	unsigned char sum[16];
	host = strrchr(desc, '@');
	if (host) {
		*host = '\0';
		host++;
		port = strchr(host, ':');
		if (port) {
			*port = '\0';
			port++;
			if ((sscanf(port, "%i", &portno) != 1) || (portno < 1) || (portno > 65535)) {
				fprintf(stderr, "Invalid port number '%s' at line '%d'\n", port, lineno);
			}
		}
	} else {
		fprintf(stderr, "Invalid report syntax at line '%d' (no '@' symbol)\n", lineno);
		return;
	}
	secret = strchr(desc, ':');
	if (secret) {
		*secret = '\0';
		secret++;
	} else {
		fprintf(stderr, "Invalid report syntax at line '%d' (no ':' symbol for secret)\n", lineno);
		return;
	}
	if (!strlen(desc) || !strlen(secret) || !strlen(host)) {
		fprintf(stderr, "Invalid report syntax at line '%d', needs user, password, and host!\n", lineno);
		return;
	}
	hp = gethostbyname(host);
	if (!hp) {
		fprintf(stderr, "Unable to resolve host '%s' at line %d\n", host, lineno);
		return;
	}
	bc = malloc(sizeof(struct btp_center));
	if (!bc) {
		fprintf(stderr, "Out of memory at line '%d'\n", lineno);
		return;
	}
	memset(bc, 0, sizeof(bc));
	strncpy(bc->username, desc, sizeof(bc->username) - 1);
	bc->sin.sin_family = AF_INET;
	memcpy(&bc->sin.sin_addr, hp->h_addr, sizeof(bc->sin.sin_addr));
	bc->sin.sin_port = htons(portno);
	md5sum(sum, secret, strlen(secret), bc->username);
	memset(secret, '*', strlen(secret));
	aes_encrypt_key128(sum, &bc->cx);
	bc->next = centers;
	centers = bc;
}

static int btpd_loadconfig(void)
{
	FILE *f;
	int lineno;
	int general=0;
	char buf[1024];
	char *c;
	char *var, *val;
	f = fopen("/etc/btpd.conf", "r");
	if (!f) {
		perror("Error opening /etc/btpd.conf");
		return -1;
	}
	while(!feof(f)) {
		++lineno;
		fgets(buf, sizeof(buf), f);
		if (!feof(f)) {
			/* Strip carriage return */
			buf[strlen(buf) - 1] = '\0';
			/* Strip comments */
			if ((c = strchr(buf, ';')))
				*c = '\0';
			/* Strip trailing spaces */
			c = buf + strlen(buf) - 1;
			while((c >= buf) && (*c < 33)) {
				*c = '\0';
				c--;
			}
			/* Strip leading spaces */
			c = buf;
			while(*c && (*c < 33))
				c++;
			if (strlen(c)) {
				if (c[0] == '[') {
					c++;
					if (c[strlen(c) - 1] == ']') {
						c[strlen(c) - 1] = '\0';
						if (!strcasecmp(c, "general"))
							general = 1;
						else {
							general = 2;
						}
					} else {
						fprintf(stderr, "Syntax error in section title '%s' at line %d\n",
							c, lineno);
					}
				} else {
					var = c;
					val = c;
					/* Find first space or = */
					while((*val) && (*val > 32) && (*val != '=')) val++;
					/* Skip over any space */
					while((*val) && (*val < 33) && (*val != '=')) *(val++) = '\0';
					/* If we're not at an '=' sign, this is invalid */
					if (*val != '=')
						continue;
					*(val++) = '\0';
					/* Skip optional > sign */
					if (*val == '>')
						*(val++) = '\0';
					/* Skip over trailing space */
					while(*val && (*val < 33)) val++;
					if (general == 1) {
						if (!strcasecmp(var, "report")) {
							add_center(val, lineno);
						} else if (!strcasecmp(var, "scan")) {
							add_extra(val, lineno);
						} else if (!strcasecmp(var, "discover")) {
							if (!strcasecmp(val, "yes"))
								discover = 1;
							else if (!strcasecmp(val, "no"))
								discover = 0;
							else
								fprintf(stderr, "discover should be 'yes' or 'no' at line %d\n", lineno);
						}
					}
				} 
			}
		}
	}
	fclose(f);
	return 0;
}

static int bt_find(struct btp_watch *watch)
{
	struct {
		struct hci_conn_info_req cr;
		struct hci_conn_info ci;
	} cr;
	int handle = -1;
	memset(&cr, 0, sizeof(cr));
	bacpy(&cr.cr.bdaddr, &watch->bdaddr);
	cr.cr.type = ACL_LINK;
	if (ioctl(dd, HCIGETCONNINFO, (unsigned long) &cr.cr) > -1) {
		handle = cr.cr.conn_info->handle;
		/* Get ready to read link state in a couple of seconds */
	}
	return handle;
}

static int bt_read_link_quality(struct btp_watch *watch)
{
	int res = 0;
	if (hci_send_cmd(dd, OGF_STATUS_PARAM, OCF_READ_LINK_QUALITY, 2, &watch->handle)) {
		fprintf(stderr, "lq hci_send_cmd failed to '%s': %s\n", watch->id, strerror(errno));
		watch->state = STATE_IDLE;
		res = -1;
	} else {
		watch->opcode = LQ_OPCODE;
		watch->state = STATE_LINKQUALITY;
		printf("Requested link quality from '%s', op code is %d\n", watch->id, watch->opcode);
	}
	return res;		
}

static int bt_send_disc(struct btp_watch *watch)
{
	int res = 0;
	if (hci_send_cmd(dd, OGF_STATUS_PARAM, OCF_READ_LINK_QUALITY, 2, &watch->handle)) {
		fprintf(stderr, "lq hci_send_cmd failed to '%s': %s\n", watch->id, strerror(errno));
		watch->state = STATE_IDLE;
		res = -1;
	} else {
		watch->opcode = LQ_OPCODE;
		watch->state = STATE_LINKQUALITY;
		printf("Requested link quality from '%s', op code is %d\n", watch->id, watch->opcode);
	}
	return res;		
}

static int bt_find_or_connect(struct btp_watch *watch)
{
	uint8_t role;
	int ptype;
	int res = 0;
	create_conn_cp cp;
	int handle;

	bdaddr_t *bdaddr = &watch->bdaddr;
	/* Setup parameters */
	role = 0;
	ptype = HCI_DM1 | HCI_DM3 | HCI_DM5 | HCI_DH1 | HCI_DH3 | HCI_DH5;

	if ((handle = bt_find(watch)) < 0){
		if (pendingsetup) {
			printf("Postponing connection to '%s' until '%s' finishes!\n", watch->id, pendingsetup->id);
			return 0;
		}
		if (debug > 4)
			printf("Didn't find open connection for '%s', trying for new one...\n", watch->id);
		if (watch->keepalive > 0)
			watch->keepalive--;
		else if (!watch->keepalive) {
			destroy_watch(watch);
			return -1;
		}
		pendingsetup = watch;
		memset(&cp, 0, sizeof(cp));
		bacpy(&cp.bdaddr, bdaddr);
		cp.pkt_type = ptype;
		cp.clock_offset = 0;
		cp.role_switch = role;
		
		if (hci_send_cmd(dd, OGF_LINK_CTL, OCF_CREATE_CONN, CREATE_CONN_CP_SIZE, &cp)) {
			fprintf(stderr, "hci_send_cmd failed to '%s': %s\n", watch->id, strerror(errno));
			res = -1;
			pendingsetup =  NULL;
		} else
			watch->opcode = SETUP_OPCODE;
		watch->state = STATE_TRYCONNECT;
		time(&watch->lastsetup);
		time(&watch->retry);
		watch->retry += 5;
		if (debug > 5)
			fprintf(stderr, "Sent connect command to '%s', opcode %d!\n", watch->id, watch->opcode);
	}  else {
		if (handle) {
			if (debug > 5)
				fprintf(stderr, "Already connected to '%s'!\n", watch->id);
			watch->handle = handle;
			watch->state = STATE_CONNWAIT;
			time(&watch->retry);
			time(&watch->lastsuccess);
			watch->retry += 3;
		} else {
			/* Still trying to connect */
			if (debug > 5)
				fprintf(stderr, "Still pending connection to '%s'!\n", watch->id);
			watch->state = STATE_TRYCONNECT;
			time(&watch->retry);
			watch->retry += 15;
		}
	}
	return res;
}

static struct btp_watch *nextconnect;
static time_t nextlast;

static void bt_process(struct btp_watch *watch, int fd)
{
	time_t now;
	time(&now);

	switch(watch->state) {
	case STATE_IDLE:
		/* Find/Create new connection */
		if (watch->retry < now) {
			if (!nextconnect || (watch->lastsetup < nextlast)) {
				/* We get priority on next connect */
				nextconnect = watch;
				nextlast = watch->lastsetup;
			}
		}
		break;
	case STATE_TRYCONNECT:
		/* We're waiting on a connect response to come back */
		if (watch->retry < now) {
			printf("Retrying connection to '%s' in a bit\n", watch->id);
			time(&watch->retry);
			watch->retry += 10;
			if (watch == pendingsetup)
				pendingsetup = NULL;
			watch->state = STATE_IDLE;
		}
		break;
	case STATE_CONNWAIT:
		/* Wait until a sufficient amount of time has passed and
		   start the link request */
		if (watch->retry < now) {
			printf("Time to try to do link quality on '%s'!\n", watch->id);
			bt_read_link_quality(watch);
		}
		break;
	case STATE_LINKQUALITY:
		/* We're waiting for the link request response to come back */
		break;
	default:
		printf("Watch is in unknown state '%d', returning to idle!\n", watch->state);
		watch->state = STATE_IDLE;
	}
}

static int bt_check(int fd)
{
	int res;
	unsigned char buf[HCI_MAX_EVENT_SIZE], *ptr;
	evt_cmd_complete *cc;
	evt_cmd_status   *cs;
	evt_conn_complete *ecc;
	evt_disconn_complete *dcc;
	read_link_quality_rp *rp;
	hci_event_hdr *hdr;
	struct btp_watch *watch;
	
	while((res = read(dd, buf, sizeof(buf))) < 0) {
		if ((errno == EAGAIN) || (errno == EINTR))
			continue;
		return -1;
	}
	hdr = (void *)(buf + 1);
	ptr = buf + (1 + HCI_EVENT_HDR_SIZE);
	res -= (1 + HCI_EVENT_HDR_SIZE);
	switch(hdr->evt) {
	case EVT_CMD_STATUS:
		cs = (void *)ptr;
		switch(cs->opcode) {
		case SETUP_OPCODE:
			/* Setup */
			printf("Got %d bytes of setup status!\n", res);
			if (cs->status) {
				fprintf(stderr, "Status is %02x on setup!\n", cs->status);
				if (pendingsetup) {
					/* If we got a "busy", let it retry sooner */
					if (cs->status == 0x0c) 
						pendingsetup->lastsetup = pendingsetup->lastsuccess;
					/* Give some time for someone else to try */
					watch = pendingsetup;
					bt_disconnect(pendingsetup);
				}
			}
			break;
		default:
			fprintf(stderr, "Got status %02x on unknown opcode %d/%d, length %d\n", cs->status, cs->opcode, cs->ncmd, res);
		}
		break;
	case EVT_CONN_COMPLETE:
		ecc = (void *)ptr;
		watch = bt_lookup(&ecc->bdaddr);
		if (watch) {
			printf("Got connected to '%s'\n", watch->id);
			time(&watch->lastsuccess);
			watch->state = STATE_CONNWAIT;
			watch->handle = ecc->handle;
			time(&watch->retry);
			/* Wait a couple of seconds for a retry */
			watch->retry += 2;
			if (watch == pendingsetup)
				pendingsetup = NULL;
		}
		break;
	case EVT_DISCONN_COMPLETE:
		dcc = (void *)ptr;
		watch = bt_lookuph(dcc->handle);
		if (watch)
			bt_disconnect(watch);
		break;
	case EVT_CMD_COMPLETE:
		cc = (void *)ptr;
		switch(cc->opcode) {
		case LQ_OPCODE:
			/* Link Quality Reply */
			rp = (void *)(ptr + EVT_CMD_COMPLETE_SIZE);
			watch = bt_lookuph(rp->handle);
			if (watch) {
				if (!rp->status) {
					bt_link_quality(fd, watch, rp->link_quality);
				} else {
					if (rp->status == HCI_NO_CONNECTION) {
						fprintf(stderr, "Got 'not connected' status on link quality for '%s'!\n", watch->id);
						bt_disconnect(watch);
					} else
						fprintf(stderr, "Got status %d on link quality!\n", rp->status);
				}			
			}
			break;
		case SETUP_OPCODE:
			/* Setup */
			printf("Got %d bytes of setup complete!\n", res);
			break;
		default:
			fprintf(stderr, "Got complete on unknown opcode %d/%d, length %d\n", cc->opcode, cc->ncmd, res);
		}
		break;
	case EVT_QOS_SETUP_COMPLETE:
		/* Ignore */
		break;
	default:
		printf("Got unknown event '%02x' with %d bytes\n", hdr->evt, res);
	}
	return 0;
}

static int bt_wait(int s, int fd)
{
	struct timeval tv;
	int res;
	fd_set fds;
	tv.tv_sec = s;
	tv.tv_usec = 0;
	for (;;) {
		FD_ZERO(&fds);
		FD_SET(dd, &fds);
		res = select(dd + 1, &fds, NULL, NULL, &tv);
		if (!res)
			break;
		if (res < 0) {
			if ((errno == EAGAIN) || (errno == EINTR))
				continue;
			break;
		}
		if (FD_ISSET(dd, &fds)) {
			bt_check(fd);
		}
	}
	return 0;
}

static int bt_scan(int fd)
{
	int num_resp;
	struct btp_watch *e, *en;
	inquiry_info *info = NULL;
	if (discover) {
		num_resp = hci_inquiry(dev_id, 4, 10, NULL, &info, IREQ_CACHE_FLUSH);
		if (debug)
			fprintf(stderr, "%d Responses!\n", num_resp);
	} else {
		bt_wait(1, fd);
		num_resp = 0;
	}
	if (num_resp > -1) {
		nextconnect = NULL;
		e = watches;
		while(e) {
			if (debug > 6)
				printf("Processing extra!\n");
			en = e->next;
			/* bt_process can destroy "e" so don't reference
			   after calling */
			bt_process(e, fd);
			e = en;
			if (debug > 6)
				printf("Done processing extra!\n");
		}
		/* Try next connection if appropriate */
		if (nextconnect)
			bt_find_or_connect(nextconnect);
	} else {
		perror("HCI Inquiry");
		bt_wait(1, fd);
	}
	
	return 0;
}

static int bt_init(void)
{
	bdaddr_t bdaddr;
	struct hci_filter nf;
	memset(&bdaddr, 0, sizeof(bdaddr));
	dev_id = hci_get_route(&bdaddr);
	if (dev_id < 0) {
		perror("Bluetooth Device Unavailable.");
		return -1;
	}
	/* Open device */
	dd = hci_open_dev(dev_id);

	/* Clear out the filter */
	hci_filter_clear(&nf);
	hci_filter_all_ptypes(&nf);
	hci_filter_all_events(&nf);
	if (setsockopt(dd, SOL_HCI, HCI_FILTER, &nf, sizeof(nf))) {
		fprintf(stderr, "Failed to set filter: %s\n", strerror(errno));
		return -1;
	}
	
	return 0;
}

static void send_report(struct btp_center *c, struct btp_report *br, int s)
{
	union btp_report_data brd;
	struct {
		struct btp_msg hdr;
		unsigned char edata[32 + 80];
	} __attribute__ ((__packed__)) msg;
	char sum[16];
	int x;
	int offset;
	int res;
	
	memset(&brd, 0, sizeof(br));
	strncpy(brd.data.unit, br->id, sizeof(brd.data.unit) - 1);
	brd.data.score = htons(br->level);
	md5sum(sum, brd.raw, 24, c->username);
	memset(&msg, 0, sizeof(msg));
	for (x=0;x<8;x++) {
		brd.data.pad[x] = sum[x] ^ sum[x + 8];
	}
	offset = strlen(c->username) + 1;
	memcpy(msg.edata, c->username, offset);
	aes_encrypt(brd.raw, msg.edata + offset, &c->cx);
	aes_encrypt(brd.raw + 16, msg.edata + 16 + offset, &c->cx);
	msg.hdr.cmd = htons(BTP_CMD_REPORT);
	msg.hdr.seqno = ++c->seqno;
	printf("Sending report to '%s:%d'\n", inet_ntoa(c->sin.sin_addr), ntohs(c->sin.sin_port));
	res = sendto(s, &msg, sizeof(msg.hdr) + offset + 32, 0, (struct sockaddr *)&c->sin,  sizeof(c->sin));
	if (res < 0) 
		perror("sendto");
}

static int process_report(struct btp_report *br, int s)
{
	time_t now;
	int secs;
	struct btp_center *c;
	time(&now);
	secs = now - start;
	c = centers;
	while(c) {
		send_report(c, br, s);
		c = c->next;
	}
	printf("Report: %s is at %d (%d:%02d)\n", br->id, br->level, secs / 60, secs % 60);
	return 0;
}

static int read_network(int s)
{
	struct sockaddr_in sin;
	int res;
	char buf[1024];
	int sinlen;
	
	sinlen = sizeof(sin);
	res = recvfrom(s, buf, sizeof(buf), 0, (struct sockaddr *)&sin, &sinlen);
	if (res < 0) {
		perror("recvfrom");
		return -1;
	}
	printf("Read %d bytes from the network!\n", res);
	return 0;
}

static int read_pipe(int fd, int s)
{
	static int bytes = 0;
	static struct btp_report *br;
	char buf[sizeof(struct btp_report)];
	int res;
	res = read(fd, buf + bytes, sizeof(struct btp_report) - bytes);
	if (res < 1) {
		perror("read");
		return -1;
	}
	bytes += res;
	if (bytes>= sizeof(struct btp_report)) {
		br = (struct btp_report *)buf;
		process_report(br, s);
		bytes = 0;
	}
	return 0;
}

static int btpd_main(int fd, int s)
{
	fd_set rfds;
	int max;
	int res;
	
	time(&start);
	
	max = s;
	if (fd > max)
		max = fd;
	for(;;) {
		FD_ZERO(&rfds);
		FD_SET(s,&rfds);
		FD_SET(fd,&rfds);
		res = select(max + 1, &rfds, NULL, NULL, NULL);
		if (res < 0) {
			perror("select");
			continue;
		}
		if (FD_ISSET(fd, &rfds)) {
			read_pipe(fd, s);
		}
		if (FD_ISSET(s, &rfds)) {
			read_network(s);
		}
	} 
	return 0;
}

void deadkid(int signal)
{
	int res;
	int status;
	res = wait4(-1, &status, WNOHANG, NULL);
	if (res < 0) {
		fprintf(stderr, "Didn't reap anyone!\n");
	}
	if (WIFEXITED(status)) {
		fprintf(stderr, "Helper exited normally with status %d\n", WEXITSTATUS(status));
	} else if (WIFSIGNALED(status)) {
		fprintf(stderr, "Helper died on signal %d\n", WTERMSIG(status));
	}
	exit(1);
}

int main(void)
{
	pid_t pid;
	int fds[2];
	int s;
	long flags;
	if (btpd_loadconfig())
		exit(1);
	if (bt_init())
		exit(1);
	if (pipe(fds)) {
		perror("pipe");
		exit(1);
	}
	if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
		perror("socket");
		exit(1);
	}
	flags = fcntl(s, F_GETFL);
	fcntl(s, F_SETFL, flags | O_NONBLOCK);
	signal(SIGCHLD, deadkid);
	pid = fork();
	if (pid < 0) {
		perror("fork");
	} else if (!pid) {
		/* Scan for stuff */
		for (;;) {
			bt_scan(fds[1]);
		}
		bt_finished();
		exit(0);
	} else {
		btpd_main(fds[0], s);
	}
	exit(0);
}
