/*
 * btscanner - Displays the output of Bluetooth scans
 * Copyright (C) 2003 Pentest Limited
 * 
 * Written 2003 by Tim Hurman <timh at pentest.co.uk>
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation;
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY
 * RIGHTS.  IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE
 * FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY
 * DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 * 
 * ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS,
 * COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS SOFTWARE
 * IS DISCLAIMED.
 */

/*
 * ll.c: The linked list handlers
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif

#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif

#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif

#ifdef HAVE_STRING_H
#include <string.h>
#endif

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#ifdef HAVE_SYSLOG_H
#include <syslog.h>
#endif

#include <pthread.h>
#include <stdio.h>
#include <errno.h>
#include <signal.h>

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

#include <cfg.h>
#include <misc.h>
#include <ll.h>
#include <log.h>
#include <sdp.h>
#include <main.h>
#include <screen.h>

/* globals */
static u_int8_t ll_list_changed = 0;
static device_t *devhead = NULL;
static pthread_mutex_t devhead_mutex = PTHREAD_MUTEX_INITIALIZER;
static size_t ll_list_len = 0;
static time_t ll_last_summary = 0;

/* lock the entire list */
int ll_lock_list(void)
{
	return pthread_mutex_lock(&devhead_mutex);
}
int ll_unlock_list(void)
{
	return pthread_mutex_unlock(&devhead_mutex);
}


/* initalise */
int ll_init (void)
{
	ll_list_changed = 0;
	devhead = NULL;
	ll_list_len = 0;
	return 0;
}


/* find an item in the list */
device_t *ll_find_device(uint64_t did)
{
	device_t *dev;
	for (dev=devhead; dev && did != bd2int(&(dev->bdaddr)); dev=dev->next);
	return dev;
}


/* free up the list and destroy the mutex */
int ll_free(void)
{
	device_t *p, *pp;

	ll_lock_list();
	p = devhead;
	pp = NULL;

	while(p) {
		pp = p;
		p = p->next;

		/* the main details */
		if(pp->name) free(pp->name);
		if(pp->sdp) free(pp->sdp);
		free(pp);
	}
	devhead = NULL;
	ll_unlock_list();

	pthread_mutex_destroy(&devhead_mutex);
    return 0;
}


/* get the details and lock the device */
device_t *ll_lock_device(bdaddr_t *bdaddr)
{
	device_t *p;
	uint64_t dc = bd2int(bdaddr);

	/* mutex on the list */
	ll_lock_list();
	p = ll_find_device(dc);

	if (p) {
		/* old device */
		if (p->locked)
			p = NULL;
	} else {
		/* new device */
		p = (device_t*)malloc(sizeof(device_t));
		if (!p) {
			applog(LOG_ERR, "%s::malloc(): %s",
			  __FUNCTION__, strerror(errno));
		} else {
			memset(p, 0, sizeof(device_t));
			bacpy(&(p->bdaddr), bdaddr);
			time(&(p->first_scanned));

			/* put it on the head of the list */
			p->next = devhead;
			devhead = p;
			ll_list_len++;
		}
	}

	/* if we have a device, lock it */
	if (p) {
		p->locked = 1;
		p->locked_by = pthread_self();
	}
	ll_unlock_list();

	return p;
}


/* unlock */
int ll_unlock_device(device_t *dev)
{
	if (!dev) return 1;

	ll_lock_list();
	if (dev->locked && dev->locked_by == pthread_self()) {
		dev->locked = 0;
		dev->locked_by = 0;
		if (dev->updated) {
			ll_list_changed = 1;
			dev->updated = 0;
		}
	}
	ll_unlock_list();

	return 0;
}


/* find out if the list has been updated, and zero the update var */
int ll_ischanged(void)
{
	uint8_t i;
	ll_lock_list();
	i = ll_list_changed;
	ll_list_changed = 0;
	ll_unlock_list();
	return i;
}


/* debug only */
int ll_print_item(device_t *item)
{
	char tmp[32];
	ba2str(&(item->bdaddr), tmp);
	applog(LOG_INFO,
	  "Found Addr: %s Name: \"%s\" Clk off: 0x%4.4x Class: 0x%6.6x",
	  tmp, item->name, item->clk_off, item->class);
	return 0;
}


