#!/usr/bin/perl

use strict;
use lib qw(/usr/lib/libDrakX);
# i18n: IMPORTANT: to get correct namespace (drakx-net instead of libDrakX)
BEGIN { unshift @::textdomains, 'drakx-net' }
use c;
use common;
use standalone;
use network::network;
use network::tools;
use network::connection;
use network::connection::ethernet;
use network::vpn;
use run_program;
use mygtk2 qw(gtknew gtkset);
use dbus_object;
use network::ifw;
use network::monitor;
use network::signal_strength;
use detect_devices;
use modules;

use Gtk2::TrayIcon;

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

my $onstartupfile = "$ENV{HOME}/.net_applet";
shouldStart() or die "$onstartupfile should be set to TRUE or use net_applet --force\n";

#- Allow multiple instances, but only one per user:
is_running('net_applet') and die "net_applet already running\n";

my ($eventbox, $img);
my ($current_state, $current_interface, $current_description, $simple_menu, $menu, $wireless_device, $timeout, $update_timeout);
add_icon_path("/usr/share/libDrakX/pixmaps/");

my $net = {};
my $modules_conf = modules::any_conf->read;
my $watched_interface;

my %pixbufs =
  (
      firewall => gtknew('Pixbuf', file => 'drakfirewall'),
      firewall_icon => gtknew('Pixbuf', file => 'drakfirewall')->scale_simple(24, 24, 'hyper'),
      state => { map { $_ => gtknew('Pixbuf', file => $_) } qw(connected disconnected unconfigured) },
      link_level => { map {
          $_ => gtknew('Pixbuf', file => 'wifi-' . sprintf('%03d', $_))->scale_simple(24, 24, 'hyper');
      } qw(20 40 60 80 100) },
      encryption => { map {
          $_ => gtknew('Pixbuf', file => "encryption-$_-24");
      } qw(open weak strong) },
  );
my %wireless_networks;

sub get_state_message {
    my ($o_interface) = @_;
    my $interface = $o_interface || $current_interface;
    my $network = get_current_network();
    formatAlaTeX(
        $current_state eq 'connected' ?
          N("Network is up on interface %s.", $interface) .
          "\n\n" . N("IP address: %s", network::tools::get_interface_ip_address($net, $interface)) .
          "\n\n" . N("Gateway: %s", [ network::tools::get_interface_status($interface) ]->[1]) .
            ($network && "\n\n" . N("Connected to %s (link level: %d %%)", $network->{name}, $network->{signal_strength}))
        : $current_state eq 'disconnected' ?
          N("Network is down on interface %s.", $interface)
        :
          N("You do not have any configured Internet connection.
Run the \"%s\" assistant from the Mandriva Linux Control Center", N("Set up a new network interface (LAN, ISDN, ADSL, ...)"))
    );
}

