#!/usr/bin/perl use strict; use lib qw(/usr/lib/libDrakX); use c; use common; use standalone; use network::network; use network::tools; use run_program; use mygtk2 qw(gtknew); use dbus_object; use network::activefw; use network::monitor; use detect_devices; 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"; # Allow multiple instances, but only one per user: is_running('net_applet') and die "net_applet already running\n"; my ($eventbox, $img, $balloon); my ($current_state, $current_interface, $menu, $wireless_menu, $timeout, $update_timeout); add_icon_path("/usr/share/libDrakX/pixmaps/"); my $net = {}; my $watched_interface; my $dbus = dbus_object::system_bus(); my $monitor = network::monitor->new($dbus); my ($activefw, $interactive_cb, @attacks_queue); my %pixbufs = ( state => { map { $_ => gtkcreate_pixbuf($_) } qw(connected disconnected) }, link_level => { map { my $f = "./wifi-$_.png"; my $pixbuf = gtkcreate_pixbuf(-r $f ? $f : 'default')->scale_simple(24, 24, 'hyper'); $_ => $pixbuf; } qw(20 40 60 80 100) }, keyring => gtkcreate_pixbuf("/usr/share/pixmaps/keyring-small.png")->scale_simple(24, 24, 'hyper'), #- provided by usermode, required by drakxtools ); my %wireless_networks; my %tooltips = ( connected => N_("Network is up on interface %s"), disconnected => #-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 => 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 }, 'monitorAFW' => { name => N("Active Firewall"), launch => \&run_drakids }, 'wireless' => { name => N("Manage wireless networks"), launch => \&run_drakroam }, '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 %{$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(); } }, '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 }, network::tools::wrap_command_for_root('/sbin/set-netprofile', $net->{PROFILE})); } }, 'help' => { name => N("Get Online 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) ) ); $eventbox->signal_connect(button_press_event => sub { $_[1]->button == 1 and run_net_monitor(); $_[1]->button == 3 && $menu and $menu->popup(undef, undef, undef, undef, $_[1]->button, $_[1]->time); }); $icon->show_all; $balloon = Gtk2::Balloon->new_from_window($icon->window); $balloon->add_events('button-press-mask'); $balloon->signal_connect(hide => sub { #- on timeout, apply default policy exists $attacks_queue[0]->{handled} or set_blacklist_verdict($attacks_queue[0]->{seq}, undef); }); $balloon->signal_connect(button_press_event => sub { $attacks_queue[0]->{handled} = 1; Gtk2::Balloon::hide_text($balloon); ask_attack_verdict($attacks_queue[0]); }); $activefw = network::activefw->new($dbus, sub { my ($_con, $msg) = @_; my $member = $msg->get_member; if ($member eq 'Attack') { handle_attack($msg->get_args_list); } elsif ($member eq 'Init') { $activefw->attach_object; checkNetworkForce(); } }); checkNetworkForce(); cronNetwork(); cronUpdate(); get_unprocessed_attacks(); $SIG{HUP} = sub { print "received SIGHUP, reloading network configuration\n"; checkNetworkForce(); }; Gtk2->main; ugtk2::exit(0); sub is_running { my ($name) = @_; any { my ($ppid, $pid, $n) = /^\s*(\d+)\s+(\d+)\s+(.*)/; $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 run_net_monitor() { run_program::raw({ detach => 1 }, '/usr/sbin/net_monitor', '--defaultintf', $current_interface) unless is_running('net_monitor'); } sub run_drakroam() { run_program::raw({ detach => 1 }, '/usr/sbin/drakroam') unless is_running('drakroam'); } sub run_drakids() { run_program::raw({ detach => 1 }, '/usr/sbin/drakids') unless is_running('drakids'); } sub generate_wireless_menuitem { my ($net) = @_; $net->{menuitem} = Gtk2::CheckMenuItem->new; $net->{menuitem}->set_draw_as_radio(1); $net->{menuitem}->add(gtkpack_(gtkshow(Gtk2::HBox->new), 1, gtkset_alignment($net->{ssid_label} = Gtk2::Label->new, 0, 0), 0, $net->{keyring_image} = Gtk2::Image->new_from_pixbuf($pixbufs{keyring}), 0, $net->{level_image} = Gtk2::Image->new)); $net->{activate} = $net->{menuitem}->signal_connect('activate' => sub { if (exists $net->{id}) { eval { $monitor->select_network($net->{id}) }; $@ and err_dialog(N("Active Firewall"), N("Unable to contact daemon")); } else { run_drakroam(); } checkNetworkForce(); }); undef $current_state; #- force menu redraw } sub update_wireless_item { my ($net, $ap_address) = @_; $net->{ssid_label}->set_text($net->{ssid} || "[$ap_address]"); $net->{keyring_image}->visible(defined $net->{flags}); $net->{level_image}->set_from_pixbuf($pixbufs{link_level}{$net->{approx_level}}); $net->{menuitem}->signal_handler_block($net->{activate}); $net->{menuitem}->set_active($net->{current}); $net->{menuitem}->signal_handler_unblock($net->{activate}); } sub checkWireless() { my $networks = $monitor->list_wireless; foreach (keys %$networks) { my $net = $wireless_networks{$_} ||= {}; put_in_hash($net, $networks->{$_}); exists $net->{menuitem} or generate_wireless_menuitem($net); update_wireless_item($net, $_); } $wireless_networks{$_}{menuitem}->visible(exists $networks->{$_}) foreach keys %wireless_networks; } sub checkNetwork() { checkWireless(); 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' : 'notconfigured', $gw_intf); } sub checkNetworkForce() { $net = {}; network::network::read_net_conf($net); undef $current_state; checkNetwork(); } sub cronNetwork() { $timeout = Glib::Timeout->add(2000, sub { checkNetwork(); 1; }); } sub cronUpdate() { my $current_md5 = common::md5file($0); $update_timeout = Glib::Timeout->add(60000, sub { common::md5file($0) ne $current_md5 and exec($0); }); } sub go2State { my ($state_type, $interface) = @_; if ($current_state ne $state_type || $current_interface ne $interface) { $current_state = $state_type; $current_interface = $interface; if ($menu) { if (my $m = $wireless_menu && $wireless_menu->get_submenu) { $_->{menuitem}->get_parent and $m->remove($_->{menuitem}) foreach values %wireless_networks; } $menu->destroy; } $menu = generate_menu($state_type, $interface); } } sub generate_menu { my ($state_type, $interface) = @_; $img->set_from_pixbuf($pixbufs{state}{$state_type eq 'connected' ? 'connected' : 'disconnected'}); gtkset_tip(Gtk2::Tooltips->new, $eventbox, formatAlaTeX(common::sprintf_fixutf8(translate($tooltips{$state_type}), $interface))); my $menu = Gtk2::Menu->new; my $create_item = sub { my ($action) = @_; my $name = ref($actions{$action}{name}) eq 'CODE' ? $actions{$action}{name}->($interface) : $actions{$action}{name}; my $launch = $actions{$action}{launch}; my @choices = exists $actions{$action}{choices} ? $actions{$action}{choices}->() : (); my $w; if (@choices == 0) { $w = gtksignal_connect(gtkshow(Gtk2::MenuItem->new_with_label($name)), activate => sub { $launch->($interface) }); } elsif (@choices > 1) { my $selected = $actions{$action}{choice_selected}; $w = 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{$action}{choices}->())); } #- don't add submenu if only one choice exists $w; }; my (@settings); my $has_wireless = detect_devices::has_wireless(); if ($state_type eq 'connected') { $menu->append($create_item->($_)) foreach qw(downNetwork monitorNetwork monitorAFW); } elsif ($state_type eq 'disconnected') { $menu->append($create_item->('upNetwork')); } $menu->append($create_item->('confNetwork')); if ($state_type ne 'notconfigured') { $menu->append($create_item->('wireless')) if $has_wireless; push @settings, $create_item->('chooseInterface'); } push @settings, $create_item->('chooseProfile'); my $interactive; eval { $interactive = $activefw->get_interactive }; if (defined $interactive) { $interactive_cb = gtkshow(gtksignal_connect(gtkset_active(Gtk2::CheckMenuItem->new_with_label(N("Interactive intrusion detection")), $interactive), toggled => sub { eval { $activefw->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)); $has_wireless and $menu->append(gtkshow($wireless_menu = create_menu(N("Wireless networks"), map { $_->{menuitem} } values %wireless_networks))); $menu->append(gtkshow(create_menu(N("Settings"), grep { $_ } @settings))); $menu->append(gtkshow(Gtk2::SeparatorMenuItem->new)); $menu->append($create_item->('help')); $menu->append($create_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_attacks() { my @packets = eval { $activefw->get_reports }; while (my @attack = splice(@packets, 0, 9)) { handle_attack(@attack); } } sub handle_attack { my $attack = { mapn { $_[0] => $_[1] } [ 'seq', 'timestamp', 'indev', 'prefix', 'sensor', 'protocol', 'addr', 'port', 'icmp_type'], \@_ }; $attack->{ip_addr} = network::activefw::get_ip_address($attack->{addr}); $attack->{hostname} = network::activefw::resolve_address($attack->{ip_addr}); $attack->{service} = network::activefw::get_service($attack->{port}); $attack->{msg} = $attack->{prefix} eq "SCAN" ? N("A port scanning attack has been attempted by %s.", $attack->{hostname}) : $attack->{prefix} eq "SERV" ? N("The %s service has been attacked by %s.", $attack->{service}, $attack->{hostname}) : $attack->{prefix} eq "PASS" ? N("A password cracking attack has been attempted by %s.", $attack->{hostname}) : undef; push @attacks_queue, $attack; @attacks_queue == 1 and notify_attack($attacks_queue[0]); } sub set_blacklist_verdict { my ($seq, $verdict) = @_; #- default is to blacklist defined $verdict or $verdict = 1; eval { $activefw->blacklist($seq, $verdict) }; $@ and err_dialog(N("Active Firewall"), N("Unable to contact daemon")); shift @attacks_queue; #- wait for some time so that the new balloon is noticeable @attacks_queue and Glib::Timeout->add(500, sub { notify_attack($attacks_queue[0]); 0; }); } sub notify_attack { my ($attack) = @_; unless ($attack->{msg}) { print "unhandled attack type, skipping\n"; return; } Gtk2::Balloon::show_text($balloon, $attack->{msg}, 5000); } sub ask_attack_verdict { my ($attack) = @_; 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, $attack->{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_blacklist_verdict($attack->{seq}, 0); }), $yes = gtknew('Button', text => N("Yes"), clicked => sub { $auto->get_active and $interactive_cb->set_active(0); $w->destroy; set_blacklist_verdict($attack->{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", network::activefw::format_date($attack->{timestamp})), 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})), if_($attack->{service}, N("Service attacked: %s", $attack->{service})), if_($attack->{port}, N("Port attacked: %s", $attack->{port})), if_($attack->{icmp_type}, N("Type of ICMP attack: %s", $attack->{icmp_type})) ]) ])), ])); $yes->grab_focus; gtksignal_connect($w, delete_event => sub { $auto->get_active and $interactive_cb->set_active(0); #- apply default policy set_blacklist_verdict($attack->{seq}, undef); }); $w->show_all; } package Gtk2::Balloon; use ugtk2 qw(:wrappers); sub new_from_window { my ($_class, $window) = @_; my $w = Gtk2::Window->new('GTK_WINDOW_POPUP'); $w->{ref_window} = $window; $w->set_name("gtk-tooltips"); $w->set_app_paintable(1); $w->set_resizable(0); $w->set_border_width(4); $w->signal_connect("expose_event" => sub { my $req = $w->size_request; $w->get_style->paint_flat_box($w->window, 'normal', 'out', undef, $w, "tooltip", 0, 0, $req->width, $req->height); }); ugtk2::gtkadd($w, $w->{label} = ugtk2::gtkset_alignment(ugtk2::gtkset_line_wrap(Gtk2::Label->new, 1), 0.5, 0.5)); } sub show_text { my ($balloon, $text, $timeout) = @_; $balloon->{label}->set_text($text); $balloon->{label}->show; my ($ref_x, $ref_y) = $balloon->{ref_window}->get_origin; my (undef, undef, $ref_w, $ref_h) = $balloon->{ref_window}->get_geometry; my ($x, $y) = ($ref_x, $ref_y); my $req = $balloon->size_request; my ($w, $h) = ($req->width, $req->height); #- code mostly from gtktooltips.c $x += $ref_w / 2 - $w / 2 - $balloon->get_border_width; my $screen = Gtk2::Gdk::Screen->get_default; my $monitor_num = $screen->get_monitor_at_window($balloon->{ref_window}); my ($monitor) = $screen->get_monitor_geometry($monitor_num); if ($x + $w > $monitor->x + $monitor->width) { $x = $monitor->x + $monitor->width - $w - $balloon->get_border_width; } elsif ($x < $monitor->x) { $x = $monitor->x + $balloon->get_border_width; } if ($y + $h + $ref_h + $balloon->get_border_width > $monitor->y + $monitor->height) { $y = $y - $h - $balloon->get_border_width; } else { $y += $ref_h + $balloon->get_border_width; } $balloon->move($x, $y); $balloon->show; $balloon->{timeout} = Glib::Timeout->add($timeout, sub { $balloon->hide; 0 }); } sub hide_text { my ($balloon) = @_; Glib::Source->remove($balloon->{timeout}); $balloon->hide; } 1;