~ubuntu-branches/debian/wheezy/wicd/wheezy

« back to all changes in this revision

Viewing changes to .pc/39-fix_channel_digit_parsing.patch/wicd/wnettools.py

  • Committer: Package Import Robot
  • Author(s): David Paleino
  • Date: 2011-10-22 11:25:07 UTC
  • mfrom: (14.1.9 sid)
  • Revision ID: package-import@ubuntu.com-20111022112507-o25prpqfvqtgcsff
Tags: 1.7.0+ds1-9
Fix postinst: it breaks dh_python2's layout

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
# -*- coding: utf-8 -*-
 
3
 
 
4
""" Network interface control tools for wicd.
 
5
 
 
6
This module implements functions to control and obtain information from
 
7
network interfaces.
 
8
 
 
9
class BaseInterface() -- Control a network interface.
 
10
class BaseWiredInterface() -- Control a wired network interface.
 
11
class BaseWirelessInterface() -- Control a wireless network interface.
 
12
 
 
13
"""
 
14
 
 
15
#
 
16
#   Copyright (C) 2007 - 2009 Adam Blackburn
 
17
#   Copyright (C) 2007 - 2009 Dan O'Reilly
 
18
#   Copyright (C) 2007 - 2009 Byron Hillis
 
19
#   Copyright (C) 2009        Andrew Psaltis
 
20
#
 
21
#   This program is free software; you can redistribute it and/or modify
 
22
#   it under the terms of the GNU General Public License Version 2 as
 
23
#   published by the Free Software Foundation.
 
24
#
 
25
#   This program is distributed in the hope that it will be useful,
 
26
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
 
27
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
28
#   GNU General Public License for more details.
 
29
#
 
30
#   You should have received a copy of the GNU General Public License
 
31
#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
32
#
 
33
 
 
34
import os
 
35
import re
 
36
import random
 
37
import time
 
38
from string import maketrans, translate
 
39
 
 
40
import wpath
 
41
import misc
 
42
from misc import find_path 
 
43
 
 
44
# Regular expressions.
 
45
_re_mode = (re.I | re.M | re.S)
 
46
essid_pattern = re.compile('.*ESSID:"?(.*?)"?\s*\n', _re_mode)
 
47
ap_mac_pattern = re.compile('.*Address: (.*?)\n', _re_mode)
 
48
channel_pattern = re.compile('.*Channel:?=? ?(\d\d?)', _re_mode)
 
49
strength_pattern = re.compile('.*Quality:?=? ?(\d+)\s*/?\s*(\d*)', _re_mode)
 
50
altstrength_pattern = re.compile('.*Signal level:?=? ?(\d+)\s*/?\s*(\d*)', _re_mode)
 
51
signaldbm_pattern = re.compile('.*Signal level:?=? ?(-\d\d*)', _re_mode)
 
52
bitrates_pattern = re.compile('(\d+\s+\S+/s)', _re_mode)
 
53
mode_pattern = re.compile('.*Mode:([A-Za-z-]*?)\n', _re_mode)
 
54
freq_pattern = re.compile('.*Frequency:(.*?)\n', _re_mode)
 
55
wep_pattern = re.compile('.*Encryption key:(.*?)\n', _re_mode)
 
56
altwpa_pattern = re.compile('(wpa_ie)', _re_mode)
 
57
wpa1_pattern = re.compile('(WPA Version 1)', _re_mode)
 
58
wpa2_pattern = re.compile('(WPA2)', _re_mode)
 
59
 
 
60
#iwconfig-only regular expressions.
 
61
ip_pattern = re.compile(r'inet [Aa]d?dr[^.]*:([^.]*\.[^.]*\.[^.]*\.[0-9]*)', re.S)
 
62
bssid_pattern = re.compile('.*Access Point: (([0-9A-Z]{2}:){5}[0-9A-Z]{2})', _re_mode)
 
63
bitrate_pattern = re.compile('.*Bit Rate[=:](.*?/s)', _re_mode)
 
64
opmode_pattern = re.compile('.*Mode:(.*?) ', _re_mode)
 
65
authmethods_pattern = re.compile('.*Authentication capabilities :\n(.*?)Current', _re_mode)
 
66
 
 
67
# Regular expressions for wpa_cli output
 
68
auth_pattern = re.compile('.*wpa_state=(.*?)\n', _re_mode)
 
69
 
 
70
RALINK_DRIVER = 'ralink legacy'
 
71
 
 
72
blacklist_strict = '!"#$%&\'()*+,./:;<=>?@[\\]^`{|}~ '
 
73
blacklist_norm = ";`$!*|><&\\"
 
74
blank_trans = maketrans("", "")
 
75
 
 
76
def _sanitize_string(string):
 
77
    if string:
 
78
        return translate(str(string), blank_trans, blacklist_norm)
 
79
    else:
 
80
        return string
 
81
 
 
82
def _sanitize_string_strict(string):
 
83
    if string:
 
84
        return translate(str(string), blank_trans, blacklist_strict)
 
85
    else:
 
86
        return string
 
87
  
 
88
_cache = {}
 
89
def timedcache(duration=5):
 
90
    """ A caching decorator for use with wnettools methods.
 
91
    
 
92
    Caches the results of a function for a given number of
 
93
    seconds (defaults to 5).
 
94
    
 
95
    """
 
96
    def _timedcache(f):
 
97
        def __timedcache(self, *args, **kwargs):
 
98
            key = str(args) + str(kwargs) + str(f)
 
99
            if hasattr(self, 'iface'):
 
100
                key += self.iface
 
101
            if (key in _cache and 
 
102
                (time.time() - _cache[key]['time']) < duration):
 
103
                return _cache[key]['value']
 
104
            else:
 
105
                value = f(self, *args, **kwargs)
 
106
                _cache[key] = { 'time' : time.time(), 'value' : value }
 
107
                return value
 
108
            
 
109
        return __timedcache
 
110
    
 
111
    return _timedcache
 
112
 
 
113
def GetDefaultGateway():
 
114
    """ Attempts to determine the default gateway by parsing route -n. """
 
115
    route_info = misc.Run("route -n")
 
116
    lines = route_info.split('\n')
 
117
    gateway = None
 
118
    for line in lines:
 
119
        words = line.split()
 
120
        print words
 
121
        if not words:
 
122
            continue
 
123
        if words[0] == '0.0.0.0':
 
124
            gateway = words[1]
 
125
            break
 
126
        
 
127
    if not gateway:
 
128
        print 'couldn\'t retrieve default gateway from route -n'
 
129
    return gateway
 
130
 
 
131
def GetWirelessInterfaces():
 
132
    """ Get available wireless interfaces.
 
133
 
 
134
    Attempts to get an interface first by parsing /proc/net/wireless,
 
135
    and should that fail, by parsing iwconfig.
 
136
    Returns:
 
137
    The first interface available.
 
138
 
 
139
    """
 
140
    dev_dir = '/sys/class/net/'
 
141
    ifnames = [iface for iface in os.listdir(dev_dir)
 
142
               if os.path.isdir(dev_dir + iface) and 
 
143
                  'wireless' in os.listdir(dev_dir + iface)]
 
144
    
 
145
    return ifnames
 
146
 
 
147
def GetWiredInterfaces():
 
148
    """ Returns a list of wired interfaces on the system. """
 
149
    basedir = '/sys/class/net/'
 
150
    return [iface for iface in os.listdir(basedir)
 
151
            if os.path.isdir(basedir + iface) and not 'wireless'
 
152
            in os.listdir(basedir + iface) and
 
153
            open(basedir + iface + "/type").readlines()[0].strip() == "1"]
 
154
 
 
155
def NeedsExternalCalls():
 
156
    """ Returns True if the backend needs to use an external program. """
 
157
    raise NotImplementedError
 
158
 
 
159
def GetWpaSupplicantDrivers():
 
160
    """ Returns a list of all valid wpa_supplicant drivers. """
 
161
    output = misc.Run(["wpa_supplicant", "-h"])
 
162
    try:
 
163
        output = output.split("drivers:")[1].split("options:")[0].strip()
 
164
    except:
 
165
        print "Warning: Couldn't get list of valid wpa_supplicant drivers"
 