/* the functions below are using in the screen output */
/* print a line into a string */
int ll_print_dev_line(device_t *item, uint64_t *id, char *s, int len)
{
	int i;
	struct tm tm;
	char tmp[32];

	/* the locking is done outside, in here, the whole list is locked */
	if (!item || !s) return 0;
	/* we can read from the device because we have a lock on the entire
	 * list */

	*id = bd2int(&(item->bdaddr));
	localtime_r(&(item->last_scanned), &tm);
	tm.tm_year +=1900; tm.tm_mon++;
	ba2str(&(item->bdaddr), tmp);
	i = snprintf(s, len,
	  "%04d/%02d/%02d %02d:%02d:%02d  %s  0x%4.4x   0x%6.6x  %s",
	  tm.tm_year, tm.tm_mon, tm.tm_mday,
	  tm.tm_hour, tm.tm_min, tm.tm_sec,
	  tmp, item->clk_off, item->class, item->name?item->name:"(unknown)");

	/* pad if required */
	for (; i<len; i++) s[i] = ' ';
	s[len] = 0;

	return 0;
}


/* print a timestamp */
int ll_print_timestamp(device_t *dev, cbuf_t *cb)
{
	int inc;
	size_t initial;
	struct tm ls;

	memset(&ls, 0, sizeof(ls));
	ll_lock_list();
	localtime_r(&(dev->last_scanned), &ls);
	ll_unlock_list();

	inc = getpagesize();
	initial = cb->len;
	while (1) {
		/* allocate if needed */
		if (cb->len >= cb->sz) {
			cb->len = initial; /* re-print from where we started */
			cb->sz += inc;
			cb->buf = (char*)realloc(cb->buf, sizeof(char) * cb->sz);
			if (!cb->buf) {
				applog(LOG_ERR,
				  "%s::realloc(): %s", __FUNCTION__, strerror(errno));
				return 1;
			}
		}

		cb->len = snprintf(cb->buf, cb->sz,
		  "%04d/%02d/%02d %02d:%02d:%02d\n",
		  ls.tm_year+1900, ls.tm_mon+1, ls.tm_mday,
		  ls.tm_hour, ls.tm_min, ls.tm_sec);
		if (cb->len < cb->sz) break;
	}

	return 0;
}


/* save the dummary log */
int ll_save_summary(const char *filename)
{
	device_t *dev;
	struct tm f, l;
	char tmp[32];
	time_t now;

	/* open the log file */
	if (log_sum_open(filename)) {
		if (EEXIST == errno)
			screen_log("File already exists");
		else
			screen_log("Unable to save, please check the logfile");
		return 1;
	}

	/* log the time + date started */
	time(&now);
	localtime_r(&now, &f);
	f.tm_year+=1900; f.tm_mon++;
	summary("Summary saved at %04d/%02d/%02d %02d:%02d:%02d",
	  f.tm_year, f.tm_mon, f.tm_mday, f.tm_hour, f.tm_min, f.tm_sec);

	/* go through the list, dump if required */
	ll_lock_list();
	for(dev=devhead; dev; dev=dev->next) {
		if (dev->last_scanned >= ll_last_summary) {
			localtime_r(&(dev->first_scanned), &f);
			localtime_r(&(dev->last_scanned), &l);
			f.tm_year+=1900;
			l.tm_year+=1900;
			f.tm_mon++;
			l.tm_mon++;
			ba2str(&(dev->bdaddr), tmp);
			summary("%04d/%02d/%02d %02d:%02d:%02d,"
			  "%04d/%02d/%02d %02d:%02d:%02d,%s,%s",
			  f.tm_year, f.tm_mon, f.tm_mday,
			  f.tm_hour, f.tm_min, f.tm_sec,
			  l.tm_year, l.tm_mon, l.tm_mday,
			  l.tm_hour, l.tm_min, l.tm_sec,
			  tmp, dev->name?dev->name:"");
		}
	}
	time(&ll_last_summary);
	ll_unlock_list();
	

	/* close the log file */
	log_sum_close();
	return 0;
}


