/*****
*
* Copyright (C) 2003-2005 Nicolas Delon <nicolas@prelude-ids.org>
* All Rights Reserved
*
* This file is part of the Prelude program.
*
* 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, 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; see the file COPYING.  If not, write to
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
*
*****/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/socket.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <unistd.h>

#include <libprelude/prelude.h>
#include <libprelude/prelude-log.h>
#include <libprelude/daemonize.h>

#include "pflogger.h"
#include "process_packet.h"
#include "config.h"

#define DEFAULT_SNAPLEN        96
#define DEFAULT_INTERFACE      "pflog0"

#define ANALYZER_CLASS "HIDS"
#define ANALYZER_MODEL "Prelude PFlogger"
#define ANALYZER_MANUFACTURER "The Prelude Team http://www.prelude-ids.org"


static char *pid_file = NULL;
static const char *conf_file = PRELUDE_PFLOGGER_CONF;
static char *interface = DEFAULT_INTERFACE;
static int bring_interface_down_at_shutdown = 0;

static prelude_client_t *pflogger_client = NULL;
static idmef_analyzer_t *pflogger_analyzer = NULL;



static int print_help(prelude_option_t *opt, const char *arg, prelude_string_t *errmsg, void *context)
{
	prelude_option_print(NULL, PRELUDE_OPTION_TYPE_CLI, 25, stderr);

	exit(0);
}



static int print_version(prelude_option_t *opt, const char *arg, prelude_string_t *errmsg, void *context)
{
	printf("prelude-pflogger %s\n", VERSION);

	exit(0);
}



static	int set_daemon_mode(prelude_option_t *opt, const char *arg, prelude_string_t *errmsg, void *context)
{
	int ret;

	ret = prelude_daemonize(pid_file);
	if ( ret < 0 )
		return ret;

	prelude_log_set_flags(prelude_log_get_flags()|PRELUDE_LOG_FLAGS_SYSLOG);
	
	return 0;
}



static int set_pid_file(prelude_option_t *opt, const char *arg, prelude_string_t *errmsg, void *context)
{
        pid_file = strdup(arg);
        if ( ! pid_file )
		return prelude_error_from_errno(errno);

        return 0;
}



static int set_conf_file(prelude_option_t *opt, const char *arg, prelude_string_t *errmsg, void *context)
{
	conf_file = strdup(arg);
	if ( ! conf_file )
		return prelude_error_from_errno(errno);

	return 0;
}



static int init_sensor_options(int *argc, char **argv)
{
	prelude_option_t *ropt, *opt;
	prelude_string_t *errmsg;
	int ret;

	ret = prelude_option_new_root(&ropt);
	if ( ret < 0 )
		return ret;
	
	prelude_option_add(ropt, &opt, PRELUDE_OPTION_TYPE_CLI, 'v', "version",
			   "Print version number", PRELUDE_OPTION_ARGUMENT_NONE,
			   print_version, NULL);
	prelude_option_set_priority(opt, PRELUDE_OPTION_PRIORITY_IMMEDIATE);

	prelude_option_add(ropt, &opt, PRELUDE_OPTION_TYPE_CLI, 'h', "help",
			   "Print this help", PRELUDE_OPTION_ARGUMENT_NONE,
			   print_help, NULL);
	prelude_option_set_priority(opt, PRELUDE_OPTION_PRIORITY_IMMEDIATE);

	prelude_option_add(ropt, &opt, PRELUDE_OPTION_TYPE_CLI|PRELUDE_OPTION_TYPE_CFG, 'd', "daemon",
			   "Run in daemon mode", PRELUDE_OPTION_ARGUMENT_NONE,
			   set_daemon_mode, NULL);
	prelude_option_set_priority(opt, PRELUDE_OPTION_PRIORITY_FIRST);

	prelude_option_add(ropt, &opt, PRELUDE_OPTION_TYPE_CLI, 'c', "config",
			   "Configuration file to use", PRELUDE_OPTION_ARGUMENT_REQUIRED,
			   set_conf_file, NULL);
	prelude_option_set_priority(opt, PRELUDE_OPTION_PRIORITY_IMMEDIATE);

	prelude_option_add(ropt, &opt, PRELUDE_OPTION_TYPE_CLI|PRELUDE_OPTION_TYPE_CFG, 'P', "pidfile",
			   "Write Prelude PFlogger pid to the specified pidfile", PRELUDE_OPTION_ARGUMENT_REQUIRED,
			   set_pid_file, NULL);
	prelude_option_set_priority(opt, PRELUDE_OPTION_PRIORITY_IMMEDIATE);

	ret = prelude_option_read(ropt, &conf_file, argc, argv, &errmsg, NULL);
	if ( ret < 0 && errmsg ) {
		prelude_log(PRELUDE_LOG_WARN, "Could not start Prelude PFlogger: %s.\n",
			    prelude_string_get_string(errmsg));
		exit(1);
	}

	return ret;
}



