Make Dispatcher log message translatable
[wifi-radar.git] / wifiradar / connections.py
blob24d9f93fb9de77ae5de9add110b81820b799c4ee
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 import logging
38 import os
39 import re
40 from signal import SIGTERM
41 from subprocess import CalledProcessError, Popen, PIPE, STDOUT
42 from threading import Event
43 from time import sleep
45 from wifiradar.config import make_section_name, ConfigManager
46 from wifiradar.misc import *
47 from wifiradar.pubsub import Message
49 if PYVERSION < 3:
50 from ConfigParser import NoOptionError, NoSectionError
51 else:
52 from configparser import NoOptionError, NoSectionError
54 # create a logger
55 logger = logging.getLogger(__name__)
58 def get_enc_mode(use_wpa, key):
59 """ Return the WiFi encryption mode based on the combination of
60 'use_wpa' and 'key'. Possible return values are 'wpa', 'wep',
61 and 'none'.
62 """
63 if use_wpa:
64 return 'wpa'
65 elif key == '':
66 return 'none'
67 else:
68 return 'wep'
71 def scanner(config_manager, msg_pipe):
72 """ Scan for access point information and send a profile for each.
73 :data:`config_manager` is a :class:`ConfigManager` object with
74 minimal configuration information (i.e. device and iwlist command).
75 :data:`msg_pipe` is a :class:`multiprocessing.Connection` object
76 used to receive commands and report AP profiles.
77 """
78 # Setup our pattern matchers. The important value must be the second
79 # regular expression group in the pattern.
80 patterns = dict()
81 patterns['essid'] = re.compile("ESSID\s*(:|=)\s*\"([^\"]+)\"", re.I | re.M | re.S)
82 patterns['bssid'] = re.compile("Address\s*(:|=)\s*([a-zA-Z0-9:]+)", re.I | re.M | re.S)
83 patterns['protocol'] = re.compile("Protocol\s*(:|=)\s*IEEE 802.11\s*([abgn]+)", re.I | re.M | re.S)
84 patterns['mode'] = re.compile("Mode\s*(:|=)\s*([^\n]+)", re.I | re.M | re.S)
85 patterns['channel'] = re.compile("Channel\s*(:|=)*\s*(\d+)", re.I | re.M | re.S)
86 patterns['encrypted'] = re.compile("Encryption key\s*(:|=)\s*(on|off)", re.I | re.M | re.S)
87 patterns['signal'] = re.compile("Signal level\s*(:|=)\s*(-?[0-9]+)", re.I | re.M | re.S)
89 trans_enc = dict(on=True, off=False)
91 running = True
92 scan = True
93 while running:
94 if msg_pipe.poll(1):
95 try:
96 msg = msg_pipe.recv()
97 except (EOFError, IOError) as e:
98 # This is bad, really bad.
99 logger.critical(_('read on closed Pipe '
100 '({PIPE}), failing...').format(PIPE=msg_pipe))
101 raise PipeError(e)
102 else:
103 if msg.topic == 'EXIT':
104 msg_pipe.close()
105 break
106 elif msg.topic == 'SCAN-START':
107 scan = True
108 elif msg.topic == 'SCAN-STOP':
109 scan = False
110 try:
111 device = config_manager.get_network_device()
112 except NoDeviceError as e:
113 logger.critical(_('Wifi device not found, '
114 'please set this in the preferences.'))
115 scan = False
116 running = False
118 if scan:
119 # update the signal strengths
120 iwlist_command = [
121 config_manager.get_opt('DEFAULT', 'iwlist_command'),
122 device, 'scan']
123 try:
124 scandata = Popen(iwlist_command, stdout=PIPE,
125 stderr=STDOUT).stdout
126 except OSError as e:
127 logger.critical(_('iwlist command not found, '
128 'please set this in the preferences.'))
129 running = False
130 scan = False
131 else:
132 # Start with a blank profile to fill in.
133 profile = get_new_profile()
134 # It's cleaner to code the gathering of AP profiles
135 # from bottom to top with the iwlist output.
136 for line in reversed(list(scandata)):
137 line = line.strip()
138 for pattern in patterns:
139 m = patterns[pattern].search(line)
140 if m is not None:
141 profile[pattern] = m.group(2)
142 # Stop looking for more matches on this line.
143 break
145 if line.startswith('Cell '):
146 # Each AP starts with the keyword "Cell",
147 # which mean we now have one whole profile.
148 # But, first translate the 'encrypted'
149 # property to a boolean value.
150 profile['encrypted'] = trans_enc[profile['encrypted']]
151 msg_pipe.send(Message('ACCESSPOINT', profile))
152 # Restart with a blank profile to fill in.
153 profile = get_new_profile()
156 class ConnectionManager(object):
157 """ Manage a connection; including reporting connection state,
158 connecting/disconnecting from an AP, and returning current IP, ESSID, and BSSID.
160 def __init__(self, msg_pipe):
161 """ Create a new connection manager which communicates with a
162 controlling process or thread through 'msg_pipe' (a Pipe
163 object).
165 self.msg_pipe = msg_pipe
166 self._watching = Event()
167 self._watching.set()
169 def run(self):
170 """ Watch for incoming messages.
172 while self._watching.is_set():
173 try:
174 msg = self.msg_pipe.recv()
175 except (EOFError, IOError) as e:
176 # This is bad, really bad.
177 logger.critical(_('read on closed Pipe '
178 '({PIPE}), failing...').format(PIPE=self.msg_pipe))
179 self._watching.clear()
180 raise PipeError(e)
181 else:
182 self._check_message(msg)
184 def _check_message(self, msg):
185 """ Process incoming messages.
187 if msg.topic == 'EXIT':
188 self.msg_pipe.close()
189 self._watching.clear()
190 elif msg.topic == 'CONFIG-UPDATE':
191 # Replace configuration manager with the one in msg.details.
192 self.set_config(msg.details)
193 elif msg.topic == 'CONNECT':
194 # Try to connect to the profile in msg.details.
195 self.connect(msg.details)
196 elif msg.topic == 'DISCONNECT':
197 # Try to disconnect from the profile in msg.details.
198 self.disconnect(msg.details)
199 elif msg.topic == 'IF-CHANGE':
200 # Try to connect to the profile in msg.details.
201 try:
202 self.if_change(msg.details)
203 except DeviceError as e:
204 self.msg_pipe.send(Message('ERROR', e))
205 except ValueError as e:
206 logger.warning(e)
207 elif msg.topic == 'QUERY-IP':
208 # Send out a message with the current IP address.
209 self.msg_pipe.send(Message('ANN-IP', self.get_current_ip()))
210 elif msg.topic == 'QUERY-ESSID':
211 # Send out a message with the current ESSID.
212 self.msg_pipe.send(Message('ANN-ESSID', self.get_current_essid()))
213 elif msg.topic == 'QUERY-BSSID':
214 # Send out a message with the current BSSID.
215 self.msg_pipe.send(Message('ANN-BSSID', self.get_current_bssid()))
216 else:
217 logger.warning(_('unrecognized Message: "{MSG}"').format(MSG=msg))
219 def set_config(self, config_manager):
220 """ Set the configuration manager to 'config_manager'. This method
221 must be called before any other public method or a caller will
222 likely receive an AttributeError about a missing 'config_manager'
223 attribute.
225 Raises TypeError if 'config_manager' is not a ConfigManager object.
227 if not isinstance(config_manager, ConfigManager):
228 raise TypeError(_('config must be a ConfigManager object'))
229 self.config = config_manager
231 def _run_script(self, script_name, profile, device):
232 """ Run the script (e.g. connection prescript) in 'profile' which
233 is named in 'script_name'.
235 if profile[script_name]:
236 logger.info('running {NAME}'.format(NAME=script_name))
237 profile_name = make_section_name(profile['essid'], profile['bssid'])
238 enc_mode = get_enc_mode(profile['use_wpa'], profile['key'])
239 custom_env = {
240 "WIFIRADAR_IP": self.get_current_ip(),
241 "WIFIRADAR_ESSID": self.get_current_essid(),
242 "WIFIRADAR_BSSID": self.get_current_bssid(),
243 "WIFIRADAR_PROFILE": profile_name,
244 "WIFIRADAR_ENCMODE": enc_mode,
245 "WIFIRADAR_SECMODE": profile['security'],
246 "WIFIRADAR_IF": device}
247 try:
248 shellcmd(profile[script_name].split(' '), custom_env)
249 except CalledProcessError as e:
250 logger.error('script "{NAME}" failed: {EXC}'.format(
251 NAME=script_name, EXC=e))
252 self.msg_pipe.send(Message('ERROR',
253 'script "{NAME}" failed: {EXC}'.format(NAME=script_name,
254 EXC=e)))
256 def _prepare_nic(self, profile, device):
257 """ Configure the NIC for upcoming connection.
259 # Start building iwconfig command line.
260 iwconfig_command = [
261 self.config.get_opt('DEFAULT', 'iwconfig_command'),
262 device,
263 'essid', "'{ESSID}'".format(ESSID=profile['essid']),
266 # Setting key
267 iwconfig_command.append('key')
268 if (not profile['key']) or (profile['key'] == 's:'):
269 iwconfig_command.append('off')
270 else:
271 # Setting this stops association from working, so remove it for now
272 #if profile['security'] != '':
273 #iwconfig_command.append(profile['security'])
274 iwconfig_command.append("'{KEY}'".format(KEY=profile['key']))
275 #iwconfig_commands.append( "key %s %s" % ( profile['security'], profile['key'] ) )
277 # Set mode.
278 profile['mode'] = profile['mode'].lower()
279 if profile['mode'] == 'master' or profile['mode'] == 'auto':
280 profile['mode'] = 'managed'
281 iwconfig_command.extend(['mode', profile['mode']])
283 # Set channel.
284 if 'channel' in profile:
285 iwconfig_command.extend(['channel', profile['channel']])
287 # Set AP address (do this last since iwconfig seems to want it only there).
288 iwconfig_command.extend(['ap', profile['bssid']])
290 # Some cards require a commit
291 if self.config.get_opt_as_bool('DEFAULT', 'commit_required'):
292 iwconfig_command.append('commit')
294 logger.debug('iwconfig_command: {IWC}'.format(IWC=iwconfig_command))
296 try:
297 shellcmd(iwconfig_command)
298 except CalledProcessError as e:
299 logger.error('Failed to prepare NIC: {EXC}'.format(EXC=e))
300 self.msg_pipe.send(Message('ERROR',
301 _('Failed to prepare NIC: {EXC}').format(EXC=e)))
302 raise DeviceError(_('Could not configure wireless options.'))
304 def _stop_dhcp(self, device):
305 """ Stop any DHCP client daemons running with our 'device'.
307 logger.info(_('Stopping any DHCP clients on "{DEV}"').format(DEV=device))
308 if os.access(self.config.get_opt('DHCP', 'pidfile'), os.R_OK):
309 if self.config.get_opt('DHCP', 'kill_args'):
310 dhcp_command = [self.config.get_opt('DHCP', 'command')]
311 dhcp_command.extend(self.config.get_opt('DHCP', 'kill_args').split(' '))
312 dhcp_command.append(device)
313 logger.info(_('DHCP command: {DHCP}').format(DHCP=dhcp_command))
315 # call DHCP client command and wait for return
316 logger.info(_('Stopping DHCP with kill_args'))
317 try:
318 shellcmd(dhcp_command)
319 except CalledProcessError as e:
320 logger.error(_('Attempt to stop DHCP failed: '
321 '{EXC}').format(EXC=e))
322 else:
323 logger.info(_('Stopping DHCP manually...'))
324 os.kill(int(open(self.config.get_opt('DHCP', 'pidfile'), mode='r').readline()), SIGTERM)
326 def _start_dhcp(self, device):
327 """ Start a DHCP client daemon on 'device'.
329 logger.debug('Starting DHCP command on "{DEV}"'.format(DEV=device))
330 self.msg_pipe.send(Message('STATUS', _('Acquiring IP Address (DHCP)')))
331 dhcp_command = [self.config.get_opt('DHCP', 'command')]
332 dhcp_command.extend(self.config.get_opt('DHCP', 'args').split(' '))
333 dhcp_command.append(device)
334 logger.info(_('dhcp_command: {DHCP}').format(DHCP=dhcp_command))
335 try:
336 dhcp_proc = Popen(dhcp_command, stdout=None, stderr=None)
337 except OSError as e:
338 if e.errno == 2:
339 logger.critical(_('DHCP client not found, '
340 'please set this in the preferences.'))
341 else:
342 # The DHCP client daemon should timeout on its own, hence
343 # the +3 seconds on timeout so we don't cut the daemon off
344 # while it is finishing up.
345 timeout = self.config.get_opt_as_int('DHCP', 'timeout') + 3
346 tick = 0.25
347 dhcp_status = dhcp_proc.poll()
348 while dhcp_status is None:
349 if timeout < 0:
350 dhcp_proc.terminate()
351 else:
352 timeout = timeout - tick
353 sleep(tick)
354 dhcp_status = dhcp_proc.poll()
355 if self.get_current_ip() is not None:
356 self.msg_pipe.send(Message('STATUS',
357 _('Got IP address. Done.')))
358 else:
359 self.msg_pipe.send(Message('STATUS',
360 _('Could not get IP address!')))
362 def _stop_wpa(self):
363 """ Stop all WPA supplicants.
365 logger.info(_('Kill off any existing WPA supplicants running...'))
366 if os.access(self.config.get_opt('WPA', 'pidfile'), os.R_OK):
367 logger.info(_('Killing existing WPA supplicant...'))
368 try:
369 if self.config.get_opt('WPA', 'kill_command'):
370 wpa_command = [self.config.get_opt('WPA', 'kill_command').split(' ')]
371 try:
372 shellcmd(wpa_command)
373 except CalledProcessError as e:
374 logger.error(_('Attempt to stop WPA supplicant '
375 'failed: {EXC}').format(EXC=e))
376 else:
377 os.kill(int(open(self.config.get_opt('WPA', 'pidfile'), mode='r').readline()), SIGTERM)
378 except OSError:
379 logger.info(_('Failed to kill WPA supplicant.'))
381 def _start_wpa(self):
382 """ Start WPA supplicant and let it associate with the AP.
384 self.msg_pipe.send(Message('STATUS', _('WPA supplicant starting')))
386 wpa_command = [self.config.get_opt('WPA', 'command')]
387 wpa_command.extend(self.config.get_opt('WPA', 'args').split(' '))
388 try:
389 shellcmd(wpa_command)
390 except CalledProcessError as e:
391 logger.error(_('WPA supplicant failed to start: '
392 '{EXC}').format(EXC=e))
394 def _start_manual_network(self, profile, device):
395 """ Manually configure network settings after association.
397 # Bring down the interface before trying to make changes.
398 ifconfig_command = [
399 self.config.get_opt('DEFAULT', 'ifconfig_command'),
400 device, 'down']
401 try:
402 shellcmd(ifconfig_command)
403 except CalledProcessError as e:
404 logger.error(_('Device "{DEV}" failed to go down: '
405 '{EXC}').format(DEV=device, EXC=e))
406 # Bring the interface up with our manual IP.
407 ifconfig_command = [
408 self.config.get_opt('DEFAULT', 'ifconfig_command'),
409 device, profile['ip'],
410 'netmask', profile['netmask']]
411 try:
412 shellcmd(ifconfig_command)
413 except CalledProcessError as e:
414 logger.error(_('Device "{DEV}" failed to configure: '
415 '{EXC}').format(DEV=device, EXC=e))
416 # Configure routing information.
417 route_command = [
418 self.config.get_opt('DEFAULT', 'route_command'),
419 'add', 'default', 'gw', profile['gateway']]
420 try:
421 shellcmd(route_command)
422 except CalledProcessError as e:
423 logger.error(_('Failed to configure routing information: '
424 '{EXC}').format(EXC=e))
425 # Build the /etc/resolv.conf file, if needed.
426 resolv_contents = ''
427 if profile['domain']:
428 resolv_contents += "domain {DOM}\n".format(DOM=profile['domain'])
429 if profile['dns1']:
430 resolv_contents += "nameserver {NS1}\n".format(NS1=profile['dns1'])
431 if profile['dns2']:
432 resolv_contents += "nameserver {NS2}\n".format(NS2=profile['dns2'])
433 if resolv_contents:
434 with open('/etc/resolv.conf', 'w') as resolv_file:
435 resolv_file.write(resolv_contents)
437 def connect(self, profile):
438 """ Connect to the access point specified by 'profile'.
440 if not profile['bssid']:
441 raise ValueError('missing BSSID')
442 logger.info(_('Connecting to the {ESSID} ({BSSID}) network').format(
443 ESSID=profile['essid'], BSSID=profile['bssid']))
445 device = self.config.get_network_device()
447 # Ready to dance...
448 self.msg_pipe.send(Message('STATUS', 'starting con_prescript'))
449 self._run_script('con_prescript', profile, device)
450 self.msg_pipe.send(Message('STATUS', 'con_prescript has run'))
452 self._prepare_nic(profile, device)
454 self._stop_dhcp(device)
455 self._stop_wpa()
457 logger.debug(_('Disable scan while connection attempt in progress...'))
458 self.msg_pipe.send(Message('SCAN-STOP', ''))
460 if profile['use_wpa'] :
461 self._start_wpa()
463 if profile['use_dhcp'] :
464 self._start_dhcp(device)
465 else:
466 self._start_manual_network(profile, device)
468 # Begin scanning again
469 self.msg_pipe.send(Message('SCAN-START', ''))
471 # Run the connection postscript.
472 self.msg_pipe.send(Message('STATUS', 'starting con_postscript'))
473 self._run_script('con_postscript', profile, device)
474 self.msg_pipe.send(Message('STATUS', 'con_postscript has run'))
476 self.msg_pipe.send(Message('STATUS', 'connected'))
478 def disconnect(self, profile):
479 """ Disconnect from the AP with which a connection has been
480 established/attempted.
482 logger.info(_('Disconnecting...'))
484 # Pause scanning while manipulating card
485 self.msg_pipe.send(Message('SCAN-STOP', ''))
487 device = self.config.get_network_device()
489 self.msg_pipe.send(Message('STATUS', 'starting dis_prescript'))
490 self._run_script('dis_prescript', profile, device)
491 self.msg_pipe.send(Message('STATUS', 'dis_prescript has run'))
493 self._stop_dhcp(device)
495 self._stop_wpa()
497 # Clear out the wireless stuff.
498 iwconfig_command = [
499 self.config.get_opt('DEFAULT.iwconfig_command'),
500 device,
501 'essid', 'any',
502 'key', 'off',
503 'mode', 'managed',
504 'channel', 'auto',
505 'ap', 'off']
506 try:
507 shellcmd(iwconfig_command)
508 except CalledProcessError as e:
509 logger.error(_('Failed to clean up wireless configuration: '
510 '{EXC}').format(EXC=e))
512 # Since it may be brought back up by the next scan, unset its IP.
513 ifconfig_command = [
514 self.config.get_opt('DEFAULT.ifconfig_command'),
515 device,
516 '0.0.0.0']
517 try:
518 shellcmd(ifconfig_command)
519 except CalledProcessError as e:
520 logger.error(_('Failed to unset IP address: {EXC}').format(EXC=e))
522 # Now take the interface down. Taking down the interface too
523 # quickly can crash my system, so pause a moment.
524 sleep(0.25)
525 self.if_change('down')
527 self.msg_pipe.send(Message('STATUS', 'starting dis_postscript'))
528 self._run_script('dis_postscript', profile, device)
529 self.msg_pipe.send(Message('STATUS', 'dis_postscript has run'))
531 logger.info(_('Disconnect complete.'))
533 # Begin scanning again
534 self.msg_pipe.send(Message('SCAN-START', ''))
536 def if_change(self, state):
537 """ Change the interface to 'state', i.e. 'up' or 'down'.
539 'if_change' raises ValueError if 'state' is not recognized,
540 raises OSError if there is a problem running ifconfig, and
541 raises DeviceError if ifconfig reports the change failed.
543 state = state.lower()
544 if ((state == 'up') or (state == 'down')):
545 device = self.config.get_network_device()
546 if device:
547 ifconfig_command = [
548 self.config.get_opt('DEFAULT', 'ifconfig_command'),
549 device, state]
550 try:
551 logger.info('changing interface {DEV} '
552 'state to {STATE}'.format(DEV=device, STATE=state))
553 ifconfig_info = Popen(ifconfig_command, stdout=PIPE,
554 stderr=STDOUT).stdout
555 except OSError as e:
556 if e.errno == 2:
557 logger.critical(_('ifconfig command not found, '
558 'please set this in the preferences.'))
559 raise e
560 else:
561 for line in ifconfig_info:
562 if len(line) > 0:
563 raise DeviceError(_('Could not change '
564 'device state: {ERR}').format(ERR=line))
565 else:
566 raise ValueError(_('unrecognized state for device: '
567 '{STATE}').format(STATE=state))
569 def get_current_ip(self):
570 """ Return the current IP address as a string or None.
572 device = self.config.get_network_device()
573 if device:
574 ifconfig_command = [
575 self.config.get_opt('DEFAULT', 'ifconfig_command'),
576 device]
577 try:
578 ifconfig_info = Popen(ifconfig_command, stdout=PIPE).stdout
579 # Be careful to the language (inet adr: in French for example)
581 # Hi Brian
583 # I'm using wifi-radar on a system with German translations (de_CH-UTF-8).
584 # There the string in ifconfig is inet Adresse for the IP which isn't
585 # found by the current get_current_ip function in wifi-radar. I changed
586 # the according line (#289; gentoo, v1.9.6-r1) to
587 # >ip_re = re.compile(r'inet [Aa]d?dr[^.]*:([^.]*\.[^.]*\.[^.]*\.[0-9]*)')
588 # which works on my system (LC_ALL=de_CH.UTF-8) and still works with LC_ALL=C.
590 # I'd be happy if you could incorporate this small change because as now
591 # I've got to change the file every time it is updated.
593 # Best wishes
595 # Simon
596 ip_re = re.compile(r'inet [Aa]d?dr[^.]*:([^.]*\.[^.]*\.[^.]*\.[0-9]*)')
597 line = ifconfig_info.read()
598 if ip_re.search(line):
599 return ip_re.search(line).group(1)
600 except OSError as e:
601 if e.errno == 2:
602 logger.critical(_('ifconfig command not found, '
603 'please set this in the preferences.'))
604 return None
606 def get_current_essid(self):
607 """ Return the current ESSID as a string or None.
609 device = self.config.get_network_device()
610 if device:
611 iwconfig_command = [
612 self.config.get_opt('DEFAULT', 'iwconfig_command'),
613 device]
614 try:
615 iwconfig_info = Popen(iwconfig_command, stdout=PIPE, stderr=STDOUT).stdout
616 essid_re = re.compile(r'ESSID\s*(:|=)\s*"([^"]+)"',
617 re.I | re.M | re.S)
618 line = iwconfig_info.read()
619 if essid_re.search(line):
620 return essid_re.search(line).group(2)
621 except OSError as e:
622 if e.errno == 2:
623 logger.critical(_('iwconfig command not found, '
624 'please set this in the preferences.'))
625 return None
627 def get_current_bssid(self):
628 """ Return the current BSSID as a string or None.
630 device = self.config.get_network_device()
631 if device:
632 iwconfig_command = [
633 self.config.get_opt('DEFAULT', 'iwconfig_command'),
634 device]
635 try:
636 iwconfig_info = Popen(iwconfig_command, stdout=PIPE, stderr=STDOUT).stdout
637 bssid_re = re.compile(r'Access Point\s*(:|=)\s*([a-fA-F0-9:]{17})',
638 re.I | re.M | re.S )
639 line = iwconfig_info.read()
640 if bssid_re.search(line):
641 return bssid_re.search(line).group(2)
642 except OSError as e:
643 if e.errno == 2:
644 logger.critical(_('iwconfig command not found, '
645 'please set this in the preferences.'))
646 return None
649 # Make so we can be imported
650 if __name__ == "__main__":
651 pass