#!/usr/bin/perl

use strict;
use lib qw(/usr/lib/libDrakX);
use common;
use standalone;

use Socket;
use mygtk2 qw(gtknew);
use ugtk2 qw(:dialogs);
use POSIX qw(strftime);
use dbus_object;
use network::ifw;

use Gtk2::SimpleList;

use ugtk2 qw(:create :helpers :wrappers);

my $loglist = create_attack_list();
$loglist->get_selection->set_mode('single');

my $blacklist = create_attack_list();
$blacklist->get_selection->set_mode('multiple');

my $whitelist = Gtk2::SimpleList->new(addr => 'hidden',
                                      N("Allowed addresses") => 'text',
                                  );
$whitelist->get_selection->set_mode('multiple');
$whitelist->set_headers_clickable(1);
$whitelist->get_column(0)->signal_connect('clicked', \&sort_by_column, $whitelist->get_model);
$whitelist->get_column(0)->set_sort_column_id(0);

my $w = ugtk2->new(N("Interactive Firewall"),
                   icon => "/usr/lib/libDrakX/icons/drakfirewall.png");

my $ifw = network::ifw->new(dbus_object::system_bus(), sub {
    my ($_con, $msg) = @_;
    my $member = $msg->get_member;
    if ($member eq 'Attack') {
        handle_log($msg->get_args_list);
    } elsif ($member eq 'Blacklist') {
	handle_blacklist($msg->get_args_list);
    } elsif ($member eq 'Whitelist') {
	handle_whitelist($msg->get_args_list);
    } elsif ($member eq 'Clear') {
	clear_lists();
    } elsif ($member eq 'Init') {
	handle_init();
    } elsif ($member eq 'ManageRequest') {
	$w->{window}->present;
    }
});
init_lists();

gtkadd($w->{window},
        gtknew('Notebook', children => [
            gtknew('Label', text => N("Log")),
            gtknew('VBox', spacing => 5, children => [
                1, gtknew('ScrolledWindow', width => 600, height => 400, child => $loglist),
                0, gtknew('HButtonBox', layout => 'edge', children_loose => [
                    gtknew('Button', text => N("Clear logs"), clicked => \&clear_log),
                    gtknew('Button', text => N("Blacklist"), clicked => sub { blacklist(get_selected_log_seq()) }),
                    gtknew('Button', text => N("Whitelist"), clicked => sub { whitelist(get_selected_log()) }),
                    gtknew('Button', text => N("Close"), clicked => sub { Gtk2->main_quit })
                ]),
            ]),
            gtknew('Label', text => N("Blacklist")),
            gtknew('VBox', spacing => 5, children => [
                1, gtknew('ScrolledWindow', width => 600, height => 400, child => $blacklist),
                0, gtknew('HButtonBox', layout => 'edge', children_loose => [
                    gtknew('Button', text => N("Remove from blacklist"), clicked => sub { unblacklist(get_selected_blacklist()) }),
                    gtknew('Button', text => N("Move to whitelist"), clicked => sub {
                        my @addr = get_selected_blacklist();
                        unblacklist(@addr);
                        whitelist(@addr);
                    }),
                    gtknew('Button', text => N("Close"), clicked => sub { Gtk2->main_quit })
                ]),
            ]),
            gtknew('Label', text => N("Whitelist")),
            gtknew('VBox', spacing => 5, children => [
                1, gtknew('ScrolledWindow', width => 600, height => 400, child => $whitelist),
                0, gtknew('HButtonBox', layout => 'edge', children_loose => [
                    gtknew('Button', text => N("Remove from whitelist"), clicked => sub { unwhitelist(get_selected_whitelist()) }),
                    gtknew('Button', text => N("Close"), clicked => sub { Gtk2->main_quit })
                ]),
            ]),
        ]),
);
$w->show;
Gtk2->main;

ugtk2::exit(0);

sub sort_by_column {
    my ($column, $model) = @_;
    my $col_id = $column->get_sort_column_id;
    my ($old_id, $old_order) = $model->get_sort_column_id;
    $model->set_sort_column_id($col_id, $old_id == $col_id && $old_order ne 'descending' ? 'ascending' : 'descending');
}

sub handle_init() {
    $ifw->attach_object;
    init_lists();
}

sub list_remove_addr {
    my ($list, @addr) = @_;
    #- workaround buggy Gtk2::SimpleList array abstraction, it destroys references
    @$list = map { member($_->[0], @addr) ? () : [ @$_ ] } @$list;
}