166
        return [""]
 
167
    patt = re.compile("(\S+)\s+=.*")
 
168
    drivers = patt.findall(output) or [""]
 
169
    # We cannot use the "wired" driver for wireless interfaces.
 
170
    if 'wired' in drivers:
 
171
        drivers.remove('wired')
 
172
    return drivers
 
173
def IsValidWpaSuppDriver(driver):
 
174
    """ Returns True if given string is a valid wpa_supplicant driver. """
 
175
    output = misc.Run(["wpa_supplicant", "-D%s" % driver, "-iolan19",
 
176
                       "-c/etc/abcd%sdefzz.zconfz" % random.randint(1, 1000)])
 
177
    return not "Unsupported driver" in output
 
178
    
 
179
def neediface(default_response):
 
180
    """ A decorator for only running a method if self.iface is defined.
 
181
    
 
182
    This decorator is wrapped around Interface methods, and will
 
183
    return a provided default_response value if self.iface is not
 
184
    defined.
 
185
    
 
186
    """
 
187
    def wrapper(func):
 
188
        def newfunc(self, *args, **kwargs):
 
189
            if not self.iface or \
 
190
               not os.path.exists('/sys/class/net/%s' % self.iface):
 
191
                return default_response
 
192
            return func(self, *args, **kwargs)
 
193
        newfunc.__dict__ = func.__dict__
 
194
        newfunc.__doc__ = func.__doc__
 
195
        newfunc.__module__ = func.__module__
 
196
        return newfunc
 
197
    return wrapper
 
198
 
 
199
 
 
200
class BaseInterface(object):
 
201
    """ Control a network interface. """
 
202
    def __init__(self, iface, verbose=False):
 
203
        """ Initialise the object.
 
204
 
 
205
        Keyword arguments:
 
206
        iface -- the name of the interface
 
207
        verbose -- whether to print every command run
 
208
 
 
209
        """
 
210
        self.iface = _sanitize_string_strict(iface)
 
211
        self.verbose = verbose
 
212
        self.DHCP_CLIENT = None
 
213
        self.flush_tool = None
 
214
        self.link_detect = None       
 
215
        self.dhcp_object = None
 
216
    
 
217
    def SetDebugMode(self, value):
 
218
        """ If True, verbose output is enabled. """
 
219
        self.verbose = value
 
220
 
 
221
    def SetInterface(self, iface):
 
222
        """ Sets the interface.
 
223
        
 
224
        Keyword arguments:
 
225
        iface -- the name of the interface.
 
226
        
 
227
        """
 
228
        self.iface = _sanitize_string_strict(str(iface))
 
229
        
 
230
    def _find_program_path(self, program):
 
231
        """ Determines the full path for the given program.
 
232
        
 
233
        Searches for a given program name on the PATH.
 
234
        
 
235
        Keyword arguments:
 
236
        program -- The name of the program to search for
 
237
        
 
238
        Returns:
 
239
        The full path of the program or None
 
240
        
 
241
        """
 
242
        path = find_path(program)
 
243
        if not path and self.verbose:
 
244
            print "WARNING: No path found for %s" % program
 
245
        return path
 
246
 
 
247
    
 
248
    def _get_dhcp_command(self, flavor=None, hostname=None):
 
249
        """ Returns the correct DHCP client command. 
 
250
       
 
251
        Given a type of DHCP request (create or release a lease),
 
252
        this method will build a command to complete the request
 
253
        using the correct dhcp client, and cli options.
 
254
        
 
255
        """
 
256
        def get_client_name(cl):
 
257
            """ Converts the integer value for a dhcp client to a string. """
 
258
            if self.dhcpcd_cmd and cl in [misc.DHCPCD, misc.AUTO]:
 
259
                client = "dhcpcd"
 
260
                cmd = self.dhcpcd_cmd
 
261
            elif self.pump_cmd and cl in [misc.PUMP, misc.AUTO]: 
 
262
                client = "pump"
 
263
                cmd = self.pump_cmd
 
264
            elif self.dhclient_cmd and cl in [misc.DHCLIENT, misc.AUTO]:
 
265
                client = "dhclient"
 
266
                cmd = self.dhclient_cmd
 
267
                if self.dhclient_needs_verbose:
 
268
                    cmd += ' -v'
 
269
            elif self.udhcpc_cmd and cl in [misc.UDHCPC, misc.AUTO]:
 
270
                client = "udhcpc"
 
271
                cmd = self.udhcpc_cmd
 
272
            else:
 
273
                client = None
 
274
                cmd = ""
 
275
            return (client, cmd)
 
276
 
 
277
                # probably /var/lib/wicd/dhclient.conf with defaults
 
278
        dhclient_conf_path = os.path.join(
 
279
                    wpath.varlib,
 
280
                    'dhclient.conf'
 
281
                )
 
282
        
 
283
        client_dict = {
 
284
            "dhclient" : 
 
285
                {'connect' : r"%(cmd)s -cf %(dhclientconf)s %(iface)s",
 
286
                 'release' : r"%(cmd)s -r %(iface)s",
 
287
                 'id' : misc.DHCLIENT, 
 
288
                 },
 
289
            "pump" : 
 
290
                { 'connect' : r"%(cmd)s -i %(iface)s -h %(hostname)s",
 
291
                  'release' : r"%(cmd)s -r -i %(iface)s",
 
292
                  'id' : misc.PUMP,
 
293
                },
 
294
            "dhcpcd" : 
 
295
                {'connect' : r"%(cmd)s -h %(hostname)s --noipv4ll %(iface)s ",
 
296
                 'release' : r"%(cmd)s -k %(iface)s",
 
297
                 'id' : misc.DHCPCD,
 
298
                },
 
299
            "udhcpc":
 
300
                {'connect' : r"%(cmd)s -n -i %(iface)s -H %(hostname)s ",
 
301
                 'release' : r"killall -SIGUSR2 %(cmd)s",
 
302
                 'id' : misc.UDHCPC,
 
303
                },
 
304
        }
 
305
        (client_name, cmd) = get_client_name(self.DHCP_CLIENT)
 
306
 
 
307
        # cause dhclient doesn't have a handy dandy argument
 
308
        # for specifing the hostname to be sent
 
309
        if client_name == "dhclient" and flavor:
 
310
            if hostname == None:
 
311
                # <hostname> will use the system hostname
 
312
                # we'll use that if there is hostname passed
 
313
                # that shouldn't happen, though
 
314
                hostname = '<hostname>'
 
315
            print 'attempting to set hostname with dhclient'
 
316
            print 'using dhcpcd or another supported client may work better'
 
317
            dhclient_template = \
 
318
                open(os.path.join(wpath.etc, 'dhclient.conf.template'), 'r')
 
319
 
 
320
            output_conf = open(dhclient_conf_path, 'w')
 
321
 
 
322
            for line in dhclient_template.readlines():
 
323
                line = line.replace('$_HOSTNAME', hostname)
 
324
                output_conf.write(line)
 
325
 
 
326
            output_conf.close()
 
327
            dhclient_template.close()
 
328
            os.chmod(dhclient_conf_path, 0644)
 
329
 
 
330
        if not client_name or not cmd:
 
331
            print "WARNING: Failed to find a valid dhcp client!"
 
332
            return ""
 
333
            
 
334
        if flavor == "connect":
 
335
            if not hostname:
 
336
                hostname = os.uname()[1]
 
337
            return client_dict[client_name]['connect'] % \
 
338
                    { "cmd" : cmd,
 
339
                      "iface" : self.iface,
 
340
                      "hostname" : hostname,
 
341
                      'dhclientconf' : dhclient_conf_path }
 
342
        elif flavor == "release":
 
343
            return client_dict[client_name]['release'] % {"cmd":cmd, "iface":self.iface}
 
344
        else:
 
345
            return client_dict[client_name]['id']
 
346
    
 
347
    def AppAvailable(self, app):
 
348
        """ Return whether a given app is available.
 
349
        
 
350
        Given the name of an executable, determines if it is
 
351
        available for use by checking for a defined 'app'_cmd
 
352
        instance variable.
 
353
        
 
354
        """
 
355
        return bool(self.__dict__.get("%s_cmd" % app.replace("-", "")))
 
