#!/usr/bin/perl

use strict;
use lib qw(/usr/lib/libDrakX);
use c;
use common;
use standalone;
use Digest::MD5;
use network::netconnect;
use network::tools;

use mygtk2 qw(gtknew);
use network::activefw;

use Gtk2::TrayIcon;

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

my $enable_activefw;

my ($eventbox, $img);
my ($current_state, $menu, $timeout);
my $onstartupfile = "$ENV{HOME}/.net_applet";
add_icon_path("/usr/share/libDrakX/pixmaps/");
# Allow multiple instances, but only one per user:
is_running('net_applet') and die "net_applet already running\n";
my $prog_name = "/usr/bin/net_applet";
my $current_md5 = md5file($prog_name);

my $netcnx = {};
my $netc = {};
my $intf = {};
my $watched_interface;

$SIG{HUP} = sub {
    print "received SIGHUP, reloading network configuration\n";
    checkNetworkForce();
};

my %appletstate = (
             connected => {
  		          colour => [ 'connected' ],
			  changes => [ 'disconnected', 'error', 'busy' ],
                          menu => [ 'downNetwork', 'chooseInterface', 'chooseProfile', 'confNetwork', 'monitorNetwork', 'refresh', 'help' ],
			  tt => [ N_("Network is up on interface %s") ]
			  },
             disconnected => {
			      colour => [ 'disconnected' ],
			      changes => [ 'connected', 'error', 'busy' ],
			      menu => [ 'upNetwork', 'chooseInterface', 'chooseProfile', 'confNetwork', 'refresh', 'help' ],
			      tt => [
                            #-PO: keep the "Configure Network" substring synced with the "Configure Network" message below
                            N_("Network is down on interface %s. Click on \"Configure Network\"")
                           ]
                             },
             notconfigured => {
  		          colour => [ 'disconnected' ],
			  changes => [ 'connected' ],
			  menu => [ 'confNetwork', 'refresh', 'help' ],
			  tt => [
                                 N_("You do not have any configured Internet connection.
Run the \"%s\" assistant from the Mandrivalinux Control Center", N("Set up a new network interface (LAN, ISDN, ADSL, ...)"))
                                ]
			  }
	    );

my %actions = (
               'upNetwork' => { name => sub { N("Connect %s", $_[0]) }, launch => \&network::tools::start_interface },
               'downNetwork' => { name => sub { N("Disconnect %s", $_[0]) }, launch => \&network::tools::stop_interface },
               'monitorNetwork' => { name => N("Monitor Network"), launch => sub { system("/usr/sbin/net_monitor --defaultintf $_[0] &") } },
               'confNetwork' => { name => N("Configure Network"), launch => sub { system("/usr/sbin/drakconnect --skip-wizard &") } },
               'chooseInterface' => {
                   name => N("Watched interface"),
                   choices => sub { N("Auto-detect"), sort keys %$intf },
                   choice_selected => sub { $watched_interface ? $_[0] eq $watched_interface : $_[0] eq N("Auto-detect") },
                   launch => sub {
                       $watched_interface = $_[0] eq N("Auto-detect") ? undef : $_[0];
                       checkNetworkForce();
                   }
               },
               'chooseProfile' => {
                   name => N("Profiles"),
                   choices => sub { network::netconnect::get_profiles() },
                   choice_selected => sub { $_[0] eq $netcnx->{PROFILE} },
                   launch => sub {
                       $netcnx->{PROFILE} = $_[0];
                       network::tools::bg_command_as_root('/sbin/set-netprofile', $netcnx->{PROFILE});
                   }
               },
	       'refresh' => { name => N("Refresh"), launch => sub { checkNetworkForce() } },
               'help' => { name => N("Get Online Help"), launch => sub { system("drakhelp --id internet-connection &") } }
              );

gtkadd(my $icon = Gtk2::TrayIcon->new("Net_Applet"),
       gtkadd($eventbox = Gtk2::EventBox->new,
              gtkpack($img = Gtk2::Image->new)
             )
      );
$eventbox->signal_connect(button_press_event => sub {
			      if ($_[1]->button == 1) {
				  is_running('net_monitor') or netMonitor();
			      }
                              $_[1]->button == 3 && $menu and $menu->popup(undef, undef, undef, undef, $_[1]->button, $_[1]->time);
                          });

shouldStart() or die "$onstartupfile should be set to TRUE or use net_applet --force";

my $activefw;
my $interactive_cb;
my @attacks_queue;

if ($enable_activefw) {
    $activefw = activefw->new(sub {
        my ($con, $msg) = @_;
        handle_attack($msg->get_args_list) if
          $msg->get_interface eq "com.mandrakesoft.activefirewall" &&
          $msg->get_path eq "/com/mandrakesoft/activefirewall" &&
          $msg->get_member eq "Attack";
    });
}