/* print a formatted device info */
int ll_print_dev_info(uint64_t did, cbuf_t *cb)
{
	device_t *dev;
	size_t initial;
	int inc;
	char tmp[32], foundby[32];
	char *lmpi;
	struct tm fs, ls;
	rangedef_t *range;

	/* find the dev */
	ll_lock_list();
	dev = ll_find_device(did);
	ll_unlock_list();
	if (NULL == dev) return 1;
	range = cfg_get_range(did);

	memset(&fs, 0, sizeof(fs));
	localtime_r(&(dev->first_scanned), &fs);
	memset(&ls, 0, sizeof(ls));
	localtime_r(&(dev->last_scanned), &ls);

	inc = getpagesize();
	initial = cb->len;
	while (1) {
		/* allocate if needed */
		if (cb->len >= cb->sz) {
			cb->len = initial; /* re-print from where we started */
			cb->sz += inc;
			cb->buf = (char*)realloc(cb->buf, sizeof(char) * cb->sz);
			if (!cb->buf) {
				applog(LOG_ERR,
				  "%s::realloc(): %s", __FUNCTION__, strerror(errno));
				return 1;
			}
		}

		/* basic info */
		ll_lock_list();
		ba2str(&dev->bdaddr, tmp);
		ba2str(&dev->bd_scan, foundby);
		cb->len = snprintf(cb->buf, cb->sz,
		  "Address:       %s\n"
		  "Found by:      %s\n"
		  "OUI owner:     %s\n"
		  "First seen:    %04d/%02d/%02d %02d:%02d:%02d\n"
		  "Last seen:     %04d/%02d/%02d %02d:%02d:%02d\n"
		  "Name:          %s\n"
		  "Vulnerable to: %s\n"
		  "Clk off:       0x%4.4x\n"
		  "Class:         0x%6.6x\n"
		  "               ",
		  tmp, foundby, dev->oui?dev->oui:"",
		  fs.tm_year+1900, fs.tm_mon+1, fs.tm_mday,
		  fs.tm_hour, fs.tm_min, fs.tm_sec,
		  ls.tm_year+1900, ls.tm_mon+1, ls.tm_mday,
		  ls.tm_hour, ls.tm_min, ls.tm_sec,
		  dev->name?dev->name:"unknown",
		  range->vulns?range->vulns:"",
		  dev->clk_off, dev->class);
		ll_unlock_list();
		if (cb->len >= cb->sz) continue;


		/* device classes and features */
		ll_lock_list();
		cb->len = sdp_format_classes(dev, cb->buf, cb->sz, cb->len);
		ll_unlock_list();
		if (cb->len >= cb->sz) continue;

		/* HCI version */
		ll_lock_list();
        if (dev->got_version) {
			lmpi = lmp_vertostr(dev->version.lmp_ver);

			cb->len += snprintf(cb->buf + cb->len, cb->sz - cb->len,
			  "\nHCI Version\n-----------\n"
			  "LMP Version: %s (0x%x) LMP Subversion: 0x%x\n"
			  "Manufacturer: %s (%d)\n",
			  lmpi?lmpi:"",
			  dev->version.lmp_ver,
			  dev->version.lmp_subver,
			  bt_compidtostr(dev->version.manufacturer),
			  dev->version.manufacturer);

			if (lmpi) free(lmpi);
			lmpi = NULL;
		} else {
			cb->len += snprintf(cb->buf + cb->len, cb->sz - cb->len,
			  "\nHCI Version\n-----------\n"
			  "LMP Version:  n/a (n/a) LMP Subversion: n/a\n"
			  "Manufacturer: n/a (n/a)\n");
		}
		ll_unlock_list();
		if (cb->len >= cb->sz) continue;

		/* HCI features */
		ll_lock_list();
		if (dev->got_features) {
			lmpi = lmp_featurestostr(dev->features, "    ", 70);

			cb->len += snprintf(cb->buf + cb->len, cb->sz - cb->len,
			  "\nHCI Features\n------------\n"
			  "Features:     0x%2.2x 0x%2.2x 0x%2.2x 0x%2.2x\n%s\n",
			  dev->features[0], dev->features[1],
			  dev->features[2], dev->features[3],
			  lmpi?lmpi:"");
			if (lmpi) free(lmpi);
			lmpi = NULL;
		} else {
			cb->len += snprintf(cb->buf + cb->len, cb->sz - cb->len,
			  "\nHCI Features\n------------\n"
			  "Features:     n/a n/a n/a n/a\n");
		}
		ll_unlock_list();
		if (cb->len >= cb->sz) continue;

		/* the sdp info */
		ll_lock_list();
		if (dev->sdp) {
			cb->len += snprintf(cb->buf + cb->len, cb->sz - cb->len,
			  "\nSDP probe\n---------\n%s",
			  dev->sdp);
		}
		ll_unlock_list();
		if (cb->len < cb->sz) break;
	}

	return 0;
}


