/*
 * 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.
 */

/*
 * cfg.c: Config options
 */

#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_SYS_STAT_H
#include <sys/stat.h>
#endif

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

#ifdef HAVE_STDARG_H
#include <stdarg.h>
#endif

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

#ifdef HAVE_LIBXML_PARSER_H
#include <libxml/parser.h>
#endif

#ifdef HAVE_LIBXML_TREE_H
#include <libxml/tree.h>
#endif

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

#include <cfg.h>
#include <log.h>
#include <misc.h>

/* note to self. I hate myself for using XML in a config file */

/* globals */
static char *cfg_log = NULL;
static char *cfg_oui = NULL;
static char *cfg_store = NULL;
static char *cfg_file = CFG_FILE;

/* rangedef head */
static rangedef_t *cfg_ranges = NULL;
static size_t cfg_ranges_sz = 0;


/* copy a string value */
char *cfg_copy_content(xmlNode *node)
{
	char *s, *h;
	size_t hlen, len;

	if (!node->children || !node->children->content) {
		fprintf(stderr, "%s(): \'log\' item found but no content\n",
		  __FUNCTION__);
		return NULL;
	}

	/* do we have to expand $HOME? */
	if ('~' == node->children->content[0]) {
		h = getenv("HOME");
		if (!h) {
			fprintf(stderr, "%s(): cannot expand \'~\', $HOME not defined\n",
			  __FUNCTION__);
			return NULL;
		}

		
		hlen = strlen(h);
		len = strlen((char*)node->children->content) + hlen;
		s = (char*)malloc(sizeof(char)*(len+1));
		if (!s) {
			fprintf(stderr, "%s::malloc(): %s\n", __FUNCTION__,
			  strerror(errno));
			return NULL;
		}
		strncpy(s, h, len);
		strncpy(s+hlen, (char*)node->children->content+1, len - hlen);
		if ('/' == s[len-1]) s[len-1] = 0; /* no trailing '/' */
	} else {

		s=strdup((char*)node->children->content);
		if (!s) {
			fprintf(stderr, "%s::xmlCharStrdup(): %s\n", 
			  __FUNCTION__, strerror(errno));
			return NULL;
		}
	}

	return s;
}


/* parse a files node */
int cfg_parse_files(xmlNode *node)
{
	xmlNode *n;
	if (!node || !node->children) return 1;

	for(n=node->children; n; n=n->next) {
		if (n->type != XML_ELEMENT_NODE) continue;

		if (!xmlStrncmp((const xmlChar*)"log", n->name, 3)) {
			cfg_log = cfg_copy_content(n);
		} else if (!xmlStrncmp((const xmlChar*)"oui", n->name, 3)) {
			cfg_oui = cfg_copy_content(n);
		} else if (!xmlStrncmp((const xmlChar*)"store", n->name, 5)) {
			cfg_store = cfg_copy_content(n);
		}
	}

	if (!cfg_log || !cfg_oui || !cfg_store) {
		fprintf(stderr, "%s(): Not all file variables were defined\n",
		  __FUNCTION__);
		return 1;
	}

	return 0;
}


