/*
 * Copyright (c) 2002, 2003, 2004 Niels Provos <provos@citi.umich.edu>
 * All rights reserved.
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/*
 * Copyright (c) 1999, 2000 Dug Song <dugsong@monkey.org>
 * All rights reserved, all wrongs reversed.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/types.h>
#include <sys/param.h>

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <sys/queue.h>
#include <sys/tree.h>
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif

#define _GNU_SOURCE
#include <err.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <fcntl.h>
#include <assert.h>

#include <pcap.h>
#include <dnet.h>
#include <event.h>

#include "honeyd.h"
#include "util.h"

int trace_on;		/* determines if we trace file descriptor calls */

int
pcap_dloff(pcap_t *pd)
{
	int offset = -1;
	
	switch (pcap_datalink(pd)) {
	case DLT_EN10MB:
		offset = 14;
		break;
	case DLT_IEEE802:
		offset = 22;
		break;
	case DLT_FDDI:
		offset = 21;
		break;
#ifdef DLT_PPP
        case DLT_PPP:
                offset = 24;
                break;
#endif
#ifdef DLT_LINUX_SLL
        case DLT_LINUX_SLL:
                offset = 16;
                break;
#endif
#ifdef DLT_LOOP
	case DLT_LOOP:
#endif
	case DLT_NULL:
		offset = 4;
		break;
	default:
		warnx("unsupported datalink type");
		break;
	}
	return (offset);
}

char *
strrpl(char *str, size_t size, char *match, char *value)
{
	char *p, *e;
	int len, rlen;

	p = str;
	e = p + strlen(p);
	len = strlen(match);

	/* Try to match against the variable */
	while ((p = strchr(p, match[0])) != NULL) {
		if (!strncmp(p, match, len) && !isalnum(p[len]))
			break;
		/* This could be optimized but we don't really care */
		p += 1;

		if (p >= e)
			return (NULL);
		    
	}

	if (p == NULL)
		return (NULL);

	rlen = strlen(value);

	if (strlen(str) - len + rlen > size)
		return (NULL);

	memmove(p + rlen, p + len, strlen(p + len) + 1);
	memcpy(p, value, rlen);

	return (p);
}

/*
 * Checks if <addr> is contained in the network specified by <net>
 */

int
addr_contained(struct addr *net, struct addr *addr)
{
	struct addr tmp;

	tmp = *net;
	tmp.addr_bits = IP_ADDR_BITS;
	if (addr_cmp(&tmp, addr) > 0)
		return (0);

	addr_bcast(net, &tmp);
	tmp.addr_bits = IP_ADDR_BITS;
	if (addr_cmp(&tmp, addr) < 0)
		return (0);

	return (1);
}

char *
strnsep(char **line, char *delim)
{
	char *ret, *p;

	if (line == NULL)
		return (NULL);

	ret = *line;
	if (ret == NULL)
		return (NULL);

	p = strpbrk(ret, delim);
	if (p == NULL) {
		*line = NULL;
		return (ret);
	}

	*line = p + strspn(p, delim);
	*p = '\0';

	return (ret);
}

#ifndef HAVE_FGETLN
char *
fgetln(FILE *stream, size_t *len)
{
	static char buf[1024];

	if (fgets(buf, sizeof(buf), stream) == NULL)
		return (NULL);
	
	*len = strlen(buf);

	return (buf);
}
#endif

/* Either connect or bind */

int
make_socket_ai(int (*f)(int, const struct sockaddr *, socklen_t), int type,
    struct addrinfo *ai)
{
        struct linger linger;
        int fd, on = 1;

        /* Create listen socket */
        fd = socket(AF_INET, type, 0);
        if (fd == -1) {
                warn("socket");
                return (-1);
        }

        if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) {
                warn("fcntl(O_NONBLOCK)");
                goto out;
        }

        if (fcntl(fd, F_SETFD, 1) == -1) {
                warn("fcntl(F_SETFD)");
                goto out;
        }

	if (type == SOCK_STREAM) {
		setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE,
		    (void *)&on, sizeof(on));
		setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
		    (void *) &on, sizeof(on));
#ifdef SO_REUSEPORT
		setsockopt(fd, SOL_SOCKET, SO_REUSEPORT,
		    (void *) &on, sizeof(on));
#endif
		linger.l_onoff = 1;
		linger.l_linger = 5;
		setsockopt(fd, SOL_SOCKET, SO_LINGER, &linger, sizeof(linger));
	}

        if ((f)(fd, ai->ai_addr, ai->ai_addrlen) == -1) {
		if (errno != EINPROGRESS) {
			warn("%s", __func__);
			goto out;
		}
        }

	return (fd);

 out:
	close(fd);
	return (-1);
}

/*
 * Connect to an address:port from a specified local IP address.
 */