/* copy a device name */
int ll_copy_name(device_t *td, char *name)
{
	size_t i;
	if (td->name != NULL)
		free(td->name);

	i = strlen(name)+1;
	td->name = (char *)malloc((i+1) * sizeof(char));
	if (NULL == td->name)
		return 1;

	strncpy(td->name, name, i);
	return 0;
}

/* first item */
device_t *ll_first(void)
{
	device_t *d;
	d = devhead;
	return d;
}

/* next */
device_t *ll_next(device_t *curr)
{
	device_t *d;
	d = curr->next;
	return d;
}


/* check the stat in a nice manner */
uint8_t ll_check_got_name(device_t *dev)
{
	uint8_t ret;
	ll_lock_list();
	ret = dev->got_name;
	ll_unlock_list();
	return ret;
}
uint8_t ll_check_got_version(device_t *dev)
{
	uint8_t ret;
	ll_lock_list();
	ret = dev->got_version;
	ll_unlock_list();
	return ret;
}
uint8_t ll_check_got_features(device_t *dev)
{
	uint8_t ret;
	ll_lock_list();
	ret = dev->got_features;
	ll_unlock_list();
	return ret;
}
uint8_t ll_check_got_oui(device_t *dev)
{
	uint8_t ret;
	ll_lock_list();
	ret = dev->got_oui;
	ll_unlock_list();
	return ret;
}
uint8_t ll_check_got_sdp(device_t *dev)
{
	uint8_t ret;
	ll_lock_list();
	if (dev->sdp)
		ret = 1;
	else
		ret = 0;
	ll_unlock_list();
	return ret;
}

size_t ll_dev_count(void)
{
	return ll_list_len;
}


int ll_get_last_update_time(uint64_t did, time_t *t)
{
	device_t *dev;
	if (NULL == t) return 1;

	/* find the dev */
	ll_lock_list();
	dev = ll_find_device(did);
	ll_unlock_list();
	if (NULL == dev) return 1;

	*t = dev->last_scanned;
	return 0;
}


/* the sorting functions */
void ll_do_swap(device_t *pdev, device_t *dev)
{
	device_t *tmp;
	/* we are going to swap, dev and dev->next, pdev is for continuity */
	tmp = dev->next;
	dev->next = tmp->next;
	tmp->next = dev;
	if (NULL == pdev)
		devhead = tmp;
	else
		pdev->next = tmp;
}

int ll_need_swap(device_t *dev, int sort)
{
	int ret = 0;
	/* check if we need to swap dev and dev->next, ret 1 if swap */
	if (NULL == dev || NULL == dev->next) return 0;

	if (LL_SORT_BD ==  (sort & LL_SORT_BD)) {
		if (bd2int(&(dev->bdaddr)) > bd2int(&(dev->next->bdaddr)))
			ret = 1;
	} else if (LL_SORT_FS == (sort & LL_SORT_FS)) {
		if (dev->first_scanned > dev->next->first_scanned)
			ret = 1;
	} else if (LL_SORT_LS == (sort & LL_SORT_LS)) {
		if (dev->last_scanned > dev->next->last_scanned)
			ret = 1;
	}

	if (LL_SORT_REV == (sort & LL_SORT_REV))
		ret = !ret;
	return ret;
}

int ll_sortlist(int sort)
{
	device_t *dev, *pdev;
	int swaps;
	if (LL_SORT_NONE == sort || LL_SORT_REV == sort) return 0;

	do {
		for (pdev=NULL, swaps = 0, dev=devhead; dev; pdev=dev, dev=dev->next)
			if (ll_need_swap(dev, sort)) {
				ll_do_swap(pdev, dev);
				swaps++;
			}
	} while (swaps != 0);

	return 0;
}
