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

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

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

#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#ifdef HAVE_SYS_IOCCOM_H
#include <sys/ioccom.h>
#endif
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/tree.h>
#include <sys/wait.h>
#include <sys/queue.h>

#include <err.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <signal.h>
#ifdef HAVE_TIME_H
#include <time.h>
#endif
#include <fcntl.h>
#include <unistd.h>
#include <getopt.h>
#include <dnet.h>

#undef timeout_pending
#undef timeout_initialized

#include <event.h>

#include "honeyd.h"
#include "tagging.h"
#include "untagging.h"
#include "stats.h"
#include "histogram.h"
#include "honeydstats.h"
#include "analyze.h"

/* Stubs to make it compile */

int pcap_datalink(void *some) {	return (-1); }
char *honeyd_osfp_name(struct ip_hdr *hdr) { return (NULL); }
void hooks_add_packet_hook(int protocol, int dir, void *callback, void *arg) {}

/* Prototypes */
int
make_socket(int (*f)(int, const struct sockaddr *, socklen_t), int type,
    char *address, uint16_t port);

static int
user_compare(struct user *a, struct user *b)
{
	return (strcmp(a->name, b->name));
}

struct usertree users;

SPLAY_PROTOTYPE(usertree, user, node, user_compare);
SPLAY_GENERATE(usertree, user, node, user_compare);

int checkpoint_fd = -1;
struct evbuffer *checkpoint_evbuf;
static struct timeval checkpoint_tv;
static int checkpoint_doreplay = 0;

void
user_new(const char *name, const char *password)
{
	struct user *user = NULL, tmp;

	tmp.name = name;
	if ((user = SPLAY_FIND(usertree, &users, &tmp)) == NULL) {
		if ((user = calloc(1, sizeof(struct user))) == NULL)
			err(1, "%s: calloc", __func__);

		if ((user->name = strdup(name)) == NULL)
			err(1, "%s: strdup", __func__);

		gettimeofday(&user->tv_when, NULL);

		SPLAY_INSERT(usertree, &users, user);

		syslog(LOG_NOTICE, "Added user '%s'", name);
	}

	hmac_init(&user->hmac, password);
}

/*
 * Reads rows of username:password entries.  We use this information
 * to authenticate and validate the reports that we receive.
 */

int
user_read_config(const char *filename)
{
	FILE *fin;
	char line[1024];
	int nrline = 0, res = -1;

	if ((fin = fopen(filename, "r")) == NULL)
		return (-1);

	while (fgets(line, sizeof(line), fin) != NULL) {
		char *user, *password, *p = line;

		nrline++;
		user = strsep(&p, ":");
		password = strsep(&p, ":\r\n");

		if (user == NULL || password == NULL) {
			syslog(LOG_WARNING,
			    "%s:%d: cannot read user and password",
			    filename, nrline);
			goto out;
		}

		user_new(user, password);
	}

	res = 0;
 out:
	fclose(fin);
	return (res);
}

/* 
 * We just got a single record - now fold it into the different
 * stats structures.
 */

int
record_process(struct user *user, struct evbuffer *evbuf)
{
	struct record *record;
	int res = -1;

	if ((record = calloc(1, sizeof(struct record))) == NULL)
		err(1, "%s: calloc", __func__);

	if (tag_unmarshal_record(evbuf, M_RECORD, record) == -1) {
		syslog(LOG_WARNING,
		    "%s: failed to unmashal record for user '%s'",
		    __func__, user->name);
		goto out;
	}

	analyze_record(record);

	res = 0;
 out:
	record_clean(record);
	free(record);
	return (res);
}

int
measurement_process(struct user *user, struct evbuffer *evbuf)
{
	uint32_t counter;
	struct timeval tv_start, tv_end, tv_diff;
	time_t tstart;
	uint8_t tag;

	if (tag_unmarshal_int(evbuf, M_COUNTER, &counter) == -1)
		return (-1);
	if (tag_unmarshal_timeval(evbuf, M_TV_START, &tv_start) == -1)
		return (-1);

	if (tag_unmarshal_timeval(evbuf, M_TV_END, &tv_end) == -1)
		return (-1);

	timersub(&tv_end, &tv_start, &tv_diff);
	tstart =  tv_start.tv_sec;
	if (!checkpoint_doreplay || user->nreports % 60 == 0)
		syslog(LOG_INFO,
		    "%s: %ld seconds of data at measurement period %.24s",
		    user->name, tv_diff.tv_sec, ctime(&tstart));

	/* 
	 * If we get a new time then we can update the counter,
	 * otherwise we accept only counters that are newer than
	 * our sequence number.
	 */
	if (timercmp(&user->tv_last, &tv_start, <)) {
		user->tv_last = tv_start;
		user->seqnr = counter;
	} else if (counter - user->seqnr > 0x80000000L) {
		syslog(LOG_WARNING, "%s: replayed packet: %d, expecting %d",
		    user->name, counter, user->seqnr);
		return (-1);
	}

	/* Write the data that we previously appended */
	if (checkpoint_fd != -1) {
		/* XXX - this might block */
		evbuffer_write(checkpoint_evbuf, checkpoint_fd);
	} else if (checkpoint_doreplay &&
	    timercmp(&checkpoint_tv, &tv_end, <)) {
		checkpoint_tv = tv_end;
	}

	user->nreports++;
	user->seqnr = counter;

	while (tag_peek(evbuf, &tag) != -1) {
		if (tag != M_RECORD) {
			tag_consume(evbuf);
			continue;
		}

		/* This is a record tag */
		record_process(user, evbuf);
	}

	return (0);
}