356
        
 
357
    def Check(self):
 
358
        """ Check that all required tools are available. """
 
359
        # THINGS TO CHECK FOR: ethtool, pptp-linux, dhclient, host
 
360
        self.CheckDHCP()
 
361
        self.CheckWiredTools()
 
362
        self.CheckWirelessTools()
 
363
        self.CheckSudoApplications()
 
364
        self.CheckRouteFlushTool()
 
365
        self.CheckResolvConf()
 
366
 
 
367
    def CheckResolvConf(self):
 
368
        """ Checks for the existence of resolvconf."""
 
369
        self.resolvconf_cmd = self._find_program_path("resolvconf")
 
370
        
 
371
    def CheckDHCP(self):
 
372
        """ Check for the existence of valid DHCP clients. 
 
373
        
 
374
        Checks for the existence of a supported DHCP client.  If one is
 
375
        found, the appropriate values for DHCP_CMD, DHCP_RELEASE, and
 
376
        DHCP_CLIENT are set.  If a supported client is not found, a
 
377
        warning is printed.
 
378
        
 
379
        """
 
380
        self.dhclient_cmd = self._find_program_path("dhclient")
 
381
        if self.dhclient_cmd != None:
 
382
            output = misc.Run(self.dhclient_cmd + " --version",
 
383
                    include_stderr=True)
 
384
            if '4.' in output:
 
385
                self.dhclient_needs_verbose = True
 
386
            else:
 
387
                self.dhclient_needs_verbose = False
 
388
        self.dhcpcd_cmd = self._find_program_path("dhcpcd")
 
389
        self.pump_cmd = self._find_program_path("pump")
 
390
        self.udhcpc_cmd = self._find_program_path("udhcpc")
 
391
        
 
392
    def CheckWiredTools(self):
 
393
        """ Check for the existence of ethtool and mii-tool. """
 
394
        self.miitool_cmd = self._find_program_path("mii-tool")
 
395
        self.ethtool_cmd = self._find_program_path("ethtool")
 
396
            
 
397
    def CheckWirelessTools(self):
 
398
        """ Check for the existence of wpa_cli """
 
399
        self.wpa_cli_cmd = self._find_program_path("wpa_cli")
 
400
        if not self.wpa_cli_cmd:
 
401
            print "wpa_cli not found.  Authentication will not be validated."
 
402
     
 
403
    def CheckRouteFlushTool(self):
 
404
        """ Check for a route flush tool. """
 
405
        self.ip_cmd = self._find_program_path("ip")
 
406
        self.route_cmd = self._find_program_path("route")
 
407
            
 
408
    def CheckSudoApplications(self):
 
409
        self.gksudo_cmd = self._find_program_path("gksudo")
 
410
        self.kdesu_cmd = self._find_program_path("kdesu")
 
411
        self.ktsuss_cmd = self._find_program_path("ktsuss")
 
412
 
 
413
    @neediface(False)
 
414
    def Up(self):
 
415
        """ Bring the network interface up.
 
416
        
 
417
        Returns:
 
418
        True
 
419
        
 
420
        """
 
421
        cmd = 'ifconfig ' + self.iface + ' up'
 
422
        if self.verbose: print cmd
 
423
        misc.Run(cmd)
 
424
        return True
 
425
 
 
426
    @neediface(False)
 
427
    def Down(self):
 
428
        """ Take down the network interface. 
 
429
        
 
430
        Returns:
 
431
        True
 
432
        
 
433
        """
 
434
        cmd = 'ifconfig ' + self.iface + ' down'
 
435
        if self.verbose: print cmd
 
436
        misc.Run(cmd)
 
437
        return True
 
438
    
 
439
    @timedcache(2)
 
440
    @neediface("")
 
441
    def GetIfconfig(self):
 
442
        """ Runs ifconfig and returns the output. """
 
443
        cmd = "ifconfig %s" % self.iface
 
444
        if self.verbose: print cmd
 
445
        return misc.Run(cmd)
 
446
 
 
447
    @neediface("")
 
448
    def SetAddress(self, ip=None, netmask=None, broadcast=None):
 
449
        """ Set the IP addresses of an interface.
 
450
 
 
451
        Keyword arguments:
 
452
        ip -- interface IP address in dotted quad form
 
453
        netmask -- netmask address in dotted quad form
 
454
        broadcast -- broadcast address in dotted quad form
 
455
 
 
456
        """
 
457
        for val in [ip, netmask, broadcast]:
 
458
            if not val:
 
459
                continue
 
460
            if not misc.IsValidIP(val):
 
461
                print 'WARNING: Invalid IP address found, aborting!'
 
462
                return False
 
463
        
 
464
        cmd = ''.join(['ifconfig ', self.iface, ' '])
 
465
        if ip:
 
466
            cmd = ''.join([cmd, ip, ' '])
 
467
        if netmask:
 
468
            cmd = ''.join([cmd, 'netmask ', netmask, ' '])
 
469
        if broadcast:
 
470
            cmd = ''.join([cmd, 'broadcast ', broadcast, ' '])
 
471
        if self.verbose: print cmd
 
472
        misc.Run(cmd)
 
473
 
 
474
    def _parse_dhclient(self, pipe):
 
475
        """ Parse the output of dhclient.
 
476
        
 
477
        Parses the output of dhclient and returns the status of
 
478
        the connection attempt.
 
479
 
 
480
        Keyword arguments:
 
481
        pipe -- stdout pipe to the dhclient process.
 
482
        
 
483
        Returns:
 
484
        'success' if succesful', an error code string otherwise.
 
485
        
 
486
        """
 
487
        dhclient_complete = False
 
488
        dhclient_success = False
 
489
        
 
490
        while not dhclient_complete:
 
491
            line = pipe.readline()
 
492
            if line == '':  # Empty string means dhclient is done.
 
493
                dhclient_complete = True
 
494
            else:
 
495
                print misc.to_unicode(line.strip('\n'))
 
496
            if line.startswith('bound'):
 
497
                dhclient_success = True
 
498
                dhclient_complete = True
 
499
                
 
500
        return self._check_dhcp_result(dhclient_success)
 
501
        
 
502
    def _parse_pump(self, pipe):
 
503
        """ Determines if obtaining an IP using pump succeeded.
 
504
 
 
505
        Keyword arguments:
 
506
        pipe -- stdout pipe to the pump process.
 
507
        
 
508
        Returns:
 
509
        'success' if succesful, an error code string otherwise.
 
510
        
 
511
        """
 
512
        pump_complete = False
 
513
        pump_success = True
 
514
        
 
515
        while not pump_complete:
 
516
            line = pipe.readline()
 
517
            if line == '':
 
518
                pump_complete = True
 
519
            elif line.strip().lower().startswith('Operation failed.'):
 
520
                pump_success = False
 
521
                pump_complete = True
 
522
            print misc.to_unicode(line)
 
523
    
 
524
        return self._check_dhcp_result(pump_success)
 
525
 
 
526
    def _parse_dhcpcd(self, pipe):
 
527
        """ Determines if obtaining an IP using dhcpcd succeeded.
 
528
        
 
529
        Keyword arguments:
 
530
        pipe -- stdout pipe to the dhcpcd process.
 
531
        
 
532
        Returns:
 
533
        'success' if succesful, an error code string otherwise.
 
534
        
 
535
        """
 
536
        dhcpcd_complete = False
 
537
        dhcpcd_success = True
 
538
        
 
539
        while not dhcpcd_complete:
 
540
            line = pipe.readline()
 
541
            if "Error" in line or "timed out" in line:
 
542
                dhcpcd_success = False
 
543
                dhcpcd_complete = True
 
544
            elif line == '':
 
545
                dhcpcd_complete = True
 
546
            print misc.to_unicode(line)
 
547
            
 
548
        return self._check_dhcp_result(dhcpcd_success)
 
549
 
 
550
    def _parse_udhcpc(self, pipe):
 
551
        """ Determines if obtaining an IP using udhcpc succeeded.
 
552
 
 
553
        Keyword arguments:
 
554
        pipe -- stdout pipe to the dhcpcd process.
 
555
 
 
556
        Returns:
 
557
        'success' if successful, an error code string otherwise.
 
558
 
 
559
        """
 