int
make_bound_connect(int type, char *address, uint16_t port, char *local_address)
{
        struct addrinfo ai, *aitop, *local_aitop;
        char strport[NI_MAXSERV];
	int fd;
	
        memset(&ai, 0, sizeof (ai));
        ai.ai_family = AF_INET;
        ai.ai_socktype = type;
        snprintf(strport, sizeof (strport), "%d", port);
        if (getaddrinfo(address, strport, &ai, &aitop) != 0) {
                warn("getaddrinfo");
                return (-1);
        }
        
        memset(&ai, 0, sizeof (ai));
        ai.ai_family = AF_INET;
        ai.ai_socktype = type;
        strlcpy(strport, "0", sizeof(strport));
        if (getaddrinfo(local_address, strport, &ai, &local_aitop) != 0) {
                warn("getaddrinfo");
		freeaddrinfo(aitop);
                return (-1);
        }

	fd = make_socket_ai(bind, type, local_aitop);

	if (fd != -1 ) {
		if (connect(fd, aitop->ai_addr, aitop->ai_addrlen) == -1) {
			if (errno != EINPROGRESS) {
				warn("%s", __func__);
				close(fd);
				fd = -1;
			}
		}
        }

	freeaddrinfo(aitop);
	freeaddrinfo(local_aitop);

	return (fd);
}

int
make_socket(int (*f)(int, const struct sockaddr *, socklen_t), int type,
    char *address, uint16_t port)
{
        struct addrinfo ai, *aitop;
        char strport[NI_MAXSERV];
	int fd;
	
        memset(&ai, 0, sizeof (ai));
        ai.ai_family = AF_INET;
        ai.ai_socktype = type;
        ai.ai_flags = f != connect ? AI_PASSIVE : 0;
        snprintf(strport, sizeof (strport), "%d", port);
        if (getaddrinfo(address, strport, &ai, &aitop) != 0) {
                warn("getaddrinfo");
                return (-1);
        }
        
	fd = make_socket_ai(f, type, aitop);

	freeaddrinfo(aitop);

	return (fd);
}

#define DIFF(a,b) do { \
	if ((a) < (b)) return -1; \
	else if ((a) > (b)) return 1; \
} while (0)

int
conhdr_compare(struct tuple *a, struct tuple *b)
{
	DIFF(a->ip_src, b->ip_src);
	DIFF(a->ip_dst, b->ip_dst);
	DIFF(a->sport, b->sport);
	DIFF(a->dport, b->dport);

	return (0);
}

char *
honeyd_contoa(const struct tuple *hdr)
{
	static char buf[128];
	char asrc[24], adst[24];
	struct addr src, dst;
	u_short sport, dport;
	
	addr_pack(&src, ADDR_TYPE_IP, IP_ADDR_BITS, &hdr->ip_src, IP_ADDR_LEN);
	addr_pack(&dst, ADDR_TYPE_IP, IP_ADDR_BITS, &hdr->ip_dst, IP_ADDR_LEN);

	/* For a local connection switch the address around */
	if (hdr->local) {
		struct addr tmp;

		tmp = src;
		src = dst;
		dst = tmp;

		sport = hdr->dport;
		dport = hdr->sport;
	} else {
		sport = hdr->sport;
		dport = hdr->dport;
	}

	addr_ntop(&src, asrc, sizeof(asrc));
	addr_ntop(&dst, adst, sizeof(adst));

	snprintf(buf, sizeof(buf), "(%s:%d - %s:%d)",
	    asrc, sport, adst, dport);

	return (buf);
}

/* Some stupid keyvalue stuff */

void
kv_add(struct keyvalueq *head, char *key, char *value)
{
	struct keyvalue *entry = malloc(sizeof(struct keyvalue));

	if (entry == NULL)
		err(1, "%s: malloc", __func__);

	entry->key = strdup(key);
	entry->value = strdup(value);

	if (entry->key == NULL || entry->value == NULL)
		err(1, "%s: strdup", __func__);

	TAILQ_INSERT_TAIL(head, entry, next);
}

char *
kv_find(struct keyvalueq *head, char *key)
{
	struct keyvalue *entry;

	TAILQ_FOREACH(entry, head, next) {
		if (strcmp(key, entry->key) == 0)
			return (entry->value);
	}
	return (NULL);
}

int
kv_remove(struct keyvalueq *head, char *key)
{
	struct keyvalue *entry;

	TAILQ_FOREACH(entry, head, next) {
		if (strcmp(key, entry->key) == 0)
			break;
	}

	if (entry == NULL)
		return (0);

	TAILQ_REMOVE(head, entry, next);
	free(entry->value);
	free(entry->key);
	free(entry);

	return (1);
}

void
kv_replace(struct keyvalueq *head, char *key, char *value)
{
	struct keyvalue *entry;

	TAILQ_FOREACH(entry, head, next) {
		if (strcmp(key, entry->key) == 0)
		    break;
	}

	if (entry == NULL) {
		entry = malloc(sizeof(struct keyvalue));
		if (entry == NULL)
			err(1, "%s: malloc", __func__);
	} else {
		free(entry->key);
		free(entry->value);
		TAILQ_REMOVE(head, entry, next);
	}

	entry->key = strdup(key);
	entry->value = strdup(value);

	if (entry->key == NULL || entry->value == NULL)
		err(1, "%s: strdup", __func__);

	TAILQ_INSERT_TAIL(head, entry, next);
}