my %actions = (
               'upNetwork' => { name => sub { N("Connect %s", $_[0]) }, launch => sub { network::tools::start_interface($_[0], 1) } },
               'downNetwork' => { name => sub { N("Disconnect %s", $_[0]) }, launch => sub { network::tools::stop_interface($_[0], 1) } },
               'monitorNetwork' => { name => N("Monitor Network"), launch => \&run_net_monitor },
               'monitorIFW' => { name => N("Interactive Firewall"), launch => \&run_drakids },
               'wireless' => { name => N("Manage wireless networks"), launch => sub { run_drakroam() } },
               'drakvpn' => {
                   name => N("Manage VPN connections"), launch => sub {
                       run_program::raw({ detach => 1 }, '/usr/sbin/drakvpn');
                   },
               },
               'confNetwork' => { name => N("Configure Network"), launch => sub { system("/usr/sbin/drakconnect &") } },
               'chooseInterface' => {
                   name => N("Watched interface"),
                   choices => sub { N("Auto-detect"), sort keys %{$net->{ifcfg}} },
                   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();
                   }
               },
               'setInterface' => {
                   name => N("Active interfaces"),
                   use_checkbox => 1,
                   choices => sub { sort keys %{$net->{ifcfg}} },
                   choice_selected => sub {
                       my ($is_up, $_gw) = network::tools::get_interface_status($_[0]);
                       $is_up;
                   },
                   get_icon => sub {
                       my $ifcfg = $net->{ifcfg}{$_[0]};
                       require network::connection;
                       my $type = $ifcfg && network::connection->find_ifcfg_type($ifcfg);
                       $type && $type->get_type_icon;
                   },
                   launch => sub {
                       my ($is_up, $_gw) = network::tools::get_interface_status($_[0]);
                       if ($is_up) {
                           network::tools::stop_interface($_[0], 1);
                       } else {
                           network::tools::start_interface($_[0], 1);
                       }
                       checkNetworkForce();
                   }
               },
               'chooseProfile' => {
                   name => N("Profiles"),
                   choices => sub { network::network::netprofile_list() },
                   choice_selected => sub { $_[0] eq $net->{PROFILE} },
                   launch => sub {
		       require run_program;
		       $net->{PROFILE} = $_[0];
		       run_program::raw({ detach => 1 }, common::wrap_command_for_root('/sbin/set-netprofile', $net->{PROFILE}));
                   }
               },
               'chooseVPN' => {
                   name => N("VPN connection"),
                   header => "drakvpn",
                   choices => sub { map { $_->get_configured_connections } network::vpn::list_types },
                   allow_single_choice => 1,
                   format_choice => \&network::vpn::get_label,
                   choice_selected => sub { $_[0]->is_started },
                   launch => sub { require interactive; $_[0]->is_started ? $_[0]->stop : $_[0]->start(interactive->vnew) },
               },
               'help' => { name => N("Help"), launch => sub { system("drakhelp --id internet-connection &") } },
               'quit' => { name => N("Quit"), launch => \&mainQuit },
              );

gtkadd(my $icon = Gtk2::TrayIcon->new("Net_Applet"),
       gtkadd($eventbox = Gtk2::EventBox->new,
              gtkpack($img = Gtk2::Image->new)
             )
      );
$icon->show_all;

my ($dbus, $monitor, $ifw, $interactive_cb, $ifw_alert);
eval { $dbus = dbus_object::system_bus() };
eval { $monitor = network::monitor->new($dbus) } if $dbus;
eval {
    $ifw = network::ifw->new($dbus, sub {
        my ($_con, $msg) = @_;
        my $member = $msg->get_member;
        if ($member eq 'Attack') {
            handle_ifw_message($msg->get_args_list);
        } elsif ($member eq 'Listen') {
            handle_ifw_listen($msg->get_args_list);
        } elsif ($member eq 'Init') {
            $ifw->attach_object;
            checkNetworkForce();
        } elsif ($member eq 'AlertAck') {
            $ifw_alert = 0;
        }
    });
} if $dbus;

my $bubble_queue = Gtk2::NotificationBubble::Queue->new;
$bubble_queue->{bubble}->attach($icon);

$eventbox->signal_connect(button_press_event => sub {
    if ($_[1]->button == 1) {
        if ($ifw_alert) {
            run_drakids();
        } elsif ($simple_menu) {
           $simple_menu->popup(undef, undef, undef, undef, $_[1]->button, $_[1]->time);
       } else {
            run_netcenter();
        }
    } elsif ($_[1]->button == 3 && $menu) {
        $menu->popup(undef, undef, undef, undef, $_[1]->button, $_[1]->time);
    }
});

checkNetworkForce();
cronNetwork();
get_unprocessed_ifw_messages();

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

Gtk2->main;

ugtk2::exit(0);