/* parse a rangedef */
int cfg_parse_rangedef(xmlNode *node, rangedef_t *rd)
{
	int ret;
	xmlChar *p;
	xmlNode *n;
	bdaddr_t bd;

	if (!node || !node->children) return 1;
	ret = 1;
	memset(rd, 0, sizeof(rangedef_t));

	/* get the attribs */
	p = xmlGetProp(node, (xmlChar*)"start");
	if (!p) {
		fprintf(stderr, "%s(): Mandatory start attribute not found\n",
		  __FUNCTION__);
		goto cfg_parse_rangedef_leave;
	}
	str2ba((const char*)p, &bd);
	rd->start = bd2int(&bd);
	xmlFree(p);

	p = xmlGetProp(node, (xmlChar*)"end");
	if (!p) {
		fprintf(stderr, "%s(): Mandatory end attribute not found\n",
		  __FUNCTION__);
		goto cfg_parse_rangedef_leave;
	}
	str2ba((const char*)p, &bd);
	rd->end = bd2int(&bd);
	xmlFree(p);

	/* check for a good start/end value */
	if (rd->start > rd->end) {
		fprintf(stderr,
		  "%s(): start address is greater than the end address",
		  __FUNCTION__);
		goto cfg_parse_rangedef_leave;
	}

	/* go through each of the specified flags */
	for (n=node->children; n; n=n->next) {
		if (n->type != XML_ELEMENT_NODE) continue;

		if (!xmlStrncmp((const xmlChar*)"vulnerabilities", n->name, 15)) {
			if (NULL != n->children) {
				rd->vulns = strdup((char*)n->children->content);
				if (!rd->vulns) {
					fprintf(stderr, "%s::xmlCharStrdup(): %s\n",
					  __FUNCTION__, strerror(errno));
				}
			}
		} else if (!xmlStrncmp((const xmlChar*)"name", n->name, 4))
			rd->sf |= SF_NAME;
		else if (!xmlStrncmp((const xmlChar*)"hci", n->name, 3))
			rd->sf |= SF_HCI;
		else if (!xmlStrncmp((const xmlChar*)"rssi", n->name, 4))
			rd->sf |= SF_RSSI;
		else if (!xmlStrncmp((const xmlChar*)"lq", n->name, 2))
			rd->sf |= SF_LQ;
		else if (!xmlStrncmp((const xmlChar*)"txpwr", n->name, 5))
			rd->sf |= SF_TXPWR;
		else if (!xmlStrncmp((const xmlChar*)"sdp", n->name, 3))
			rd->sf |= SF_SDP;
	}

	ret = 0;
	/* go away */
	cfg_parse_rangedef_leave:
	return ret;
}


/* parse the contents of a root node */
int cfg_parse_root(xmlNode *node)
{
	xmlNode *n;
	int ret = 0;

	for(n=node; n; n=n->next) {
		if (n->type != XML_ELEMENT_NODE) continue;

		if (!xmlStrncmp((const xmlChar*)"files", n->name, 5)) {
			ret=cfg_parse_files(n);
			if (ret) break;
		} else if (!xmlStrncmp((const xmlChar*)"rangedef", n->name, 8)) {
			/* make the rangedef bigger */
			cfg_ranges_sz++;
			cfg_ranges = (rangedef_t*)realloc(cfg_ranges,
			  sizeof(rangedef_t)*cfg_ranges_sz);
			if (!cfg_ranges) {
				fprintf(stderr, "%s::realloc(): %s\n", __FUNCTION__,
				  strerror(errno));
				ret = 1;
				break;
			}

			/* eval the rangedef */
			ret=cfg_parse_rangedef(n, &(cfg_ranges[cfg_ranges_sz-1]));
			if (ret) break;
		}

	}

	return ret;
}


/* output an error from the XML parser */
void cfg_xml_err(void *ctx, const char *fmt, ...)
{
	va_list ap;
	ctx=ctx;

	va_start(ap, fmt);
	vfprintf(stderr, fmt, ap);
	va_end(ap);
}