#- may throw an exception
sub init_blacklist() {
    my @packets = $ifw->get_blacklist;
    while (my @blacklist = splice(@packets, 0, 8)) {
        handle_blacklist(@blacklist);
    }
}

sub clear_blacklist() {
    @{$blacklist->{data}} = ();
}

sub handle_blacklist {
    attack_list_add($blacklist, network::ifw::attack_to_hash(\@_));
}

sub get_selected_blacklist() {
    uniq(map { $blacklist->{data}[$_][0] } $blacklist->get_selected_indices);
}

sub blacklist {
    my @seq = @_;
    eval { $ifw->set_blacklist_verdict($_, 1) foreach @seq };
    $@ and err_dialog(N("Interactive Firewall"), N("Unable to contact daemon"));
}

sub unblacklist {
    my @addr = @_;
    eval { $ifw->unblacklist($_) foreach @addr };
    if (!$@) {
        list_remove_addr($blacklist->{data}, @addr);
    } else {
        err_dialog(N("Interactive Firewall"), N("Unable to contact daemon"));
    }
}

#- may throw an exception
sub init_whitelist() {
    handle_whitelist($_) foreach $ifw->get_whitelist;
}

sub clear_whitelist() {
    @{$whitelist->{data}} = ();
}

sub handle_whitelist {
    my ($addr) = @_;
    push @{$whitelist->{data}}, [ $addr, network::ifw::resolve_address(network::ifw::get_ip_address($addr)) ];
}

sub get_selected_whitelist() {
    uniq(map { $whitelist->{data}[$_][0] } $whitelist->get_selected_indices);
}

sub whitelist {
    my @addr = @_;
    eval { $ifw->whitelist($_) foreach @addr };
    $@ and err_dialog(N("Interactive Firewall"), N("Unable to contact daemon"));
}

sub unwhitelist {
    my @addr = @_;
    eval { $ifw->unwhitelist($_) foreach @addr };
    if (!$@) {
        list_remove_addr($whitelist->{data}, @addr);
    } else {
        err_dialog(N("Interactive Firewall"), N("Unable to contact daemon"));
    }
}

sub init_lists() {
    eval {
        init_loglist();
        init_blacklist();
        init_whitelist();
    };
    $@ and print "$@\n", err_dialog(N("Interactive Firewall"), N("Unable to contact daemon"));
}

sub clear_lists() {
    clear_loglist();
    clear_blacklist();
    clear_whitelist();
}

sub create_attack_list() {
    my $attacklist = Gtk2::SimpleList->new(addr => 'hidden',
                                           seq => 'hidden',
                                           timestamp => 'hidden',
                                           N("Date") => 'text',
                                           N("Attacker") => 'text',
                                           N("Attack type") => 'text',
                                           N("Service") => 'text',
                                           N("Network interface") => 'text',
                                           N("Protocol") => 'text',
                                       );
    $attacklist->set_headers_clickable(1);
    foreach (0, 1, 2) {
        $attacklist->get_column($_)->signal_connect('clicked', \&sort_by_column, $attacklist->get_model);
        #- sort on timestamp if Date column is clicked
        #- sort columns include hidden columns while list columns don't
        $attacklist->get_column($_)->set_sort_column_id($_ == 0 ? 1 : $_ + 2);
    }
    $attacklist;
}

sub attack_list_add {
    my ($attacklist, $attack) = @_;
    push @{$attacklist->{data}}, [
        $attack->{addr},
        $attack->{seq},
        $attack->{timestamp},
        $attack->{date},
        $attack->{hostname},
        $attack->{type},
        $attack->{service},
        $attack->{indev},
        $attack->{protocol},
    ];
}

#- may throw an exception
sub init_loglist() {
    my @packets = $ifw->get_reports(1);
    while (my @attack = splice(@packets, 0, 10)) {
        handle_log(@attack);
    }
}

sub clear_loglist() {
    @{$loglist->{data}} = ();
}

sub handle_log {
    attack_list_add($loglist, network::ifw::attack_to_hash(\@_));
}

sub get_selected_log_seq() {
    uniq(map { $loglist->{data}[$_][1] } $loglist->get_selected_indices);
}
sub get_selected_log() {
    uniq(map { $loglist->{data}[$_][0] } $loglist->get_selected_indices);
}

sub clear_log {
    eval {
        $ifw->clear_processed_reports;
        $ifw->send_alert_ack;
    };
    if (!$@) {
        clear_loglist();
    } else {
        err_dialog(N("Interactive Firewall"), N("Unable to contact daemon"));
    }
}