560
        udhcpc_complete = False
 
561
        udhcpc_success = True
 
562
 
 
563
        while not udhcpc_complete:
 
564
            line = pipe.readline()
 
565
            if line.endswith("failing."):
 
566
                udhcpc_success = False
 
567
                udhcpc_complete = True
 
568
            elif line == '':
 
569
                udhcpc_complete = True
 
570
            print line
 
571
 
 
572
        return self._check_dhcp_result(udhcpc_success)
 
573
 
 
574
    def _check_dhcp_result(self, success):
 
575
        """ Print and return the correct DHCP connection result. 
 
576
        
 
577
        Keyword Arguments:
 
578
        success -- boolean specifying if DHCP was succesful.
 
579
        
 
580
        Returns:
 
581
        'success' if success == True, 'dhcp_failed' otherwise.
 
582
        
 
583
        """
 
584
        if success:
 
585
            print 'DHCP connection successful'
 
586
            return 'success'
 
587
        else:
 
588
            print 'DHCP connection failed'
 
589
            return 'dhcp_failed'
 
590
            
 
591
    @neediface(False)
 
592
    def StartDHCP(self, hostname):
 
593
        """ Start the DHCP client to obtain an IP address.
 
594
 
 
595
        Keyword Arguments:
 
596
        hostname -- the hostname to send to the DHCP server
 
597
        
 
598
        Returns:
 
599
        A string representing the result of the DHCP command.  See
 
600
        _check_dhcp_result for the possible values.
 
601
        
 
602
        """
 
603
        cmd = self._get_dhcp_command('connect', hostname)
 
604
        if self.verbose: print cmd
 
605
        self.dhcp_object = misc.Run(cmd, include_stderr=True, return_obj=True)
 
606
        pipe = self.dhcp_object.stdout
 
607
        client_dict = { misc.DHCLIENT : self._parse_dhclient,
 
608
                        misc.DHCPCD : self._parse_dhcpcd,
 
609
                        misc.PUMP : self._parse_pump,
 
610
                        misc.UDHCPC : self._parse_udhcpc,
 
611
                      }
 
612
        
 
613
        DHCP_CLIENT = self._get_dhcp_command()
 
614
        if DHCP_CLIENT in client_dict:
 
615
            ret = client_dict[DHCP_CLIENT](pipe)
 
616
        else:
 
617
            print "ERROR: no dhcp client found"
 
618
            ret = None
 
619
        self.dhcp_object.wait()
 
620
        return ret
 
621
        
 
622
    @neediface(False)
 
623
    def ReleaseDHCP(self):
 
624
        """ Release the DHCP lease for this interface. """
 
625
        cmd = self._get_dhcp_command("release")
 
626
        if self.verbose: print cmd
 
627
        misc.Run(cmd)
 
628
 
 
629
    @neediface(False)
 
630
    def DelDefaultRoute(self):
 
631
        """ Delete only the default route for a device. """
 
632
        if self.ip_cmd and self.flush_tool in [misc.AUTO, misc.IP]:
 
633
            cmd = '%s route del default dev %s' % (self.ip_cmd, self.iface)
 
634
        elif self.route_cmd and self.flush_tool in [misc.AUTO, misc.ROUTE]:
 
635
            cmd = '%s del default dev %s' % (self.route_cmd, self.iface)
 
636
        else:
 
637
            print "No route manipulation command available!"
 
638
            return 
 
639
        if self.verbose: print cmd
 
640
        misc.Run(cmd)
 
641
 
 
642
    @neediface(False)
 
643
    def SetDNS(self, dns1=None, dns2=None, dns3=None, 
 
644
               dns_dom=None, search_dom=None):
 
645
        """ Set the DNS of the system to the specified DNS servers.
 
646
 
 
647
        Opens up resolv.conf and writes in the nameservers.
 
648
 
 
649
        Keyword arguments:
 
650
        dns1 -- IP address of DNS server 1
 
651
        dns2 -- IP address of DNS server 2
 
652
        dns3 -- IP address of DNS server 3
 
653
        dns_dom -- DNS domain
 
654
        search_dom -- DNS search domain
 
655
 
 
656
        """
 
657
        resolv_params = ""
 
658
        if dns_dom:
 
659
            resolv_params += 'domain %s\n' % dns_dom
 
660
        if search_dom:
 
661
            resolv_params += 'search %s\n' % search_dom
 
662
 
 
663
        valid_dns_list = []
 
664
        for dns in (dns1, dns2, dns3):
 
665
            if dns:
 
666
                if misc.IsValidIP(dns):
 
667
                    if self.verbose:
 
668
                        print 'Setting DNS : ' + dns
 
669
                    valid_dns_list.append("nameserver %s\n" % dns)
 
670
                else:
 
671
                    print 'DNS IP %s is not a valid IP address, skipping' % dns
 
672
 
 
673
        if valid_dns_list:
 
674
            resolv_params += ''.join(valid_dns_list)
 
675
 
 
676
        if self.resolvconf_cmd:
 
677
            cmd = [self.resolvconf_cmd, '-a', self.iface]
 
678
            if self.verbose: print cmd
 
679
            p = misc.Run(cmd, include_stderr=True, return_obj=True)
 
680
            p.communicate(input=resolv_params)
 
681
        else:
 
682
            resolv = open("/etc/resolv.conf", "w")
 
683
            resolv.write(resolv_params + "\n")
 
684
            resolv.close()
 
685
        
 
686
    @neediface(False)
 
687
    def FlushRoutes(self):
 
688
        """ Flush network routes for this device. """
 
689
        if self.ip_cmd and self.flush_tool in [misc.AUTO, misc.IP]:
 
690
            cmds = ['%s route flush dev %s' % (self.ip_cmd, self.iface)]
 
691
        elif self.route_cmd and self.flush_tool in [misc.AUTO, misc.ROUTE]:
 
692
            cmds = ['%s del dev %s' % (self.route_cmd, self.iface)]
 
693
        else:
 
694
            print "No flush command available!"
 
695
            cmds = []
 
696
        for cmd in cmds:
 
697
            if self.verbose: print cmd
 
698
            misc.Run(cmd)
 
699
 
 
700
    @neediface(False)
 
701
    def SetDefaultRoute(self, gw):
 
702
        """ Add a default route with the specified gateway.
 
703
 
 
704
        Keyword arguments:
 
705
        gw -- gateway of the default route in dotted quad form
 
706
 
 
707
        """
 
708
        if not misc.IsValidIP(gw):
 
709
            print 'WARNING: Invalid gateway found.  Aborting!'
 
710
            return False
 
711
        cmd = 'route add default gw %s dev %s' % (gw, self.iface)
 
712
        if self.verbose: print cmd
 
713
        misc.Run(cmd)
 
714
 
 
715
    @neediface("")
 
716
    def GetIP(self, ifconfig=""):
 
717
        """ Get the IP address of the interface.
 
718
 
 
719
        Returns:
 
720
        The IP address of the interface in dotted quad form.
 
721
 
 
722
        """
 
723
        if not ifconfig:
 
724
            output = self.GetIfconfig()
 
725
        else:
 
726
            output = ifconfig
 
727
        return misc.RunRegex(ip_pattern, output)
 
728
    
 
729
    @neediface(False)
 
730
    def VerifyAPAssociation(self, gateway):
 
731
        """ Verify assocation with an access point. 
 
732
        
 
733
        Verifies that an access point can be contacted by
 
734
        trying to ping it.
 
735
        
 
736
        """
 
737
        if "iputils" in misc.Run(["ping", "-V"]):
 
738
            cmd = "ping -q -w 3 -c 1 %s" % gateway
 
739
        else:
 
740
            # ping is from inetutils-ping (which doesn't support -w)
 
741
            # or from some other package
 
742
            #
 
743
            # If there's no AP association, this will wait for
 
744
            # timeout, while the above will wait (-w) 3 seconds at
 
745
            # most.
 
746
            cmd = "ping -q -c 1 %s" % gateway
 
747
        if self.verbose: print cmd
 
748
        return misc.LaunchAndWait(cmd)
 
749
 
 
750
    @neediface(False)
 