sub is_running {
    my ($name, $o_user) = @_;
    my $user = $o_user || $ENV{USER};
    any {
	my ($ppid, $pid, $n) = /^\s*(\d+)\s+(\d+)\s+(.*)/;
	$ppid != 1 && $pid != $$ && $n eq $name;
    } `ps -o '%P %p %c' -u $user`;
}
sub shouldStart() {
    my ($opt) = @ARGV;
    if ($opt eq '--force' || $opt eq '-f') {
        return 1;
    }
    return getAutoStart();
}
sub run_net_monitor() {
    run_program::raw({ detach => 1 }, '/usr/sbin/net_monitor', '--defaultintf', $current_interface) unless is_running('net_monitor');
}
sub run_netcenter() {
    run_program::raw({ detach => 1 }, '/usr/bin/draknetcenter') unless is_running('draknetcenter', 'root');
}
sub run_drakroam {
    my ($o_ap) = @_;
    run_program::raw({ detach => 1 }, '/usr/sbin/drakroam', if_($o_ap, "--ap=$o_ap")) unless is_running('drakroam', 'root');
}
sub run_drakids() {
    $ifw_alert = 0;
    if (is_running('drakids')) {
        eval { $ifw->send_manage_request };
    } else {
        run_program::raw({ detach => 1 }, '/usr/sbin/drakids');
    }
}
sub generate_wireless_menuitem {
    my ($wnet) = @_;
    my $menuitem = {};
    $menuitem->{widget} = Gtk2::CheckMenuItem->new;
    $menuitem->{widget}->set_draw_as_radio(1);
    $menuitem->{widget}->add(gtkpack_(gtkshow(gtknew('HBox')),
                                            1, gtkset_alignment($menuitem->{label} = gtknew('Label'), 0, 0.5),
                                            0, $menuitem->{strength} = Gtk2::Image->new,
                                            0, $menuitem->{security} = Gtk2::Image->new,
                                ));
    $menuitem->{activate} = $menuitem->{widget}->signal_connect('activate' => sub {
        if (exists $wnet->{id}) {
            eval { $monitor->select_network($wnet->{id}) };
            $@ and err_dialog(N("Interactive Firewall"), N("Unable to contact daemon"));
        } else {
            run_drakroam($wnet->{ap});
        }
        checkNetworkForce();
    });
    update_wireless_item($menuitem, $wnet);
    push @{$wnet->{menuitems}}, $menuitem;
    return $menuitem->{widget};
}
sub update_wireless_item {
    my ($menuitem, $wnet) = @_;
    $menuitem->{label}->set_text($wnet->{name});
    $menuitem->{security}->set_from_pixbuf($pixbufs{encryption}{$wnet->{flags} =~ /WPA/i ? 'strong' : $wnet->{flags} =~ /WEP/i ? 'weak' : 'open'});
    $menuitem->{strength}->set_from_pixbuf(network::signal_strength::get_strength_icon($wnet));

    $menuitem->{widget}->signal_handler_block($menuitem->{activate});
    $menuitem->{widget}->set_active($wnet->{current});
    $menuitem->{widget}->signal_handler_unblock($menuitem->{activate});
}
sub checkWireless() {
    $wireless_device or return;
    my ($networks) =  network::monitor::list_wireless($monitor);
    my $force_applet_update;
    foreach (keys %$networks) {
        exists $wireless_networks{$_} or $force_applet_update = 1;
        put_in_hash($wireless_networks{$_} ||= {}, $networks->{$_});
    }
    if ($force_applet_update) {
        undef $current_state;
    } else {
        foreach my $wnet (values %wireless_networks) {
            my $is_valuable = exists $networks->{$wnet->{ap}};
            foreach (@{$wnet->{menuitems}}) {
                update_wireless_item($_, $wnet) if $is_valuable;
                $_->{widget}->visible($is_valuable);
            }
        }
    }
}
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($net);
    go2State($gw_address ? 'connected' : $gw_intf ? 'disconnected' : 'unconfigured', $gw_intf);
}
sub checkNetworkForce() {
    $net = {};
    network::network::read_net_conf($net);
    undef $current_state;
    $wireless_device = detect_devices::get_wireless_interface();
    checkWireless();
    checkNetwork();
}
sub cronNetwork() {
    my $i;
    $timeout = Glib::Timeout->add(2000, sub {
        checkWireless() if !($i++%30);
        checkNetwork();
        1;
    });
}
sub go2State {
    my ($state_type, $interface) = @_;
    my $need_update;
    my ($old_interface, $old_description);
    if ($current_interface ne $interface) {
        my $card = find { $_->[0] eq $interface } network::connection::ethernet::get_eth_cards($modules_conf);
        if ($state_type eq 'disconnected') {
            $old_interface = $current_interface;
            $old_description = $current_description;
        }
        $current_description = $card && $card->[2];
        $current_interface = $interface;
        $need_update = 1;
    }
    if ($current_state ne $state_type) {
        my $show = defined $current_state; # don't show bubble at applet startup
        $current_state = $state_type;
        $bubble_queue->add({
            title => $old_description || $current_description || N("Network connection"),
            pixbuf => get_state_pixbuf(),
            message => get_state_message($old_interface || $current_interface),
        }) if $show;
        $need_update = 1;
    }

    update_applet() if $need_update;
}