int
signature_process(struct evbuffer *evbuf)
{
	struct user *user = NULL, tmpuser;
	uint8_t tag;
	struct evbuffer *tmp = NULL;
	char *username = NULL;
	u_char digest[SHA1_DIGESTSIZE];
	int res = -1;

	if (checkpoint_fd != -1) {
		evbuffer_drain(checkpoint_evbuf, -1);
		evbuffer_add(checkpoint_evbuf,
		    EVBUFFER_DATA(evbuf), EVBUFFER_LENGTH(evbuf));
	}

	if (tag_unmarshal_string(evbuf, SIG_NAME, &username) == -1)
		goto out;
	if (tag_unmarshal_fixed(evbuf, SIG_DIGEST, digest,
		sizeof(digest)) == -1)
		goto out;

	tmpuser.name = username;
	if ((user = SPLAY_FIND(usertree, &users, &tmpuser)) == NULL) {
		syslog(LOG_WARNING, "Unknown user '%s'", username);
		goto out;
	}

	if ((tmp = evbuffer_new()) == NULL)
		err(1, "%s: evbuffer_new");
	if (tag_unmarshal(evbuf, &tag, tmp) == -1)
		goto out;

	/* Validate signature */
	if (!hmac_verify(&user->hmac, digest, sizeof(digest),
		EVBUFFER_DATA(tmp), EVBUFFER_LENGTH(tmp))) {
		syslog(LOG_WARNING, "Bad signature on data from user '%s'",
		    username);
		goto out;
	}

	switch(tag) {
	case SIG_COMPRESSED_DATA:
		if (stats_decompress(tmp) == -1) {
			syslog(LOG_WARNING,
			    "failed to decompress for user '%s'", username);
			goto out;
		}
		/* FALLTHROUGH */
	case SIG_DATA:
		measurement_process(user, tmp);
		break;
	default:
		syslog(LOG_NOTICE, "%s: unknown signature tag %d", 
		    __func__, tag);
		goto out;
	}

	res = 0;
 out:
	if (tmp != NULL)
		evbuffer_free(tmp);
	if (username != NULL)
		free(username);

	return (res);
}

/* Sleazy little code to peek into marshalled signatures */

static int
signature_length(struct evbuffer *evbuf)
{
	struct evbuffer tmp;
	uint32_t length, tlen;

	tmp = *evbuf;

	/* name */
	if (tag_peek_length(&tmp, &tlen) == -1 || EVBUFFER_LENGTH(&tmp) < tlen)
		return (-1);
		
	length = tlen;
	tmp.buffer += tlen;
	tmp.off -= tlen;

	/* signature */
	if (tag_peek_length(&tmp, &tlen) == -1 || EVBUFFER_LENGTH(&tmp) < tlen)
		return (-1);
		
	length += tlen;
	tmp.buffer += tlen;
	tmp.off -= tlen;

	/* data */
	if (tag_peek_length(&tmp, &tlen) == -1 || EVBUFFER_LENGTH(&tmp) < tlen)
		return (-1);
		
	length += tlen;

	return (length);
}

void
checkpoint_replay(int fd)
{
	struct evbuffer *evbuf = evbuffer_new();
	int nread;

	fprintf(stderr, "Replaying checkpoint ...\n");
	count_set_time(&checkpoint_tv);
	checkpoint_doreplay = 1;
	analyze_set_checkpoint_doreplay(1);

	/* Read all the data and process it */
	while ((nread = evbuffer_read(evbuf, fd, 4096)) > 0) {
		int length;

		while ((length = signature_length(evbuf)) != -1 &&
		    EVBUFFER_LENGTH(evbuf) >= length) {
				signature_process(evbuf);
		}
	}

	/* Print the output at the last time we saw data from the checkpoint */
	analyze_print_report();

	checkpoint_doreplay = 0;
	analyze_set_checkpoint_doreplay(0);
	count_set_time(NULL);

	fprintf(stderr, "... checkpoint replayed\n");

	evbuffer_free(evbuf);
	close(fd);
}