751
    def IsUp(self, ifconfig=None):
 
752
        """ Determines if the interface is up.
 
753
 
 
754
        Returns:
 
755
        True if the interface is up, False otherwise.
 
756
 
 
757
        """
 
758
        flags_file = '/sys/class/net/%s/flags' % self.iface
 
759
        try:
 
760
            flags = open(flags_file, "r").read().strip()
 
761
        except IOError:
 
762
            print "Could not open %s, using ifconfig to determine status" % flags_file
 
763
            return self._slow_is_up(ifconfig)
 
764
        return bool(int(flags, 16) & 1)
 
765
        
 
766
        
 
767
    def _slow_is_up(self, ifconfig=None):
 
768
        """ Determine if an interface is up using ifconfig. """
 
769
        if not ifconfig:
 
770
            output = self.GetIfconfig()
 
771
        else:
 
772
            output = ifconfig
 
773
        lines = output.split('\n')
 
774
        if len(lines) < 5:
 
775
            return False
 
776
        for line in lines[1:4]:
 
777
            if line.strip().startswith('UP'):
 
778
                return True   
 
779
        return False
 
780
 
 
781
 
 
782
class BaseWiredInterface(BaseInterface):
 
783
    """ Control a wired network interface. """
 
784
    def __init__(self, iface, verbose=False):
 
785
        """ Initialise the wired network interface class.
 
786
 
 
787
        Keyword arguments:
 
788
        iface -- name of the interface
 
789
        verbose -- print all commands
 
790
 
 
791
        """
 
792
        BaseInterface.__init__(self, iface, verbose)
 
793
 
 
794
    @neediface(False)
 
795
    def GetPluggedIn(self):
 
796
        """ Get the current physical connection state.
 
797
 
 
798
        The method will first attempt to use ethtool do determine
 
799
        physical connection state.  Should ethtool fail to run properly,
 
800
        mii-tool will be used instead.
 
801
 
 
802
        Returns:
 
803
        True if a link is detected, False otherwise.
 
804
 
 
805
        """
 
806
        # check for link using /sys/class/net/iface/carrier
 
807
        # is usually more accurate
 
808
        sys_device = '/sys/class/net/%s/' % self.iface
 
809
        carrier_path = sys_device + 'carrier'
 
810
        if not self.IsUp():
 
811
            MAX_TRIES = 3
 
812
            tries = 0
 
813
            self.Up()
 
814
            while True:
 
815
                tries += 1
 
816
                time.sleep(2)
 
817
                if self.IsUp() or tries > MAX_TRIES: break
 
818
      
 
819
        if os.path.exists(carrier_path):
 
820
            carrier = open(carrier_path, 'r')
 
821
            try:
 
822
                link = carrier.read().strip()
 
823
                link = int(link)
 
824
                if link == 1:
 
825
                    return True
 
826
                elif link == 0:
 
827
                    return False
 
828
            except (IOError, ValueError, TypeError):
 
829
                print 'Error checking link using /sys/class/net/%s/carrier' % self.iface
 
830
                
 
831
        if self.ethtool_cmd and self.link_detect in [misc.ETHTOOL, misc.AUTO]:
 
832
            return self._eth_get_plugged_in()
 
833
        elif self.miitool_cmd and self.link_detect in [misc.MIITOOL, misc.AUTO]:
 
834
            return self._mii_get_plugged_in()
 
835
        else:
 
836
            print ('Error: No way of checking for a wired connection. Make ' +
 
837
                   'sure that either mii-tool or ethtool is installed.')
 
838
            return False
 
839
 
 
840
    def _eth_get_plugged_in(self):
 
841
        """ Use ethtool to determine the physical connection state.
 
842
        
 
843
        Returns:
 
844
        True if a link is detected, False otherwise.
 
845
        
 
846
        """
 
847
        cmd = "%s %s" % (self.ethtool_cmd, self.iface)
 
848
        if not self.IsUp():
 
849
            print 'Wired Interface is down, putting it up'
 
850
            self.Up()
 
851
            time.sleep(6)
 
852
        if self.verbose: print cmd
 
853
        tool_data = misc.Run(cmd, include_stderr=True)
 
854
        if misc.RunRegex(re.compile('(Link detected: yes)', re.I | re.M  | re.S),
 
855
                         tool_data):
 
856
            return True
 
857
        else:
 
858
            return False
 
859
    
 
860
    def _mii_get_plugged_in(self):
 
861
        """ Use mii-tool to determine the physical connection state. 
 
862
                
 
863
        Returns:
 
864
        True if a link is detected, False otherwise.
 
865
        
 
866
        """
 
867
        cmd = "%s %s" % (self.miitool_cmd, self.iface)
 
868
        if self.verbose: print cmd
 
869
        tool_data = misc.Run(cmd, include_stderr=True)
 
870
        if misc.RunRegex(re.compile('(Invalid argument)', re.I | re.M  | re.S), 
 
871
                         tool_data) is not None:
 
872
            print 'Wired Interface is down, putting it up'
 
873
            self.Up()
 
874
            time.sleep(4)
 
875
            if self.verbose: print cmd
 
876
            tool_data = misc.Run(cmd, include_stderr=True)
 
877
        
 
878
        if misc.RunRegex(re.compile('(link ok)', re.I | re.M | re.S),
 
879
                         tool_data) is not None:
 
880
            return True
 
881
        else:
 
882
            return False
 
883
        
 
884
 
 
885
class BaseWirelessInterface(BaseInterface):
 
886
    """ Control a wireless network interface. """
 
887
    def __init__(self, iface, verbose=False, wpa_driver='wext'):
 
888
        """ Initialise the wireless network interface class.
 
889
 
 
890
        Keyword arguments:
 
891
        iface -- name of the interface
 
892
        verbose -- print all commands
 
893
 
 
894
        """
 
895
        BaseInterface.__init__(self, iface, verbose)
 
896
        self.wpa_driver = wpa_driver
 
897
        self.scan_iface = None
 
898
        
 
899
    def SetWpaDriver(self, driver):
 
900
        """ Sets the wpa_driver. """
 
901
        self.wpa_driver = _sanitize_string(driver)
 
902
 
 
903
    @neediface(False)
 
904
    def SetEssid(self, essid):
 
905
        """ Set the essid of the wireless interface.
 
906
 
 
907
        Keyword arguments:
 
908
        essid -- essid to set the interface to
 
909
 
 
910
        """
 
911
        cmd = ['iwconfig', self.iface, 'essid', '--', str(essid)]
 
912
        if self.verbose: print str(cmd)
 
913
        misc.Run(cmd)
 
914
 
 
915
    @neediface(False)
 
916
    def GetKillSwitchStatus(self):
 
917
        """ Determines if the wireless killswitch is enabled.
 
918
        
 
919
        Returns:
 
920
        True if the killswitch is enabled, False otherwise.
 
921
        
 
922
        """
 
923
        output = self.GetIwconfig()
 
924
 
 
925
        killswitch_pattern = re.compile('.*radio off', re.I | re.M | re.S)
 
926
        if killswitch_pattern.search(output):
 
927
            radiostatus = True
 
928
        else:
 
929
            radiostatus = False
 
930
 
 
931
        return radiostatus
 
932
    
 
933
    @timedcache(2)
 
934
    @neediface(False)
 
935
    def GetIwconfig(self):
 
936
        """ Returns the output of iwconfig for this interface. """
 
937
        cmd = "iwconfig " + self.iface
 
938
        if self.verbose: print cmd
 
939
        return misc.Run(cmd)
 
940
 
 
941
    def _FreqToChannel(self, freq):
 
942
        """ Translate the specified frequency to a channel.
 
943
 
 
944
        Note: This function is simply a lookup dict and therefore the
 
945
        freq argument must be in the dict to provide a valid channel.
 
946
 
 
947
        Keyword arguments:
 
948
        freq -- string containing the specified frequency
 
949
 
 
950
        Returns:
 
951
        The channel number, or None if not found.
 
952
 
 
953
        """
 
954
        ret = None
 