sub get_current_network() {
    detect_devices::is_wireless_interface($current_interface) && find { $_->{current} } values %wireless_networks;
}

sub get_state_pixbuf() {
    my $wnet = $current_state eq 'connected' && get_current_network();
    $wnet ?
      network::signal_strength::get_strength_icon($wnet) :
      $pixbufs{state}{$current_state};
}

sub update_tray_icon() {
    if (!$ifw_alert || $img->get_storage_type ne 'pixbuf') {
        $img->set_from_pixbuf(get_state_pixbuf());
    } else {
        $img->set_from_stock('gtk-dialog-warning', 'small-toolbar');
    }
}

sub enable_ifw_alert() {
    unless ($ifw_alert) {
        $ifw_alert = 1;
        update_tray_icon();
        Glib::Timeout->add(1000, sub {
            update_tray_icon();
            $ifw_alert;
        });
    }
}

sub disable_ifw_alert() {
    eval { $ifw->send_alert_ack };
    $ifw_alert = 0;
    update_tray_icon();
}

sub update_applet() {
    $wireless_device = detect_devices::get_wireless_interface();

    generate_menu();

    update_tray_icon();
    gtkset_tip(Gtk2::Tooltips->new, $eventbox, get_state_message()); #gtkset($eventbox, tip => get_state_message());
}

sub create_menu_choices {
    my ($action, $o_allow_single_choice) = @_;
    my @choices =  $actions{$action}{choices}->();
    #- don't add submenu if only zero or one choice exists
    my $allow_single_choice = $actions{$action}{allow_single_choice} || $o_allow_single_choice;
    @choices > ($allow_single_choice ? 0 : 1) or return ();
    my $selected = $actions{$action}{choice_selected};
    my $format = $actions{$action}{format_choice};
    my $get_icon = $actions{$action}{get_icon};
    map {
        my $choice = $_;
        my $label = $format ? $format->($choice) : $choice;
        my $w = gtkshow(gtkset_active(gtkadd(
            Gtk2::CheckMenuItem->new,
            gtknew('HBox', children => [
                     1, gtkset_alignment(gtknew('Label', text => $label), 0, 0.5),
                     $get_icon ?
                       (0, gtknew('Image', file => $get_icon->($_))) :
                         (),
                 ])), $selected->($choice)));
        gtksignal_connect($w, activate => sub { $actions{$action}{launch}->($choice) });
        $w->set_draw_as_radio(!$actions{$action}{use_checkbox});
        $w;
    } $actions{$action}{choices}->();
}

sub create_action_item {
    my ($action) = @_;
    my $name = ref($actions{$action}{name}) eq 'CODE' ? $actions{$action}{name}->($current_interface) : $actions{$action}{name};
    if (exists $actions{$action}{choices}) {
        my @menu = create_menu_choices($action);
        @menu || $actions{$action}{header} or return ();
        gtkshow(create_menu($name,
                            if_($actions{$action}{header},
                                create_action_item($actions{$action}{header}),
                                gtkshow(Gtk2::SeparatorMenuItem->new),
                            ),
                            @menu,
                        ));
    } else {
        gtksignal_connect(gtkshow(Gtk2::MenuItem->new_with_label($name)), activate => sub { $actions{$action}{launch}->($current_interface) });
    }
}

sub empty_menu {
    my ($menu) = @_;
    delete $_->{menuitems} foreach values %wireless_networks;
    $menu->destroy if $menu;
    Gtk2::Menu->new;
}

sub get_wireless_networks_sorted() {
    sort {
        $b->{current} <=> $a->{current} || $b->{signal_strength} <=> $a->{signal_strength} || $a->{name} cmp $b->{name};
    } values %wireless_networks;
}

sub generate_simple_menu() {
    $simple_menu = empty_menu($simple_menu);

    if ($wireless_device) {
        my @networks = get_wireless_networks_sorted();
        my @valuable_networks = splice @networks, 0, 7;
        gtkappend($simple_menu,
                  (map { generate_wireless_menuitem($_) } @valuable_networks),
                  (@networks ? create_menu(N("More networks"), map { generate_wireless_menuitem($_) } @networks) : ()),
                  Gtk2::SeparatorMenuItem->new,
              );
    }
    gtkappend($simple_menu, create_menu_choices('setInterface', 'allow_single_choice'));
}

