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

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

#ifdef HAVE_FCNTL_H
#include <fcntl.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 <store.h>


/* make a directory, recursivley */
int store_mkdir_recursive(const char *d, struct stat *sbuf)
{
	char *dir;
	size_t i;

	/* first up, copy the buffer and format all of the data */
	dir = strdup(d);
	if (!dir) {
		applog(LOG_ERR, "%s::strdup(): %s", __FUNCTION__, strerror(errno));
		return 1;
	}

	for (i=0; ; i++) {
		if (('/' == dir[i] || 0 == dir[i]) && i > 0) {
			dir[i] = 0;

			/* exists? */
			if (stat(dir, sbuf)) {
				if (ENOENT != errno) {
					applog(LOG_WARNING,"%s::stat(): %s",
					  __FUNCTION__,strerror(errno));
					free(dir);
					return 1;
				}
			} else {
				goto store_mkdir_recursive_next;
			}

			/* make */
			if (mkdir(dir, S_IRWXU)) {
				applog(LOG_WARNING, "%s::mkdir(): %s",
				  __FUNCTION__, strerror(errno));
				free(dir);
				return 1;
			}

			store_mkdir_recursive_next:
			if (0 == d[i])
				break;
			else
				dir[i] = '/';
		}
	}

	free(dir);
	if (stat(d, sbuf)) {
		applog(LOG_WARNING,"%s::stat(): %s", __FUNCTION__,strerror(errno));
		return 1;
	}

	return 0;
}

/* make a directory from a filename */
int store_mkdir_from_filename(char *d, size_t d_len, struct stat *sbuf)
{
	int ret = 0;

	/* find the last occourance of '/' */
	for (; 0 != d_len && '/' != d[d_len]; d_len--);
	if (0 == d_len) return 1;

	d[d_len] = 0;
	ret = store_mkdir_recursive(d, sbuf);
	d[d_len] = '/';

	return ret;
}


/* write a file to disk */
int store_writefile(char *fn, size_t fn_len, cbuf_t *cb)
{
	struct stat sbuf;
	int fd;
	size_t ret;

	if (!fn || !cb || !cb->buf) return 1;

	if (store_mkdir_from_filename(fn, fn_len, &sbuf))
		return 1;

	/* is the directory writable? */
	if ((sbuf.st_mode & S_IWUSR) != S_IWUSR) {
		applog(LOG_WARNING, "%s(): store device directory is not writable",
		  __FUNCTION__);
		return 1;
	}

	fd = open(fn, O_WRONLY|O_CREAT|O_TRUNC, S_IRGRP|S_IWGRP);
	if (-1 == fd) {
		applog(LOG_WARNING, "%s::open(): %s", __FUNCTION__, strerror(errno));
		return 1;
	}

	ret = write(fd, cb->buf, cb->len);
	if (-1 == (int)ret) {
		applog(LOG_WARNING, "%s::write(): %s", __FUNCTION__, strerror(errno));
		return 1;
	} else if (ret != cb->len) {
		applog(LOG_WARNING, "%s::write(): not all bytes written", __FUNCTION__);
		return 1;
	}

	close(fd);
	return 0;
}


/* append to a file on disk */
int store_appendfile(char *fn, size_t fn_len, cbuf_t *cb)
{
	struct stat sbuf;
	int fd;
	size_t ret;

	if (!fn || !cb || !cb->buf) return 1;

	if (store_mkdir_from_filename(fn, fn_len, &sbuf))
		return 1;

	/* is the directory writable? */
	if ((sbuf.st_mode & S_IWUSR) != S_IWUSR) {
		applog(LOG_WARNING, "%s(): store device directory is not writable",
		  __FUNCTION__);
		return 1;
	}

	fd = open(fn, O_WRONLY|O_CREAT|O_APPEND, S_IRGRP|S_IWGRP);
	if (-1 == fd) {
		applog(LOG_WARNING, "%s::open(): %s", __FUNCTION__, strerror(errno));
		return 1;
	}

	ret = write(fd, cb->buf, cb->len);
	if (-1 == (int)ret) {
		applog(LOG_WARNING, "%s::write(): %s", __FUNCTION__, strerror(errno));
		return 1;
	} else if (ret != cb->len) {
		applog(LOG_WARNING, "%s::write(): not all bytes written", __FUNCTION__);
		return 1;
	}

	close(fd);
	return 0;
}


void store_conv_filename(char *tmp)
{
	int i;
	for (i=0; 0 != tmp[i]; i++)
		if (':' == tmp[i]) tmp[i] = '_';
}