955
        freq_dict = {'2.412 GHz': 1, '2.417 GHz': 2, '2.422 GHz': 3,
 
956
                         '2.427 GHz': 4, '2.432 GHz': 5, '2.437 GHz': 6,
 
957
                         '2.442 GHz': 7, '2.447 GHz': 8, '2.452 GHz': 9,
 
958
                         '2.457 GHz': 10, '2.462 GHz': 11, '2.467 GHz': 12,
 
959
                         '2.472 GHz': 13, '2.484 GHz': 14 }
 
960
        try:
 
961
            ret = freq_dict[freq]
 
962
        except KeyError:
 
963
            print "Couldn't determine channel number for frequency: " + str(freq)
 
964
        
 
965
        return ret
 
966
 
 
967
    def _GetRalinkInfo(self):
 
968
        """ Get a network info list used for ralink drivers
 
969
    
 
970
        Calls iwpriv <wireless interface> get_site_survey, which
 
971
        on some ralink cards will return encryption and signal
 
972
        strength info for wireless networks in the area.
 
973
    
 
974
        """
 
975
        iwpriv = misc.Run('iwpriv ' + self.iface + ' get_site_survey')
 
976
        if self.verbose:
 
977
            print iwpriv
 
978
        lines = iwpriv.splitlines()[2:]
 
979
        aps = {}
 
980
        patt = re.compile("((?:[0-9A-Z]{2}:){5}[0-9A-Z]{2})")
 
981
        for x in lines:
 
982
            ap = {}
 
983
            info = x.split("   ")
 
984
            info = filter(None, [x.strip() for x in info])
 
985
            if len(info) < 5:
 
986
                continue
 
987
            if re.match(patt, info[2].upper()):
 
988
                bssid = info[2].upper()
 
989
                offset = -1
 
990
            elif re.match(patt, info[3].upper()):
 
991
                bssid = info[3].upper()
 
992
                offset = 0
 
993
            else:  # Invalid
 
994
                print 'Invalid iwpriv line.  Skipping it.'
 
995
                continue
 
996
            ap['nettype'] = info[-1]
 
997
            ap['strength'] = info[1]
 
998
            if info[4 + offset] == 'WEP':
 
999
                ap['encryption_method'] = 'WEP'
 
1000
                ap['enctype'] = 'WEP'
 
1001
                ap['keyname'] = 'Key1'
 
1002
                ap['authmode'] = info[5 + offset]
 
1003
            elif info[5 + offset] in ['WPA-PSK', 'WPA']:
 
1004
                ap['encryption_method'] = 'WPA'
 
1005
                ap['authmode'] = "WPAPSK"
 
1006
                ap['keyname'] = "WPAPSK"
 
1007
                ap['enctype'] = info[4 + offset]
 
1008
            elif info[5 + offset] == 'WPA2-PSK':
 
1009
                ap['encryption_method'] = 'WPA2'
 
1010
                ap['authmode'] ="WPA2PSK"
 
1011
                ap['keyname'] = "WPA2PSK"
 
1012
                ap['enctype'] = info[4 + offset]
 
1013
            elif info[4 + offset] == "NONE":
 
1014
                ap['encryption_method'] = None
 
1015
            else:
 
1016
                print "Unknown AuthMode, can't assign encryption_method!"
 
1017
                ap['encryption_method'] = 'Unknown'
 
1018
            aps[bssid] = ap
 
1019
        if self.verbose: print str(aps)
 
1020
        return aps
 
1021
 
 
1022
    def _ParseRalinkAccessPoint(self, ap, ralink_info, cell):
 
1023
        """ Parse encryption and signal strength info for ralink cards
 
1024
 
 
1025
        Keyword arguments:
 
1026
        ap -- array containing info about the current access point
 
1027
        ralink_info -- dict containing available network info
 
1028
        cell -- string containing cell information
 
1029
 
 
1030
        Returns:
 
1031
        Updated array containing info about the current access point
 
1032
 
 
1033
        """
 
1034
        wep_pattern = re.compile('.*Encryption key:(.*?)\n', re.I | re.M  | re.S)
 
1035
        if ralink_info.has_key(ap['bssid']):
 
1036
            info = ralink_info[ap['bssid']]
 
1037
            for key in info.keys():
 
1038
                ap[key] = info[key]
 
1039
            if misc.RunRegex(wep_pattern, cell) == 'on':
 
1040
                ap['encryption'] = True
 
1041
            else:
 
1042
                ap['encryption'] = False
 
1043
        return ap
 
1044
 
 
1045
    @neediface(False)
 
1046
    def SetMode(self, mode):
 
1047
        """ Set the mode of the wireless interface.
 
1048
 
 
1049
        Keyword arguments:
 
1050
        mode -- mode to set the interface to
 
1051
 
 
1052
        """
 
1053
        mode = _sanitize_string_strict(mode)
 
1054
        if mode.lower() == 'master':
 
1055
            mode = 'managed'
 
1056
        cmd = 'iwconfig %s mode %s' % (self.iface, mode)
 
1057
        if self.verbose: print cmd
 
1058
        misc.Run(cmd)
 
1059
 
 
1060
    @neediface(False)
 
1061
    def SetChannel(self, channel):
 
1062
        """ Set the channel of the wireless interface.
 
1063
 
 
1064
        Keyword arguments:
 
1065
        channel -- channel to set the interface to
 
1066
 
 
1067
        """
 
1068
        if not channel.isdigit():
 
1069
            print 'WARNING: Invalid channel found.  Aborting!'
 
1070
            return False
 
1071
        
 
1072
        cmd = 'iwconfig %s channel %s' % (self.iface, str(channel))
 
1073
        if self.verbose: print cmd
 
1074
        misc.Run(cmd)
 
1075
 
 
1076
    @neediface(False)
 
1077
    def SetKey(self, key):
 
1078
        """ Set the encryption key of the wireless interface.
 
1079
 
 
1080
        Keyword arguments:
 
1081
        key -- encryption key to set
 
1082
 
 
1083
        """
 
1084
        cmd = 'iwconfig %s key %s' % (self.iface, key)
 
1085
        if self.verbose: print cmd
 
1086
        misc.Run(cmd)
 
1087
 
 
1088
    @neediface(False)
 
1089
    def Associate(self, essid, channel=None, bssid=None):
 
1090
        """ Associate with the specified wireless network.
 
1091
 
 
1092
        Keyword arguments:
 
1093
        essid -- essid of the network
 
1094
        channel -- channel of the network
 
1095
        bssid -- bssid of the network
 
1096
 
 
1097
        """
 
1098
        self.SetEssid(essid)
 
1099
        base = "iwconfig %s" % self.iface
 
1100
        if channel and str(channel).isdigit():
 
1101
            cmd = "%s channel %s" % (base, str(channel))
 
1102
            if self.verbose: print cmd
 
1103
            misc.Run(cmd)
 
1104
        if bssid:
 
1105
            cmd = "%s ap %s" % (base, bssid)
 
1106
            if self.verbose: print cmd
 
1107
            misc.Run(cmd)
 
1108
        
 
1109
    def GeneratePSK(self, network):
 
1110
        """ Generate a PSK using wpa_passphrase. 
 
1111
 
 
1112
        Keyword arguments:
 
1113
        network -- dictionary containing network info
 
1114
        
 
1115
        """
 
1116
        wpa_pass_path = misc.find_path('wpa_passphrase')
 
1117
        if not wpa_pass_path: return None
 
1118
        key_pattern = re.compile('network={.*?\spsk=(.*?)\n}.*',
 
1119
                                 re.I | re.M  | re.S)
 
1120
        cmd = [wpa_pass_path, str(network['essid']), str(network['key'])]
 
1121
        if self.verbose: print cmd
 
1122
        return misc.RunRegex(key_pattern, misc.Run(cmd))
 
1123
 
 
1124
    @neediface(False)
 
1125
    def Authenticate(self, network):
 
1126
        """ Authenticate with the specified wireless network.
 
1127
 
 
1128
        Keyword arguments:
 
1129
        network -- dictionary containing network info
 
1130
 
 
1131
        """
 
1132
        misc.ParseEncryption(network)
 
1133
        if self.wpa_driver == RALINK_DRIVER:
 
1134
            self._AuthenticateRalinkLegacy(network)
 
1135
        else:
 
