summaryrefslogtreecommitdiffstats
path: root/lib/network/connection/wireless.pm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/network/connection/wireless.pm')
-rw-r--r--lib/network/connection/wireless.pm744
1 files changed, 744 insertions, 0 deletions
diff --git a/lib/network/connection/wireless.pm b/lib/network/connection/wireless.pm
new file mode 100644
index 0000000..df1a210
--- /dev/null
+++ b/lib/network/connection/wireless.pm
@@ -0,0 +1,744 @@
+package network::connection::wireless;
+
+use strict;
+use common;
+
+use base qw(network::connection::ethernet);
+
+use strict;
+use common;
+
+sub get_type_name() { N("Wireless") }
+sub get_type_icon() { 'wireless-24.png' }
+sub get_devices {
+ my ($_class, %options) = @_;
+ require detect_devices;
+ my @devices = detect_devices::probe_category('network/wireless');
+ my @wireless = grep { detect_devices::is_wireless_interface($_) } detect_devices::get_lan_interfaces();
+ my @all_devices = (@devices, network::connection::ethernet::get_unlisted_devices(\@wireless, \@devices));
+ foreach (@all_devices) {
+ my $interface = network::connection::ethernet::device_to_interface($_) or next;
+ my $driver = network::connection::ethernet::interface_to_driver($interface) or next;
+ $_->{driver} = $driver if $driver;
+ }
+ @all_devices,
+ if_(!$options{automatic_only}, {
+ driver => 'ndiswrapper',
+ description => N("Use a Windows driver (with ndiswrapper)"),
+ });
+}
+
+sub handles_ifcfg {
+ my ($_class, $ifcfg) = @_;
+ require detect_devices;
+ detect_devices::is_wireless_interface($ifcfg->{DEVICE}) || exists $ifcfg->{WIRELESS_MODE};
+}
+
+sub get_metric { 35 }
+
+#- class attributes:
+#- network: ID of the selected network
+
+our %wireless_enc_modes = (
+ none => N_("None"),
+ open => N_("Open WEP"),
+ restricted => N_("Restricted WEP"),
+ 'wpa-psk' => N_("WPA Pre-Shared Key"),
+);
+
+my @thirdparty_settings = (
+ {
+ name => 'zd1201',
+ description => 'ZyDAS ZD1201',
+ url => 'http://linux-lc100020.sourceforge.net/',
+ firmware => {
+ test_file => 'zd1201*.fw',
+ },
+ },
+
+ (map {
+ {
+ name => "ipw${_}",
+ description => "Intel(R) PRO/Wireless ${_}",
+ url => "http://ipw${_}.sourceforge.net/",
+ firmware => {
+ url => "http://ipw${_}.sourceforge.net/firmware.php",
+ test_file => "ipw$_-*.fw",
+ },
+ };
+ } (2100, 2200)),
+
+ {
+ name => "ipw3945",
+ description => "Intel(R) PRO/Wireless 3945",
+ url => "http://ipw3945.sourceforge.net/",
+ firmware => {
+ package => "ipw3945-ucode",
+ test_file => "ipw3945.ucode",
+ },
+ tools => {
+ package => "ipw3945d",
+ test_file => '/usr/sbin/ipw3945d',
+ },
+ },
+
+ {
+ name => 'prism54',
+ description => 'Prism GT / Prism Duette / Prism Indigo Chipsets',
+ url => 'http://prism54.org/',
+ firmware => {
+ url => 'http://prism54.org/~mcgrof/firmware/',
+ test_file => "isl38*",
+ },
+ },
+
+ {
+ name => 'atmel',
+ matching => [ qw(at76_usb atmel_cs atmel_pci) ],
+ description => 'Atmel at76c50x cards',
+ url => 'http://thekelleys.org.uk/atmel/',
+ firmware => {
+ test_file => 'atmel_at76c50*',
+ },
+ links => 'http://at76c503a.berlios.de/',
+ },
+
+ {
+ name => 'madwifi',
+ matching => 'ath_pci',
+ description => 'Multiband Atheros Driver for WiFi',
+ url => 'http://madwifi.org/',
+ kernel_module => 1,
+ tools => {
+ optional => 1,
+ test_file => '/usr/bin/athstats',
+ },
+ },
+
+ {
+ name => 'prism2',
+ matching => qr/^prism2_/,
+ description => 'Prism2 based cards',
+ tools => {
+ package => 'prism2-utils',
+ test_file => '/sbin/wlanctl-ng',
+ },
+ },
+
+ {
+ name => 'zd1211',
+ matching => 'zd1211rw',
+ description => 'ZD1211 chip',
+ firmware => {
+ url => 'http://sourceforge.net/projects/zd1211/',
+ test_file => 'zd1211/zd1211_*',
+ },
+ },
+
+ {
+ name => 'bcm43xx',
+ description => 'Broadcom bcm43xx wireless chips',
+ url => 'http://bcm43xx.berlios.de/',
+ firmware => {
+ test_file => 'bcm43xx_microcode*.fw',
+ extract => {
+ name => 'bcm43xx-fwcutter',
+ test_file => '/usr/bin/bcm43xx-fwcutter',
+ windows_source => 'bcmwl5.sys',
+ default_source => 'bcmwl5.sys',
+ run => sub {
+ my ($file) = @_;
+ run_program::rooted($::prefix, '/usr/bin/bcm43xx-fwcutter',
+ '-w', $network::thirdparty::firmware_directory, $file);
+ },
+ },
+ },
+ },
+
+ {
+ name => 'acx100',
+ matching => [ qw(acx-pci acx-usb) ],
+ description => 'ACX100/ACX111/TNETW1450',
+ firmware => {
+ url => 'http://acx100.sourceforge.net/wiki/Firmware',
+ test_file => 'tiacx1*',
+ no_package => 1,
+ },
+ },
+
+ {
+ name => 'ndiswrapper',
+ description => 'Wireless device using ndiswrapper (windows drivers)',
+ tools => {
+ test_file => '/usr/sbin/ndiswrapper',
+ },
+ firmware => {
+ user_install => sub {
+ my ($settings, $in) = @_;
+ require network::ndiswrapper;
+ $settings->{device} = network::ndiswrapper::select_device($in) or return;
+ $settings->{device}{driver} = $settings->{name};
+ network::ndiswrapper::setup_device($in, $settings->{device});
+ },
+ url => 'http://ndiswrapper.sourceforge.net/mediawiki/index.php/List',
+ explanations => N_("Firmware files are required for this device."),
+ no_package => 1,
+ },
+ },
+
+ {
+ name => 'rt61',
+ description => 'Ralink RT61 802.11abg WLAN',
+ firmware => {
+ url => 'http://rt2x00.serialmonkey.com/',
+ test_file => 'rt2661.bin',
+ },
+ },
+
+ {
+ name => 'rt73',
+ description => 'Ralink RT73 802.11abg WLAN',
+ firmware => {
+ url => 'http://rt2x00.serialmonkey.com/',
+ test_file => 'rt73.bin',
+ },
+ },
+
+);
+
+sub get_packages { 'wireless-tools' }
+
+sub get_thirdparty_settings() {
+ \@thirdparty_settings;
+}
+
+sub setup_thirdparty {
+ my ($self, $in) = @_;
+ require network::rfswitch;
+ network::rfswitch::configure();
+ if ($self->get_driver eq 'ndiswrapper') {
+ require network::ndiswrapper;
+ my @devices = map { network::ndiswrapper::present_devices($_) } network::ndiswrapper::installed_drivers();
+ return {} if member($self->{device}, @devices) && network::ndiswrapper::find_interface($self->{device});
+ }
+ my $thirdparty = $self->SUPER::setup_thirdparty($in);
+ my $driver = $self->get_driver;
+ if ($self->{thirdparty} && $driver eq 'ipw3945' && !$self->rf_killed && !$self->SUPER::check_device) {
+ log::explanations("Reloading module $driver");
+ eval { modules::unload($driver) };
+ eval { modules::load($driver) };
+ }
+ $thirdparty;
+}
+
+sub rf_killed {
+ my ($self) = @_;
+ if ($self->{device}{sysfs_device}) {
+ my $rf_kill_path = $self->{device}{sysfs_device} . "/rf_kill";
+ if (-e $rf_kill_path) {
+ my $rf_kill = chomp_(cat_($rf_kill_path));
+ #- for ipw drivers, 0 means no RF kill switch
+ return $rf_kill != 0;
+ }
+ }
+ undef;
+}
+
+sub check_device {
+ my ($self) = @_;
+ if ($self->rf_killed) {
+ $self->{device}{error} = N("Your wireless card is disabled, please enable the wireless switch (RF kill switch) first.");
+ return 0;
+ }
+ return $self->SUPER::check_device;
+}
+
+sub load_interface_settings {
+ my ($self) = @_;
+ $self->SUPER::load_interface_settings;
+}
+
+sub get_networks {
+ my ($self) = @_;
+ require network::monitor;
+ ($self->{networks}, $self->{control}{roaming}) = network::monitor::list_wireless(undef, $self->get_interface);
+ $self->{networks};
+}
+
+sub guess_network {
+ my ($_self) = @_;
+ #- FIXME: try to find the AP matching $self->{ifcfg}{WIRELESS_ESSID};
+}
+
+sub get_network_ifcfg {
+ my ($ssid) = @_;
+ require network::network;
+ my $file = $::prefix . $network::network::wireless_d . '/' . $ssid;
+ -f $file && { getVarsFromSh($file) };
+}
+
+sub guess_network_access_settings {
+ my ($self) = @_;
+
+ my $ifcfg;
+ my $network = $self->get_selected_network;
+ $ifcfg = $network ?
+ get_network_ifcfg($network->{ap}) || get_network_ifcfg($network->{essid}) :
+ $self->{ifcfg};
+ $ifcfg ||= {};
+
+ $self->{access}{network}{essid} = $network && $network->{essid} || $ifcfg->{WIRELESS_ESSID} || !$network && "any";
+ ($self->{access}{network}{key}, my $restricted) = get_wep_key_from_iwconfig($ifcfg->{WIRELESS_ENC_KEY});
+ $self->{access}{network}{encryption} =
+ $network && $network->{flags} =~ /wpa/i ?
+ 'wpa-psk' :
+ $network && $network->{flags} =~ /wep/i || $self->{access}{network}{key} ?
+ ($restricted ? 'restricted' : 'open') :
+ 'none';
+
+ undef $self->{ifcfg}{WIRELESS_IWPRIV} if is_old_rt2x00($self->get_driver) && $self->{ifcfg}{WIRELESS_IWPRIV} =~ /WPAPSK/;
+
+ $self->{control}{roaming} = exists $self->{ifcfg}{WIRELESS_WPA_DRIVER} && !is_old_rt2x00($self->get_driver);
+
+ $self->{access}{network}{mode} =
+ $network && $network->{mode} ||
+ $ifcfg->{WIRELESS_MODE} ||
+ 'Managed';
+}
+
+sub get_network_access_settings_label { N("Wireless settings") }
+
+sub get_network_access_settings {
+ my ($self) = @_;
+ [
+ { label => N("Operating Mode"), val => \$self->{access}{network}{mode},
+ list => [ N_("Ad-hoc"), N_("Managed"), N_("Master"), N_("Repeater"), N_("Secondary"), N_("Auto") ],
+ format => \&translate,
+ },
+ { label => N("Network name (ESSID)"), val => \$self->{access}{network}{essid},
+ disabled => sub { my $network = $self->get_selected_network; $network && $network->{essid} } },
+ { label => N("Encryption mode"), val => \$self->{access}{network}{encryption}, list => [ keys %wireless_enc_modes ],
+ sort => 1, format => sub { translate($wireless_enc_modes{$_[0]}) } },
+ { label => N("Encryption key"), val => \$self->{access}{network}{key},
+ disabled => sub { $self->{access}{network}{encryption} eq 'none' } },
+ { label => N("Network ID"), val => \$self->{ifcfg}{WIRELESS_NWID}, advanced => 1 },
+ { label => N("Operating frequency"), val => \$self->{ifcfg}{WIRELESS_FREQ}, advanced => 1 },
+ { label => N("Sensitivity threshold"), val => \$self->{ifcfg}{WIRELESS_SENS}, advanced => 1 },
+ { label => N("Bitrate (in b/s)"), val => \$self->{ifcfg}{WIRELESS_RATE}, advanced => 1 },
+ { label => N("RTS/CTS"), val => \$self->{ifcfg}{WIRELESS_RTS}, advanced => 1,
+ help => N("RTS/CTS adds a handshake before each packet transmission to make sure that the
+channel is clear. This adds overhead, but increase performance in case of hidden
+nodes or large number of active nodes. This parameter sets the size of the
+smallest packet for which the node sends RTS, a value equal to the maximum
+packet size disable the scheme. You may also set this parameter to auto, fixed
+or off.")
+ },
+ { label => N("Fragmentation"), val => \$self->{ifcfg}{WIRELESS_FRAG}, advanced => 1 },
+ { label => N("iwconfig command extra arguments"), val => \$self->{ifcfg}{WIRELESS_IWCONFIG}, advanced => 1,
+ help => N("Here, one can configure some extra wireless parameters such as:
+ap, channel, commit, enc, power, retry, sens, txpower (nick is already set as the hostname).
+
+See iwconfig(8) man page for further information."),
+ },
+ { label =>
+ #-PO: split the "xyz command extra argument" translated string into two lines if it's bigger than the english one
+ N("iwspy command extra arguments"), val => \$self->{ifcfg}{WIRELESS_IWSPY}, advanced => 1,
+ help => N("iwspy is used to set a list of addresses in a wireless network
+interface and to read back quality of link information for each of those.
+
+This information is the same as the one available in /proc/net/wireless :
+quality of the link, signal strength and noise level.
+
+See iwpspy(8) man page for further information."),
+ },
+ { label => N("iwpriv command extra arguments"), val => \$self->{ifcfg}{WIRELESS_IWPRIV}, advanced => 1,
+ disabled => sub { $self->need_rt2x00_iwpriv },
+ help => N("iwpriv enable to set up optionals (private) parameters of a wireless network
+interface.
+
+iwpriv deals with parameters and setting specific to each driver (as opposed to
+iwconfig which deals with generic ones).
+
+In theory, the documentation of each device driver should indicate how to use
+those interface specific commands and their effect.
+
+See iwpriv(8) man page for further information."),
+ },
+ ];
+}
+
+sub check_network_access_settings {
+ my ($self) = @_;
+
+ if ($self->{access}{network}{encryption} ne 'none' && !$self->{access}{network}{key}) {
+ $self->{network_access}{error}{message} = N("An encryption key is required.");
+ $self->{network_access}{error}{field} = \$self->{access}{network}{key};
+ return 0;
+ }
+
+ if ($self->{ifcfg}{WIRELESS_FREQ} && $self->{ifcfg}{WIRELESS_FREQ} !~ /[0-9.]*[kGM]/) {
+ $self->{network_access}{error}{message} = N("Freq should have the suffix k, M or G (for example, \"2.46G\" for 2.46 GHz frequency), or add enough '0' (zeroes).");
+ $self->{network_access}{error}{field} = \$self->{ifcfg}{WIRELESS_FREQ};
+ return 0;
+ }
+
+ if ($self->{ifcfg}{WIRELESS_RATE} && $self->{ifcfg}{WIRELESS_RATE} !~ /[0-9.]*[kGM]/) {
+ $self->{network_access}{error}{message} = N("Rate should have the suffix k, M or G (for example, \"11M\" for 11M), or add enough '0' (zeroes).");
+ $self->{network_access}{error}{field} = \$self->{ifcfg}{WIRELESS_RATE};
+ return 0;
+ }
+
+ return 1;
+}
+
+sub get_control_settings {
+ my ($self) = @_;
+ [
+ @{$self->SUPER::get_control_settings},
+ { text => N("Allow access point roaming"), val => \$self->{control}{roaming}, type => "bool",
+ disabled => sub { is_wpa_supplicant_blacklisted($self->get_driver) } },
+ ];
+}
+
+sub need_wpa_supplicant {
+ my ($self) = @_;
+ ($self->{control}{roaming} || $self->{access}{network}{encryption} eq 'wpa-psk') && !is_old_rt2x00($self->get_driver);
+}
+
+sub install_packages {
+ my ($self, $in) = @_;
+ if ($self->need_wpa_supplicant) {
+ $in->do_pkgs->ensure_is_installed('wpa_supplicant', '/usr/sbin/wpa_supplicant') or return;
+ }
+ $self->SUPER::install_packages($in);
+}
+
+
+sub build_ifcfg_settings {
+ my ($self) = @_;
+ my $settings = {
+ WIRELESS_MODE => $self->{access}{network}{mode},
+ if_($self->need_wpa_supplicant,
+ WIRELESS_WPA_DRIVER => wpa_supplicant_get_driver($self->get_driver),
+ MII_NOT_SUPPORTED => 'no',
+ ),
+ WIRELESS_ESSID => $self->{access}{network}{essid},
+ if_($self->{access}{network}{encryption} ne 'none',
+ WIRELESS_ENC_KEY => convert_wep_key_for_iwconfig($self->{access}{network}{key}, $self->{access}{network}{encryption} eq 'restricted')),
+ if_($self->need_rt2x00_iwpriv,
+ #- use iwpriv for WPA with rt2400/rt2500 drivers, they don't plan to support wpa_supplicant
+ WIRELESS_IWPRIV => qq(set AuthMode=WPAPSK
+set EncrypType=TKIP
+set SSID=$self->{access}{network}{essid}
+set WPAPSK="$self->{access}{network}{key}"
+set TxRate=0)),
+ (map { $_ => $self->{ifcfg}{$_} }
+ qw(WIRELESS_NWID WIRELESS_FREQ WIRELESS_SENS WIRELESS_RATE WIRELESS_RTS WIRELESS_FRAG WIRELESS_IWCONFIG WIRELESS_IWSPY), if_(!$self->need_rt2x00_iwpriv, 'WIRELESS_IWPRIV')),
+ };
+ $self->SUPER::build_ifcfg_settings($settings);
+}
+
+sub write_settings {
+ my ($self, $o_net, $o_modules_conf) = @_;
+
+ wpa_supplicant_add_network($self->{access}{network}{essid}, $self->{access}{network}{encryption}, $self->{access}{network}{key}, $self->{access}{network}{mode}) if $self->need_wpa_supplicant;
+
+ wlan_ng_configure($self->{access}{network}{essid}, $self->{access}{network}{key}, $self->get_interface, $self->get_driver) if $self->{thirdparty}{name} eq 'prism2';
+
+ my $network = $self->get_selected_network;
+ network::network::write_wireless_conf($_, $self->build_ifcfg_settings) foreach
+ grep { $_ } ($network ? $network->{ap} : ()), $self->{access}{network}{essid};
+
+ $self->SUPER::write_settings($o_net, $o_modules_conf);
+}
+
+sub network_is_configured {
+ my ($self, $network) = @_;
+ if ($self->{control}{roaming}) {
+ return defined $network->{id};
+ } else {
+ my $wireless_ifcfg = get_network_ifcfg($network->{ap}) || defined $network->{essid} && get_network_ifcfg($network->{essid});
+ return $wireless_ifcfg;
+ }
+}
+
+sub selected_network_is_configured {
+ my ($self) = @_;
+
+ my $network = $self->get_selected_network or return;
+ $self->network_is_configured($network);
+}
+
+sub prepare_connection {
+ my ($self) = @_;
+ if ($self->{control}{roaming}) {
+ #- this should be handled by the monitoring daemon instead
+ run_program::run('/usr/sbin/wpa_cli', 'reconfigure');
+ sleep 2;
+ }
+}
+
+sub connect {
+ my ($self, $_in, $net) = @_;
+
+ $self->SUPER::connect;
+
+ if ($self->{control}{roaming}) {
+ my $network = $self->get_selected_network;
+ if ($network && defined $network->{id}) {
+ eval { $net->{monitor}->select_network($network->{id}) };
+ return !$@;
+ }
+ }
+}
+
+sub get_status_message {
+ my ($self, $status) = @_;
+ my $interface = $self->get_interface;
+ my ($current_essid, $current_ap) = get_access_point($interface);
+ my $network = $current_essid || $current_ap && "[$current_ap]";
+ {
+ link_up => N("Associated to wireless network \"%s\" on interface %s", $network, $interface),
+ link_down => N("Lost association to wireless network on interface %s", $interface),
+ }->{$status} || $self->SUPER::get_status_message($status);
+}
+
+
+
+my $wpa_supplicant_conf = "/etc/wpa_supplicant.conf";
+
+sub get_access_point {
+ my ($intf) = @_;
+ (chomp_(`/sbin/iwgetid -r $intf 2>/dev/null`), lc(chomp_(`/sbin/iwgetid -r -a $intf 2>/dev/null`)));
+}
+
+sub is_old_rt2x00 {
+ my ($module) = @_;
+ member($module, qw(rt2400 rt2500 rt2570 rt61 rt73));
+}
+
+sub is_wpa_supplicant_blacklisted {
+ my ($module) = @_;
+ is_old_rt2x00($module);
+}
+
+sub need_rt2x00_iwpriv {
+ my ($self) = @_;
+ is_old_rt2x00($self->get_driver) && $self->{access}{network}{encryption} eq 'wpa-psk';
+}
+
+sub get_hex_key {
+ my ($key) = @_;
+ if ($key =~ /^([[:xdigit:]]{4}[\:-]?)+[[:xdigit:]]{2,}$/) {
+ $key =~ s/[\:-]//g;
+ return lc($key);
+ }
+}
+
+sub convert_wep_key_for_iwconfig {
+ #- 5 or 13 characters, consider the key as ASCII and prepend "s:"
+ #- else consider the key as hexadecimal, do not strip dashes
+ #- always quote the key as string
+ my ($real_key, $restricted) = @_;
+ my $key = get_hex_key($real_key) || "s:$real_key";
+ $restricted ? "restricted $key" : "open $key";
+}
+
+sub convert_wep_key_for_wpa_supplicant {
+ my ($key) = @_;
+ get_hex_key($key) || qq("$key");
+}
+
+sub get_wep_key_from_iwconfig {
+ #- strip "s:" if the key is 5 or 13 characters (ASCII)
+ #- else the key as hexadecimal, do not modify
+ my ($key) = @_;
+ my ($mode, $real_key) = $key =~ /^(?:(open|restricted)\s+)?(.*)$/;
+ $real_key =~ s/^s://;
+ ($real_key, $mode eq 'restricted');
+}
+
+sub convert_key_for_wpa_supplicant {
+ my ($key) = @_;
+ length($key) == 64 && get_hex_key($key) || qq("$key");
+}
+
+#- FIXME: to be improved (quotes, comments) and moved in common files
+sub wlan_ng_update_vars {
+ my ($file, $vars) = @_;
+ substInFile {
+ while (my ($key, $value) = each(%$vars)) {
+ s/^#?\Q$key\E=(?:"[^#]*"|[^#\s]*)(\s*#.*)?/$key=$value$1/ and delete $vars->{$key};
+ }
+ $_ .= join('', map { "$_=$vars->{$_}\n" } keys %$vars) if eof;
+ } $file;
+}
+
+sub wlan_ng_configure {
+ my ($essid, $key, $device, $module) = @_;
+ my $wlan_conf_file = "$::prefix/etc/wlan/wlan.conf";
+ my @wlan_devices = split(/ /, (cat_($wlan_conf_file) =~ /^WLAN_DEVICES="(.*)"/m)[0]);
+ push @wlan_devices, $device unless member($device, @wlan_devices);
+ #- enable device and make it use the choosen ESSID
+ wlan_ng_update_vars($wlan_conf_file,
+ {
+ WLAN_DEVICES => qq("@wlan_devices"),
+ "SSID_$device" => qq("$essid"),
+ "ENABLE_$device" => "y"
+ });
+
+ my $wlan_ssid_file = "$::prefix/etc/wlan/wlancfg-$essid";
+ #- copy default settings for this ESSID if config file does not exist
+ -f $wlan_ssid_file or cp_f("$::prefix/etc/wlan/wlancfg-DEFAULT", $wlan_ssid_file);
+
+ #- enable/disable encryption
+ wlan_ng_update_vars($wlan_ssid_file,
+ {
+ (map { $_ => $key ? "true" : "false" } qw(lnxreq_hostWEPEncrypt lnxreq_hostWEPDecrypt dot11PrivacyInvoked dot11ExcludeUnencrypted)),
+ AuthType => $key ? qq("sharedkey") : qq("opensystem"),
+ if_($key,
+ dot11WEPDefaultKeyID => 0,
+ dot11WEPDefaultKey0 => qq("$key")
+ )
+ });
+ #- hide settings for non-root users
+ chmod 0600, $wlan_conf_file;
+ chmod 0600, $wlan_ssid_file;
+
+ #- apply settings on wlan interface
+ require services;
+ services::restart($module eq 'prism2_cs' ? 'pcmcia' : 'wlan');
+}
+
+sub wpa_supplicant_get_driver {
+ my ($module) = @_;
+ $module =~ /^hostap_/ ? "hostap" :
+ $module eq "prism54" ? "prism54" :
+ $module =~ /^ath_/ ? "madwifi" :
+ $module =~ /^at76c50|atmel_/ ? "atmel" :
+ "wext";
+}
+
+sub wpa_supplicant_add_network {
+ my ($essid, $enc_mode, $key, $mode) = @_;
+ my $conf = wpa_supplicant_read_conf();
+ my $network = {
+ ssid => qq("$essid"),
+ scan_ssid => 1,
+ };
+
+ if ($enc_mode eq 'wpa-psk') {
+ $network->{psk} = convert_key_for_wpa_supplicant($key);
+ } else {
+ $network->{key_mgmt} = 'NONE';
+ $network->{mode} = to_bool($mode eq 'Ad-Hoc');
+ if (member($enc_mode, qw(open restricted))) {
+ put_in_hash($network, {
+ wep_key0 => convert_wep_key_for_wpa_supplicant($key),
+ wep_tx_keyidx => 0,
+ auth_alg => $enc_mode eq 'restricted' ? 'SHARED' : 'OPEN',
+ });
+ }
+ }
+
+ @$conf = difference2($conf, [ wpa_supplicant_find_similar($conf, $network) ]);
+ push @$conf, $network;
+ wpa_supplicant_write_conf($conf);
+}
+
+sub wpa_supplicant_find_similar {
+ my ($conf, $network) = @_;
+ grep {
+ my $current = $_;
+ any { exists $network->{$_} && $network->{$_} eq $current->{$_} } qw(ssid bssid);
+ } @$conf;
+}
+
+sub wpa_supplicant_read_conf() {
+ my @conf;
+ my $network;
+ foreach (cat_($::prefix . $wpa_supplicant_conf)) {
+ if ($network) {
+ #- in a "network = {}" block
+ if (/^\s*(\w+)=(.*?)(?:\s*#.*)?$/) {
+ $network->{$1} = $2;
+ } elsif (/^\}/) {
+ #- end of network block
+ push @conf, $network;
+ undef $network;
+ }
+ } elsif (/^\s*network={/) {
+ #- beginning of a new network block
+ $network = {};
+ }
+ }
+ \@conf;
+}
+
+sub wpa_supplicant_write_conf {
+ my ($conf) = @_;
+ my $buf;
+ my @conf = @$conf;
+ my $network;
+ foreach (cat_($::prefix . $wpa_supplicant_conf)) {
+ if ($network) {
+ #- in a "network = {}" block
+ if (/^\s*(\w+)=(.*)$/) {
+ push @{$network->{entries}}, { key => $1, value => $2 };
+ member($1, qw(ssid bssid)) and $network->{$1} = $2;
+ } elsif (/^\}/) {
+ #- end of network block, write it
+ $buf .= "network={$network->{comment}\n";
+
+ my $new_network = first(wpa_supplicant_find_similar(\@conf, $network));
+ foreach (@{$network->{entries}}) {
+ my $key = $_->{key};
+ if ($new_network) {
+ #- do not write entry if not provided in the new network
+ exists $new_network->{$key} or next;
+ #- update value from the new network
+ $_->{value} = delete $new_network->{$key};
+ }
+ $buf .= " ";
+ $buf .= "$key=$_->{value}" if $key;
+ $buf .= "$_->{comment}\n";
+ }
+ if ($new_network) {
+ #- write new keys
+ while (my ($key, $value) = each(%$new_network)) {
+ $buf .= " $key=$value\n";
+ }
+ }
+ $buf .= "}\n";
+ $new_network and @conf = grep { $_ != $new_network } @conf;
+ undef $network;
+ } else {
+ #- unrecognized, keep it anyway
+ push @{$network->{entries}}, { comment => $_ };
+ }
+ } else {
+ if (/^\s*network={/) {
+ #- beginning of a new network block
+ $network = {};
+ } else {
+ #- keep other options, comments
+ $buf .= $_;
+ }
+ }
+ }
+
+ #- write remaining networks
+ foreach (@conf) {
+ $buf .= "\nnetwork={\n";
+ while (my ($key, $value) = each(%$_)) {
+ $buf .= " $key=$value\n";
+ }
+ $buf .= "}\n";
+ }
+
+ output($::prefix . $wpa_supplicant_conf, $buf);
+ #- hide keys for non-root users
+ chmod 0600, $::prefix . $wpa_supplicant_conf;
+}
+
+1;