checkNetworkForce();
cronNetwork();
get_unprocessed_attacks() if $enable_activefw;

$icon->show_all;
Gtk2->main;

ugtk2::exit(0);

sub is_running {
    my ($name) = @_;
    any {
	my ($ppid, $pid, $n) = /^\s*(\d+)\s+(\d+)\s+(.*)/;
	#- to run ps, perl may create some process with $name as name and 1 as ppid
	$ppid != 1 && $pid != $$ && $n eq $name;
    } `ps -o '%P %p %c' -u $ENV{USER}`;
}
sub shouldStart() {
    my ($opt) = @ARGV;
    if ($opt eq '--force' || $opt eq '-f') {
        return 1;
    }
    return getAutoStart();
}
sub md5file {
    my @md5;
    foreach my $file (@_) {
	open(my $FILE, $file) or do { print STDERR "Can not open '$file': $!"; push @md5, "" };
	binmode($FILE);
	push @md5, Digest::MD5->new->addfile($FILE)->hexdigest;
	close($FILE);
    }
    return wantarray() ? @md5 : $md5[0];
}
sub netMonitor() {
    system("/usr/sbin/net_monitor&");
    checkNetwork();
}
sub checkNetwork() {
    my ($gw_intf, $is_up, $gw_address) = $watched_interface ?
      ($watched_interface, network::tools::get_interface_status($watched_interface)) :
      network::tools::get_internet_connection($netc, $intf);
    go2State($gw_address ? 'connected' : $gw_intf ? 'disconnected' : 'notconfigured', $gw_intf);

    my $new_md5 = md5file($prog_name);
    if ($new_md5 ne $current_md5) { exec($prog_name) }
}
sub checkNetworkForce() {
    $netcnx = {};
    $netc = {};
    $intf = {};
    network::netconnect::read_net_conf($netcnx, $netc, $intf);
    undef $current_state;
    checkNetwork();
}
sub getIP {
    my ($interface) = shift;
    my $ifconfig = '/sbin/ifconfig';
    my @lines = `$ifconfig $interface`;
    my @ip = map { if_(/inet adr:([\d.]+)/, $1) } @lines;
    return wantarray() ? @ip : $ip[0];
}
sub cronNetwork() {
    $timeout = Glib::Timeout->add(2000, sub {
                                      checkNetwork();
                                      1;
                                  });
}
sub go2State {
    my ($state_type, $interface) = @_;
    if ($current_state ne $state_type) {
        $current_state = $state_type;
        $menu and $menu->destroy;
        $menu = setState($state_type, $interface);
    }
}
sub setState {
    my ($state_type, $interface) = @_;
    my $arr = $appletstate{$state_type}{menu};
    my $tmp = gtkcreate_pixbuf($appletstate{$state_type}{colour}[0]);
    $img->set_from_pixbuf($tmp);
    gtkset_tip(Gtk2::Tooltips->new, $eventbox, formatAlaTeX(common::sprintf_fixutf8(translate($appletstate{$state_type}{tt}[0]), $interface)));
    my $menu = Gtk2::Menu->new;
    foreach (@$arr) {
        my $name = ref($actions{$_}{name}) eq 'CODE' ? $actions{$_}{name}->($interface) : $actions{$_}{name};
        my $launch = $actions{$_}{launch};
        if ($actions{$_}{choices}) {
           my $selected = $actions{$_}{choice_selected};
           $menu->append(gtkshow(create_menu($name, map {
               my $choice = $_;
               my $w = gtkshow(gtkset_active(Gtk2::CheckMenuItem->new_with_label($choice), $selected->($choice)));
               gtksignal_connect($w, activate => sub { $launch->($choice) });
               $w->set_draw_as_radio(1);
               $w;
           } $actions{$_}{choices}->())));
       } else {
           $menu->append(gtksignal_connect(gtkshow(Gtk2::MenuItem->new_with_label($name)), activate => sub { $launch->($interface) }));
       }
    }
    $menu->append(gtkshow(Gtk2::SeparatorMenuItem->new));
    if ($enable_activefw) {
        $menu->append($interactive_cb = gtkshow(gtksignal_connect(gtkset_active(Gtk2::CheckMenuItem->new_with_label(N("Interactive intrusion detection")),
                                                                                $activefw->get_interactive),
                                                                  toggled => sub { $activefw->set_interactive(to_bool($_[0]->get_active)) })));
    }
    $menu->append(gtkshow(gtksignal_connect(gtkset_active(Gtk2::CheckMenuItem->new_with_label(N("Always launch on startup")), getAutoStart()),
                                            toggled => sub { setAutoStart(uc(bool2text($_[0]->get_active))) })));
    $menu->append(gtksignal_connect(gtkshow(Gtk2::MenuItem->new_with_label(N("Quit"))), activate => sub { mainQuit() }));
    $menu;
}
sub mainQuit() {
     Glib::Source->remove($timeout) if $timeout;
     Gtk2->main_quit;
}
sub getAutoStart {
    my %p = getVarsFromSh($onstartupfile);
    return to_bool($p{AUTOSTART} ne 'FALSE');
}
sub setAutoStart {
    my $state = shift;
    output_p $onstartupfile,
    qq(AUTOSTART=$state
);
}

