Apply SIGINT restoration to new module layout
[wifi-radar.git] / wifiradar / connections.py
blob8297be0b6e869ea98ea54b78c021836760f0a424
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
4 # connections.py - collection of classes for supporting WiFi connections
6 # Part of WiFi Radar: A utility for managing WiFi profiles on GNU/Linux.
8 # Copyright (C) 2004-2005 Ahmad Baitalmal <ahmad@baitalmal.com>
9 # Copyright (C) 2005 Nicolas Brouard <nicolas.brouard@mandrake.org>
10 # Copyright (C) 2005-2009 Brian Elliott Finley <brian@thefinleys.com>
11 # Copyright (C) 2006 David Decotigny <com.d2@free.fr>
12 # Copyright (C) 2006 Simon Gerber <gesimu@gmail.com>
13 # Copyright (C) 2006-2007 Joey Hurst <jhurst@lucubrate.org>
14 # Copyright (C) 2012 Anari Jalakas <anari.jalakas@gmail.com>
15 # Copyright (C) 2006, 2009 Ante Karamatic <ivoks@ubuntu.com>
16 # Copyright (C) 2009-2010,2014 Sean Robinson <robinson@tuxfamily.org>
17 # Copyright (C) 2010 Prokhor Shuchalov <p@shuchalov.ru>
19 # This program is free software; you can redistribute it and/or modify
20 # it under the terms of the GNU General Public License as published by
21 # the Free Software Foundation; version 2 of the License.
23 # This program is distributed in the hope that it will be useful,
24 # but WITHOUT ANY WARRANTY; without even the implied warranty of
25 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 # GNU General Public License in LICENSE.GPL for more details.
28 # You should have received a copy of the GNU General Public License
29 # along with this program; if not, write to:
30 # Free Software Foundation, Inc.
31 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
35 from __future__ import unicode_literals
37 from configparser import NoOptionError, NoSectionError
38 import logging
39 import os
40 import re
41 from signal import SIGTERM
42 from subprocess import CalledProcessError, Popen, PIPE, STDOUT
43 from threading import Event
44 from time import sleep
46 from wifiradar.config import make_section_name, ConfigManager
47 from wifiradar.misc import *
48 from wifiradar.pubsub import Message
50 # create a logger
51 logger = logging.getLogger(__name__)
54 def get_enc_mode(use_wpa, key):
55 """ Return the WiFi encryption mode based on the combination of
56 'use_wpa' and 'key'. Possible return values are 'wpa', 'wep',
57 and 'none'.
58 """
59 if use_wpa:
60 return 'wpa'
61 elif key == '':
62 return 'none'
63 else:
64 return 'wep'
67 def scanner(config_manager, msg_pipe):
68 """ Scan for access point information and send a profile for each.
69 :data:`config_manager` is a :class:`ConfigManager` object with
70 minimal configuration information (i.e. device and iwlist command).
71 :data:`msg_pipe` is a :class:`multiprocessing.Connection` object
72 used to receive commands and report AP profiles.
73 """
74 # Setup our pattern matchers. The important value must be the second
75 # regular expression group in the pattern.
76 patterns = dict()
77 patterns['essid'] = re.compile("ESSID\s*(:|=)\s*\"([^\"]+)\"", re.I | re.M | re.S)
78 patterns['bssid'] = re.compile("Address\s*(:|=)\s*([a-zA-Z0-9:]+)", re.I | re.M | re.S)
79 patterns['protocol'] = re.compile("Protocol\s*(:|=)\s*IEEE 802.11\s*([abgn]+)", re.I | re.M | re.S)
80 patterns['mode'] = re.compile("Mode\s*(:|=)\s*([^\n]+)", re.I | re.M | re.S)
81 patterns['channel'] = re.compile("Channel\s*(:|=)*\s*(\d+)", re.I | re.M | re.S)
82 patterns['encrypted'] = re.compile("Encryption key\s*(:|=)\s*(on|off)", re.I | re.M | re.S)
83 patterns['signal'] = re.compile("Signal level\s*(:|=)\s*(-?[0-9]+)", re.I | re.M | re.S)
85 trans_enc = dict(on=True, off=False)
87 running = True
88 scan = True
89 while running:
90 if msg_pipe.poll(1):
91 try:
92 msg = msg_pipe.recv()
93 except (EOFError, IOError) as e:
94 # This is bad, really bad.
95 logger.critical(_('read on closed Pipe '
96 '({PIPE}), failing...').format(PIPE=msg_pipe))
97 raise PipeError(e)
98 else:
99 if msg.topic == 'EXIT':
100 msg_pipe.close()
101 break
102 elif msg.topic == 'SCAN-START':
103 scan = True
104 elif msg.topic == 'SCAN-STOP':
105 scan = False
106 try:
107 device = config_manager.get_network_device()
108 except NoDeviceError as e:
109 logger.critical(_('Wifi device not found, '
110 'please set this in the preferences.'))
111 scan = False
112 running = False
114 if scan:
115 # update the signal strengths
116 iwlist_command = [
117 config_manager.get_opt('GENERAL', 'iwlist_command'),
118 device, 'scan']
119 try:
120 scandata = Popen(iwlist_command, stdout=PIPE,
121 stderr=STDOUT).stdout
122 except OSError as e:
123 logger.critical(_('iwlist command not found, '
124 'please set this in the preferences.'))
125 running = False
126 scan = False
127 else:
128 # Start with a blank profile to fill in.
129 profile = get_new_profile()
130 # It's cleaner to code the gathering of AP profiles
131 # from bottom to top with the iwlist output.
132 for line in reversed(list(scandata)):
133 line = line.strip()
134 for pattern in patterns:
135 m = patterns[pattern].search(line)
136 if m is not None:
137 profile[pattern] = m.group(2)
138 # Stop looking for more matches on this line.
139 break
141 if line.startswith('Cell '):
142 # Each AP starts with the keyword "Cell",
143 # which mean we now have one whole profile.
144 # But, first translate the 'encrypted'
145 # property to a boolean value.
146 profile['encrypted'] = trans_enc[profile['encrypted']]
147 msg_pipe.send(Message('ACCESSPOINT', profile))
148 # Restart with a blank profile to fill in.
149 profile = get_new_profile()
152 class ConnectionManager(object):
153 """ Manage a connection; including reporting connection state,
154 connecting/disconnecting from an AP, and returning current IP, ESSID, and BSSID.
156 def __init__(self, msg_pipe):
157 """ Create a new connection manager which communicates with a
158 controlling process or thread through 'msg_pipe' (a Pipe
159 object).
161 self.msg_pipe = msg_pipe
162 self._watching = Event()
163 self._watching.set()
165 def run(self):
166 """ Watch for incoming messages.
168 while self._watching.is_set():
169 try:
170 msg = self.msg_pipe.recv()
171 except (EOFError, IOError) as e:
172 # This is bad, really bad.
173 logger.critical(_('read on closed Pipe '
174 '({PIPE}), failing...').format(PIPE=self.msg_pipe))
175 self._watching.clear()
176 raise PipeError(e)
177 else:
178 self._check_message(msg)
180 def _check_message(self, msg):
181 """ Process incoming messages.
183 if msg.topic == 'EXIT':
184 self.msg_pipe.close()
185 self._watching.clear()
186 elif msg.topic == 'CONFIG-UPDATE':
187 # Replace configuration manager with the one in msg.details.
188 self.set_config(msg.details)
189 elif msg.topic == 'CONNECT':
190 # Try to connect to the profile in msg.details.
191 self.connect(msg.details)
192 elif msg.topic == 'DISCONNECT':
193 # Try to disconnect from the profile in msg.details.
194 self.disconnect(msg.details)
195 elif msg.topic == 'IF-CHANGE':
196 # Try to connect to the profile in msg.details.
197 try:
198 self.if_change(msg.details)
199 except DeviceError as e:
200 self.msg_pipe.send(Message('ERROR', e))
201 except ValueError as e:
202 logger.warning(e)
203 elif msg.topic == 'QUERY-IP':
204 # Send out a message with the current IP address.
205 self.msg_pipe.send(Message('ANN-IP', self.get_current_ip()))
206 elif msg.topic == 'QUERY-ESSID':
207 # Send out a message with the current ESSID.
208 self.msg_pipe.send(Message('ANN-ESSID', self.get_current_essid()))
209 elif msg.topic == 'QUERY-BSSID':
210 # Send out a message with the current BSSID.
211 self.msg_pipe.send(Message('ANN-BSSID', self.get_current_bssid()))
212 else:
213 logger.warning(_('unrecognized Message: "{MSG}"').format(MSG=msg))
215 def set_config(self, config_manager):
216 """ Set the configuration manager to 'config_manager'. This method
217 must be called before any other public method or a caller will
218 likely receive an AttributeError about a missing 'config_manager'
219 attribute.
221 Raises TypeError if 'config_manager' is not a ConfigManager object.
223 if not isinstance(config_manager, ConfigManager):
224 raise TypeError(_('config must be a ConfigManager object'))
225 self.config = config_manager
227 def _run_script(self, script_name, profile, device):
228 """ Run the script (e.g. connection prescript) in 'profile' which
229 is named in 'script_name'.
231 if profile[script_name]:
232 logger.info('running {NAME}'.format(NAME=script_name))
233 profile_name = make_section_name(profile['essid'], profile['bssid'])
234 enc_mode = get_enc_mode(profile['use_wpa'], profile['key'])
235 custom_env = {
236 'WIFIRADAR_IP': self.get_current_ip(),
237 'WIFIRADAR_ESSID': self.get_current_essid(),
238 'WIFIRADAR_BSSID': self.get_current_bssid(),
239 'WIFIRADAR_PROFILE': profile_name,
240 'WIFIRADAR_ENCMODE': enc_mode,
241 'WIFIRADAR_SECMODE': profile['security'],
242 'WIFIRADAR_IF': device}
243 try:
244 shellcmd(profile[script_name].split(' '), custom_env)
245 except CalledProcessError as e:
246 logger.error(_('script "{NAME}" failed: {EXC}').format(
247 NAME=script_name, EXC=e))
248 self.msg_pipe.send(Message('ERROR',
249 _('script "{NAME}" failed: {EXC}').format(
250 NAME=script_name, EXC=e)))
252 def _prepare_nic(self, profile, device):
253 """ Configure the NIC for upcoming connection.
255 # Start building iwconfig command line.
256 iwconfig_command = [
257 self.config.get_opt('GENERAL', 'iwconfig_command'),
258 device,
259 'essid', "'{ESSID}'".format(ESSID=profile['essid']),
262 # Setting key
263 iwconfig_command.append('key')
264 if (not profile['key']) or (profile['key'] == 's:'):
265 iwconfig_command.append('off')
266 else:
267 # Setting this stops association from working, so remove it for now
268 #if profile['security'] != '':
269 #iwconfig_command.append(profile['security'])
270 iwconfig_command.append("'{KEY}'".format(KEY=profile['key']))
271 #iwconfig_commands.append( "key %s %s" % ( profile['security'], profile['key'] ) )
273 # Set mode.
274 profile['mode'] = profile['mode'].lower()
275 if profile['mode'] == 'master' or profile['mode'] == 'auto':
276 profile['mode'] = 'managed'
277 iwconfig_command.extend(['mode', profile['mode']])
279 # Set channel.
280 if 'channel' in profile:
281 iwconfig_command.extend(['channel', profile['channel']])
283 # Set AP address (do this last since iwconfig seems to want it only there).
284 iwconfig_command.extend(['ap', profile['bssid']])
286 # Some cards require a commit
287 if self.config.get_opt_as_bool('GENERAL', 'commit_required'):
288 iwconfig_command.append('commit')
290 logger.debug('iwconfig_command: {IWC}'.format(IWC=iwconfig_command))
292 try:
293 shellcmd(iwconfig_command)
294 except CalledProcessError as e:
295 logger.error('Failed to prepare NIC: {EXC}'.format(EXC=e))
296 self.msg_pipe.send(Message('ERROR',
297 _('Failed to prepare NIC: {EXC}').format(EXC=e)))
298 raise DeviceError(_('Could not configure wireless options.'))
300 def _stop_dhcp(self, device):
301 """ Stop any DHCP client daemons running with our 'device'.
303 logger.info(_('Stopping any DHCP clients on "{DEV}"').format(DEV=device))
304 if os.access(self.config.get_opt('DHCP', 'pidfile'), os.R_OK):
305 if self.config.get_opt('DHCP', 'kill_args'):
306 dhcp_command = [self.config.get_opt('DHCP', 'command')]
307 dhcp_command.extend(self.config.get_opt('DHCP', 'kill_args').split(' '))
308 dhcp_command.append(device)
309 logger.info(_('DHCP command: {DHCP}').format(DHCP=dhcp_command))
311 # call DHCP client command and wait for return
312 logger.info(_('Stopping DHCP with kill_args'))
313 try:
314 shellcmd(dhcp_command)
315 except CalledProcessError as e:
316 logger.error(_('Attempt to stop DHCP failed: '
317 '{EXC}').format(EXC=e))
318 else:
319 logger.info(_('Stopping DHCP manually...'))
320 os.kill(int(open(self.config.get_opt('DHCP', 'pidfile'), mode='r').readline()), SIGTERM)
322 def _start_dhcp(self, device):
323 """ Start a DHCP client daemon on 'device'.
325 logger.debug('Starting DHCP command on "{DEV}"'.format(DEV=device))
326 self.msg_pipe.send(Message('STATUS', _('Acquiring IP Address (DHCP)')))
327 dhcp_command = [self.config.get_opt('DHCP', 'command')]
328 dhcp_command.extend(self.config.get_opt('DHCP', 'args').split(' '))
329 dhcp_command.append(device)
330 logger.info(_('dhcp_command: {DHCP}').format(DHCP=dhcp_command))
331 try:
332 dhcp_proc = Popen(dhcp_command, stdout=None, stderr=None)
333 except OSError as e:
334 if e.errno == 2:
335 logger.critical(_('DHCP client not found, '
336 'please set this in the preferences.'))
337 else:
338 # The DHCP client daemon should timeout on its own, hence
339 # the +3 seconds on timeout so we don't cut the daemon off
340 # while it is finishing up.
341 timeout = self.config.get_opt_as_int('DHCP', 'timeout') + 3
342 tick = 0.25
343 dhcp_status = dhcp_proc.poll()
344 while dhcp_status is None:
345 if timeout < 0:
346 dhcp_proc.terminate()
347 else:
348 timeout = timeout - tick
349 sleep(tick)
350 dhcp_status = dhcp_proc.poll()
351 if self.get_current_ip() is not None:
352 self.msg_pipe.send(Message('STATUS',
353 _('Got IP address. Done.')))
354 else:
355 self.msg_pipe.send(Message('STATUS',
356 _('Could not get IP address!')))
358 def _stop_wpa(self):
359 """ Stop all WPA supplicants.
361 logger.info(_('Kill off any existing WPA supplicants running...'))
362 if os.access(self.config.get_opt('WPA', 'pidfile'), os.R_OK):
363 logger.info(_('Killing existing WPA supplicant...'))
364 try:
365 if self.config.get_opt('WPA', 'kill_command'):
366 wpa_command = [self.config.get_opt('WPA', 'kill_command').split(' ')]
367 try:
368 shellcmd(wpa_command)
369 except CalledProcessError as e:
370 logger.error(_('Attempt to stop WPA supplicant '
371 'failed: {EXC}').format(EXC=e))
372 else:
373 os.kill(int(open(self.config.get_opt('WPA', 'pidfile'), mode='r').readline()), SIGTERM)
374 except OSError:
375 logger.info(_('Failed to kill WPA supplicant.'))
377 def _start_wpa(self):
378 """ Start WPA supplicant and let it associate with the AP.
380 self.msg_pipe.send(Message('STATUS', _('WPA supplicant starting')))
382 wpa_command = [self.config.get_opt('WPA', 'command')]
383 wpa_command.extend(self.config.get_opt('WPA', 'args').split(' '))
384 try:
385 shellcmd(wpa_command)
386 except CalledProcessError as e:
387 logger.error(_('WPA supplicant failed to start: '
388 '{EXC}').format(EXC=e))
390 def _start_manual_network(self, profile, device):
391 """ Manually configure network settings after association.
393 # Bring down the interface before trying to make changes.
394 ifconfig_command = [
395 self.config.get_opt('GENERAL', 'ifconfig_command'),
396 device, 'down']
397 try:
398 shellcmd(ifconfig_command)
399 except CalledProcessError as e:
400 logger.error(_('Device "{DEV}" failed to go down: '
401 '{EXC}').format(DEV=device, EXC=e))
402 # Bring the interface up with our manual IP.
403 ifconfig_command = [
404 self.config.get_opt('GENERAL', 'ifconfig_command'),
405 device, profile['ip'],
406 'netmask', profile['netmask']]
407 try:
408 shellcmd(ifconfig_command)
409 except CalledProcessError as e:
410 logger.error(_('Device "{DEV}" failed to configure: '
411 '{EXC}').format(DEV=device, EXC=e))
412 # Configure routing information.
413 route_command = [
414 self.config.get_opt('GENERAL', 'route_command'),
415 'add', 'default', 'gw', profile['gateway']]
416 try:
417 shellcmd(route_command)
418 except CalledProcessError as e:
419 logger.error(_('Failed to configure routing information: '
420 '{EXC}').format(EXC=e))
421 # Build the /etc/resolv.conf file, if needed.
422 resolv_contents = ''
423 if profile['domain']:
424 resolv_contents += 'domain {DOM}\n'.format(DOM=profile['domain'])
425 if profile['dns1']:
426 resolv_contents += 'nameserver {NS1}\n'.format(NS1=profile['dns1'])
427 if profile['dns2']:
428 resolv_contents += 'nameserver {NS2}\n'.format(NS2=profile['dns2'])
429 if resolv_contents:
430 with open('/etc/resolv.conf', 'w') as resolv_file:
431 resolv_file.write(resolv_contents)
433 def connect(self, profile):
434 """ Connect to the access point specified by 'profile'.
436 if not profile['bssid']:
437 raise ValueError('missing BSSID')
438 logger.info(_('Connecting to the {ESSID} ({BSSID}) network').format(
439 ESSID=profile['essid'], BSSID=profile['bssid']))
441 device = self.config.get_network_device()
443 # Ready to dance...
444 self.msg_pipe.send(Message('STATUS', 'starting con_prescript'))
445 self._run_script('con_prescript', profile, device)
446 self.msg_pipe.send(Message('STATUS', 'con_prescript has run'))
448 self._prepare_nic(profile, device)
450 self._stop_dhcp(device)
451 self._stop_wpa()
453 logger.debug(_('Disable scan while connection attempt in progress...'))
454 self.msg_pipe.send(Message('SCAN-STOP', ''))
456 if profile['use_wpa'] :
457 self._start_wpa()
459 if profile['use_dhcp'] :
460 self._start_dhcp(device)
461 else:
462 self._start_manual_network(profile, device)
464 # Begin scanning again
465 self.msg_pipe.send(Message('SCAN-START', ''))
467 # Run the connection postscript.
468 self.msg_pipe.send(Message('STATUS', 'starting con_postscript'))
469 self._run_script('con_postscript', profile, device)
470 self.msg_pipe.send(Message('STATUS', 'con_postscript has run'))
472 self.msg_pipe.send(Message('STATUS', 'connected'))
474 def disconnect(self, profile):
475 """ Disconnect from the AP with which a connection has been
476 established/attempted.
478 logger.info(_('Disconnecting...'))
480 # Pause scanning while manipulating card
481 self.msg_pipe.send(Message('SCAN-STOP', ''))
483 device = self.config.get_network_device()
485 self.msg_pipe.send(Message('STATUS', 'starting dis_prescript'))
486 self._run_script('dis_prescript', profile, device)
487 self.msg_pipe.send(Message('STATUS', 'dis_prescript has run'))
489 self._stop_dhcp(device)
491 self._stop_wpa()
493 # Clear out the wireless stuff.
494 iwconfig_command = [
495 self.config.get_opt('GENERAL.iwconfig_command'),
496 device,
497 'essid', 'any',
498 'key', 'off',
499 'mode', 'managed',
500 'channel', 'auto',
501 'ap', 'off']
502 try:
503 shellcmd(iwconfig_command)
504 except CalledProcessError as e:
505 logger.error(_('Failed to clean up wireless configuration: '
506 '{EXC}').format(EXC=e))
508 # Since it may be brought back up by the next scan, unset its IP.
509 ifconfig_command = [
510 self.config.get_opt('GENERAL.ifconfig_command'),
511 device,
512 '0.0.0.0']
513 try:
514 shellcmd(ifconfig_command)
515 except CalledProcessError as e:
516 logger.error(_('Failed to unset IP address: {EXC}').format(EXC=e))
518 # Now take the interface down. Taking down the interface too
519 # quickly can crash my system, so pause a moment.
520 sleep(0.25)
521 self.if_change('down')
523 self.msg_pipe.send(Message('STATUS', 'starting dis_postscript'))
524 self._run_script('dis_postscript', profile, device)
525 self.msg_pipe.send(Message('STATUS', 'dis_postscript has run'))
527 logger.info(_('Disconnect complete.'))
529 # Begin scanning again
530 self.msg_pipe.send(Message('SCAN-START', ''))
532 def if_change(self, state):
533 """ Change the interface to 'state', i.e. 'up' or 'down'.
535 'if_change' raises ValueError if 'state' is not recognized,
536 raises OSError if there is a problem running ifconfig, and
537 raises DeviceError if ifconfig reports the change failed.
539 state = state.lower()
540 if ((state == 'up') or (state == 'down')):
541 device = self.config.get_network_device()
542 if device:
543 ifconfig_command = [
544 self.config.get_opt('GENERAL', 'ifconfig_command'),
545 device, state]
546 try:
547 logger.info('changing interface {DEV} '
548 'state to {STATE}'.format(DEV=device, STATE=state))
549 ifconfig_info = Popen(ifconfig_command, stdout=PIPE,
550 stderr=STDOUT).stdout
551 except OSError as e:
552 if e.errno == 2:
553 logger.critical(_('ifconfig command not found, '
554 'please set this in the preferences.'))
555 raise e
556 else:
557 for line in ifconfig_info:
558 if len(line) > 0:
559 raise DeviceError(_('Could not change '
560 'device state: {ERR}').format(ERR=line))
561 else:
562 raise ValueError(_('unrecognized state for device: '
563 '{STATE}').format(STATE=state))
565 def get_current_ip(self):
566 """ Return the current IP address as a string or None.
568 device = self.config.get_network_device()
569 if device:
570 ifconfig_command = [
571 self.config.get_opt('GENERAL', 'ifconfig_command'),
572 device]
573 try:
574 ifconfig_info = Popen(ifconfig_command, stdout=PIPE).stdout
575 # Be careful to the language (inet adr: in French for example)
577 # Hi Brian
579 # I'm using wifi-radar on a system with German translations (de_CH-UTF-8).
580 # There the string in ifconfig is inet Adresse for the IP which isn't
581 # found by the current get_current_ip function in wifi-radar. I changed
582 # the according line (#289; gentoo, v1.9.6-r1) to
583 # >ip_re = re.compile(r'inet [Aa]d?dr[^.]*:([^.]*\.[^.]*\.[^.]*\.[0-9]*)')
584 # which works on my system (LC_ALL=de_CH.UTF-8) and still works with LC_ALL=C.
586 # I'd be happy if you could incorporate this small change because as now
587 # I've got to change the file every time it is updated.
589 # Best wishes
591 # Simon
592 ip_re = re.compile(r'inet [Aa]d?dr[^.]*:([^.]*\.[^.]*\.[^.]*\.[0-9]*)')
593 line = ifconfig_info.read()
594 if ip_re.search(line):
595 return ip_re.search(line).group(1)
596 except OSError as e:
597 if e.errno == 2:
598 logger.critical(_('ifconfig command not found, '
599 'please set this in the preferences.'))
600 return None
602 def get_current_essid(self):
603 """ Return the current ESSID as a string or None.
605 device = self.config.get_network_device()
606 if device:
607 iwconfig_command = [
608 self.config.get_opt('GENERAL', 'iwconfig_command'),
609 device]
610 try:
611 iwconfig_info = Popen(iwconfig_command, stdout=PIPE, stderr=STDOUT).stdout
612 essid_re = re.compile(r'ESSID\s*(:|=)\s*"([^"]+)"',
613 re.I | re.M | re.S)
614 line = iwconfig_info.read()
615 if essid_re.search(line):
616 return essid_re.search(line).group(2)
617 except OSError as e:
618 if e.errno == 2:
619 logger.critical(_('iwconfig command not found, '
620 'please set this in the preferences.'))
621 return None
623 def get_current_bssid(self):
624 """ Return the current BSSID as a string or None.
626 device = self.config.get_network_device()
627 if device:
628 iwconfig_command = [
629 self.config.get_opt('GENERAL', 'iwconfig_command'),
630 device]
631 try:
632 iwconfig_info = Popen(iwconfig_command, stdout=PIPE, stderr=STDOUT).stdout
633 bssid_re = re.compile(r'Access Point\s*(:|=)\s*([a-fA-F0-9:]{17})',
634 re.I | re.M | re.S )
635 line = iwconfig_info.read()
636 if bssid_re.search(line):
637 return bssid_re.search(line).group(2)
638 except OSError as e:
639 if e.errno == 2:
640 logger.critical(_('iwconfig command not found, '
641 'please set this in the preferences.'))
642 return None
645 # Make so we can be imported
646 if __name__ == '__main__':
647 pass