sub generate_menu() {
    $menu = empty_menu($menu);

    my (@settings);
    my $interactive;
    eval { $interactive = $ifw->get_interactive };

    if ($current_state eq 'connected') {
        $menu->append(create_action_item($_)) foreach qw(downNetwork monitorNetwork);
    } elsif ($current_state eq 'disconnected') {
        $menu->append(create_action_item('upNetwork'));
    }
    $menu->append(create_action_item('monitorIFW')) if $current_state ne 'unconfigured' && defined $interactive;

    $menu->append(create_action_item('confNetwork'));

    push @settings, create_action_item('chooseInterface') if $current_state ne 'unconfigured';

    push @settings, create_action_item('chooseProfile');
    if (defined $interactive) {
        $interactive_cb = gtkshow(gtksignal_connect(gtkset_active(Gtk2::CheckMenuItem->new_with_label(N("Interactive Firewall automatic mode")),
                                                                  !$interactive),
                                                    toggled => sub { eval { $ifw->set_interactive(to_bool(!$_[0]->get_active)) } }));
        push @settings, $interactive_cb;
    }
    push @settings, 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(gtkshow(Gtk2::SeparatorMenuItem->new));
    if ($current_state ne 'unconfigured' && $wireless_device) {
        $menu->append(gtkshow(create_menu(N("Wireless networks"),
                                          create_action_item('wireless'),
                                          gtkshow(Gtk2::SeparatorMenuItem->new),
                                          map { generate_wireless_menuitem($_) } get_wireless_networks_sorted())));
    }
    if (my $vpn = create_action_item('chooseVPN')) { $menu->append($vpn) }
    if (my $set = $current_state ne 'unconfigured' && create_action_item('setInterface')) { $menu->append($set) }
    $menu->append(gtkshow(create_menu(N("Settings"), @settings)));
    $menu->append(gtkshow(Gtk2::SeparatorMenuItem->new));
    $menu->append(create_action_item('help'));
    $menu->append(create_action_item('quit'));
    $menu;
}
sub mainQuit() {
     Glib::Source->remove($timeout) if $timeout;
     Glib::Source->remove($update_timeout) if $update_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_ifw_messages() {
    my @packets = eval { $ifw->get_reports };
    while (my @ifw_message = splice(@packets, 0, 10)) {
        handle_ifw_message(@ifw_message);
    }
}

sub set_verdict {
    my ($attack, $apply_verdict) = @_;
    eval { $apply_verdict->($attack) };
    $@ and err_dialog(N("Interactive Firewall"), N("Unable to contact daemon"));
}

sub apply_verdict_blacklist {
    my ($attack) = @_;
    $ifw->set_blacklist_verdict($attack->{seq}, 1);
}

sub apply_verdict_ignore {
    my ($attack) = @_;
    $ifw->set_blacklist_verdict($attack->{seq}, 0);
}

sub apply_verdict_whitelist {
    my ($attack) = @_;
    $ifw->whitelist($attack->{addr});
    apply_verdict_ignore($attack);
}

sub handle_ifw_message {
    my $message = network::ifw::attack_to_hash(\@_);
    unless ($message->{msg}) {
        print "unhandled attack type, skipping\n";
        return;
    }
    my $is_attack = $message->{prefix} ne 'NEW';
    enable_ifw_alert() if $is_attack;
    $bubble_queue->add({
        title => N("Interactive Firewall"),
        pixbuf => $pixbufs{firewall},
        message => $message->{msg},
        timeout => sub {
            set_verdict($message, \&apply_verdict_ignore);
        },
        clicked => sub {
            if ($is_attack) {
                disable_ifw_alert();
                ask_attack_verdict($message);
            } else {
                set_verdict($message, \&apply_verdict_ignore);
                $bubble_queue->process_next;
            }
        },
    });
}

sub ask_attack_verdict {
    my ($attack) = @_;

    my $w = ugtk2->new(N("Interactive Firewall: intrusion detected"),
                       icon => "drakfirewall");
    my ($blacklist, $whitelist, $ignore, $auto);

    my $update_automatic_mode = sub { $auto->get_active and $interactive_cb->set_active(1) };
    my $set_verdict = sub {
        my ($verdict) = @_;
        set_verdict($attack, $verdict);
        $bubble_queue->process_next;
    };
    gtkadd($w->{window},
           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, $attack->{msg},
                       0, N("What do you want to do with this attacker?")
                   ])
               ]),
               gtksignal_connect(gtkadd(Gtk2::Expander->new(N("Attack details")),
                                        gtknew('HBox', children => [
                                            0, gtknew('Label', text => "     "),
                                            1, gtknew('VBox', children_loose => [
                                                N("Attack time: %s", $attack->{date}),
                                                N("Network interface: %s", $attack->{indev}),
                                                N("Attack type: %s", $attack->{prefix}),
                                                if_($attack->{protocol}, N("Protocol: %s", $attack->{protocol})),
                                                N("Attacker IP address: %s", $attack->{ip_addr}),
                                                if_($attack->{hostname} ne $attack->{ip_addr}, N("Attacker hostname: %s", $attack->{hostname})),
                                                (
                                                    $attack->{service} ne $attack->{port} ?
                                                      N("Service attacked: %s", $attack->{service}) :
                                                      N("Port attacked: %s", $attack->{port}),
                                                ),
                                                if_($attack->{icmp_type}, N("Type of ICMP attack: %s", $attack->{icmp_type}))
                                            ]),
                                        ])),
                                 activate => sub { $_[0]->get_expanded and $w->shrink_topwindow }
                             ),
               $auto = gtknew('CheckButton', text => N("Always blacklist (do not ask again)"), toggled => sub {
                   $whitelist->set_sensitive(!$_[0]->get_active);
                   $ignore->set_sensitive(!$_[0]->get_active);
               }),
               gtknew('HButtonBox', layout => 'edge', children_loose => [
                   $blacklist = gtknew('Button', text => N("Blacklist"), clicked => sub {
                       $w->destroy;
                       $update_automatic_mode->();
                       $set_verdict->(\&apply_verdict_blacklist);
                   }),
                   $whitelist = gtknew('Button', text => N("Whitelist"), clicked => sub {
                       $w->destroy;
                       $update_automatic_mode->();
                       $set_verdict->(\&apply_verdict_whitelist);
                   }),
                   $ignore = gtknew('Button', text => N("Ignore"), clicked => sub {
                       $w->destroy;
                       $set_verdict->(\&apply_verdict_ignore);
                   }),
               ]),
           ]));
    eval { $auto->set_active(!$ifw->get_interactive) };
    $blacklist->grab_focus;
    gtksignal_connect($w->{window}, delete_event => sub {
        $set_verdict->(\&apply_verdict_ignore);
    });
    $w->{window}->show_all;
}