sub get_unprocessed_attacks {
    my @packets = $activefw->get_reports;
    while (my @attack = splice(@packets, 0, 9)) {
        handle_attack(@attack);
    }
}

sub handle_attack {
    push @attacks_queue, [ @_ ];
    @attacks_queue == 1 and ask_attack_verdict($attacks_queue[0]);
}

sub set_attack_verdict {
    my ($seq, $verdict) = @_;
    $activefw->blacklist($seq, $verdict);
    shift @attacks_queue;
    @attacks_queue and ask_attack_verdict($attacks_queue[0]);
}

sub ask_attack_verdict {
    my ($attack) = @_;
    my ($seq, $timestamp, $indev, $prefix, $sensor, $protocol, $addr, $port, $icmp_type) = @$attack;

    unless ($interactive_cb->get_active) {
        #- let the daemon handle the blacklist policy in automatic mode
        set_attack_verdict($seq, undef);
        return;
    }

    my $ip_addr = activefw::get_ip_address($addr);
    my $hostname = activefw::resolve_address($ip_addr);
    my $service = activefw::get_service($port);

    my $msg = $prefix eq "SCAN" ? N("A port scanning attack has been attempted by %s.", $hostname)
            : $prefix eq "SERV" ? N("The %s service has been attacked by %s.", $service , $hostname)
            : $prefix eq "PASS" ? N("A password cracking attack has been attempted by %s.", $hostname)
            : undef;
    unless ($msg) {
        print "unhandled attack type, skipping\n";
        return;
    }

    my $w = Gtk2::Window->new;
    $w->set_title(N("Active Firewall: intrusion detected"));
    $w->set_icon(gtknew('Pixbuf', file => "/usr/lib/libDrakX/icons/drakfirewall.png"));
    my ($yes, $no, $auto);

    gtkadd($w,
           gtknew('VBox', spacing => 5, children_loose => [
               gtknew('HBox', children => [
                   0, Gtk2::Image->new_from_stock('gtk-dialog-warning', 'dialog'),
                   0, gtknew('Label', text => "   "),
                   1, gtknew('VBox', children => [
                       0, $msg,
                       0, N("Do you want to blacklist the attacker?")
                   ])
               ]),
               gtknew('HButtonBox', layout => 'edge', children_loose => [
                   $no = gtknew('Button', text => N("No"), clicked => sub {
                       $w->destroy;
                       set_attack_verdict($seq, 0);
                   }),
                   $yes = gtknew('Button', text => N("Yes"), clicked => sub {
                       $auto->get_active and $interactive_cb->set_active(0);
                       $w->destroy;
                       set_attack_verdict($seq, 1);
                   })
               ]),
               $auto = gtknew('CheckButton', text => N("Always blacklist (do not ask again)"), toggled => sub {
                   $no->set_sensitive(!$_[0]->get_active);
               }),
               gtkadd(Gtk2::Expander->new(N("Attack details")),
                      gtknew('HBox', children => [
                          0, gtknew('Label', text => "     "),
                          1, gtknew('VBox', children_loose => [
                              N("Attack time: %s", activefw::format_date($timestamp)),
                              N("Network interface: %s", $indev),
                              N("Attack type: %s", $prefix),
                              if_($protocol, N("Protocol: %s", $protocol)),
                              N("Attacker IP address: %s", $ip_addr),
                              if_($hostname ne $ip_addr, N("Attacker hostname: %s", $hostname)),
                              if_($service, N("Service attacked: %s", $service)),
                              if_($port, N("Port attacked: %s", $port)),
                              if_($icmp_type, N("Type of ICMP attack: %s", $icmp_type))
                          ])
                      ])),
           ]));
    $yes->grab_focus;
    gtksignal_connect($w, delete_event => sub {
        if ($auto->get_active) {
            $interactive_cb->set_active(0);
            set_attack_verdict($seq, 1);
        } else {
            set_attack_verdict($seq, 0);
        }
    });
    $w->show_all;
}