void
name_from_addr(struct sockaddr *sa, socklen_t salen,
    char **phost, char **pport)
{
	static char ntop[NI_MAXHOST];
	static char strport[NI_MAXSERV];

	if (getnameinfo(sa, salen,
		ntop, sizeof(ntop), strport, sizeof(strport),
		NI_NUMERICHOST|NI_NUMERICSERV) != 0)
		err(1, "%s: getnameinfo failed", __func__);

	*phost = ntop;
	*pport = strport;
}

/* File descriptor sharing */

static int *fds_refs;
static int fds_refsize;

static int
fdshare_init(int fd)
{
	int n = fds_refsize;
	int *tmp;
	
	if (!n) 
		n = 32;

	while (n <= fd)
		n <<= 1;

	tmp = realloc(fds_refs, n * sizeof(int));
	if (tmp == NULL)
		return (-1);

	/* Initialize everything to a 0 refcount */
	memset(tmp + fds_refsize, 0, (n - fds_refsize) * sizeof(int));

	fds_refs = tmp;
	fds_refsize = n;

	return (0);
}

int
fdshare_dup(int fd)
{
	int res;

	assert(fd >= 0);

	if (fds_refs == NULL || fd >= fds_refsize) {
		res = fdshare_init(fd);
		if (res == -1)
			return (-1);
	}

	++fds_refs[fd];

	return (fd);
}

/* Check if the ref counter is not zero; if it is close the fd */

int
fdshare_close(int fd)
{
	assert(fds_refs != NULL);
	assert(fd >= 0 && fd < fds_refsize);

	if (--fds_refs[fd])
		return (0);

	TRACE_RESET(fd, close(fd));

	return (0);
}

/* Quick tool for debugging */

int
fdshare_inspect(int fd)
{
	if (fds_refs == NULL || fd < 0 || fd >= fds_refsize)
		return (-1);

	return (fds_refs[fd]);
}

/* Tracing facility */

struct trace {
	TAILQ_ENTRY(trace) next;
	char *line;
	int closed;
};

static TAILQ_HEAD(traceq, trace) **trace_refs;
static int trace_refsize;

static int
trace_init(int fd)
{
	int n = trace_refsize, i;
	struct traceq **tmp;
	
	if (!n) 
		n = 32;

	while (n <= fd)
		n <<= 1;

	tmp = (struct traceq **)realloc(trace_refs, n * sizeof(struct traceq));
	if (tmp == NULL)
		return (-1);

	/* Initialize all queues */
	for (i = trace_refsize; i < n; ++i) {
		struct traceq *head = malloc(sizeof(struct traceq *));
		if (head == NULL)
			err(1, "%s: malloc", __func__);
		TAILQ_INIT(head);
		tmp[i] = head;
	}

	trace_refs = tmp;
	trace_refsize = n;

	return (0);
}

static void
trace_free(int fd)
{
	struct traceq *head = trace_refs[fd];
	struct trace *tmp;

	while ((tmp = TAILQ_FIRST(head)) != NULL) {
		TAILQ_REMOVE(head, tmp, next);
		free(tmp->line);
		free(tmp);
	}
}

int
trace_enter(int fd, char *line, int closed)
{
	struct trace *tmp;
	int res;

	assert(fd >= 0);

	if (trace_refs == NULL || fd >= trace_refsize) {
		res = trace_init(fd);
		if (res == -1)
			goto error;
	}

	if ((tmp = TAILQ_LAST(trace_refs[fd], traceq)) != NULL) {
		if (tmp->closed)
			trace_free(fd);
	}

	if ((tmp = malloc(sizeof(struct trace))) == NULL)
		goto error;

	tmp->line = line;
	tmp->closed = closed;
	TAILQ_INSERT_TAIL(trace_refs[fd], tmp, next);

	return (0);

 error:
	free(line);
	return (-1);
}

#define TRACE_UNKNOWN	"<unknown>\n"

int
trace_inspect(int fd, struct evbuffer *buffer)
{
	struct trace *tmp;
	if (trace_refs == NULL || fd < 0 || fd >= trace_refsize) {
		evbuffer_add(buffer, TRACE_UNKNOWN, strlen(TRACE_UNKNOWN) + 1);
		return (-1);
	}

	TAILQ_FOREACH(tmp, trace_refs[fd], next) {
		evbuffer_add_printf(buffer, "%s\n", tmp->line);
	}
	evbuffer_add(buffer, "\0", 1);
	
	return (0);
}

void
trace_onoff(int on) {
	trace_on = on;
	if (!on) {
		int i;
		for (i = 0; i < trace_refsize; ++i)
			trace_free(i);
	}
}