sub handle_ifw_listen {
    my $listen = network::ifw::parse_listen_message(\@_);
    enable_ifw_alert();
    $bubble_queue->add({
        title => N("Interactive Firewall: new service"),
        pixbuf => $pixbufs{firewall},
        message => $listen->{message},
        clicked => sub {
            disable_ifw_alert();
            ask_listen_verdict($listen);
        },
    });
}

sub ask_listen_verdict {
    my ($listen) = @_;

    my $w = ugtk2->new(N("Interactive Firewall: new service"), icon => "drakfirewall");
    my $set_verdict = sub {
        $bubble_queue->process_next;
    };
    gtkadd($w->{window},
           gtknew('VBox', spacing => 5, children_loose => [
               gtknew('HBox', children => [
                   0, Gtk2::Image->new_from_stock('gtk-dialog-warning', 'dialog'),
                   1, gtknew('VBox', children => [
                       0, $listen->{message},
                       0, N("Do you want to open this service?"),
                   ])
               ]),
               gtknew('CheckButton', text => N("Remember this answer"), toggled => sub {}),
               gtknew('HButtonBox', layout => 'edge', children_loose => [
                   gtknew('Button', text => N("Allow"), clicked => sub { $w->destroy; $set_verdict->(1) }),
                   gtknew('Button', text => N("Block"), clicked => sub { $w->destroy; $set_verdict->(0) }),
               ]),
           ]));
    gtksignal_connect($w->{window}, delete_event => sub { $set_verdict->() });
    $w->{window}->show_all;
}