#!/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::Notify -init, 'notify'; 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 ($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 }, ); my $icon = Gtk2::StatusIcon->new; 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 $notification_queue = Gtk2::Notify::Queue->new($icon); $icon->signal_connect(activate => sub { my ($_icon, $button, $time) = @_; if ($ifw_alert) { run_drakids(); } elsif ($simple_menu) { $simple_menu->popup(undef, undef, undef, undef, $button, $time); } else { run_netcenter(); } }); $icon->signal_connect(popup_menu => sub { my ($_icon, $button, $time) = @_; $menu->popup(undef, undef, undef, undef, $button, $time) if $menu; }); 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 notification at applet startup $current_state = $state_type; $notification_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 || $icon->get_storage_type ne 'pixbuf') { $icon->set_from_pixbuf(get_state_pixbuf()); } else { $icon->set_from_stock('gtk-dialog-warning'); } } 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(); $icon->set_tooltip(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; $notification_queue->add({ title => N("Interactive Firewall"), pixbuf => $pixbufs{firewall}, message => $message->{msg}, timeout => sub { set_verdict($message, \&apply_verdict_ignore); }, if_($is_attack, actions => [ { action => 'clicked', label => N("Process attack"), callback => sub { disable_ifw_alert(); ask_attack_verdict($message); }, } ], ), }); } 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); $notification_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(); $notification_queue->add({ title => N("Interactive Firewall: new service"), pixbuf => $pixbufs{firewall}, message => $listen->{message}, actions => [ { action => 'clicked', label => N("Process connection"), callback => 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 { $notification_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; }