1136
            cmd = ['wpa_supplicant', '-B', '-i', self.iface, '-c',
 
1137
                   os.path.join(wpath.networks, 
 
1138
                                network['bssid'].replace(':', '').lower()),
 
1139
                   '-D', self.wpa_driver]
 
1140
            if self.verbose: print cmd
 
1141
            misc.Run(cmd)
 
1142
 
 
1143
    def _AuthenticateRalinkLegacy(self, network):
 
1144
        """ Authenticate with the specified wireless network.
 
1145
 
 
1146
        This function handles Ralink legacy cards that cannot use
 
1147
        wpa_supplicant.
 
1148
 
 
1149
        Keyword arguments:
 
1150
        network -- dictionary containing network info
 
1151
 
 
1152
        """
 
1153
        if network.get('key') != None:
 
1154
            try:
 
1155
                info = self._GetRalinkInfo()[network.get('bssid')]
 
1156
            except KeyError:
 
1157
                print "Could not find current network in iwpriv " + \
 
1158
                      "get_site_survey results.  Cannot authenticate."
 
1159
                return
 
1160
            
 
1161
            if info['enctype'] == "WEP" and info['authtype'] == 'OPEN':
 
1162
                print 'Setting up WEP'
 
1163
                cmd = ''.join(['iwconfig ', self.iface, ' key ',
 
1164
                              network.get('key')])
 
1165
                if self.verbose: print cmd
 
1166
                misc.Run(cmd)
 
1167
            else:
 
1168
                cmd_list = []
 
1169
                cmd_list.append('NetworkType=' + info['nettype'])
 
1170
                cmd_list.append('AuthMode=' + info['authmode'])
 
1171
                cmd_list.append('EncrypType=' + info['enctype'])
 
1172
                cmd_list.append('SSID="%s"' % network['essid'])
 
1173
                cmd_list.append('%s="%s"' % (network['keyname'], network['key']))
 
1174
                if info['nettype'] == 'SHARED' and info['enctype'] == 'WEP':
 
1175
                    cmd_list.append('DefaultKeyID=1')
 
1176
    
 
1177
                for cmd in cmd_list:
 
1178
                    cmd = ['iwpriv', self.iface, 'set', cmd]
 
1179
                    if self.verbose: print ' '.join(cmd)
 
1180
                    misc.Run(cmd)
 
1181
 
 
1182
    @neediface([])
 
1183
    def GetNetworks(self):
 
1184
        """ Get a list of available wireless networks.
 
1185
 
 
1186
        Returns:
 
1187
        A list containing available wireless networks.
 
1188
 
 
1189
        """
 
1190
        cmd = 'iwlist ' + self.iface + ' scan'
 
1191
        if self.verbose: print cmd
 
1192
        results = misc.Run(cmd)
 
1193
        # Split the networks apart, using Cell as our split point
 
1194
        # this way we can look at only one network at a time.
 
1195
        # The spaces around '   Cell ' are to minimize the chance that someone
 
1196
        # has an essid named Cell...
 
1197
        networks = results.split( '   Cell ' )
 
1198
 
 
1199
        # Get available network info from iwpriv get_site_survey
 
1200
        # if we're using a ralink card (needed to get encryption info)
 
1201
        if self.wpa_driver == RALINK_DRIVER:
 
1202
            ralink_info = self._GetRalinkInfo()
 
1203
        else:
 
1204
            ralink_info = None
 
1205
 
 
1206
        # An array for the access points
 
1207
        access_points = []
 
1208
        access_points = {}
 
1209
        for cell in networks:
 
1210
            # Only use sections where there is an ESSID.
 
1211
            if 'ESSID:' in cell:
 
1212
                # Add this network to the list of networks
 
1213
                entry = self._ParseAccessPoint(cell, ralink_info)
 
1214
                if entry is not None:
 
1215
                    # Normally we only get duplicate bssids with hidden
 
1216
                    # networks.  If we hit this, we only want the entry
 
1217
                    # with the real essid to be in the network list.
 
1218
                    if (entry['bssid'] not in access_points 
 
1219
                        or not entry['hidden']):
 
1220
                        access_points[entry['bssid']] = entry
 
1221
 
 
1222
        return access_points.values()
 
1223
    
 
1224
    def _ParseAccessPoint(self, cell, ralink_info):
 
1225
        """ Parse a single cell from the output of iwlist.
 
1226
 
 
1227
        Keyword arguments:
 
1228
        cell -- string containing the cell information
 
1229
        ralink_info -- string contating network information needed
 
1230
                       for ralink cards.
 
1231
 
 
1232
        Returns:
 
1233
        A dictionary containing the cell networks properties.
 
1234
 
 
1235
        """
 
1236
        ap = {}
 
1237
        ap['essid'] = misc.RunRegex(essid_pattern, cell)
 
1238
        try:
 
1239
            ap['essid'] = misc.to_unicode(ap['essid'])
 
1240
        except (UnicodeDecodeError, UnicodeEncodeError):
 
1241
            print 'Unicode problem with current network essid, ignoring!!'
 
1242
            return None
 
1243
        if ap['essid'] in ['Hidden', '<hidden>', "", None]:
 
1244
            print 'hidden'
 
1245
            ap['hidden'] = True
 
1246
            ap['essid'] = "<hidden>"
 
1247
        else:
 
1248
            ap['hidden'] = False
 
1249
 
 
1250
        # Channel - For cards that don't have a channel number,
 
1251
        # convert the frequency.
 
1252
        ap['channel'] = misc.RunRegex(channel_pattern, cell)
 
1253
        if ap['channel'] == None:
 
1254
            freq = misc.RunRegex(freq_pattern, cell)
 
1255
            ap['channel'] = self._FreqToChannel(freq)
 
1256
 
 
1257
        # Bit Rate
 
1258
        ap['bitrates'] = misc.RunRegex(bitrates_pattern,
 
1259
                                       cell.split("Bit Rates")[-1])
 
1260
     
 
1261
        # BSSID
 
1262
        ap['bssid'] = misc.RunRegex(ap_mac_pattern, cell)
 
1263
 
 
1264
        # Mode
 
1265
        ap['mode'] = misc.RunRegex(mode_pattern, cell)
 
1266
        if ap['mode'] is None:
 
1267
            print 'Invalid network mode string, ignoring!'
 
1268
            return None
 
1269
 
 
1270
        # Break off here if we're using a ralink card
 
1271
        if self.wpa_driver == RALINK_DRIVER:
 
1272
            ap = self._ParseRalinkAccessPoint(ap, ralink_info, cell)
 
1273
        elif misc.RunRegex(wep_pattern, cell) == 'on':
 
1274
            # Encryption - Default to WEP
 
1275
            ap['encryption'] = True
 
1276
            ap['encryption_method'] = 'WEP'
 
1277
 
 
1278
            if misc.RunRegex(wpa1_pattern, cell) == 'WPA Version 1':
 
1279
                ap['encryption_method'] = 'WPA'
 
1280
 
 
1281
            if misc.RunRegex(altwpa_pattern, cell) == 'wpa_ie':
 
1282
                ap['encryption_method'] = 'WPA'
 
1283
 
 
1284
            if misc.RunRegex(wpa2_pattern, cell) == 'WPA2':
 
1285
                ap['encryption_method'] = 'WPA2'
 
1286
        else:
 
1287
            ap['encryption'] = False
 
1288
 
 
1289
        # Link Quality
 
1290
        # Set strength to -1 if the quality is not found
 
1291
        ap['quality'] = self._get_link_quality(cell)
 
1292
        if ap['quality'] is None:
 
1293
            ap['quality'] = -1
 
1294
 
 
1295
        # Signal Strength (only used if user doesn't want link
 
1296
        # quality displayed or it isn't found)
 
1297
        if misc.RunRegex(signaldbm_pattern, cell):
 
1298
            ap['strength'] = misc.RunRegex(signaldbm_pattern, cell)
 
1299
        elif self.wpa_driver != RALINK_DRIVER:  # This is already set for ralink
 
1300
            ap['strength'] = -1
 
1301
 
 
1302
        return ap
 
1303
 
 
1304
    def ValidateAuthentication(self, auth_time):
 