static int init_sensor_idmef_infos(void)
{
	prelude_string_t *string;
	int ret;

	pflogger_analyzer = prelude_client_get_analyzer(pflogger_client);

	ret = idmef_analyzer_new_model(pflogger_analyzer, &string);
	if ( ret < 0 )
		return ret;
	prelude_string_set_constant(string, ANALYZER_MODEL);

	ret = idmef_analyzer_new_class(pflogger_analyzer, &string);
	if ( ret < 0 )
		return ret;
	prelude_string_set_constant(string, ANALYZER_CLASS);

	ret = idmef_analyzer_new_manufacturer(pflogger_analyzer, &string);
	if ( ret < 0 )
		return ret;
	prelude_string_set_constant(string, ANALYZER_MANUFACTURER);

	ret = idmef_analyzer_new_version(pflogger_analyzer, &string);
	if ( ret < 0 )
		return ret;
	prelude_string_set_constant(string, VERSION);

	return 0;
}



static int init_sensor(int *argc, char **argv)
{
	int ret;

	prelude_init(argc, argv);

	ret = init_sensor_options(argc, argv);
	if ( ret < 0 )
		return ret;

	ret = prelude_client_new(&pflogger_client, "prelude-pflogger");
	if ( ret < 0 )
		return ret;

	prelude_client_set_config_filename(pflogger_client, conf_file);

	ret = init_sensor_idmef_infos();
	if ( ret < 0 )
		return ret;

	ret = prelude_client_set_flags(pflogger_client, prelude_client_get_flags(pflogger_client) | PRELUDE_CLIENT_FLAGS_ASYNC_SEND|PRELUDE_CLIENT_FLAGS_ASYNC_TIMER);
	if ( ret < 0 )
		return ret;

	return prelude_client_start(pflogger_client);
}



/*
 * Check if interface is up
 * if not, bring it up and set a flag so that
 * the interface will be brought down when pflogger shutdown
 */

static int init_interface(void)
{
	int sock;
	struct ifreq ifreq;

	sock = socket(AF_INET, SOCK_DGRAM, 0);
	if ( sock < 0 )
		return prelude_error_from_errno(errno);

	strncpy(ifreq.ifr_name, interface, sizeof (ifreq.ifr_name));

	if ( ioctl(sock, SIOCGIFFLAGS, &ifreq) < 0 ) {
		close(sock);
		return prelude_error_from_errno(errno);
	}

	if ( !(ifreq.ifr_flags & IFF_UP) ) {

		prelude_log(PRELUDE_LOG_INFO, "interface %s is down, bring it up\n", interface);
 
		ifreq.ifr_flags |= IFF_UP;

		if ( ioctl(sock, SIOCSIFFLAGS, &ifreq) )
			return prelude_error_from_errno(errno);

		bring_interface_down_at_shutdown = 1;
	}

	close(sock);

	return 0;
}


static int shutdown_interface(void)
{
	int sock;
	struct ifreq ifreq;

	if ( ! bring_interface_down_at_shutdown )
		return 0;

	sock = socket(AF_INET, SOCK_DGRAM, 0);
	if ( sock < 0 )
		return prelude_error_from_errno(errno);

	strncpy(ifreq.ifr_name, interface, sizeof (ifreq.ifr_name));

	if ( ioctl(sock, SIOCGIFFLAGS, &ifreq) < 0 ) {
		close(sock);
		return prelude_error_from_errno(errno);
	}

	ifreq.ifr_flags &= ~IFF_UP;

	if ( ioctl(sock, SIOCSIFFLAGS, &ifreq) )
		return prelude_error_from_errno(errno);

	close(sock);

	return 0;	
}


static void pflogger_shutdown(void)
{
	process_packet_stop();
	(void) shutdown_interface();
}



static void signal_handler(int signo)
{
	prelude_log(PRELUDE_LOG_INFO, "caught signal %d\n", signo);

	pflogger_shutdown();

	exit(0);
}



static void init_signal_handlers(void)
{
	signal(SIGINT, signal_handler);
	signal(SIGTERM, signal_handler);
	signal(SIGQUIT, signal_handler);
	signal(SIGHUP, signal_handler);
}


int pflogger_send_alert(idmef_message_t *message)
{
	idmef_alert_t *alert = idmef_message_get_alert(message);
	idmef_time_t *create_time;
	int ret;

	ret = idmef_time_new_from_gettimeofday(&create_time);
	if ( ret < 0 )
		return ret;
	idmef_alert_set_create_time(alert, create_time);
	
	idmef_alert_set_analyzer(alert, idmef_analyzer_ref(pflogger_analyzer), 0);

	prelude_client_send_idmef(pflogger_client, message);

	return 0;		
}



int main(int argc, char **argv)
{
	int ret;

	ret = init_sensor(&argc, argv);
	if ( ret < 0 )
		goto error;

	ret = init_interface();
	if ( ret < 0 )
		goto error;

	ret = process_packet_init(interface, DEFAULT_SNAPLEN);
	if ( ret < 0 )
		goto error;

	init_signal_handlers();

	ret = process_packet_mainloop();
	if ( ret < 0 )
		goto error;

	pflogger_shutdown();

	return 0;

 error:
	if ( ret != -1 )
		prelude_perror(ret, "error");

	return 1;
}
