pax_global_header00006660000000000000000000000064125716421330014516gustar00rootroot0000000000000052 comment=4be3188b88bd4664ead05da492f4fb3a07b63049 MANIFEST000066400000000000000000000002011257164213300122040ustar00rootroot00000000000000Makefile.PL MANIFEST README t/Ceni-Backend.t lib/Ceni/Backend.pm bin/Ceni bin/Ceni_read_config bin/Ceni_write_config bin/nicinfo Makefile.PL000066400000000000000000000005021257164213300130310ustar00rootroot00000000000000use ExtUtils::MakeMaker; WriteMakefile( NAME => 'Ceni::Backend', VERSION_FROM => 'lib/Ceni/Backend.pm', ( $] >= 5.005 ? ( ABSTRACT_FROM => 'lib/Ceni/Backend.pm', AUTHOR => 'Kel Modderman ' ) : () ), EXE_FILES => [ 'bin/Ceni', 'bin/nicinfo' ], ); README000066400000000000000000000017641257164213300117520ustar00rootroot00000000000000Ceni - Curses /etc/network/interfaces ===================================== A Curses user interface for configuring network interfaces with ifupdown. COPYRIGHT AND LICENCE Put the correct copyright and licence information here. Copyright (C) 2007 Kel Modderman This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this package; if not, see On Debian GNU/Linux systems, the text of the GPL license, version 2, can be found in /usr/share/common-licenses/GPL-2. bin/000077500000000000000000000000001257164213300116325ustar00rootroot00000000000000bin/Ceni000077500000000000000000001057241257164213300124470ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use FindBin; if (not (@ARGV and "@ARGV" =~ m/--no-act/) and $> != 0) { print "Ceni requires root priviledges! Be careful!\n"; exec('su', 'root', '-c', "$FindBin::Bin/$FindBin::Script @ARGV"); exit 1; } use lib "$FindBin::RealBin/../lib"; use Ceni::Backend; use Curses::UI; use Getopt::Long; use File::Spec; use Term::ReadKey; use File::Copy; #===============================================# my $dbg = 0; my $eni = '/etc/network/interfaces'; my $esn = '/etc/systemd/network/'; my $act = 1; my $iface; my $wcell; my %conf; #===============================================# GetOptions( 'act!' => \$act, 'debug' => \$dbg, 'file=s' => \$eni, 'iface=s' => \$iface, ); my $ceni = new Curses::UI( -color_support => 1, -mouse_support => 1, -clear_on_exit => 1, ); $ceni->set_binding(sub { exit 1; }, "\cC", "\cQ"); if (not -r $eni) { $ceni->error("ERROR! $eni cannot be read!\n"); exit 1; } my $bend = new Ceni::Backend({ act => $act, debug => $dbg, file => $eni, }); #===============================================# #%conf = ( # 'method' => 'static', # 'class' => 'auto', # 'stanza' => { # 'network' => '192.168.1.0', # 'gateway' => '192.168.1.254', # 'wpa-ssid' => 'kelnet', # 'wpa-psk' => 'mypasswd', # 'broadcast' => '192.168.0.255', # 'dns-nameservers' => '192.168.0.254', # 'address' => '192.168.1.99', # 'netmask' => '255.255.255.0' # }, # 'pre-up' => [ # '/path/to/script -i eth2', # ], # 'comment' => [ # "\t#wpa-psk myoldpasswd", # "\t#wpa-foo bar # ], #); # #$bend->set_iface_conf("eth2", \%conf); # #===============================================# sub redirect_stderr { open OLDERR, '>&', \*STDERR or die "E: Failed to dup STDERR to OLDERR: $!"; my $log = $dbg ? '/tmp/ceni.log' : File::Spec->devnull; open STDERR, '>', $log or die "E: Failed to redirect STDERR to $log: $!"; } sub restore_stderr { close STDERR or die "E: Failed to close STDERR: $!"; open STDERR, '>&', \*OLDERR or die "E: Failed to restore stderr: $!"; close OLDERR or die "E: Failed to close OLDERR: $!"; } sub is_ip { my $string = shift or return; $string =~ m/^([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])$/ and return 1; return 0; } sub is_hex { my $string = shift; $string =~ /[^A-F0-9]/i and return 0; return 1; } sub is_iface_live { if (-d $esn and -f "$esn/$iface.network") { return 1; } return 0; } sub rem_live_conf { if (is_iface_live()) { move("$esn/$iface.network", "$esn/$iface.network.ceni"); system("systemctl reload-or-try-restart systemd-networkd"); # clean up resolv.conf if no other .network files opendir (my $esnd, $esn); my @esnn = grep { /\.network$/ } readdir($esnd); closedir($esnd); if (@esnn==0 and readlink("/etc/resolv.conf") == '/run/systemd/resolve/resolv.conf') { unlink('/etc/resolv.conf'); my $cer=0; open(ER,'>/etc/resolv.conf') and $cer=1; if (open(RSRR,'/run/systemd/resolve/resolv.conf')) { while () { /^\#/ or ($cer and print ER); } close(RSRR); } $cer and close(ER); } } } sub psk_is_valid { my ($enc, $psk) = (shift, shift); my $psk_len = length $psk; if ($enc eq 'WEP') { if (is_hex($psk) and $psk_len =~ m/^(10|26|32|58)$/) { return 1; } elsif ($psk_len >= 5) { return 1; } } elsif ($enc eq 'WPA') { if (is_hex($psk) and $psk_len == 64) { return 1; } elsif ($psk_len >= 8 and $psk_len <= 63) { return 1; } } return 0; } sub interface_form { my (@i, %i, @l, %l, @m); for my $if (sort keys %{ $bend->get_iface_info() }) { my $info = $bend->get_iface_info($if); $i{$if} = sprintf "%-6s", $if; for ('connection_type', 'address', 'drivers', 'desc') { $i{$if} .= " " . $info->{$_}; } push @i, $if; } for my $if (sort keys %{ $bend->get_iface_conf() }) { if (not $bend->is_iface_valid($if) and $if ne 'lo') { $l{$if} = $if; push @l, $if; } } my $interface_form = $ceni->add( 'interface_form', 'Window', -title => 'Network Interfaces', -border => 1, -bfg => 'green', -tfg => 'green', ); $interface_form->add( 'interface_title', 'TextEntry', -text => 'Choose a network interface to configure:', -border => 0, -x => 3, -y => 1, -width => 50, -focusable => 0, -readonly => 1, ); my $interface_list = $interface_form->add( 'interface_list', 'Listbox', -title => 'Hardware interfaces', -border => 1, -bfg => 'green', -wraparound => 1, -vscrollbar => 1, -x => 1, -y => 3, -width => 76, -height => 6, -values => \@i, -labels => \%i, -onchange => sub { my $this = shift; $iface = $this->get(); $this->parent->loose_focus(); }, ); my $action_list = $interface_form->add( 'action_list', 'Listbox', -title => 'Interface Actions', -border => 1, -bfg => 'green', -wraparound => 1, -vscrollbar => 1, -x => 1, -y => 9, -width => 42, -height => 6, -ipadleft => 1, -values => [ 'new', 'wpa', ], -labels => { 'new' => 'Configure new logical interface ...', 'wpa' => 'Configure all wpa-roam mappings ...', }, -onchange => sub { my $this = shift; if ($this->get() eq 'new') { $iface = $ceni->question("Name for new logical interface:"); $iface =~ s/\s+$//; if (not $iface or $iface !~ m/^[-_\w\d]+$/) { if ($iface) { $ceni->error("Invalid logical interface name: $iface"); } $this->clear_selection(); return; } } elsif ($this->get() eq 'wpa') { my $conf = $ceni->loadfilebrowser( -title => 'Load WPA Configuration file', -editfilename => 0, -path => '/etc/wpa_supplicant/', ); if (not $conf or not -T $conf) { if ($conf) { $ceni->error("File cannot be parsed: $conf"); } $this->clear_selection(); return; } else { @m = $bend->wpa_mappings($conf); } if (not @m) { $ceni->dialog("There are no logical interfaces " . "to be mapped"); return; } } $this->parent->loose_focus(); }, ); my $logical_list = $interface_form->add( 'logical_list', 'Listbox', -title => 'Logical interfaces', -border => 1, -bfg => 'green', -wraparound => 1, -vscrollbar => 1, -x => 43, -y => 9, -width => 34, -height => 6, -ipadleft => 1, -values => \@l, -labels => \%l, -onchange => sub { my $this = shift; $iface = $this->get(); $this->parent->loose_focus(); }, ); my $exit = $interface_form->add( 'exit', 'Buttonbox', -x => 30, -y => 15, -width => 12, -height => 4, -border => 1, -bfg => 'blue', -buttons => [ { -label => '[ Exit ]', -onpress => sub { exit 0; } }, ], ); $interface_list->focus(); $interface_form->set_binding(sub { exit 1; }, "\cC", "\cQ"); $interface_form->modalfocus(); $ceni->delete('interface_form'); if (@m) { while (@m) { $iface = pop @m; if (not $bend->get_iface_conf($iface)) { netw_conf_form(); if (not $iface) { next; }; write_eni_conf(); } } $iface = undef; } } sub configure_form { my $configure_form = $ceni->add( 'configure_form', 'Window', -title => "$iface already configured!", -border => 1, -bfg => 'green', -tfg => 'green', ); $configure_form->add( 'configure_title', 'TextEditor', -text => "The selected network interface, $iface, is already configured.\n" . "Would you like to reconfigure, remove or cancel setup of $iface?\n", -border => 0, -x => 3, -y => 2, -width => 75, -height => 4, -focusable => 0, -readonly => 1, ); my $configure_list = $configure_form->add( 'configure_list', 'Listbox', -border => 1, -bfg => 'green', -wraparound => 1, -x => 15, -y => 6, -width => 35, -height => 5, -values => [ 'reconfigure', 'deconfigure', 'cancel', ], -labels => { 'reconfigure' => "Reconfigure $iface", 'deconfigure' => "Remove configuration for $iface", 'cancel' => "Cancel", }, -onchange => sub { shift->parent->loose_focus(); }, ); $configure_list->focus(); $configure_form->set_binding(sub { exit 1; }, "\cC", "\cQ"); $configure_form->modalfocus(); $ceni->delete('configure_form'); if ($configure_list->get() =~ /^(re|de)configure$/) { if ($bend->is_iface_valid($iface)) { $ceni->status("Removing ifupdown settings for $iface..."); $bend->ifdown($iface); if (is_iface_live()) { system("/sbin/ip addr flush $iface"); } $ceni->nostatus(); } if ($configure_list->get() eq 'deconfigure') { $bend->rem_iface_conf($iface); rem_live_conf(); $iface = undef; } } else { $iface = undef; } } sub wifi_or_wpas { my $ret = $ceni->dialog( -title => "Scan or Roam?", -message => "Select 'Scan' to scan for a wireless network to " . "connect to right now.\nSelect 'Roam' set up " . "wireless roaming with wpa_supplicant/wpa_gui.", -bfg => 'green', -buttons => [ { -label => '< Scan >', }, { -label => '< Roam >', } ], ); return $ret; } sub wifi_scan { $ceni->status('Scanning for wireless networks...'); $bend->wireless_scan($iface); $ceni->nostatus(); } sub wifi_scan_form { my (@s, %s); for my $c (sort { $a <=> $b } keys %{ $bend->get_scan_res() }) { my $cell = $bend->get_scan_res($c); $s{$c} = join(" ", $c, $cell->{'bssid'}, $cell->{'qual'} > 0 ? sprintf("%-3s", $cell->{'qual'}) : sprintf("%-3s", $cell->{'level'}), $cell->{'ssid'} . " " .$cell->{'flags'}); $s{$c} =~ s/\[ESS\]//; $s{$c} =~ s/\[WPS\]//; push @s, $c; } my $wifi_scan_form = $ceni->add( 'wireless_scan_form', 'Window', -title => "Wireless Networks", -border => 1, -bfg => 'green', -tfg => 'green', ); $wifi_scan_form->add( 'wireless_ssid_title', 'TextEntry', -text => 'Choose a wireless network:', -border => 0, -x => 2, -y => 1, -width => 75, -focusable => 0, -readonly => 1, ); $wifi_scan_form->add( 'wireless_bss_text', 'TextEntry', -text => ' # BSSID SIG ESSID [FLAGS]', -border => 0, -x => 2, -y => 3, -width => 75, -focusable => 0, -readonly => 1, ); my $wifi_ssid_list = $wifi_scan_form->add( 'wireless_ssid_list', 'Listbox', -border => 1, -bfg => 'green', -wraparound => 1, -vscrollbar => 1, -x => 1, -y => 4, -width => 76, -height => 11, -values => \@s, -labels => \%s, -onchange => sub { my $this = shift; $wcell = $this->get(); $this->parent->lose_focus(); }, ); my $wifi_scan_again = $wifi_scan_form->add( 'wireless_scan_again', 'Buttonbox', -x => 15, -y => 15, -width => 12, -height => 4, -border => 1, -bfg => 'blue', -buttons => [ { -label => '[ Scan ]', -onpress => sub { $wcell = undef; shift->parent->lose_focus(); } }, ], ); my $wifi_scan_ignore = $wifi_scan_form->add( 'wireless_scan_ignore', 'Buttonbox', -x => 30, -y => 15, -width => 12, -height => 4, -border => 1, -bfg => 'blue', -buttons => [ { -label => '[ Skip ]', -onpress => sub { $wcell = 'SKIP'; shift->parent->lose_focus(); } }, ], ); my $wifi_scan_cancel = $wifi_scan_form->add( 'wireless_scan_cancel', 'Buttonbox', -x => 45, -y => 15, -width => 14, -height => 4, -border => 1, -bfg => 'blue', -buttons => [ { -label => '[ Cancel ]', -onpress => sub { $iface = undef; shift->parent->lose_focus(); } }, ], ); $wifi_ssid_list->focus(); $wifi_scan_form->set_binding(sub { exit 1; }, "\cC", "\cQ"); $wifi_scan_form->modalfocus(); $ceni->delete('wireless_scan_form'); } sub wpas_conf_form { my $current = $bend->get_iface_conf($iface); my %w; my $wpas_conf_form = $ceni->add( 'wpas_conf_form', 'Window', -title => "wpa_supplicant settings for $iface", -border => 1, -bfg => 'green', -tfg => 'green', ); $w{'wpa-debug-level'} = $wpas_conf_form->add( 'wpas_log_list', 'Listbox', -title => 'WPA Log Level', -radio => 1, -wraparound => 1, -ipadleft => 2, -x => 1, -y => 1, -width => 32, -height => 5, -border => 1, -bfg => 'green', -values => [ '0', '1', '2'], -labels => { '0' => 'quiet', '1' => 'verbose', '2' => 'debug', }, -selected => 0, ); $w{'class'} = $wpas_conf_form->add( 'wpas_class_list', 'Listbox', -title => 'Class', -radio => 1, -wraparound => 1, -ipadleft => 2, -x => 33, -y => 1, -width => 32, -height => 5, -border => 1, -bfg => 'green', -values => [ 'allow-hotplug', 'auto', 'manual'], -selected => 0, ); $w{'wpa-roam'} = $wpas_conf_form->add( 'wpas_conf_input', 'TextEntry', -title => 'Configuration Location', -text => '/etc/wpa_supplicant/wpa-roam.conf', -readonly => 1, -focusable => 0, -ipadleft => 2, -x => 1, -y => 6, -width => 64, -border => 1, -bfg => 'green', ); my $wpas_conf = $wpas_conf_form->add( 'wpas_templ_filesaver', 'Buttonbox', -x => 65, -y => 6, -width => 10, -border => 1, -bfg => 'blue', -buttons => [ { -label => '[ Save ]', -onpress => sub { my $conf = $ceni->savefilebrowser( -editfilename => 1, -file => $w{'wpa-roam'}->get(), ); $w{'wpa-roam'}->text($conf); } }, ], ); $w{'wpa-roam-template'} = $wpas_conf_form->add( 'wpas_conf_template_input', 'TextEntry', -title => 'Configuration Template', -text => '/usr/share/doc/wpasupplicant/examples/wpa-roam.conf', -readonly => 1, -focusable => 0, -ipadleft => 2, -x => 1, -y => 9, -width => 64, -border => 1, -bfg => 'green', ); my $wpas_templ = $wpas_conf_form->add( 'wpas_templ_fileloader', 'Buttonbox', -x => 65, -y => 9, -width => 10, -border => 1, -bfg => 'blue', -buttons => [ { -label => '[ Load ]', -onpress => sub { my $templ = $ceni->loadfilebrowser( -editfilename => 0, -file => $w{'wpa-roam-template'}->get(), ); if (-T $templ) { $w{'wpa-roam-template'}->text($templ); } else { $ceni->error("Invalid file: $templ"); } } }, ], ); my $wpas_blurb = $wpas_conf_form->add( 'wpas_blurb', 'TextViewer', -text => "After the roaming configuration is activated, wireless " . "network scanning and configuration can be done via " . "wpa_gui or wpa_cli. For more info see:\n" . "\tfile:///usr/share/doc/wpasupplicant/README.Debian.gz", -x => 2, -y => 12, -width => 74, -height => 3, -focusable => 0, -wrapping => 1, ); my $wpas_accept = $wpas_conf_form->add( 'wpas_confirm', 'Buttonbox', -x => 15, -y => 15, -width => 12, -border => 1, -bfg => 'blue', -buttons => [ { -label => '[ Accept ]', -onpress => sub { if ($bend->prep_wpa_roam($w{'wpa-roam-template'}->get(), $w{'wpa-roam'}->get())) { $wcell++; shift->parent->lose_focus(); } else { $ceni->error("Failed to write WPA configuration file."); } } }, ], ); my $wpas_back = $wpas_conf_form->add( 'wpas_back', 'Buttonbox', -x => 30, -y => 15, -width => 12, -border => 1, -bfg => 'blue', -buttons => [ { -label => '[ Back ]', -onpress => sub { $wcell = undef; shift->parent->lose_focus(); } }, ], ); my $wpas_cancel = $wpas_conf_form->add( 'wpas_cancel', 'Buttonbox', -x => 45, -y => 15, -width => 14, -border => 1, -bfg => 'blue', -buttons => [ { -label => '[ Cancel ]', -onpress => sub { $iface = undef; shift->parent->lose_focus(); } }, ], ); if ($current and $current->{'stanza'} and -T $current->{'stanza'}->{'wpa-roam'}) { $w{'wpa-roam'}->text($current->{'stanza'}->{'wpa-roam'}); } $wpas_accept->focus(); $wpas_conf_form->set_binding(sub { exit 1; }, "\cC", "\cQ"); $wpas_conf_form->modalfocus(); $ceni->delete('wpas_conf_form'); if ($wcell) { $conf{'class'} = $w{'class'}->get(); $conf{'method'} = 'manual'; for ('wpa-roam', 'wpa-debug-level') { if ($w{$_} and $w{$_}->get()) { $conf{'stanza'}{$_} = $w{$_}->get(); } } $bend->set_iface_conf($iface, \%conf); $bend->set_iface_conf('default', { 'method' => 'dhcp', }) if not $bend->get_iface_conf('default'); $bend->parse_eni(); } } sub wifi_conf_form { my $info = $bend->get_iface_info($iface); my $scan = $bend->get_scan_res($wcell); my %w; my $wifi_conf_form = $ceni->add( 'wifi_conf_form', 'Window', -title => "Wireless settings for $iface", -border => 1, -bfg => 'green', -tfg => 'green', ); $w{'enc'} = $wifi_conf_form->add( 'enc_list', 'Listbox', -title => 'Encryption', -radio => 1, -wraparound => 1, -ipadleft => 2, -x => 1, -y => 1, -width => 28, -height => 5, -border => 1, -bfg => 'green', -values => [ 'None', 'WEP', 'WPA', ], -selected => 0, -onchange => sub { my $this = shift; if ($this->get() eq 'None') { for ('psk', 'psk_len', 'stdin', 'clear') { $w{$_}->set_color_bfg('red'); $w{$_}->focusable(0); } } else { for ('stdin', 'clear') { $w{$_}->set_color_bfg('blue'); $w{$_}->focusable(1); } $w{'psk'}->set_color_bfg('green'); $w{'psk'}->focusable(1); } } ); $w{'mode'} = $wifi_conf_form->add( 'mode_list', 'Listbox', -title => 'Mode', -radio => 1, -wraparound => 1, -ipadleft => 2, -x => 29, -y => 1, -width => 28, -height => 5, -border => 1, -bfg => 'green', -values => [ 'managed', 'ad-hoc', 'master', ], -selected => 0, ); $w{'ssid'} = $wifi_conf_form->add( 'wireless_ssid_input', 'TextEntry', -title => 'Network ESSID', -x => 1, -y => 6, -ipadleft => 1, -width => 56, -border => 1, -bfg => 'green', ); $w{'hidden_ssid'} = $wifi_conf_form->add( 'wireless_hidden_ssid', 'Checkbox', -label => 'hidden ESSID', -x => 57, -y => 6, -width => 18, -border => 1, -bfg => 'blue', ); $w{'bssid'} = $wifi_conf_form->add( 'wireless_bssid_input', 'TextEntry', -title => 'Network BSSID', -border => 1, -bfg => 'green', -x => 1, -y => 9, -ipadleft => 1, -width => 56, -regexp => 'm/^[0-9A-F:]*$/i', ); $w{'force_bssid'} = $wifi_conf_form->add( 'wireless_force_bssid', 'Checkbox', -label => 'force BSSID', -x => 57, -y => 9, -width => 18, -border => 1, -bfg => 'blue', -onchange => sub { $w{'bssid'}->focusable(1); }, ); $w{'psk'} = $wifi_conf_form->add( 'wireless_psk_input', 'PasswordEntry', -title => 'Preshared Key', -border => 1, -showoverflow => 0, -bfg => 'red', -x => 1, -y => 12, -width => 50, -ipadleft => 1, -focusable => 0, -onchange => sub { my $this = shift; my $psk = $this->get(); my $enc = $w{'enc'}->get(); if (psk_is_valid($enc, $psk)) { $w{'psk_len'}->set_color_bfg('green'); } else { $w{'psk_len'}->set_color_bfg('red'); } $w{'psk_len'}->text(length $psk); }, ); $w{'psk_len'} = $wifi_conf_form->add( 'wireless_psk_count', 'TextEntry', -text => '0', -x => 51, -y => 12, -width => 6, -border => 1, -focusable => 0, -readonly => 1, -bfg => 'red', -ipadleft => 1, ); $w{'stdin'} = $wifi_conf_form->add( 'wireless_psk_stdin', 'Buttonbox', -x => 57, -y => 12, -width => 9, -border => 1, -bfg => 'red', -focusable => 0, -buttons => [ { -label => '[Paste]', -onpress => sub { my $this = shift; my $enc = $w{'enc'}->get(); $ceni->leave_curses(); print 'Enter preshared key: '; ReadMode('noecho'); chomp(my $psk = ReadLine(0)); ReadMode('normal'); print "\n"; $ceni->reset_curses(); $w{'psk'}->text($psk); $w{'psk_len'}->text(length $psk); $this->lose_focus(); if (psk_is_valid($enc, $psk)) { $w{'psk_len'}->set_color_bfg('green'); $w{'confirm'}->focus(); } else { $w{'psk'}->focus(); } } }, ], ); $w{'clear'} = $wifi_conf_form->add( 'wireless_psk_clear', 'Buttonbox', -x => 66, -y => 12, -width => 9, -border => 1, -bfg => 'red', -focusable => 0, -buttons => [ { -label => '[Clear]', -onpress => sub { my $this = shift; $w{'psk_len'}->text('0'); $w{'psk_len'}->set_color_bfg('red'); $this->lose_focus(); $w{'psk'}->text(''); $w{'psk'}->focus(); } }, ], ); $w{'confirm'} = $wifi_conf_form->add( 'wireless_confirm', 'Buttonbox', -x => 15, -y => 15, -width => 12, -height => 4, -border => 1, -bfg => 'blue', -buttons => [ { -label => '[ Accept ]', -onpress => sub { my $this = shift; my $enc = $w{'enc'}->get(); my $valid = 0; if ($enc eq 'None') { $valid++; } else { if (psk_is_valid($enc, $w{'psk'}->get())) { $valid++; } else { if ($enc eq 'WEP') { $ceni->error( " $enc encryption requires a key with at least 5 characters." ); } elsif ($enc eq 'WPA') { $ceni->error( " $enc encryption requires a key with 8 to 63 characters." ); } $this->lose_focus(); $w{'psk'}->focus(); } } if ($valid) { $this->parent->lose_focus(); } } }, ], ); my $wifi_scan = $wifi_conf_form->add( 'wireless_scan', 'Buttonbox', -x => 30, -y => 15, -width => 12, -height => 4, -border => 1, -bfg => 'blue', -buttons => [ { -label => '[ Scan ]', -onpress => sub { $wcell = undef; shift->parent->lose_focus(); } }, ], ); my $wifi_cancel = $wifi_conf_form->add( 'wireless_cancel', 'Buttonbox', -x => 45, -y => 15, -width => 14, -height => 4, -border => 1, -bfg => 'blue', -buttons => [ { -label => '[ Cancel ]', -onpress => sub { $iface = undef; shift->parent->lose_focus(); } }, ], ); if (defined $wcell and $wcell ne 'SKIP') { if ($scan->{'ssid'}) { if ($scan->{'ssid'} =~ m/^(any|on|off)$/i) { $w{'ssid'}->text('-- ' . $scan->{'ssid'}); $w{'ssid'}->focusable(0); } elsif ($scan->{'ssid'} !~ /^(|\*)$/) { $w{'ssid'}->text($scan->{'ssid'}); $w{'ssid'}->focusable(0); } else { if ($scan->{'ssid'} eq '') { $w{'hidden_ssid'}->check(); } $w{'ssid'}->focus(); } } if ($scan->{'flags'}) { if ($scan->{'flags'} =~ /WPA/) { $w{'enc'}->set_selection(2); } elsif ($scan->{'flags'} =~ /WEP/) { $w{'enc'}->set_selection(1); } if ($scan->{'ssid'}) { $w{'psk'}->focus(); } } # FIXME: determine ad-hoc mode from wpa_supplicant scan data #if ($scan->{'mode'}) { # if ($scan->{'mode'} eq 'ad-hoc') { # $w{'mode'}->set_selection(1); # } #} if ($scan->{'bssid'}) { $w{'bssid'}->text($scan->{'bssid'}); $w{'bssid'}->focusable(0); } } else { $w{'confirm'}->focus(); } $wifi_conf_form->set_binding(sub { exit 1; }, "\cC", "\cQ"); $wifi_conf_form->modalfocus(); $ceni->delete('wifi_conf_form'); if ($w{'mode'}->get() eq 'ad-hoc') { $conf{'stanza'}{'wpa-mode'} = '1'; } elsif ($w{'mode'}->get() eq 'master') { $conf{'stanza'}{'wpa-mode'} = '2'; } if ($w{'ssid'}->get()) { $conf{'stanza'}{'wpa-ssid'} = $w{'ssid'}->get(); } if ($w{'bssid'}->get and $w{'force_bssid'}->get()) { $conf{'stanza'}{'wpa-bssid'} = $w{'bssid'}->get(); } if ($w{'hidden_ssid'}->get()) { $conf{'stanza'}{'wpa-scan-ssid'} = '1'; $conf{'stanza'}{'wpa-ap-scan'} = '1'; } if ($w{'enc'}->get() =~ /^(None|WEP)$/) { $conf{'stanza'}{'wpa-key-mgmt'} = 'NONE'; if ($w{'enc'}->get() eq 'WEP') { $conf{'stanza'}{'wpa-wep-key0'} = $w{'psk'}->get(); $conf{'stanza'}{'wpa-wep-tx-keyidx'} = '0'; if ($conf{'stanza'}{'wpa-wep-key0'} =~ m/^-- (any|on|off)$/) { $conf{'stanza'}{'wpa-wep-key0'} =~ s/^--\s+//; } } } else { $conf{'stanza'}{'wpa-psk'} = $w{'psk'}->get(); } } sub netw_conf_form { my $current = $bend->get_iface_conf($iface); my @static = ( 'address', 'netmask', 'network', 'broadcast', 'gateway', 'dns-search', 'dns-nameservers', ); my %n; my $network_form = $ceni->add( 'network_form', 'Window', -title => "Network settings for $iface", -border => 1, -bfg => 'green', -tfg => 'green', ); $n{'method'} = $network_form->add( 'method_list', 'Listbox', -title => 'Method', -radio => 1, -wraparound => 1, -ipadleft => 2, -x => 1, -y => 1, -width => 20, -height => 5, -border => 1, -bfg => 'green', -values => [ 'dhcp', 'static', ], -selected => 0, -onchange => sub { if (shift->get() eq 'dhcp') { foreach (@static) { next unless $n{$_}; next if /^dns/; $n{$_}->focusable(0); $n{$_}->set_color_bfg('red'); $n{$_}->text(''); } } else { foreach (@static) { next unless $n{$_}; $n{$_}->focusable(1); $n{$_}->set_color_bfg('green'); if ($current->{'stanza'}->{$_}) { $n{$_}->text($current->{'stanza'}->{$_}); } } } }, ); if ($bend->is_iface_valid($iface)) { $n{'class'} = $network_form->add( 'class_list', 'Listbox', -title => 'Class', -radio => 1, -wraparound => 1, -ipadleft => 2, -x => 25, -y => 1, -width => 45, -height => 5, -border => 1, -bfg => 'green', -values => [ 'allow-hotplug', 'auto', 'manual', ], -labels => { 'allow-hotplug' => 'allow-hotplug (Removable Devices)', 'auto' => 'auto (Built-In Devices)', 'manual' => 'manual', }, -selected => 0, ); } $n{'address'} = $network_form->add( 'address_input', 'TextEntry', -title => 'IP Address', -border => 1, -bfg => 'red', -x => 1, -y => 6, -width => 20, -ipadleft => 1, -focusable => 0, -regexp => '/^[0-9\.]*$/', -onchange => sub { shift->delete_till_eol(); }, -onblur => sub { my $this = shift; if (is_ip($this->get())) { if ($this->get() =~ m/^(10\.)\d+\.\d+\.\d+$/) { $n{'network'}->text($1 . '0.0.0'); $n{'broadcast'}->text($1 . '255.255.255'); $n{'netmask'}->text('255.0.0.0'); } elsif ($this->get() =~ m/^(172\.\d+\.)\d+\.\d+$/) { $n{'network'}->text($1 . '0.0'); $n{'broadcast'}->text($1 . '255.255'); $n{'netmask'}->text('255.255.0.0'); } elsif ($this->get() =~ m/^(\d+\.\d+\.\d+\.)\d+$/) { $n{'network'}->text($1 . '0'); $n{'broadcast'}->text($1 . '255'); $n{'netmask'}->text('255.255.255.0'); } } }, ); $n{'netmask'} = $network_form->add( 'netmask_input', 'TextEntry', -title => 'Netmask', -border => 1, -bfg => 'red', -x => 1, -y => 9, -width => 20, -ipadleft => 1, -focusable => 0, -regexp => '/^[0-9\.]*$/', -onchange => sub { shift->delete_till_eol(); }, ); $n{'network'} = $network_form->add( 'network_input', 'TextEntry', -title => 'Network', -border => 1, -bfg => 'red', -x => 25, -y => 6, -width => 20, -ipadleft => 1, -focusable => 0, -regexp => '/^[0-9\.]*$/', -onchange => sub { shift->delete_till_eol(); }, ); $n{'broadcast'} = $network_form->add( 'broadcast_input', 'TextEntry', -title => 'Broadcast', -border => 1, -bfg => 'red', -x => 25, -y => 9, -width => 20, -ipadleft => 1, -focusable => 0, -regexp => '/^[0-9\.]*$/', -onchange => sub { shift->delete_till_eol(); }, ); $n{'gateway'} = $network_form->add( 'gateway_input', 'TextEntry', -title => 'Gateway', -border => 1, -bfg => 'red', -x => 50, -y => 6, -width => 20, -ipadleft => 1, -focusable => 0, -regexp => '/^[0-9\.]*$/', -onchange => sub { shift->delete_till_eol(); }, ); if (-x '/sbin/resolvconf') { $n{'dns-search'} = $network_form->add( 'dnssearch_input', 'TextEntry', -title => 'DNS Search', -border => 1, -bfg => 'green', -x => 50, -y => 9, -width => 20, -ipadleft => 1, -focusable => 1, ); $n{'dns-nameservers'} = $network_form->add( 'dnsnameservers_input', 'TextEntry', -title => 'DNS Nameservers', -border => 1, -showoverflow => 0, -bfg => 'green', -x => 1, -y => 12, -width => 69, -ipadleft => 1, -focusable => 1, ); } my $confirm = $network_form->add( 'confirm', 'Buttonbox', -x => 15, -y => 15, -width => 12, -height => 4, -border => 1, -bfg => 'blue', -buttons => [ { -label => '[ Accept ]', -onpress => sub { my $this = shift; my $invalid; for (sort keys %n) { if ($n{'method'}->get() eq 'static') { if (m/address|netmask/) { unless (is_ip($n{$_}->get())) { $invalid = $_; last; } } } $n{$_}->get() or next; if (m/broadcast|gateway|network/) { unless (is_ip($n{$_}->get())) { $invalid = $_; last; } } } if ($invalid) { $ceni->error("Invalid $invalid"); $this->lose_focus(); $n{$invalid}->focus(); } else { $this->parent->lose_focus(); } } }, ], ); my $back = $network_form->add( 'back', 'Buttonbox', -x => 30, -y => 15, -width => 12, -height => 4, -border => 1, -bfg => 'blue', -buttons => [ { -label => '[ Back ]', -onpress => sub { if ($wcell) { $wcell = undef; } else { $iface = undef; } shift->parent->lose_focus(); } }, ], ); my $cancel = $network_form->add( 'cancel', 'Buttonbox', -x => 45, -y => 15, -width => 14, -height => 4, -border => 1, -bfg => 'blue', -buttons => [ { -label => '[ Cancel ]', -onpress => sub { $iface = undef; shift->parent->lose_focus(); } }, ], ); if (not $current->{'method'}) { $n{'method'}->set_selection(0); } elsif ($current->{'method'} eq 'static') { $n{'method'}->set_selection(1); } if ($bend->is_iface_valid($iface)) { if (not $current->{'class'} and keys %{$current}) { $n{'class'}->set_selection(2); } elsif ($current->{'class'} eq 'auto') { $n{'class'}->set_selection(1); } else { $n{'class'}->set_selection(0); } } $confirm->focus(); $network_form->set_binding(sub { exit 1; }, "\cC", "\cQ"); $network_form->modalfocus(); $ceni->delete('network_form'); for ('class', 'method') { if ($n{$_} and $n{$_}->get()) { $conf{$_} = $n{$_}->get(); } } for (@static) { if ($n{$_} and $n{$_}->get()) { $conf{'stanza'}{$_} = $n{$_}->get(); } } } sub write_eni_conf { my $current = $bend->get_iface_conf($iface); if ($current->{'comment'}) { $conf{'comment'} = $current->{'comment'}; } $bend->debug(\%conf, 'conf'); $bend->set_iface_conf($iface, \%conf); $bend->parse_eni(); rem_live_conf(); } sub ifupdown_iface { $ceni->leave_curses(); restore_stderr(); $bend->ifup($iface); print "\n"; print "Press Enter key to continue ...\n"; print "\n"; ReadMode('noecho'); ReadLine(0); ReadMode('normal'); redirect_stderr(); $ceni->reset_curses(); } sub qanother_iface { my $exit = $ceni->dialog( -title => "Do you want to exit?", -message => "Select 'yes' to exit or 'no' to continue.", -bfg => 'green', -buttons => ['yes', 'no'], ); $exit or $iface = undef; } #===============================================# # sub main () { redirect_stderr(); MAIN: while (1) { %conf = (); $bend->parse_eni(); $bend->nic_info(); if (not $iface) { interface_form(); if (not $iface) { next MAIN; } } if ($bend->is_iface_configured($iface) or is_iface_live()) { configure_form(); if (not $iface) { next MAIN; } } if ($bend->is_iface_valid($iface)) { if ($bend->is_iface_wireless($iface)) { $wcell = undef; WIFI: while (1) { if (wifi_or_wpas()) { wpas_conf_form(); if (not $iface) { next MAIN; } if (not $wcell) { next WIFI; } } else { SCAN: until ($wcell) { wifi_scan(); wifi_scan_form(); if (not $iface) { next MAIN; } if (not $wcell) { next SCAN; } wifi_conf_form(); if (not $iface) { next MAIN; } if (not $wcell) { next SCAN; } netw_conf_form(); if (not $iface) { next MAIN; } if (not $wcell) { next SCAN; } } write_eni_conf(); } ifupdown_iface(); last WIFI; } } else { # Non-Wireless Interface netw_conf_form(); if (not $iface) { next MAIN; } write_eni_conf(); ifupdown_iface(); } } else { # Logical Interface netw_conf_form(); if (not $iface) { next MAIN; } write_eni_conf(); $iface = undef; next MAIN; } qanother_iface(); if (not $iface) { next MAIN; } else { last MAIN; } } $ceni->leave_curses(); system("clear"); } main(); __END__ =head1 NAME Ceni - Curses /etc/network/interfaces =head1 SYNOPSIS Ceni [ options ] =head1 DESCRIPTION A Curses user interface for configuring network interfaces with ifupdown. =head1 OPTIONS =over =item B<--file> filename Use filename as alternative /etc/network/interfaces =item B<--iface> iface Configure network interface with name iface =item B<--no-act> Call ifup and ifdown with -n option to simulate action Must be first argument to have effect. =item B<--debug> Debug output piped to /tmp/ceni.log =back =head1 AUTHOR Kel Modderman, Ekel@otaku42.deE =head1 COPYRIGHT AND LICENSE Copyright (C) 2007 - 2010 by Kel Modderman This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this package; if not, see On Debian GNU/Linux systems, the text of the GPL-2 license can be found in /usr/share/common-licenses/GPL-2. =cut bin/Ceni_read_config000077500000000000000000000044621257164213300147640ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use FindBin; use lib "$FindBin::RealBin/../lib"; use Ceni::Backend; use Getopt::Long; my $dbg = 0; my $iface; my $in = '/etc/network/interfaces'; GetOptions('debug' => \$dbg, 'iface=s' => \$iface, 'input=s' => \$in,); if (not $in or not $iface) { print "Usage: Ceni_read_config --iface [--input ]\n"; exit 0; } my $source = new Ceni::Backend({ debug => $dbg, file => $in, }); $source->parse_eni or exit 1; my $config = $source->get_iface_conf($iface); if ($config) { for my $k (sort keys %{$config}) { if ($k eq 'stanza') { for my $s (sort keys %{ $config->{$k} }) { print $s . ',' . $config->{$k}->{$s} . "\n"; } next; } elsif ($k =~ /^(pre-|post-)?(up|down)$/) { for my $s (@{ $config->{$k} }) { print $k . ',' . $s . "\n"; } next; } print $k . ',' . $config->{$k} . "\n"; } } __END__ =head1 NAME Ceni_read_config - read network interface configuration from input file =head1 SYNOPSIS Ceni_read_config --iface [--input ] =head1 DESCRIPTION This script reads an input file containing ifupdown configuration data, and prints the configuration data. =head1 OPTIONS =over =item B<--iface> The target network interface with kernel name . Exmaples of are "eth0", "ath0" and "wlan0". =item B<--input> Input file containing network interface ifupdown configuration. If no input file is specified, /etc/network/interfaces is assumed. =back =head1 AUTHOR Kel Modderman, Ekel@otaku42.deE =head1 COPYRIGHT AND LICENSE Copyright (C) 2007 - 2010 by Kel Modderman This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this package; if not, see On Debian GNU/Linux systems, the text of the GPL-2 license can be found in /usr/share/common-licenses/GPL-2. =cut bin/Ceni_write_config000077500000000000000000000044501257164213300152000ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use FindBin; use lib "$FindBin::RealBin/../lib"; use Ceni::Backend; use Getopt::Long; my $dbg = 0; my $iface; my $in; my $out = '/etc/network/interfaces'; GetOptions( 'debug' => \$dbg, 'iface=s' => \$iface, 'input=s' => \$in, 'output=s' => \$out, ); if (not $in or not $iface) { print "Usage: Ceni_write_config --iface --input [--output ]\n"; exit 0; } my $source = new Ceni::Backend({ debug => $dbg, file => $in, }); my $target = new Ceni::Backend({ debug => $dbg, file => $out, }); $source->parse_eni or exit 1; $target->set_iface_conf($iface, $source->get_iface_conf($iface)) or exit 1; __END__ =head1 NAME Ceni_write_config - write network interface configuration from input file =head1 SYNOPSIS Ceni_write_config --iface --input [--output ] =head1 DESCRIPTION This script reads an input file containing ifupdown configuration data, and writes the configuration to the output file (default /etc/network/interfaces) for the selected interface. =head1 OPTIONS =over =item B<--iface> The target network interface with kernel name . Exmaples of are "eth0", "ath0" and "wlan0". =item B<--input> Input file containing network interface ifupdown configuration. =item B<--output> Output file to write or update configuration data read from input file. If no output file is specified, /etc/network/interfaces is assumed. =back =head1 AUTHOR Kel Modderman, Ekel@otaku42.deE =head1 COPYRIGHT AND LICENSE Copyright (C) 2007 - 2010 by Kel Modderman This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this package; if not, see On Debian GNU/Linux systems, the text of the GPL-2 license can be found in /usr/share/common-licenses/GPL-2. =cut bin/nicinfo000077500000000000000000000050321257164213300132050ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use FindBin; use lib "$FindBin::RealBin/../lib"; use Ceni::Backend; use Data::Dumper; use Getopt::Long; my $dbg = 0; my $ether; my $help; my $iface; my $wifi; my @i; my %i; my @f = ('connection_type', 'drivers', 'subsystems', 'address', 'desc'); GetOptions( 'debug' => \$dbg, 'ethernet' => \$ether, 'help' => \$help, 'iface=s' => \$iface, 'wifi' => \$wifi, ); if ($help) { print "Usage: nicinfo [ --ethernet ] [ --wifi ] [ --iface ]\n"; exit 0; } my $bend = new Ceni::Backend({ debug => $dbg, }); $bend->nic_info; if ($iface) { if ($bend->is_iface_valid($iface)) { @i = ($iface); } else { print "E: interface does not exist: $iface\n"; exit 1; } } else { @i = sort keys %{ $bend->get_iface_info }; } for my $if (@i) { my $nic = $bend->get_iface_info($if); if ($wifi) { $nic->{'connection_type'} eq 'wireless' or next; } elsif ($ether) { $nic->{'connection_type'} eq 'ethernet' or next; } $i{$if} = join(',', $if, map { $nic->{$_}; } @f); } for my $if (sort keys %i) { print $i{$if} . "\n"; } __END__ =head1 NAME nicinfo - print information about network interfaces =head1 SYNOPSIS nicinfo [ --ethernet ] [ --wifi ] [ --iface ] =head1 DESCRIPTION This script detects network interfaces present in system and prints out driver, interface type, bus, mac address and a description for each interface. =head1 OPTIONS =over =item B<--iface> Detect and print information for the interface with kernel name . Exmaples of are "eth0", "ath0" and "wlan0". =item B<--ethernet> Detect only ethernet devices, not wirless. =item B<--wifi> Detect only wireless devices, not ethernet. =back =head1 AUTHOR Kel Modderman, Ekel@otaku42.deE =head1 COPYRIGHT AND LICENSE Copyright (C) 2007 - 2010 by Kel Modderman This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this package; if not, see On Debian GNU/Linux systems, the text of the GPL-2 license can be found in /usr/share/common-licenses/GPL-2. =cut bin/wpa_cli_scan.pl000066400000000000000000000042101257164213300146060ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use Data::Dumper; use Curses::UI; use Expect; my $ceni = new Curses::UI( -color_support => 1, -mouse_support => 1, -clear_on_exit => 1, ); $ceni->set_binding(sub { exit 1; }, "\cC", "\cQ"); $ceni->status('Scanning for wireless networks...'); my $iface = $ARGV[0]; $iface or die("iface must be first arg\n"); # Spawn an unconfigured wpa_supplicant process to prepare the interface for # scanning. Using the background (-B) option ensures return value indicates # if the interface was successfully setup (or not). The use of system(), # however, means we must also determine the process id from a pid file. my @ver = split(/\./, `/sbin/wpa_supplicant -v | sed -n "s/^wpa_supplicant v//p"`); my $driver = ($ver[0] == 0 and $ver[1] <= 6) ? 'wext' : 'nl80211,wext'; system("/sbin/wpa_supplicant -B -i $iface -D $driver " . "-P /run/wpa_supplicant.$iface.pid " . "-C /run/wpa_supplicant") == 0 or die($!); # Grab the wpa_supplicant process id from pid file. We may have to wait for it # to be created though... sleep 1 until -f "/run/wpa_supplicant.$iface.pid"; open my $wpasup_pid_fh, '<', "/run/wpa_supplicant.$iface.pid" or die($!); my $wpasup_pid = <$wpasup_pid_fh>; close $wpasup_pid_fh; chomp $wpasup_pid; # Start wpa_cli interactive session and communicate with it via Expect my $wpacli = new Expect; $wpacli->raw_pty(1); $wpacli->log_stdout(0); #$wpacli->log_file("/tmp/test.log"); $wpacli->spawn("/sbin/wpa_cli", "-i", $iface); # Trigger a scan and wait for scan result notification for up to 30 seconds $wpacli->send("SCAN\n"); $wpacli->expect(30, -re => ".*CTRL-EVENT-SCAN-RESULTS"); my %scan; for my $bss (0..100) { my ($reply, %data); $wpacli->clear_accum(); $wpacli->send("BSS $bss\n"); $wpacli->expect(1, -re => "^>"); $reply = $wpacli->exp_before(); last if length($reply) == 0; for (split "\n", $reply) { next unless m/=/; my ($key, undef) = split(/=/); my $val = substr($_, length($key) + 1); $data{$key} = $val; } $scan{sprintf "%02d", $bss} = \%data; } $wpacli->hard_close(); $ceni->leave_curses(); # Kill wpa_supplicant process. kill 'TERM', $wpasup_pid; print Dumper(\%scan); ceni.desktop000066400000000000000000000006621257164213300133770ustar00rootroot00000000000000[Desktop Entry] Type=Application Exec=su-to-root -c /usr/bin/Ceni Icon=ceni Terminal=true Name=Ceni GenericName=Network card configuration GenericName[de]=Netzwerkkarte konfigurieren GenericName[ru]=Конфигурация сетового адаптера GenericName[es]=Configuración de la tarjeta de red GenericName[pt]=Configuração da placa de rede Categories=Network; Keywords=Net;Network;Configuration;interfaces;ifupdown; icons/000077500000000000000000000000001257164213300121755ustar00rootroot00000000000000icons/Makefile000066400000000000000000000006661257164213300136450ustar00rootroot00000000000000#!/usr/bin/make -f NAME := ceni SVG := $(NAME).svg SIZES := 16x16 22x22 32x32 48x48 64x64 128x128 ICONS := $(addsuffix .png,$(SIZES)) all: $(ICONS) %.png: mkdir -p hicolor/$(@:.png=)/apps/ $(RM) hicolor/$(@:.png=)/apps/$(NAME).png inkscape $(SVG) --without-gui \ --export-width=$(word 1,$(subst x, ,$(@:.png=))) \ --export-height=$(word 2,$(subst x, ,$(@:.png=))) \ --export-png=hicolor/$(@:.png=)/apps/$(NAME).png icons/ceni.svg000066400000000000000000000272221257164213300136410ustar00rootroot00000000000000 icons/hicolor/000077500000000000000000000000001257164213300136345ustar00rootroot00000000000000icons/hicolor/128x128/000077500000000000000000000000001257164213300145715ustar00rootroot00000000000000icons/hicolor/128x128/apps/000077500000000000000000000000001257164213300155345ustar00rootroot00000000000000icons/hicolor/128x128/apps/ceni.png000066400000000000000000000264021257164213300171640ustar00rootroot00000000000000PNG  IHDR>asBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org< IDATx}{չ{f`fx((O:>E(M1(JbD=*)VbrM?1)|Tr(F4/Q0 0 0O|=_{޽g@1'_UWw^^[W=l0Ͽ$ dluiFM a 4at׆aKץ}߷, eYl6NdG2?@~谆7wM:͛wi*C|12"vy-B>G>`СCFD#!ܹso6 >(h?,#z@>w{U~ abJ$R 9s5M|>?W% hy!oG$|>ޞo+M9( g2̕^Prrsf =e/үΡ{i:t{`@yOהDFQ]]*2 ^zg a ?ɓ'WSS1NPqq0D(r (x~dYyQ hprSmP>C7⽈ϑ-^]];8qd>e˖m BcQ=^1Д)SRUUN.IćB!D"%a%ske#4H&Qq,\-#R#_n+++1ydb>|xk7o *!{aLrZEEl6FD"YL&*云ISY$9 W5Rp NT|e.9t'6WA*B{{;R*++]pmm퍆a]H!Ȅ &WWWore\GB!qAe!|٬siT^5pnY#HB蚞*߫?O\-Bn"C\oY@%]]]VJP#? NkLD"ȗD,|>Y^^`0p8HHPȑFt]fH&N}DAL:F&A*B2D*B&A2D:F:F*r{ppT e4x$A!ՃN%iӦ!;cf}pʕsc8,P:]*H$"#?8> eeeFBh"Y\Ts CϹyppdDdҹORH$@**@&@(R*0 ӦMsmmm ->}Ł@9jiB.|D@X,9xܱD̹Ic*Me4H}:_QljA^l6?]Ľ"L6 H!T*;v\e˖mTW\FR)"*++=f Q[[x<| A:2Mx.vIg*d{9a= $'"BHuԦr݋3flOcyp/ :,z(BYYYA$LĔ)S F^ַ.]ruNgwmer;UߩZ74Lz%i:JҫFw^%1@OO8;p/>utwB N8PȱBӜ%U8G6yq/CF ?tM#e?#NJ{Tp'xgDP(TY__98l0d>?X,op8p6Ǐw>~|m>ld~O"Ҩ&{x#cرڰ9wE7n2հqL`P&NbJWUUjl^*n2\ve8]HQg2PDG)@f^tE8SJJ rjA-ʌD"Sq6İP1iҤ\a eɧ3fK&XVNO%\X,Yf d16 l@mQ  p>h#L8_a&vڅ_~---(O<Ggg%H83O~]:`i I2"eI#]HKˎAMSLߎڑLX ,P{/ȧgakqaX|9zqW?UgGyyJi$@%l\$Ty"/(g,٬˽Su:y><-.ٳ6ٳGaϞ=. #YnC\/ s='NtcΝ38ׯǺu\ס" ?rie<p8D& 0ŐRt*#:CܥRhYRض,Johh_"F a`ѢEx\28A1T\b1\r%0-ZW_ŦM Ƃ'b"ns#ͺl4q\RdVyKIf.]+䠩2 (.\|Z444}.T@t-Uŋ555X`k$%Wf5 # `8K`K|>< $%Kxp̯{{{qF_HkBa4i>l,Zuuuʝ6m/^L:pV[.̙3qgnK/$*9V| ,mnpP[ *"1CTg ĽJ]˸< $I˲~z̛7GA\yx'A7nf͚|+4io!㬳r??B}'`JI^$H$>x0^ 6d`[V'7p.LoHrq`&>z _~A2e =\y̙3wJ41qDL8 ,CX !PKTG.:dS:Þ bh}d >N*NFvz 0 yh.x#555F mۆ]vp}/mdYIC`H0 l$r&I.dLתT*7|>,+p*I #P0$ρSGlN:Zy#dƍhmm=R8BwEss98DҒ0{2T 8EqҹGT ~.Ú5k0rRJF- 87t0?Ta8ܯr?n ?SM,_ r v( ' +Z=+9/WpǏJ\#Xƣ \AW=CsQt" ! ,0 ^z)j_ UXv- :CPJG JK\\ XdOR|xb̘1I=@,YK,R#``ZRT+7u-PZFkT>rK.lٲV]y޽pF5k```@aW"ID6A2*;bԫzBG%c7pSƜ9spgbx 63 k׮u GTB!y%Ĭxy /ӧO/k޼y8묳? /2\p*e[l/#L,PO5~/ߨT )/ Do}K[f0… q7Ν;q7jIG{{;L&d`b***0~Z[[ok;P ķJPP(THkZL8?015\+Dxb?1$ 466?ħ~.n(N:$3رc} /d./&TRKUާ4ntpO7W^y;Z'`ݺuزer%-juVlݺ< N>d\x8s<ٳ6lp!ꐢ{S`PQr>A{7t vڅ+WbΝ#z_߲,l߾~!&N%K`J?ӰRy /, X~="N:$~ڹxGGD`*++]k:^* /ڐS A#(>ڵkQUUiӦh_WDkye힭[O>A&kVPnfϏ{ 2qrM|FZTUU4N.&LbӦMضmf͚{eOfXB9XtbYbp+] VZEqT K~D!ӊ%/vw^\ BzW6yF|xG۫YI_)!Q#B~" )yCe;?x뭷uVκ*hs5K[BKϫI %dؗ}D9O  9R@6~;ϰ.PybhLE%d{(d:(qqA׻/֭[6WNdKsbWI'}$a=~/7:sA|{R!˹*ud)%*Hq5>w2j{mt:]0# C{eN+(/lݺW>ar% պb@i*Vd PLЎ|O@n #8k,zoQ HANz) sM"LF|^S w^:\]]kFy;JkZHUt+!tJ$%Z}etH(T)} p8l FQYYSzwF{{voUI\?!ljF,k?dyigEҥK2J]v)?/jxnuH]sBQm/FhH "6| =^Vx)Ơ+m,׫m^Ƣ|O$5 .&۷Ae.G H(LTyr?`Iʯ:\W K6t_N : t۬>WJ.midץ{}Ttb_A#+uAOm6 #I'PM?0sLu`^*{@%Ug#Q&/CC&*+_wPQQ@ Ϝ9?<;3<@RZj?ƯUFGj_˨Tс|~DU_ ::tIp=g?ǏGkkkZEnRG\6*O'%B1P.*ЎJx,gppE뀯%$.4_B!_tMêtU^.߱(:c빟28x_)Uɳl̶mۊʥ9韻tM Ώϧ9ya$z_wI8Z{ou~6mx s,G$!Zr?|?q&B(G+喗D@P.ת?p78> G$qD.2~ $,EzIR/L¼Z/ؼyg/ꪫ0c grB8?TyIrPypKwn^|?tJx^'ޡOGi !\Wsq/ Ԃ'g1Nx="*@e* |M~a \RνT"29[j3IH$i15sI ~0nJ%ۋ;vo{7nÙtF^v0dH^\h?i,]ڨ@/KJOgy?l`̘1xFp)T_Ah1%^H8 bٲesz6/qQVVs@tDՀLc8ZS7' YCq#(Hhu\.Oq)z70n8c֭\ezJOxk)|I_Ob~h~4|XLlS5ib„ xO>x[ > eD(m>dJuײsL?O0 SN4qylh[x㍸{1sơ_nڴ~%!ϯ 08_3gS|8pk蠿md?y̚5 _|1~ L&pBtttpQ]>xEWIS*ax+++1w\W"N#N#LF K,SO=ݻw:*'<0H d(C=T;oJ{ؿ?/_={-H}^WWx<~DKnϰ0A) ~%HPFqֆ۶m 7܀;}``vv+/,\sq Z<*~d%[L l:?Wڵ wuoNbٲe8x`M=`y*zle K\a T@hjjƍ_Jk wECCèw"۱gȗP*:~\-a^r0hnn7ߌƢ:a9)GTqq]===뮻aÆpI'+Sl̤* {UTJ` tnd2Fpw;EYW_(i6Ћ"|W T ~?w\5e/Hrp嘦 sIDATUP?(5{R[~;\i*ωsw/a6qx15׾qAUUn喂t(,2  % 0Z0bvٲe~`YV6Əz߯-Ul,i0, ҈(,+?\DȆUH"^~RJ{wy'V\{~o$o>Z 'Nq)`̙(//]^|GhllĦM{nהUpu*kɒ%x'TKO=s\2 Y^.+?F akń U:hgx饗`&LN8K.Ŵiӊy?ӧ;d@Y;d<HHvuuz\~#xLW呒²,tM!, ?=!dr %y~m۞eGW\Z|wL%4`]RD0hkE:.u )`?DWq}ns&Tfl߾݅>C&q>Et?Uzeaո袋_AH`ad`#60 o߾n" pm"X)]g<ᅬ믿?я?JER)e?$ʐjсG}UG}뮻s:;; $nJr9D @ggg7lMi @6 ٝ,^wM|@r?:3Gy}}} @K9Ar[@՗|>g}ߏk[lq|Xg.P(dvٳrf`SG/g̘Ji"(ڷs"r/r8|DO<֮]K/֭aХ28|Tj.r9}F"ĐdO l[Rsl6kɨ :; T*?mnnƊ+\ȑ:_ޟ [T zlj--l @w gVGGGlvarf{t&۷xrmȎu?\ON#QܻmѵCI`gRJ6*~'lf$@ h Poo5 b.+TyTnhy:x\2)nS #-W|>;lCqXdP6p۷WUUUh4|Љ3jק6A-rps @^oݺ*@z rU|ɤ+o8>3Z-ue0`GC*Lp s`.6= 444abM˲ׇT*R;~-Ձ+ _vX5GZJW!¯u"d2p8ݻwrn `3 C@ v6`Oh[[[`ҤI'N @yy9 SO#p0}UHD|G ".!KGJEiN[m Ғd2 ؜C烰%@C946l0;`$q! X̱@::*$ 0ls I%TΉJE\:H~Ty&Wi:D 5c߾}T*[4e;lW` Z@ ہh6 ۷Ϝ0aD\6 @\!M%Ҥǃ@yq7 &9+כgTQQx˲N$Auu5PWWg %A[A)gYyr-[ [Ly@ZC! jll,۳gO`ܹZFyy9bXniK$r/m~0l`O?9s+UMg՘CDHRfSSߏ* aL*Ѯ:,ZZZ]vYt: ;͐hm @~jO!# n`$uHGGG`'OƘ1cP^^]%  7L&188~:t&aH`l$肍;nPWyL c01iC$aa\^VT`kS};l7&=u~79? ? T@? P`ǗO0vܠ@6!aW#A;A> 냍VoC4ߟB5 qtpsm;@"'҂J#yr*$FKAHiҸ ?.QIENDB`icons/hicolor/16x16/000077500000000000000000000000001257164213300144215ustar00rootroot00000000000000icons/hicolor/16x16/apps/000077500000000000000000000000001257164213300153645ustar00rootroot00000000000000icons/hicolor/16x16/apps/ceni.png000066400000000000000000000015641257164213300170160ustar00rootroot00000000000000PNG  IHDRasBIT|d pHYs:tEXtSoftwarewww.inkscape.org<IDAT8e=HsgTƣ9DZK /8Fdqj']jvp][q+.qw0 .ҀdINrvBYP*I@aa躎]uD"A"Pq׿RI)JRELdnn˲<>>l6z=4M4M8'4?痳,lT*.t]zDQDRnENGtdߴmr:;;;e1ضRFA2Vq, qRR,b4t+l< <)}0 !Appp{F(( u|ߟ$u]6RϓL&q*  @)AضCia)aPiZDQ4a" \}Ra&@j4xy}E_vt˲~RJ}A`UH$d2cR(|~iTU666HRaq&GGG1qPEqi,--a6T*NNN&@x(D)Eb<aHVreY(0˲~]__hLNNd28ϟV1;;K`fffr U&'̚i.шFZV o-|Lf@IENDB`icons/hicolor/22x22/000077500000000000000000000000001257164213300144135ustar00rootroot00000000000000icons/hicolor/22x22/apps/000077500000000000000000000000001257164213300153565ustar00rootroot00000000000000icons/hicolor/22x22/apps/ceni.png000066400000000000000000000024731257164213300170100ustar00rootroot00000000000000PNG  IHDRĴl;sBIT|d pHYsaa0UtEXtSoftwarewww.inkscape.org<IDAT8kUW{Or?%b"AD36P"BFb,Ph:pR N @ "Eމi"xQ"*cޏ4`.Xl>{km];yi?;3h۶iibFiKPu]AӧO?-//LMMjwRI}vMTP(`&e$ B STV_VWWN8QBVV)˔elir.>$ C(" Czv^GVK?^8qMyض4MڵkOgtt$ ĩS, 󈢈 tu08- a8QJyccc\rFm q-|J%8FJI8C_1 (۶ Ð7opz3:tNQ1ivRL)%bpM>}4J)^xA`rrqVVVh۹J)$!IL)%e)jӧO!ٳgiRǏNy0TB٤^Ssdb~m6&IijE˲Fhƍ7z*NuT*000 177ǝ;wh6lllw=4M7u۶ @u"ϟѣGܽ{9͚&.]yN4?t0DRA qի"?7N .^YzCv]R;v YXX`aa\~~3e0q̙IǼoNTloocYt: bȇr@J^˗/'4QE&ahi?E*9F4vi+vѯW{>o߾Ufw700l+k ǺTkCIENDB`icons/hicolor/32x32/000077500000000000000000000000001257164213300144155ustar00rootroot00000000000000icons/hicolor/32x32/apps/000077500000000000000000000000001257164213300153605ustar00rootroot00000000000000icons/hicolor/32x32/apps/ceni.png000066400000000000000000000037551257164213300170160ustar00rootroot00000000000000PNG  IHDR szzsBIT|d pHYsvv}ՂtEXtSoftwarewww.inkscape.org<jIDATXKhTYUu*RS1ebC;D$= (t3+хE  3nGHG3QtGBhU޺Y{bt0}8ߕ/hUUA) i(ͽ^.+e_>_lٲEȲL\P(`ǒ뺘iTU$I"ȲyN4%Yr%a'''788@q]a0 B!\%c&K,P(D(Be"Dp]ReYeǡ\.cbbeYH&D"1 ,*,Iҏ˖-C4H~Mӄ@@+ž={BzaLIJ,*ea۶PǏضMSS?߾} ]oM<GuESvZZZZErUVL&I$8yxݻx(N<qfgg4 .uhQ,Q9ض8bѣݻM0 I4Mu],"Jo>zzzظq#=sR, T .rJ"-z;wDu>4446nș3ggŊXׯ!rEQ*I|p8mv100 eYTjIPU˲i,۶)\pu*yZdY,,f ~lkkc͚5 ,4MQr}%~|m C_cJhf.S*v(B9 ]m-|I6mč7mGUUѤOŔM?ٛ@$k޾}+j~gg'dYt]Guժ*}0hooҥKE$SSSBT*TʍK.]ϟ+W022B*"N8;b7m!N:ŝ;w"]%RT0M @6]L&b!ZZfggIqÇSz=\|>x8FQ*Lߤ/q]8eǎMPl6K6^edu0\;^WW,|L&3iƏ?:?7^Z ol{޿ϛ7ot:0k3|@pO.} *Cd ,BUŋo20  ZZz.IENDB`icons/hicolor/48x48/000077500000000000000000000000001257164213300144335ustar00rootroot00000000000000icons/hicolor/48x48/apps/000077500000000000000000000000001257164213300153765ustar00rootroot00000000000000icons/hicolor/48x48/apps/ceni.png000066400000000000000000000064521257164213300170310ustar00rootroot00000000000000PNG  IHDR00WsBIT|d pHYs11(RtEXtSoftwarewww.inkscape.org< IDAThklŖ=c;8`#C+AÇEY/ZaÆ}^˲h˲.a0@)W'[+'N1Mwll{?n%WFQP(YZZP((R"Bb_ub1xX,ad2Fzw5tq]wK*"Lb籴D.#)hx'8"L&I&JapN9}'}}}Z[[ O*0p]WO4JX,mv@EQ\E8,..R,q]W]=ϣP(0?? g^p?~7|pH$BX,)߭RB9e׮]yaYUUU$IjkkB.0:: 0MMM־0cV}}ՎԤR)l}P}uyUUUຮҚ,D"xH.t:CiYLFYqnn۶kl VWW{bxG.AiFZ꫹1MW_}Y۶f]}Rwy'i駟GQ,Fr9RKKKDQ?66G*;XZZ"ϫ"p tvvňD"xEǡX,N@SaXM7D<'m6Ħiw^5&ZZZؼy3L68<'N022¥^w]]Ur9" @ ayr555!, 0BL*211}ؼyk\b6r1iqub̨h L^VDT#ge{#۫gȉ/I76}0 qԮ6!H]\ץB)_HopB}mV`Y;vwfxx|իWs{QJ_^eHxذ%h9P}4@ _z5۶mc=<Aytvv/"9y8ړ2::Ϟ={hkkcΝR)}=LYo'lVI4Uci>vbzznX,VQ###<3JRkd9z(?0z" _j>' _ ?8333.$PtF˙LGyLNN۫mERR9 "7\n@_wuG}dIfggDm/433H*㏁{WR}}`9 qUT|>_Rxݴi֭SuBx ,_~9---lݺ50Fz{{o9plzK-2 w}7?|EEb1W䉋\믿_gxxݻwvueb18 H/ ~ᇩקiՁֈr >u' D4$ /p_mJXXX,+\.w~UUUk:6D`oR(gG})|Ǐsa:7|RkqJimۤi\e~~x5X j.6lpQCCfP(FzeYp)׹Mȶmu377GPO:uwUǬ@3k෭jCE Bd^ϟ¥:aY»>r ###g77p ն ֭[kjj2ɤ:>7wa !X\\$2==?44Tp]4.Ak_ہ z3?R|c_C, RS)P 3[+#,˚> yGb,`ֲ|=;/YvEI̲6k)I\MIENDB`icons/hicolor/64x64/000077500000000000000000000000001257164213300144275ustar00rootroot00000000000000icons/hicolor/64x64/apps/000077500000000000000000000000001257164213300153725ustar00rootroot00000000000000icons/hicolor/64x64/apps/ceni.png000066400000000000000000000113411257164213300170160ustar00rootroot00000000000000PNG  IHDR@@iqsBIT|d pHYsu85tEXtSoftwarewww.inkscape.org<^IDATx͛kՕtgaLl6qjjj[D۶mT*m$IL$HcZ-'ĹyjܨVi4T*C4|>OVSǎ u!naqOOIA. *FH#m&ˑft:M.#Ncfzu}?>JRhQXX,R*p]4IRR@k.]J6err}>}S1[#njjRsaܹ$0:XX}@u],YBGG@@0biB)hR>OZ H(J(X,H}$J)ǡZ+JZgg'== ,,#@[YlLM6uVV^t>t&UUA)8p4}@RB`YV0ԹR3N[z+k֬ Zh!7 >t -+d6T/җpɸI`75[ wJ%Іhy^\D;\Ŷm<ڵk7o^_9w|󟧷7,uV?~aV\I.kyv׮]h)mJ8o1qGQQ}!a*嚰-$޽{yGC M|[ߊN4 8ydL lTb\?BK4:Ç'\w mf:kZb7 H $ɉnߺS҇Wp!>= 0SVQ =  T)|Æ lܸ}^~I띢;h1W1S`@#De)X?oh"^}Uۇ8͛׾WpBׇi۶C"D@4ĔBwɊ+زe 6l>x݀ǃk&"k׮ND4rN-^\mGy$Ԩno^3jÇ9x | J%b޼y,_׳lٲлo&/_!kYV{D m//[ς .J)~344D\\p18-£>M7D\f׮]3yYM@ꄼtMs=T*cǎtWn͛qะ\] M R˗ٶmowXp ۶m̙3%u]v'NtkZq-0M3`CCOp{?,PIF(zɹ<g9dDښ@\:zvG)E\cXѣ-^$zoF}AhFTR R\eY.ܹ3p@:q"hAWo:)DIm! JBV eXeXJ)X;N^~` &gʕm 8<ΝkQ( 3DBGx{@(*RMXn][QWq_9I*:aS_5*gB@^o뀢EAGQpKp]q@tD u@*s L ZƵ ,{ettL&Y%Klٲ=#&]lDwp:Ygq W_m3Vǒ%K_AorG7qtbL;Ο}6n 7 #GߏeYu& dUYVlWx~ )F4[ۣAXh2$\pVe`&g#3k4Hϳjժ 7s̡l6hhJ"hٌ퐙[`tAc=۷ogҥ3r?殻 l[vv}mx˲駟f֭1BqE-L _B*H&ᩧüLMMl[rۮdxxG=o[ڥ_K$8ueQ.c_?#kI__k׮eɒ%$`ǙLs+V`ƍ|{{ Ni2o}:t]Hߗgڦţ/E/_w^]|ߏݎ׮$v.0"{ﬕ9b 6mԶ]3{B6l#ZR/~m,_"6*h<0pueشGOqil }v8ǏgϞ=,CCC9s&6vjPKL)s}6Js||\uOc4wׁb>28q 5 qT q?+J&Ml bzN"ٳ#M̎YzIVLLLP*IR(Q'Gd2I1~add}O'WW^Roz=HMLL/oS`I`)//uʕ$!&''F74D BPEfjF&!.Rj{ʕ`pp)^zcPD\.GWWt:qZ-F_tXEDɐ|ϟOgg'J}=… nP8hRFP #޲/6I˿Ǒ%qb\ץ\.BAS.]*+No{w&ޖ/G ti8ƻ4hF̻עKC'4$7B̡nVGD ুc1ĻzQz8Alp#Lzh|l/ E Jqc 2J|-S/Dȧ5a Gc4shvi\Q:)IENDB`lib/000077500000000000000000000000001257164213300116305ustar00rootroot00000000000000lib/Ceni/000077500000000000000000000000001257164213300125065ustar00rootroot00000000000000lib/Ceni/Backend.pm000066400000000000000000000335121257164213300143770ustar00rootroot00000000000000package Ceni::Backend; use strict; use warnings; use Carp; use Data::Dumper; use Expect; use Fcntl 'O_RDONLY'; use Tie::File; our $VERSION = '1'; sub new { my ($class, $opts) = @_; my $self = {}; while (my ($key, $value) = each %{$opts}) { $self->{$key} = $value; } bless($self, $class); return $self; } sub is_iface_wireless { my ($self, $iface) = (shift, shift); my $retval = 0; if (-d "/sys/class/net/$iface/phy80211") { $retval++; } elsif (-x "/sbin/iwgetid") { open my $iwgetid, '-|', "/sbin/iwgetid --protocol " . $iface or carp "W: could not execute iwgetid --protocol $iface: $!"; while (<$iwgetid>) { chomp; m/^$iface/ and $retval++; } close $iwgetid; } return $retval; } sub nic_info { my ($self) = (shift); my %i; my $udevinfo_cmd = (-x '/sbin/udevadm') ? '/sbin/udevadm info' : 'udevinfo'; $i{$_}{'sysfs'} = '/sys/class/net/' . $_ for map { s|.*/||; grep(!/^(lo|br|sit|tap|vmnet)\d*/, $_); } ; for my $if (sort keys %i) { my ($bus, $desc); open my $udevinfo, '-|', "$udevinfo_cmd -a -p " . $i{$if}{'sysfs'} or carp "E: could not execute $udevinfo_cmd -a -p " . $i{$if}{'sysfs'} . ": $!"; while (<$udevinfo>) { chomp; $self->debug($_); if (m/^\s+([A-Z]+({(.+)})?)=="([^"]+)"$/) { $3 ? $i{$if}{ lc $3 } ||= $4 : $i{$if}{ lc $1 } ||= $4; # ssb first KERNELS is useless to us, we want the second ($1 eq 'KERNELS')&&($4=~/^ssb/)&&(delete($i{$if}{'kernels'})); } } close $udevinfo; if ($i{$if}{'subsystems'}) { $bus = $i{$if}{'subsystems'}; $i{$if}{'connection_type'} = $self->is_iface_wireless($if) ? 'wireless' : 'ethernet'; } else { delete $i{$if}; next; } open $udevinfo, '-|', "$udevinfo_cmd -p " . $i{$if}{'sysfs'} or carp "E: could not execute $udevinfo_cmd -p " . $i{$if}{'sysfs'} . ": $!"; while (<$udevinfo>) { chomp; $self->debug($_); s/^[A-Z]\:\s//; if (m/^([A-Z_]+)=(.+?)$/) { $i{$if}{ lc $1 } ||= $2; } } close $udevinfo; if (-e $i{$if}{'sysfs'}."/address") { open my $sysinfo, $i{$if}{'sysfs'}."/address" or carp "E: could not open ".$i{$if}{'sysfs'}."/address: $!"; my $address=<$sysinfo>; close($sysinfo); chomp($address); $i{$if}{'address'}=$address; } if ($bus eq 'pci' or $bus eq 'ssb') { $desc = `lspci -s $i{$if}{'kernels'} 2>/dev/null | head -n1`; $desc ||= "PCI device ".$i{$if}{'kernels'}; chomp($desc); $desc =~ s/^.+:\s+//; } elsif ($bus eq 'usb' or $bus eq 'pcmcia') { my ($manu, $prod) = @{ $i{$if} }{ 'id_vendor_from_database', 'id_model_from_database' }; if ($manu =~ m/^linux/i or $prod =~ m/^$manu/i) { $desc = $prod; } else { $desc = "$manu $prod"; } } elsif ($bus eq 'virtio') { $desc = "KVM Virtio network device"; } # FireWire IEEE 1394 Ethernet <- who cares? if ($desc) { $desc =~ s/(\s+)?(adapter|corporation|communications|connection|controller|ethernet|integrated|manufacturer|network|semiconductor|systems|technologies|technology|group|inc\.|ltd\.|co\.|\(.+\)),?//gi; $desc =~ s/\s+/\ /g; $desc =~ s/^\s+//; $desc =~ s/\s+$//; $i{$if}{'desc'} = $desc; } else { $i{$if}{'desc'} = "Unknown description"; } } $self->debug(\%i, "i"); %{ $self->{'_data'}->{'nicinfo'} } = %i; return 1; } sub get_iface_info { my ($self, $iface) = (shift, shift); if ($iface and $self->{'_data'}->{'nicinfo'}->{$iface}) { return $self->{'_data'}->{'nicinfo'}->{$iface}; } elsif ($self->{'_data'}->{'nicinfo'}) { return $self->{'_data'}->{'nicinfo'}; } return undef; } sub is_iface_valid { my ($self, $iface) = (shift, shift); if ($iface and $self->{'_data'}->{'nicinfo'}->{$iface}) { return 1; } return 0; } sub parse_eni { my $self = shift; $self->{'file'} ||= '/etc/network/interfaces'; tie(my @eni, 'Tie::File', $self->{'file'}, mode => O_RDONLY) or croak "E: failed to open " . $self->{'file'} . ": $!"; my %e; my $l = 0; while (defined $eni[$l]) { $self->debug("| $l " . $eni[$l]); if ($eni[$l] =~ m/^\s*#/) { ; } elsif ($eni[$l] =~ m/^(auto|allow-.+)\s+(.+)/) { $e{$_}{'class'} = $1 for (split /\s/, $2); } elsif ($eni[$l] =~ m/^iface\s+(.+)\s+inet\s+(.+)/) { my ($i, $m) = ($1, $2); $e{$i}{'method'} = $m; $self->debug("+ $l"); while (defined $eni[ ++$l ]) { $self->debug("> $l " . $eni[$l]); if ($eni[$l] =~ m/^#?\s*(iface|auto|allow-.+|mapping)/) { $self->debug("- $l"); $l-- and last; } elsif ($eni[$l] =~ m/^\s*#/) { push @{ $e{$i}{'comment'} }, $eni[$l]; } elsif ($eni[$l] =~ m/^\s*(pre-|post-)?(up|down)\s+(.+)/) { if ($1) { push @{ $e{$i}{ $1 . $2 } }, $3; } else { push @{ $e{$i}{$2} }, $3; } } elsif ($eni[$l] =~ m/^\s*([^\s]+)\s+(.+)/) { my ($k, $v) = ($1, $2); $k =~ s/_/-/g; $e{$i}{'stanza'}{$k} = $v; } } } } continue { $l++; } $self->debug(\%e, "e"); %{ $self->{'_data'}->{'eni'} } = %e; return 1; } sub get_iface_conf { my ($self, $iface) = (shift, shift); if ($iface and $self->{'_data'}->{'eni'}->{$iface}) { return $self->{'_data'}->{'eni'}->{$iface}; } elsif (not $iface) { return $self->{'_data'}->{'eni'}; } return undef; } sub is_iface_configured { my ($self, $iface) = (shift, shift); if ($self->get_iface_conf($iface) and $self->get_iface_conf($iface)->{'method'}) { return 1; } return 0; } sub set_iface_conf { my ($self, $iface, $conf) = (shift, shift, shift); $self->{'file'} ||= '/etc/network/interfaces'; tie(my @eni, 'Tie::File', $self->{'file'}) or carp "E: failed to open " . $self->{'file'} . ": $!"; my @block; if ($conf->{'class'} and $conf->{'class'} ne 'manual') { push @block, $conf->{'class'} . " $iface"; delete $conf->{'class'}; } if ($conf->{'method'}) { push @block, "iface $iface inet " . $conf->{'method'}; delete $conf->{'method'}; } if ($conf->{'stanza'}) { for my $k (sort keys %{ $conf->{'stanza'} }) { push @block, "\t$k " . $conf->{'stanza'}->{$k}; } delete $conf->{'stanza'}; } for my $p ('pre-up', 'up', 'post-up', 'pre-down', 'down', 'post-down') { if ($conf->{$p}) { for my $c (@{ $conf->{$p} }) { push @block, "\t$p $c"; } delete $conf->{$p}; } } if ($conf->{'comment'}) { for my $c (@{ $conf->{'comment'} }) { push @block, $c; } delete $conf->{'comment'}; } if (@block) { push @block, ''; } $self->debug(\@block, 'block'); my $l = 0; while (defined $eni[$l]) { $self->debug("| $l " . $eni[$l]); if ($eni[$l] =~ m/^\s*#/) { ; } elsif ($eni[$l] =~ m/^(auto|allow-.+).*\s+$iface/) { $eni[$l] =~ s/\s*$iface//; $self->debug("> $l " . $eni[$l]); if (not $eni[$l] =~ m/^(auto|allow-.+)\s+.+$/) { $self->debug("* $l"); splice @eni, $l--, 1; } } elsif ($eni[$l] =~ m/^iface\s+$iface\s+inet\s+(.+)$/) { $self->debug("* $l " . $eni[$l]); splice @eni, $l--, 1; while (defined $eni[ ++$l ]) { if ($eni[$l] =~ m/^#?\s*(iface|auto|allow-.+|mapping)/) { $self->debug("- $l"); $l-- and last; } else { $self->debug("* $l " . $eni[$l]); splice @eni, $l--, 1; } } if (@block) { $l++; for my $b (@block) { $self->debug("+ $l " . $b); splice @eni, $l++, 0, $b; } @block = (); } } } continue { $l++; } if (@eni and @block) { if ($eni[-1] =~ m/^\s*\S.*$/) { push @eni, ''; } push @eni, @block; } chmod 0640, $self->{'file'} or carp "E: failed to chmod " . $self->{'file'} . ": $!"; } sub rem_iface_conf { my ($self, $iface) = (shift, shift); $self->set_iface_conf($iface, {}); } sub ifupdown { my ($self, $iface, $action) = (shift, shift, shift); my ($ret, @cmd); if ($action eq 'down' and $self->wpa_action($iface, 'check') == 0) { $ret = $self->{'act'} ? $self->wpa_action($iface, 'down') : 0; } elsif ($self->{'_data'}->{'eni'}->{$iface}->{'method'}) { @cmd = ("/sbin/if" . $action, $iface, "-i", $self->{'file'}, "-v"); $self->{'act'} or push @cmd, "-n"; $ret = system(@cmd); } else { carp "$iface has no ifupdown method"; } if ($ret != 0) { if (($? & 127) == 2) { carp "W: $action $iface interupted"; } else { carp "W: $action $iface failed due to error"; } } else { $self->{'_data'}->{'ifupdown'}->{$iface} = $action; } sleep 1; return $ret; } sub ifup { my ($self, $iface) = (shift, shift); return $self->ifupdown($iface, 'up'); } sub ifdown { my ($self, $iface) = (shift, shift); return $self->ifupdown($iface, 'down'); } sub ifstate { my ($self, $iface) = (shift, shift); if ($iface and $self->{'_data'}->{'ifupdown'}->{$iface}) { return $self->{'_data'}->{'ifupdown'}->{$iface}; } return undef; } sub wireless_scan { my ($self, $iface) = (shift, shift); my ($wpacli, $ret, $cmd, %scan, @ver, $driver); my ($wpasup_pid_fh, $wpasup_pid); #return 1 unless $self->{'act'}; # Recent versions of wpa_supplicant support cycling through driver # wrappers until one succeeds, so try nl80211 first and fallback to # wext. @ver = split(/\./, `/sbin/wpa_supplicant -v | sed -n "s/^wpa_supplicant v//p"`); $driver = ($ver[0] == 0 and $ver[1] <= 6) ? 'wext' : 'nl80211,wext'; # Spawn an unconfigured wpa_supplicant process to prepare the # interface for scanning. Using the background (-B) option ensures # return value indicates if the interface was successfully setup # (or not). The use of system(), however, means we must also determine # the process id from a pid file. $cmd = "/sbin/wpa_supplicant -B -i $iface -D $driver " . "-P /run/wpa_supplicant.$iface.pid " . "-C /run/wpa_supplicant"; $ret = system($cmd); if ($ret != 0) { if (($? & 127) == 2) { carp "W: '$cmd' interupted\n"; } else { carp "W: '$cmd' failed due to error: $!"; } } # Grab the wpa_supplicant process id from pid file. We have to wait # for it to be created ... a bit sloppy. sleep 1 until -s "/run/wpa_supplicant.$iface.pid"; open $wpasup_pid_fh, '<', "/run/wpa_supplicant.$iface.pid" or carp "W: failed to open /run/wpa_supplicant.$iface.pid: $!"; $wpasup_pid = <$wpasup_pid_fh>; close $wpasup_pid_fh; chomp $wpasup_pid; # Start wpa_cli interactive session and communicate with it via Expect $wpacli = new Expect; $wpacli->raw_pty(1); $wpacli->log_stdout(0); $wpacli->log_file("/tmp/ceni.wpacli.log", "w") if $self->{'debug'}; $wpacli->spawn("/sbin/wpa_cli", "-i", $iface); # Trigger a scan and wait for scan result notification for up to 30s $wpacli->send("SCAN\n"); $wpacli->expect(30, -re => ".*CTRL-EVENT-SCAN-RESULTS"); # Gather scan data per BSS for my $bss (0..100) { my ($reply, %data); # Clear previously accumulated output $wpacli->clear_accum(); # Request BSS data $wpacli->send("BSS $bss\n"); $wpacli->expect(1, -re => "^>"); # Process reply $reply = $wpacli->exp_before(); last if length($reply) == 0; for (split "\n", $reply) { next unless m/=/; my ($key, undef) = split(/=/); my $val = substr($_, length($key) + 1); $data{$key} = $val; } $scan{sprintf "%02d", $bss} = \%data; } # Kill wpa_cli process $wpacli->hard_close(); # Kill wpa_supplicant process. kill 'TERM', $wpasup_pid; $self->debug(\%scan, 'scan'); %{ $self->{'_data'}->{'scan'} } = %scan; return 1; } sub get_scan_res { my ($self, $cell) = (shift, shift); if (defined $cell and $self->{'_data'}->{'scan'}->{$cell}) { return $self->{'_data'}->{'scan'}->{$cell}; } elsif ($self->{'_data'}->{'scan'}) { return $self->{'_data'}->{'scan'}; } return undef; } sub wpa_action { my ($self, $iface, $action) = (shift, shift, shift); my $ret; $ret = $self->{'act'} ? system("wpa_action $iface check") : 0; if ($ret != 0) { if (($? & 127) == 2) { carp "W: ifconfig $iface up interupted"; } } return $ret; } sub wpa_mappings { my ($self, $wpa_roam_cf) = (shift, shift); my @mappings; open my $wpa, '<', $wpa_roam_cf or carp "E: failed to read $wpa_roam_cf: $!"; while (<$wpa>) { chomp; if (m/^\s*id_str="?([^"\s]+)"?/) { push @mappings, $1; } } close $wpa; return @mappings; } sub prep_wpa_roam { my ($self, $wpa_roam_ex, $wpa_roam_cf) = (shift, shift, shift); if (not -s $wpa_roam_ex) { croak "W: wpa-roam template not found: " . $wpa_roam_ex; } if (not -s $wpa_roam_cf) { tie(my @wpa, 'Tie::File', $wpa_roam_cf) or return 0; open my $example, '<', $wpa_roam_ex or return 0; while (<$example>) { chomp; s/^#\s*update_config=.*/update_config=1/; push @wpa, $_; } close $example; chmod 0640, $wpa_roam_cf or carp "E: failed to chmod $wpa_roam_cf: $!"; } return 1; } sub debug { my ($self, $data, $name) = (shift, shift, shift); return unless $self->{'debug'}; if (ref $data) { print STDERR "D: "; print STDERR Data::Dumper->Dump([$data], ["*$name"]); } else { print STDERR "D: $data\n"; } } 1; __END__ =head1 NAME Ceni::Backend - Perl extension for Ceni =head1 SYNOPSIS use Ceni::Backend; my $ceni = Ceni::Backend->new( \%options ); =head1 DESCRIPTION This is the backend for Ceni(8), it is not provided for use by scripts not provided by the ceni package, therefore remains undocuemnted. =head1 AUTHOR Kel Modderman, Ekel@otaku42.deE =head1 COPYRIGHT AND LICENSE Copyright (C) 2007 - 2010 by Kel Modderman This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this package; if not, see On Debian GNU/Linux systems, the text of the GPL-2 license can be found in /usr/share/common-licenses/GPL-2. =cut t/000077500000000000000000000000001257164213300113255ustar00rootroot00000000000000t/Ceni-Backend.t000066400000000000000000000005741257164213300137230ustar00rootroot00000000000000# Before `make install' is performed this script should be runnable with # `make test'. After `make install' it should work as `perl Ceni-Backend.t' ######################### use Test::More tests => 2; BEGIN { use_ok('Ceni::Backend') }; use Ceni::Backend; my $bend = Ceni::Backend->new({ file => '/dev/null', }); ok(defined $bend, 'Ceni::Backend->new returned an object');