diff options
-rw-r--r-- | bin/net_applet | 489 |
1 files changed, 489 insertions, 0 deletions
diff --git a/bin/net_applet b/bin/net_applet new file mode 100644 index 0000000..a0c9efe --- /dev/null +++ b/bin/net_applet @@ -0,0 +1,489 @@ +#!/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::ifw; +use network::monitor; +use detect_devices; + +use Gtk2::TrayIcon; +use Gtk2::NotificationBubble; + +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, $bubble); +my ($current_state, $current_interface, $menu, $wireless_device, $wireless_menu, $timeout, $update_timeout); +add_icon_path("/usr/share/libDrakX/pixmaps/"); + +my $net = {}; +my $watched_interface; + +my %pixbufs = + ( + firewall => gtkcreate_pixbuf('/usr/lib/libDrakX/icons/drakfirewall.png'), + firewall_icon => gtkcreate_pixbuf('/usr/lib/libDrakX/icons/drakfirewall.png')->scale_simple(24, 24, 'hyper'), + state => { map { $_ => gtkcreate_pixbuf($_) } qw(connected disconnected) }, + link_level => { map { + $_ => gtkcreate_pixbuf('wifi-' . sprintf('%03d', $_) . '.png')->scale_simple(24, 24, 'hyper'); + } 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 }, + 'monitorIFW' => { name => N("Interactive Firewall"), launch => \&run_drakids }, + 'wireless' => { name => N("Manage wireless networks"), launch => sub { 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(); + } + }, + '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; + }, + 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 }, 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) + ) + ); +$icon->show_all; + +my ($dbus, $monitor, $ifw, $interactive_cb, @attacks_queue, $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_attack($msg->get_args_list); + } elsif ($member eq 'Init') { + $ifw->attach_object; + checkNetworkForce(); + } elsif ($member eq 'AlertAck') { + $ifw_alert = 0; + } + }); +} if $dbus; + +$bubble = Gtk2::NotificationBubble->new; +$bubble->attach($icon); +$bubble->signal_connect(timeout => sub { + set_verdict($attacks_queue[0], \&apply_verdict_ignore); +}); +$bubble->signal_connect(clicked => sub { + $bubble->hide; + eval { $ifw->send_alert_ack }; + $ifw_alert = 0; + update_tray_icon(); + ask_attack_verdict($attacks_queue[0]); +}); + +$eventbox->signal_connect(button_press_event => sub { + $_[1]->button == 1 and ($ifw_alert ? run_drakids() : run_net_monitor()); + $_[1]->button == 3 && $menu and $menu->popup(undef, undef, undef, undef, $_[1]->button, $_[1]->time); +}); + +checkNetworkForce(); +cronNetwork(); +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 { + my ($o_ap) = @_; + run_program::raw({ detach => 1 }, '/usr/sbin/drakroam', if_($o_ap, "--ap=$o_ap")) unless is_running('drakroam'); +} +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, $ap) = @_; + $wnet->{menuitem} = Gtk2::CheckMenuItem->new; + $wnet->{menuitem}->set_draw_as_radio(1); + $wnet->{menuitem}->add(gtkpack_(gtkshow(Gtk2::HBox->new), + 1, gtkset_alignment($wnet->{ssid_label} = Gtk2::Label->new, 0, 0), + 0, $wnet->{keyring_image} = Gtk2::Image->new_from_pixbuf($pixbufs{keyring}), + 0, $wnet->{level_image} = Gtk2::Image->new)); + $wnet->{activate} = $wnet->{menuitem}->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($ap); + } + checkNetworkForce(); + }); + undef $current_state; #- force menu redraw +} +sub update_wireless_item { + my ($wnet, $ap_address) = @_; + $wnet->{ssid_label}->set_text($wnet->{essid} || "[$ap_address]"); + $wnet->{keyring_image}->visible(to_bool($wnet->{flags})); + $wnet->{level_image}->set_from_pixbuf($pixbufs{link_level}{$wnet->{approx_level}}); + + $wnet->{menuitem}->signal_handler_block($wnet->{activate}); + $wnet->{menuitem}->set_active($wnet->{current}); + $wnet->{menuitem}->signal_handler_unblock($wnet->{activate}); +} +sub checkWireless() { + $wireless_device or return; + my ($networks) = network::monitor::list_wireless($monitor, $wireless_device); + foreach (keys %$networks) { + my $wnet = $wireless_networks{$_} ||= {}; + put_in_hash($wnet, $networks->{$_}); + exists $wnet->{menuitem} or generate_wireless_menuitem($wnet, $_); + update_wireless_item($wnet, $_); + } + $wireless_networks{$_}{menuitem}->visible(exists $networks->{$_}) foreach keys %wireless_networks; +} +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' : 'notconfigured', $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) = @_; + if ($current_state ne $state_type || $current_interface ne $interface) { + $current_state = $state_type; + $current_interface = $interface; + $wireless_device = detect_devices::get_wireless_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($interface); + } +} +sub update_tray_icon() { + if (!$ifw_alert || $img->get_storage_type ne 'pixbuf') { + my $pixbuf; + if ($current_state eq 'connected') { + if (detect_devices::is_wireless_interface($current_interface)) { + my $wnet = find { $_->{current} } values %wireless_networks; + $pixbuf = $pixbufs{link_level}{$wnet->{approx_level}} if $wnet; + } + $pixbuf ||= $pixbufs{state}{connected}; + } else { + $pixbuf = $pixbufs{state}{disconnected}; + } + $img->set_from_pixbuf($pixbuf); + } else { + $img->set_from_stock('gtk-dialog-warning', 'small-toolbar'); + } +} +sub generate_menu { + my ($interface) = @_; + + update_tray_icon(); + gtkset_tip(Gtk2::Tooltips->new, $eventbox, formatAlaTeX(sprintf(translate($tooltips{$current_state}), $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}; + my $format = $actions{$action}{format_choice}; + $w = gtkshow(create_menu($name, map { + my $choice = $_; + my $w = gtkshow(gtkset_active(Gtk2::CheckMenuItem->new_with_label($format ? $format->($choice) : $choice), $selected->($choice))); + gtksignal_connect($w, activate => sub { $launch->($choice) }); + $w->set_draw_as_radio(!$actions{$action}{use_checkbox}); + $w; + } $actions{$action}{choices}->())); + } + #- don't add submenu if only one choice exists + $w; + }; + + my (@settings); + my $interactive; + eval { $interactive = $ifw->get_interactive }; + + if ($current_state eq 'connected') { + $menu->append($create_item->($_)) foreach qw(downNetwork monitorNetwork); + } elsif ($current_state eq 'disconnected') { + $menu->append($create_item->('upNetwork')); + } + $menu->append($create_item->('monitorIFW')) if $current_state ne 'notconfigured' && defined $interactive; + + $menu->append($create_item->('confNetwork')); + + if ($current_state ne 'notconfigured') { + $menu->append($create_item->('wireless')) if $wireless_device; + push @settings, $create_item->('chooseInterface'); + } + + push @settings, $create_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)); + $wireless_device and $menu->append(gtkshow($wireless_menu = create_menu(N("Wireless networks"), + map { $_->{menuitem} } values %wireless_networks))); + if (my $set = $current_state ne 'notconfigured' && $create_item->('setInterface')) { $menu->append($set) } + $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 { $ifw->get_reports }; + while (my @attack = splice(@packets, 0, 10)) { + handle_attack(@attack); + } +} + +sub handle_attack { + my $attack = network::ifw::attack_to_hash(\@_); + push @attacks_queue, $attack; + @attacks_queue == 1 and notify_attack($attacks_queue[0]); +} + +sub set_verdict { + my ($attack, $apply_verdict) = @_; + eval { $apply_verdict->($attack) }; + $@ and err_dialog(N("Interactive Firewall"), N("Unable to contact daemon")); + + shift @attacks_queue; + #- wait for some time so that the new bubble is noticeable + @attacks_queue and Glib::Timeout->add(500, sub { notify_attack($attacks_queue[0]); 0 }); +} + +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 notify_attack { + my ($attack) = @_; + unless ($attack->{msg}) { + print "unhandled attack type, skipping\n"; + return; + } + unless ($ifw_alert) { + $ifw_alert = 1; + update_tray_icon(); + Glib::Timeout->add(1000, sub { + update_tray_icon(); + $ifw_alert; + }); + } + $bubble->set(N("Interactive Firewall"), Gtk2::Image->new_from_pixbuf($pixbufs{firewall}), $attack->{msg}); + $bubble->show(5000); +} + +sub ask_attack_verdict { + my ($attack) = @_; + + my $w = ugtk2->new(N("Interactive Firewall: intrusion detected"), + icon => "/usr/lib/libDrakX/icons/drakfirewall.png"); + my ($blacklist, $whitelist, $ignore, $auto); + + my $update_automatic_mode = sub { $auto->get_active and $interactive_cb->set_active(1) }; + + 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($attack, \&apply_verdict_blacklist); + }), + $whitelist = gtknew('Button', text => N("Whitelist"), clicked => sub { + $w->destroy; + $update_automatic_mode->(); + set_verdict($attack, \&apply_verdict_whitelist); + }), + $ignore = gtknew('Button', text => N("Ignore"), clicked => sub { + $w->destroy; + set_verdict($attack, \&apply_verdict_ignore); + }), + ]), + ])); + eval { $auto->set_active(!$ifw->get_interactive) }; + $blacklist->grab_focus; + gtksignal_connect($w->{window}, delete_event => sub { + set_verdict($attack, \&apply_verdict_ignore); + }); + $w->{window}->show_all; +} |