/* parse the XML in a config file */
int cfg_parse_xml(char *filename)
{
	xmlParserCtxt *ctxt = NULL;
	xmlNode *root_element = NULL;
	xmlNode *n = NULL;
	xmlDoc *doc = NULL;
	struct stat sbuf;
	int ret = 1;

	if (filename)
		cfg_file = filename;

	/* does it exist */
	if (stat(cfg_file, &sbuf)) {
		fprintf(stderr, "%s::stat(): %s\n", __FUNCTION__, strerror(errno));
		return 1;
	}

	if ((sbuf.st_mode & S_IRUSR) != S_IRUSR) {
		fprintf(stderr,
		  "%s::stat(): config file is not readable\n", __FUNCTION__);
		return 1;
	}

	/* new context if you please */
	ctxt = xmlNewParserCtxt();
	if (!ctxt) {
		fprintf(stderr, "%s::xmlNewParserCtxt(): context allocation failed\n",
		  __FUNCTION__);
		goto cfg_parse_xml_leave;
	}

	/* parse the doc */
	doc = xmlCtxtReadFile(ctxt, cfg_file, NULL,
	  XML_PARSE_PEDANTIC|XML_PARSE_DTDVALID|XML_PARSE_DTDLOAD);
	if (!doc) {
		fprintf(stderr,"%s::xmlCtxtReadFile(): failed to parse config file\n",
		  __FUNCTION__);
		goto cfg_parse_xml_leave;
	}

	/* was there a DTD? */
	if (!doc->intSubset) {
		fprintf(stderr, "%s(): no DTD was specified in the config file\n",
		  __FUNCTION__);
		goto cfg_parse_xml_leave;
	}

	/* the root element, if you please */
	root_element = xmlDocGetRootElement(doc);
	if (!root_element) {
		fprintf(stderr, "%s(): could not locate root element\n", __FUNCTION__);
		goto cfg_parse_xml_leave;
	}

	/* evaluate the top level elements */
	for (ret=0,n=root_element; n; n=n->next) {
		if (n->type != XML_ELEMENT_NODE) continue;

		if (!xmlStrncmp((const xmlChar*)"btscanner", n->name, 9)) {
			ret = cfg_parse_root(n->children);
			if(ret) break;
		}
	}

	cfg_parse_xml_leave:
	if (doc) xmlFreeDoc(doc);
	if (ctxt) xmlFreeParserCtxt(ctxt);

	return ret;
}


/* read a config file */
int cfg_parsefile(char *filename)
{
	size_t i;
	uint64_t cv = 0xffffffffffff;

	/* set up the XML */
	xmlSetGenericErrorFunc(stdout, NULL);
	xmlSetStructuredErrorFunc(stdout, NULL);


	/* parse */
	if(cfg_parse_xml(filename)) {
		fprintf(stderr, "%s::cfg_parse_xml(): parse failed\n", __FUNCTION__);
		return 1;
	}

	/* check make sure we have a full global range */
	for (i=0; i < cfg_ranges_sz; i++ ) {
		if (0 == cfg_ranges[i].start && cv == cfg_ranges[i].end)
			break;
	}

	if (i == cfg_ranges_sz) {
		fprintf(stderr, "%s(): No global rangedef was found\n", __FUNCTION__);
		return 1;
	}

	return 0;
}


/* tidy up */
int cfg_cleanup(void)
{
	size_t i;

	for (i=0; i < cfg_ranges_sz; i++) {
		if (NULL != cfg_ranges[i].vulns)
			free(cfg_ranges[i].vulns);
	}
	free(cfg_ranges);
	cfg_ranges = NULL;

	if (cfg_log) free(cfg_log);
	if (cfg_oui) free(cfg_oui);
	if (cfg_store) free(cfg_store);
	cfg_log = cfg_oui = cfg_store = NULL;
	return 0;
}

/* get the file locations */
const char *cfg_log_filename(void)
{
	return cfg_log;
}
const char *cfg_oui_filename(void)
{
	return cfg_oui;
}
const char *cfg_store_filename(void)
{
	return cfg_store;
}


/* get a rangedef for a particular address */
rangedef_t *cfg_get_range(uint64_t did)
{
	rangedef_t *ret = NULL;
	size_t i;

	for (i=0; i < cfg_ranges_sz; i++ ) {
		/* in range */
		if (did < cfg_ranges[i].start || cfg_ranges[i].end < did) continue;

		/* if this is our first find, assign it */
		if (!ret) {
			ret = &(cfg_ranges[i]);
			continue;
		}

		/* if cfg_ranges[i] has a smaller range, this is chosen */
		if ( (cfg_ranges[i].end - cfg_ranges[i].start) <
		  (ret->end - ret->start))
			ret = &(cfg_ranges[i]);
	}

	/* should never be NULL */
	return ret;
}