/* save the details of a device to disk */
int store_device(device_t *dev)
{
	const char *d;
	char *fn, tmp[32];
	struct stat sbuf;
	cbuf_t cb;
	size_t fn_sz, fn_len;
	int ret = 0;
	uint64_t did;

	d = cfg_store_filename();
	if (!d) {
		applog(LOG_WARNING, "%s(): Null store directory", __FUNCTION__);
		return 1;
	}

	/* check the store directory */
	if (stat(d, &sbuf)) {
		/* eep find out what happened */
		if (ENOENT == errno) {
			/* no dir, try creating it */
			if (store_mkdir_recursive(d, &sbuf)) return 1;
		} else {
			applog(LOG_WARNING,"%s::stat(): %s", __FUNCTION__,strerror(errno));
			return 1;
		}
	}

	/* is the directory writable? */
	if ((sbuf.st_mode & S_IWUSR) != S_IWUSR) {
		applog(LOG_WARNING, "%s(): store directory is not writable",
		  __FUNCTION__);
		return 1;
	}

	memset(&cb, 0, sizeof(cbuf_t));
	fn_sz = fn_len = 0;

	/* save the general info + SDP */
	did = bd2int(&(dev->bdaddr));
	if (ll_print_dev_info(did, &cb))
		return 1;

	ll_lock_list();
	ba2str(&dev->bdaddr, tmp);
	ll_unlock_list();
	/* convert tmp to make it safe for use */
	tmp[31] = 0;
	store_conv_filename(tmp);
	
	/* make a new directory */
	fn = NULL;
	while (1) {
		fn_sz += 2048;
		fn = (char*)realloc(fn, sizeof(char)*fn_sz);
		if (!fn) {
			applog(LOG_ERR, "%s::malloc(): %s",
			  __FUNCTION__, strerror(errno));
			if (cb.buf) free(cb.buf);
			return 1;
		}

		fn_len = snprintf(fn, fn_sz, "%s/%s/info", d, tmp);
		if (fn_len < fn_sz) break;
	}
	if (store_writefile(fn, fn_len, &cb)) {
		if (cb.buf) free(cb.buf);
		if (fn) free(fn);
		return 1;
	}

	fn_len = 0;
	if (cb.buf) free(cb.buf);
	cb.buf = NULL;
	if(fn) free(fn);

	return ret;
}


/* save a timestamp */
int store_timestamp(device_t *dev)
{
	const char *d;
	char *fn, tmp[32];
	struct stat sbuf;
	cbuf_t cb;
	size_t fn_sz, fn_len;
	int ret = 0;

	d = cfg_store_filename();
	if (!d) {
		applog(LOG_WARNING, "%s(): Null store directory", __FUNCTION__);
		return 1;
	}

	/* check the store directory */
	if (stat(d, &sbuf)) {
		/* eep find out what happened */
		if (ENOENT == errno) {
			/* no dir, try creating it */
			if (store_mkdir_recursive(d, &sbuf)) return 1;
		} else {
			applog(LOG_WARNING,"%s::stat(): %s", __FUNCTION__,strerror(errno));
			return 1;
		}
	}

	/* is the directory writable? */
	if ((sbuf.st_mode & S_IWUSR) != S_IWUSR) {
		applog(LOG_WARNING, "%s(): store directory is not writable",
		  __FUNCTION__);
		return 1;
	}

	memset(&cb, 0, sizeof(cbuf_t));
	fn_sz = fn_len = 0;

	/* save the general info + SDP */
	if (ll_print_timestamp(dev, &cb))
		return 1;

	ll_lock_list();
	ba2str(&dev->bdaddr, tmp);
	ll_unlock_list();
	/* convert tmp to make it safe for use */
	tmp[31] = 0;
	store_conv_filename(tmp);
	
	/* make a new directory */
	fn = NULL;
	while (1) {
		fn_sz += 2048;
		fn = (char*)realloc(fn, sizeof(char)*fn_sz);
		if (!fn) {
			applog(LOG_ERR, "%s::malloc(): %s",
			  __FUNCTION__, strerror(errno));
			if (cb.buf) free(cb.buf);
			return 1;
		}

		fn_len = snprintf(fn, fn_sz, "%s/%s/timestamps", d, tmp);
		if (fn_len < fn_sz) break;
	}
	if (store_appendfile(fn, fn_len, &cb)) {
		if (cb.buf) free(cb.buf);
		if (fn) free(fn);
		return 1;
	}

	if (cb.buf) free(cb.buf);
	if(fn) free(fn);
	return ret;
}