1305
        """ Validate WPA authentication.
 
1306
 
 
1307
            Validate that the wpa_supplicant authentication
 
1308
            process was successful.
 
1309
 
 
1310
            NOTE: It's possible this could return False,
 
1311
            though in reality wpa_supplicant just isn't
 
1312
            finished yet.
 
1313
            
 
1314
            Keyword arguments:
 
1315
            auth_time -- The time at which authentication began.
 
1316
            
 
1317
            Returns:
 
1318
            True if wpa_supplicant authenticated succesfully,
 
1319
            False otherwise.
 
1320
 
 
1321
        """
 
1322
        # Right now there's no way to do this for these drivers
 
1323
        if self.wpa_driver == RALINK_DRIVER or not self.wpa_cli_cmd:
 
1324
            return True
 
1325
 
 
1326
        MAX_TIME = 35
 
1327
        MAX_DISCONNECTED_TIME = 3
 
1328
        disconnected_time = 0
 
1329
        forced_rescan = False
 
1330
        while (time.time() - auth_time) < MAX_TIME:
 
1331
            cmd = '%s -i %s status' % (self.wpa_cli_cmd, self.iface)
 
1332
            output = misc.Run(cmd)
 
1333
            result = misc.RunRegex(auth_pattern, output)
 
1334
            if self.verbose:
 
1335
                print 'WPA_CLI RESULT IS', result
 
1336
 
 
1337
            if not result:
 
1338
                return False
 
1339
            if result == "COMPLETED":
 
1340
                return True
 
1341
            elif result == "DISCONNECTED" and not forced_rescan:
 
1342
                disconnected_time += 1
 
1343
                if disconnected_time > MAX_DISCONNECTED_TIME:
 
1344
                    disconnected_time = 0
 
1345
                    # Force a rescan to get wpa_supplicant moving again.
 
1346
                    forced_rescan = True
 
1347
                    self._ForceSupplicantScan()
 
1348
                    MAX_TIME += 5
 
1349
            else:
 
1350
                disconnected_time = 0
 
1351
            time.sleep(1)
 
1352
 
 
1353
        print 'wpa_supplicant authentication may have failed.'
 
1354
        return False
 
1355
        
 
1356
 
 
1357
    def _ForceSupplicantScan(self):
 
1358
        """ Force wpa_supplicant to rescan available networks.
 
1359
    
 
1360
        This function forces wpa_supplicant to rescan.
 
1361
        This works around authentication validation sometimes failing for
 
1362
        wpa_supplicant because it remains in a DISCONNECTED state for 
 
1363
        quite a while, after which a rescan is required, and then
 
1364
        attempting to authenticate.  This whole process takes a long
 
1365
        time, so we manually speed it up if we see it happening.
 
1366
        
 
1367
        """
 
1368
        print 'wpa_supplicant rescan forced...'
 
1369
        cmd = 'wpa_cli -i' + self.iface + ' scan'
 
1370
        misc.Run(cmd)
 
1371
        
 
1372
    @neediface(False)
 
1373
    def StopWPA(self):
 
1374
        """ Terminates wpa using wpa_cli"""
 
1375
        cmd = 'wpa_cli -i %s terminate' % self.iface
 
1376
        if self.verbose: print cmd
 
1377
        misc.Run(cmd)
 
1378
 
 
1379
    @neediface("")
 
1380
    def GetBSSID(self, iwconfig=None):
 
1381
        """ Get the MAC address for the interface. """
 
1382
        if not iwconfig:
 
1383
            output = self.GetIwconfig()
 
1384
        else:
 
1385
            output = iwconfig
 
1386
            
 
1387
        bssid = misc.RunRegex(bssid_pattern, output)
 
1388
        return bssid
 
1389
 
 
1390
    @neediface("")
 
1391
    def GetCurrentBitrate(self, iwconfig=None):
 
1392
        """ Get the current bitrate for the interface. """
 
1393
        if not iwconfig:
 
1394
            output = self.GetIwconfig()
 
1395
        else:
 
1396
            output = iwconfig
 
1397
            
 
1398
        bitrate = misc.RunRegex(bitrate_pattern, output)
 
1399
        return bitrate
 
1400
 
 
1401
    @neediface("")
 
1402
    def GetOperationalMode(self, iwconfig=None):
 
1403
        """ Get the operational mode for the interface. """
 
1404
        if not iwconfig:
 
1405
            output = self.GetIwconfig()
 
1406
        else:
 
1407
            output = iwconfig
 
1408
            
 
1409
        opmode = misc.RunRegex(opmode_pattern, output)
 
1410
        if opmode:
 
1411
            opmode = opmode.strip()
 
1412
        return opmode
 
1413
 
 
1414
    @neediface("")
 
1415
    def GetAvailableAuthMethods(self, iwlistauth=None):
 
1416
        """ Get the available authentication methods for the interface. """
 
1417
        if not iwlistauth:
 
1418
            cmd = 'iwlist ' + self.iface + ' auth'
 
1419
            if self.verbose: print cmd
 
1420
            output = misc.Run(cmd)
 
1421
        else:
 
1422
            output = iwlistauth
 
1423
            
 
1424
        authm = misc.RunRegex(authmethods_pattern, output)
 
1425
        authm_list = [m.strip() for m in authm.split('\n') if m.strip()]
 
1426
        return ';'.join(authm_list)
 
1427
 
 
1428
    def _get_link_quality(self, output):
 
1429
        """ Parse out the link quality from iwlist scan or iwconfig output. """
 
1430
        try:
 
1431
            [(strength, max_strength)] = strength_pattern.findall(output)
 
1432
        except ValueError:
 
1433
            (strength, max_strength) = (None, None)
 
1434
 
 
1435
        if strength in ['', None]:
 
1436
            try:
 
1437
                [(strength, max_strength)] = altstrength_pattern.findall(output)
 
1438
            except ValueError:
 
1439
                # if the pattern was unable to match anything
 
1440
                # we'll return 101, which will allow us to stay
 
1441
                # connected even though we don't know the strength
 
1442
                # it also allows us to tell if 
 
1443
                return 101
 
1444
        if strength not in ['', None] and max_strength:
 
1445
            return (100 * int(strength) // int(max_strength))
 
1446
        elif strength not in ["", None]:
 
1447
            return int(strength)
 
1448
        else:
 
1449
            return None
 
1450
 
 
1451
    @neediface(-1)
 
1452
    def GetSignalStrength(self, iwconfig=None):
 
1453
        """ Get the signal strength of the current network.
 
1454
 
 
1455
        Returns:
 
1456
        The signal strength.
 
1457
 
 
1458
        """
 
1459
        if not iwconfig:
 
1460
            output = self.GetIwconfig()
 
1461
        else:
 
1462
            output = iwconfig
 
1463
        return self._get_link_quality(output)
 
1464
    
 
1465
    @neediface(-100)
 
1466
    def GetDBMStrength(self, iwconfig=None):
 
1467
        """ Get the dBm signal strength of the current network.
 
1468
 
 
1469
        Returns:
 
1470
        The dBm signal strength.
 
1471
 
 
1472
        """
 
1473
        if not iwconfig:
 
1474
            output = self.GetIwconfig()
 
1475
        else:
 
1476
            output = iwconfig
 
1477
        signaldbm_pattern = re.compile('.*Signal level:?=? ?(-\d\d*)',
 
1478
                                       re.I | re.M | re.S)
 
1479
        dbm_strength = misc.RunRegex(signaldbm_pattern, output)
 
1480
        return dbm_strength
 
1481
 
 
1482
    @neediface("")
 
1483
    def GetCurrentNetwork(self, iwconfig=None):
 
1484
        """ Get the essid of the current network.
 
1485
 
 
1486
        Returns:
 
1487
        The current network essid.
 
1488
 
 
1489
        """
 
1490
        if not iwconfig:
 
1491
            output = self.GetIwconfig()
 
1492
        else:
 
1493
            output = iwconfig
 
1494
        network = misc.RunRegex(re.compile('.*ESSID:"(.*?)"',
 
1495
                                           re.I | re.M  | re.S), output)
 
1496
        if network:
 
1497
            network = misc.to_unicode(network)
 
1498
        return network