~matttbe/wicd/ubuntu_python27

« back to all changes in this revision

Viewing changes to .pc/19-dhcpcd_dont_handle_link-local.patch/wicd/wnettools.py

  • Committer: Bazaar Package Importer
  • Author(s): David Paleino
  • Date: 2010-03-05 18:12:51 UTC
  • mfrom: (8.2.8 sid)
  • Revision ID: james.westby@ubuntu.com-20100305181251-0fcsn0sty5oy8wlq
Tags: 1.7.0+ds1-2
Fix RC bug: daemon doesn't start anymore because copy.deepcopy()
fails with the iniparse object, coming from 20-use_iniparse.patch.
Bug 568326 reopened. (Closes: #572599)

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:(.*?)\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 %(iface)s -h %(hostname)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
 
 
329
        if not client_name or not cmd:
 
330
            print "WARNING: Failed to find a valid dhcp client!"
 
331
            return ""
 
332
            
 
333
        if flavor == "connect":
 
334
            if not hostname:
 
335
                hostname = os.uname()[1]
 
336
            return client_dict[client_name]['connect'] % \
 
337
                    { "cmd" : cmd,
 
338
                      "iface" : self.iface,
 
339
                      "hostname" : hostname,
 
340
                      'dhclientconf' : dhclient_conf_path }
 
341
        elif flavor == "release":
 
342
            return client_dict[client_name]['release'] % {"cmd":cmd, "iface":self.iface}
 
343
        else:
 
344
            return client_dict[client_name]['id']
 
345
    
 
346
    def AppAvailable(self, app):
 
347
        """ Return whether a given app is available.
 
348
        
 
349
        Given the name of an executable, determines if it is
 
350
        available for use by checking for a defined 'app'_cmd
 
351
        instance variable.
 
352
        
 
353
        """
 
354
        return bool(self.__dict__.get("%s_cmd" % app.replace("-", "")))
 
355
        
 
356
    def Check(self):
 
357
        """ Check that all required tools are available. """
 
358
        # THINGS TO CHECK FOR: ethtool, pptp-linux, dhclient, host
 
359
        self.CheckDHCP()
 
360
        self.CheckWiredTools()
 
361
        self.CheckWirelessTools()
 
362
        self.CheckSudoApplications()
 
363
        self.CheckRouteFlushTool()
 
364
        self.CheckResolvConf()
 
365
 
 
366
    def CheckResolvConf(self):
 
367
        """ Checks for the existence of resolvconf."""
 
368
        self.resolvconf_cmd = self._find_program_path("resolvconf")
 
369
        
 
370
    def CheckDHCP(self):
 
371
        """ Check for the existence of valid DHCP clients. 
 
372
        
 
373
        Checks for the existence of a supported DHCP client.  If one is
 
374
        found, the appropriate values for DHCP_CMD, DHCP_RELEASE, and
 
375
        DHCP_CLIENT are set.  If a supported client is not found, a
 
376
        warning is printed.
 
377
        
 
378
        """
 
379
        self.dhclient_cmd = self._find_program_path("dhclient")
 
380
        if self.dhclient_cmd != None:
 
381
            output = misc.Run(self.dhclient_cmd + " --version",
 
382
                    include_stderr=True)
 
383
            if '4.' in output:
 
384
                self.dhclient_needs_verbose = True
 
385
            else:
 
386
                self.dhclient_needs_verbose = False
 
387
        debian_dhcpcd_cmd = self._find_program_path('dhcpcd-bin')
 
388
        if debian_dhcpcd_cmd:
 
389
            self.dhcpcd_cmd = debian_dhcpcd_cmd
 
390
        else:
 
391
            self.dhcpcd_cmd = self._find_program_path("dhcpcd")
 
392
        self.pump_cmd = self._find_program_path("pump")
 
393
        self.udhcpc_cmd = self._find_program_path("udhcpc")
 
394
        
 
395
    def CheckWiredTools(self):
 
396
        """ Check for the existence of ethtool and mii-tool. """
 
397
        self.miitool_cmd = self._find_program_path("mii-tool")
 
398
        self.ethtool_cmd = self._find_program_path("ethtool")
 
399
            
 
400
    def CheckWirelessTools(self):
 
401
        """ Check for the existence of wpa_cli """
 
402
        self.wpa_cli_cmd = self._find_program_path("wpa_cli")
 
403
        if not self.wpa_cli_cmd:
 
404
            print "wpa_cli not found.  Authentication will not be validated."
 
405
     
 
406
    def CheckRouteFlushTool(self):
 
407
        """ Check for a route flush tool. """
 
408
        self.ip_cmd = self._find_program_path("ip")
 
409
        self.route_cmd = self._find_program_path("route")
 
410
            
 
411
    def CheckSudoApplications(self):
 
412
        self.gksudo_cmd = self._find_program_path("gksudo")
 
413
        self.kdesu_cmd = self._find_program_path("kdesu")
 
414
        self.ktsuss_cmd = self._find_program_path("ktsuss")
 
415
 
 
416
    @neediface(False)
 
417
    def Up(self):
 
418
        """ Bring the network interface up.
 
419
        
 
420
        Returns:
 
421
        True
 
422
        
 
423
        """
 
424
        cmd = 'ifconfig ' + self.iface + ' up'
 
425
        if self.verbose: print cmd
 
426
        misc.Run(cmd)
 
427
        return True
 
428
 
 
429
    @neediface(False)
 
430
    def Down(self):
 
431
        """ Take down the network interface. 
 
432
        
 
433
        Returns:
 
434
        True
 
435
        
 
436
        """
 
437
        cmd = 'ifconfig ' + self.iface + ' down'
 
438
        if self.verbose: print cmd
 
439
        misc.Run(cmd)
 
440
        return True
 
441
    
 
442
    @timedcache(2)
 
443
    @neediface("")
 
444
    def GetIfconfig(self):
 
445
        """ Runs ifconfig and returns the output. """
 
446
        cmd = "ifconfig %s" % self.iface
 
447
        if self.verbose: print cmd
 
448
        return misc.Run(cmd)
 
449
 
 
450
    @neediface("")
 
451
    def SetAddress(self, ip=None, netmask=None, broadcast=None):
 
452
        """ Set the IP addresses of an interface.
 
453
 
 
454
        Keyword arguments:
 
455
        ip -- interface IP address in dotted quad form
 
456
        netmask -- netmask address in dotted quad form
 
457
        broadcast -- broadcast address in dotted quad form
 
458
 
 
459
        """
 
460
        for val in [ip, netmask, broadcast]:
 
461
            if not val:
 
462
                continue
 
463
            if not misc.IsValidIP(val):
 
464
                print 'WARNING: Invalid IP address found, aborting!'
 
465
                return False
 
466
        
 
467
        cmd = ''.join(['ifconfig ', self.iface, ' '])
 
468
        if ip:
 
469
            cmd = ''.join([cmd, ip, ' '])
 
470
        if netmask:
 
471
            cmd = ''.join([cmd, 'netmask ', netmask, ' '])
 
472
        if broadcast:
 
473
            cmd = ''.join([cmd, 'broadcast ', broadcast, ' '])
 
474
        if self.verbose: print cmd
 
475
        misc.Run(cmd)
 
476
 
 
477
    def _parse_dhclient(self, pipe):
 
478
        """ Parse the output of dhclient.
 
479
        
 
480
        Parses the output of dhclient and returns the status of
 
481
        the connection attempt.
 
482
 
 
483
        Keyword arguments:
 
484
        pipe -- stdout pipe to the dhclient process.
 
485
        
 
486
        Returns:
 
487
        'success' if succesful', an error code string otherwise.
 
488
        
 
489
        """
 
490
        dhclient_complete = False
 
491
        dhclient_success = False
 
492
        
 
493
        while not dhclient_complete:
 
494
            line = pipe.readline()
 
495
            if line == '':  # Empty string means dhclient is done.
 
496
                dhclient_complete = True
 
497
            else:
 
498
                print misc.to_unicode(line.strip('\n'))
 
499
            if line.startswith('bound'):
 
500
                dhclient_success = True
 
501
                dhclient_complete = True
 
502
                
 
503
        return self._check_dhcp_result(dhclient_success)
 
504
        
 
505
    def _parse_pump(self, pipe):
 
506
        """ Determines if obtaining an IP using pump succeeded.
 
507
 
 
508
        Keyword arguments:
 
509
        pipe -- stdout pipe to the pump process.
 
510
        
 
511
        Returns:
 
512
        'success' if succesful, an error code string otherwise.
 
513
        
 
514
        """
 
515
        pump_complete = False
 
516
        pump_success = True
 
517
        
 
518
        while not pump_complete:
 
519
            line = pipe.readline()
 
520
            if line == '':
 
521
                pump_complete = True
 
522
            elif line.strip().lower().startswith('Operation failed.'):
 
523
                pump_success = False
 
524
                pump_complete = True
 
525
            print misc.to_unicode(line)
 
526
    
 
527
        return self._check_dhcp_result(pump_success)
 
528
 
 
529
    def _parse_dhcpcd(self, pipe):
 
530
        """ Determines if obtaining an IP using dhcpcd succeeded.
 
531
        
 
532
        Keyword arguments:
 
533
        pipe -- stdout pipe to the dhcpcd process.
 
534
        
 
535
        Returns:
 
536
        'success' if succesful, an error code string otherwise.
 
537
        
 
538
        """
 
539
        dhcpcd_complete = False
 
540
        dhcpcd_success = True
 
541
        
 
542
        while not dhcpcd_complete:
 
543
            line = pipe.readline()
 
544
            if "Error" in line or "timed out" in line:
 
545
                dhcpcd_success = False
 
546
                dhcpcd_complete = True
 
547
            elif line == '':
 
548
                dhcpcd_complete = True
 
549
            print misc.to_unicode(line)
 
550
            
 
551
        return self._check_dhcp_result(dhcpcd_success)
 
552
 
 
553
    def _parse_udhcpc(self, pipe):
 
554
        """ Determines if obtaining an IP using udhcpc succeeded.
 
555
 
 
556
        Keyword arguments:
 
557
        pipe -- stdout pipe to the dhcpcd process.
 
558
 
 
559
        Returns:
 
560
        'success' if successful, an error code string otherwise.
 
561
 
 
562
        """
 
563
        udhcpc_complete = False
 
564
        udhcpc_success = True
 
565
 
 
566
        while not udhcpc_complete:
 
567
            line = pipe.readline()
 
568
            if line.endswith("failing."):
 
569
                udhcpc_success = False
 
570
                udhcpc_complete = True
 
571
            elif line == '':
 
572
                udhcpc_complete = True
 
573
            print line
 
574
 
 
575
        return self._check_dhcp_result(udhcpc_success)
 
576
 
 
577
    def _check_dhcp_result(self, success):
 
578
        """ Print and return the correct DHCP connection result. 
 
579
        
 
580
        Keyword Arguments:
 
581
        success -- boolean specifying if DHCP was succesful.
 
582
        
 
583
        Returns:
 
584
        'success' if success == True, 'dhcp_failed' otherwise.
 
585
        
 
586
        """
 
587
        if success:
 
588
            print 'DHCP connection successful'
 
589
            return 'success'
 
590
        else:
 
591
            print 'DHCP connection failed'
 
592
            return 'dhcp_failed'
 
593
            
 
594
    @neediface(False)
 
595
    def StartDHCP(self, hostname):
 
596
        """ Start the DHCP client to obtain an IP address.
 
597
 
 
598
        Keyword Arguments:
 
599
        hostname -- the hostname to send to the DHCP server
 
600
        
 
601
        Returns:
 
602
        A string representing the result of the DHCP command.  See
 
603
        _check_dhcp_result for the possible values.
 
604
        
 
605
        """
 
606
        cmd = self._get_dhcp_command('connect', hostname)
 
607
        if self.verbose: print cmd
 
608
        self.dhcp_object = misc.Run(cmd, include_stderr=True, return_obj=True)
 
609
        pipe = self.dhcp_object.stdout
 
610
        client_dict = { misc.DHCLIENT : self._parse_dhclient,
 
611
                        misc.DHCPCD : self._parse_dhcpcd,
 
612
                        misc.PUMP : self._parse_pump,
 
613
                        misc.UDHCPC : self._parse_udhcpc,
 
614
                      }
 
615
        
 
616
        DHCP_CLIENT = self._get_dhcp_command()
 
617
        if DHCP_CLIENT in client_dict:
 
618
            ret = client_dict[DHCP_CLIENT](pipe)
 
619
        else:
 
620
            print "ERROR: no dhcp client found"
 
621
            ret = None
 
622
        return ret
 
623
        
 
624
    @neediface(False)
 
625
    def ReleaseDHCP(self):
 
626
        """ Release the DHCP lease for this interface. """
 
627
        cmd = self._get_dhcp_command("release")
 
628
        if self.verbose: print cmd
 
629
        misc.Run(cmd)
 
630
 
 
631
    @neediface(False)
 
632
    def DelDefaultRoute(self):
 
633
        """ Delete only the default route for a device. """
 
634
        if self.ip_cmd and self.flush_tool in [misc.AUTO, misc.IP]:
 
635
            cmd = '%s route del default dev %s' % (self.ip_cmd, self.iface)
 
636
        elif self.route_cmd and self.flush_tool in [misc.AUTO, misc.ROUTE]:
 
637
            cmd = '%s del default dev %s' % (self.route_cmd, self.iface)
 
638
        else:
 
639
            print "No route manipulation command available!"
 
640
            return 
 
641
        if self.verbose: print cmd
 
642
        misc.Run(cmd)
 
643
 
 
644
    @neediface(False)
 
645
    def SetDNS(self, dns1=None, dns2=None, dns3=None, 
 
646
               dns_dom=None, search_dom=None):
 
647
        """ Set the DNS of the system to the specified DNS servers.
 
648
 
 
649
        Opens up resolv.conf and writes in the nameservers.
 
650
 
 
651
        Keyword arguments:
 
652
        dns1 -- IP address of DNS server 1
 
653
        dns2 -- IP address of DNS server 2
 
654
        dns3 -- IP address of DNS server 3
 
655
        dns_dom -- DNS domain
 
656
        search_dom -- DNS search domain
 
657
 
 
658
        """
 
659
        resolv_params = ""
 
660
        if dns_dom:
 
661
            resolv_params += 'domain %s\n' % dns_dom
 
662
        if search_dom:
 
663
            resolv_params += 'search %s\n' % search_dom
 
664
 
 
665
        valid_dns_list = []
 
666
        for dns in (dns1, dns2, dns3):
 
667
            if dns:
 
668
                if misc.IsValidIP(dns):
 
669
                    if self.verbose:
 
670
                        print 'Setting DNS : ' + dns
 
671
                    valid_dns_list.append("nameserver %s\n" % dns)
 
672
                else:
 
673
                    print 'DNS IP %s is not a valid IP address, skipping' % dns
 
674
 
 
675
        if valid_dns_list:
 
676
            resolv_params += ''.join(valid_dns_list)
 
677
 
 
678
        if self.resolvconf_cmd:
 
679
            cmd = [self.resolvconf_cmd, '-a', self.iface]
 
680
            if self.verbose: print cmd
 
681
            p = misc.Run(cmd, include_stderr=True, return_obj=True)
 
682
            p.communicate(input=resolv_params)
 
683
        else:
 
684
            resolv = open("/etc/resolv.conf", "w")
 
685
            resolv.write(resolv_params + "\n")
 
686
            resolv.close()
 
687
        
 
688
    @neediface(False)
 
689
    def FlushRoutes(self):
 
690
        """ Flush network routes for this device. """
 
691
        if self.ip_cmd and self.flush_tool in [misc.AUTO, misc.IP]:
 
692
            cmds = ['%s route flush dev %s' % (self.ip_cmd, self.iface)]
 
693
        elif self.route_cmd and self.flush_tool in [misc.AUTO, misc.ROUTE]:
 
694
            cmds = ['%s del dev %s' % (self.route_cmd, self.iface)]
 
695
        else:
 
696
            print "No flush command available!"
 
697
            cmds = []
 
698
        for cmd in cmds:
 
699
            if self.verbose: print cmd
 
700
            misc.Run(cmd)
 
701
 
 
702
    @neediface(False)
 
703
    def SetDefaultRoute(self, gw):
 
704
        """ Add a default route with the specified gateway.
 
705
 
 
706
        Keyword arguments:
 
707
        gw -- gateway of the default route in dotted quad form
 
708
 
 
709
        """
 
710
        if not misc.IsValidIP(gw):
 
711
            print 'WARNING: Invalid gateway found.  Aborting!'
 
712
            return False
 
713
        cmd = 'route add default gw %s dev %s' % (gw, self.iface)
 
714
        if self.verbose: print cmd
 
715
        misc.Run(cmd)
 
716
 
 
717
    @neediface("")
 
718
    def GetIP(self, ifconfig=""):
 
719
        """ Get the IP address of the interface.
 
720
 
 
721
        Returns:
 
722
        The IP address of the interface in dotted quad form.
 
723
 
 
724
        """
 
725
        if not ifconfig:
 
726
            output = self.GetIfconfig()
 
727
        else:
 
728
            output = ifconfig
 
729
        return misc.RunRegex(ip_pattern, output)
 
730
    
 
731
    @neediface(False)
 
732
    def VerifyAPAssociation(self, gateway):
 
733
        """ Verify assocation with an access point. 
 
734
        
 
735
        Verifies that an access point can be contacted by
 
736
        trying to ping it.
 
737
        
 
738
        """
 
739
        if "iputils" in misc.Run(["ping", "-V"]):
 
740
            cmd = "ping -q -w 3 -c 1 %s" % gateway
 
741
        else:
 
742
            # ping is from inetutils-ping (which doesn't support -w)
 
743
            # or from some other package
 
744
            #
 
745
            # If there's no AP association, this will wait for
 
746
            # timeout, while the above will wait (-w) 3 seconds at
 
747
            # most.
 
748
            cmd = "ping -q -c 1 %s" % gateway
 
749
        if self.verbose: print cmd
 
750
        return misc.LaunchAndWait(cmd)
 
751
 
 
752
    @neediface(False)
 
753
    def IsUp(self, ifconfig=None):
 
754
        """ Determines if the interface is up.
 
755
 
 
756
        Returns:
 
757
        True if the interface is up, False otherwise.
 
758
 
 
759
        """
 
760
        flags_file = '/sys/class/net/%s/flags' % self.iface
 
761
        try:
 
762
            flags = open(flags_file, "r").read().strip()
 
763
        except IOError:
 
764
            print "Could not open %s, using ifconfig to determine status" % flags_file
 
765
            return self._slow_is_up(ifconfig)
 
766
        return bool(int(flags, 16) & 1)
 
767
        
 
768
        
 
769
    def _slow_is_up(self, ifconfig=None):
 
770
        """ Determine if an interface is up using ifconfig. """
 
771
        if not ifconfig:
 
772
            output = self.GetIfconfig()
 
773
        else:
 
774
            output = ifconfig
 
775
        lines = output.split('\n')
 
776
        if len(lines) < 5:
 
777
            return False
 
778
        for line in lines[1:4]:
 
779
            if line.strip().startswith('UP'):
 
780
                return True   
 
781
        return False
 
782
 
 
783
 
 
784
class BaseWiredInterface(BaseInterface):
 
785
    """ Control a wired network interface. """
 
786
    def __init__(self, iface, verbose=False):
 
787
        """ Initialise the wired network interface class.
 
788
 
 
789
        Keyword arguments:
 
790
        iface -- name of the interface
 
791
        verbose -- print all commands
 
792
 
 
793
        """
 
794
        BaseInterface.__init__(self, iface, verbose)
 
795
 
 
796
    @neediface(False)
 
797
    def GetPluggedIn(self):
 
798
        """ Get the current physical connection state.
 
799
 
 
800
        The method will first attempt to use ethtool do determine
 
801
        physical connection state.  Should ethtool fail to run properly,
 
802
        mii-tool will be used instead.
 
803
 
 
804
        Returns:
 
805
        True if a link is detected, False otherwise.
 
806
 
 
807
        """
 
808
        # check for link using /sys/class/net/iface/carrier
 
809
        # is usually more accurate
 
810
        sys_device = '/sys/class/net/%s/' % self.iface
 
811
        carrier_path = sys_device + 'carrier'
 
812
        if not self.IsUp():
 
813
            MAX_TRIES = 3
 
814
            tries = 0
 
815
            self.Up()
 
816
            while True:
 
817
                tries += 1
 
818
                time.sleep(2)
 
819
                if self.IsUp() or tries > MAX_TRIES: break
 
820
      
 
821
        if os.path.exists(carrier_path):
 
822
            carrier = open(carrier_path, 'r')
 
823
            try:
 
824
                link = carrier.read().strip()
 
825
                link = int(link)
 
826
                if link == 1:
 
827
                    return True
 
828
                elif link == 0:
 
829
                    return False
 
830
            except (IOError, ValueError, TypeError):
 
831
                print 'Error checking link using /sys/class/net/%s/carrier' % self.iface
 
832
                
 
833
        if self.ethtool_cmd and self.link_detect in [misc.ETHTOOL, misc.AUTO]:
 
834
            return self._eth_get_plugged_in()
 
835
        elif self.miitool_cmd and self.link_detect in [misc.MIITOOL, misc.AUTO]:
 
836
            return self._mii_get_plugged_in()
 
837
        else:
 
838
            print ('Error: No way of checking for a wired connection. Make ' +
 
839
                   'sure that either mii-tool or ethtool is installed.')
 
840
            return False
 
841
 
 
842
    def _eth_get_plugged_in(self):
 
843
        """ Use ethtool to determine the physical connection state.
 
844
        
 
845
        Returns:
 
846
        True if a link is detected, False otherwise.
 
847
        
 
848
        """
 
849
        cmd = "%s %s" % (self.ethtool_cmd, self.iface)
 
850
        if not self.IsUp():
 
851
            print 'Wired Interface is down, putting it up'
 
852
            self.Up()
 
853
            time.sleep(6)
 
854
        if self.verbose: print cmd
 
855
        tool_data = misc.Run(cmd, include_stderr=True)
 
856
        if misc.RunRegex(re.compile('(Link detected: yes)', re.I | re.M  | re.S),
 
857
                         tool_data):
 
858
            return True
 
859
        else:
 
860
            return False
 
861
    
 
862
    def _mii_get_plugged_in(self):
 
863
        """ Use mii-tool to determine the physical connection state. 
 
864
                
 
865
        Returns:
 
866
        True if a link is detected, False otherwise.
 
867
        
 
868
        """
 
869
        cmd = "%s %s" % (self.miitool_cmd, self.iface)
 
870
        if self.verbose: print cmd
 
871
        tool_data = misc.Run(cmd, include_stderr=True)
 
872
        if misc.RunRegex(re.compile('(Invalid argument)', re.I | re.M  | re.S), 
 
873
                         tool_data) is not None:
 
874
            print 'Wired Interface is down, putting it up'
 
875
            self.Up()
 
876
            time.sleep(4)
 
877
            if self.verbose: print cmd
 
878
            tool_data = misc.Run(cmd, include_stderr=True)
 
879
        
 
880
        if misc.RunRegex(re.compile('(link ok)', re.I | re.M | re.S),
 
881
                         tool_data) is not None:
 
882
            return True
 
883
        else:
 
884
            return False
 
885
        
 
886
 
 
887
class BaseWirelessInterface(BaseInterface):
 
888
    """ Control a wireless network interface. """
 
889
    def __init__(self, iface, verbose=False, wpa_driver='wext'):
 
890
        """ Initialise the wireless network interface class.
 
891
 
 
892
        Keyword arguments:
 
893
        iface -- name of the interface
 
894
        verbose -- print all commands
 
895
 
 
896
        """
 
897
        BaseInterface.__init__(self, iface, verbose)
 
898
        self.wpa_driver = wpa_driver
 
899
        self.scan_iface = None
 
900
        
 
901
    def SetWpaDriver(self, driver):
 
902
        """ Sets the wpa_driver. """
 
903
        self.wpa_driver = _sanitize_string(driver)
 
904
 
 
905
    @neediface(False)
 
906
    def SetEssid(self, essid):
 
907
        """ Set the essid of the wireless interface.
 
908
 
 
909
        Keyword arguments:
 
910
        essid -- essid to set the interface to
 
911
 
 
912
        """
 
913
        cmd = ['iwconfig', self.iface, 'essid', '--', str(essid)]
 
914
        if self.verbose: print str(cmd)
 
915
        misc.Run(cmd)
 
916
 
 
917
    @neediface(False)
 
918
    def GetKillSwitchStatus(self):
 
919
        """ Determines if the wireless killswitch is enabled.
 
920
        
 
921
        Returns:
 
922
        True if the killswitch is enabled, False otherwise.
 
923
        
 
924
        """
 
925
        output = self.GetIwconfig()
 
926
 
 
927
        killswitch_pattern = re.compile('.*radio off', re.I | re.M | re.S)
 
928
        if killswitch_pattern.search(output):
 
929
            radiostatus = True
 
930
        else:
 
931
            radiostatus = False
 
932
 
 
933
        return radiostatus
 
934
    
 
935
    @timedcache(2)
 
936
    @neediface(False)
 
937
    def GetIwconfig(self):
 
938
        """ Returns the output of iwconfig for this interface. """
 
939
        cmd = "iwconfig " + self.iface
 
940
        if self.verbose: print cmd
 
941
        return misc.Run(cmd)
 
942
 
 
943
    def _FreqToChannel(self, freq):
 
944
        """ Translate the specified frequency to a channel.
 
945
 
 
946
        Note: This function is simply a lookup dict and therefore the
 
947
        freq argument must be in the dict to provide a valid channel.
 
948
 
 
949
        Keyword arguments:
 
950
        freq -- string containing the specified frequency
 
951
 
 
952
        Returns:
 
953
        The channel number, or None if not found.
 
954
 
 
955
        """
 
956
        ret = None
 
957
        freq_dict = {'2.412 GHz': 1, '2.417 GHz': 2, '2.422 GHz': 3,
 
958
                         '2.427 GHz': 4, '2.432 GHz': 5, '2.437 GHz': 6,
 
959
                         '2.442 GHz': 7, '2.447 GHz': 8, '2.452 GHz': 9,
 
960
                         '2.457 GHz': 10, '2.462 GHz': 11, '2.467 GHz': 12,
 
961
                         '2.472 GHz': 13, '2.484 GHz': 14 }
 
962
        try:
 
963
            ret = freq_dict[freq]
 
964
        except KeyError:
 
965
            print "Couldn't determine channel number for frequency: " + str(freq)
 
966
        
 
967
        return ret
 
968
 
 
969
    def _GetRalinkInfo(self):
 
970
        """ Get a network info list used for ralink drivers
 
971
    
 
972
        Calls iwpriv <wireless interface> get_site_survey, which
 
973
        on some ralink cards will return encryption and signal
 
974
        strength info for wireless networks in the area.
 
975
    
 
976
        """
 
977
        iwpriv = misc.Run('iwpriv ' + self.iface + ' get_site_survey')
 
978
        if self.verbose:
 
979
            print iwpriv
 
980
        lines = iwpriv.splitlines()[2:]
 
981
        aps = {}
 
982
        patt = re.compile("((?:[0-9A-Z]{2}:){5}[0-9A-Z]{2})")
 
983
        for x in lines:
 
984
            ap = {}
 
985
            info = x.split("   ")
 
986
            info = filter(None, [x.strip() for x in info])
 
987
            if len(info) < 5:
 
988
                continue
 
989
            if re.match(patt, info[2].upper()):
 
990
                bssid = info[2].upper()
 
991
                offset = -1
 
992
            elif re.match(patt, info[3].upper()):
 
993
                bssid = info[3].upper()
 
994
                offset = 0
 
995
            else:  # Invalid
 
996
                print 'Invalid iwpriv line.  Skipping it.'
 
997
                continue
 
998
            ap['nettype'] = info[-1]
 
999
            ap['strength'] = info[1]
 
1000
            if info[4 + offset] == 'WEP':
 
1001
                ap['encryption_method'] = 'WEP'
 
1002
                ap['enctype'] = 'WEP'
 
1003
                ap['keyname'] = 'Key1'
 
1004
                ap['authmode'] = info[5 + offset]
 
1005
            elif info[5 + offset] in ['WPA-PSK', 'WPA']:
 
1006
                ap['encryption_method'] = 'WPA'
 
1007
                ap['authmode'] = "WPAPSK"
 
1008
                ap['keyname'] = "WPAPSK"
 
1009
                ap['enctype'] = info[4 + offset]
 
1010
            elif info[5 + offset] == 'WPA2-PSK':
 
1011
                ap['encryption_method'] = 'WPA2'
 
1012
                ap['authmode'] ="WPA2PSK"
 
1013
                ap['keyname'] = "WPA2PSK"
 
1014
                ap['enctype'] = info[4 + offset]
 
1015
            elif info[4 + offset] == "NONE":
 
1016
                ap['encryption_method'] = None
 
1017
            else:
 
1018
                print "Unknown AuthMode, can't assign encryption_method!"
 
1019
                ap['encryption_method'] = 'Unknown'
 
1020
            aps[bssid] = ap
 
1021
        if self.verbose: print str(aps)
 
1022
        return aps
 
1023
 
 
1024
    def _ParseRalinkAccessPoint(self, ap, ralink_info, cell):
 
1025
        """ Parse encryption and signal strength info for ralink cards
 
1026
 
 
1027
        Keyword arguments:
 
1028
        ap -- array containing info about the current access point
 
1029
        ralink_info -- dict containing available network info
 
1030
        cell -- string containing cell information
 
1031
 
 
1032
        Returns:
 
1033
        Updated array containing info about the current access point
 
1034
 
 
1035
        """
 
1036
        wep_pattern = re.compile('.*Encryption key:(.*?)\n', re.I | re.M  | re.S)
 
1037
        if ralink_info.has_key(ap['bssid']):
 
1038
            info = ralink_info[ap['bssid']]
 
1039
            for key in info.keys():
 
1040
                ap[key] = info[key]
 
1041
            if misc.RunRegex(wep_pattern, cell) == 'on':
 
1042
                ap['encryption'] = True
 
1043
            else:
 
1044
                ap['encryption'] = False
 
1045
        return ap
 
1046
 
 
1047
    @neediface(False)
 
1048
    def SetMode(self, mode):
 
1049
        """ Set the mode of the wireless interface.
 
1050
 
 
1051
        Keyword arguments:
 
1052
        mode -- mode to set the interface to
 
1053
 
 
1054
        """
 
1055
        mode = _sanitize_string_strict(mode)
 
1056
        if mode.lower() == 'master':
 
1057
            mode = 'managed'
 
1058
        cmd = 'iwconfig %s mode %s' % (self.iface, mode)
 
1059
        if self.verbose: print cmd
 
1060
        misc.Run(cmd)
 
1061
 
 
1062
    @neediface(False)
 
1063
    def SetChannel(self, channel):
 
1064
        """ Set the channel of the wireless interface.
 
1065
 
 
1066
        Keyword arguments:
 
1067
        channel -- channel to set the interface to
 
1068
 
 
1069
        """
 
1070
        if not channel.isdigit():
 
1071
            print 'WARNING: Invalid channel found.  Aborting!'
 
1072
            return False
 
1073
        
 
1074
        cmd = 'iwconfig %s channel %s' % (self.iface, str(channel))
 
1075
        if self.verbose: print cmd
 
1076
        misc.Run(cmd)
 
1077
 
 
1078
    @neediface(False)
 
1079
    def SetKey(self, key):
 
1080
        """ Set the encryption key of the wireless interface.
 
1081
 
 
1082
        Keyword arguments:
 
1083
        key -- encryption key to set
 
1084
 
 
1085
        """
 
1086
        cmd = 'iwconfig %s key %s' % (self.iface, key)
 
1087
        if self.verbose: print cmd
 
1088
        misc.Run(cmd)
 
1089
 
 
1090
    @neediface(False)
 
1091
    def Associate(self, essid, channel=None, bssid=None):
 
1092
        """ Associate with the specified wireless network.
 
1093
 
 
1094
        Keyword arguments:
 
1095
        essid -- essid of the network
 
1096
        channel -- channel of the network
 
1097
        bssid -- bssid of the network
 
1098
 
 
1099
        """
 
1100
        cmd = ['iwconfig', self.iface, 'essid', essid]
 
1101
        if self.verbose: print str(cmd)
 
1102
        misc.Run(cmd)
 
1103
        base = "iwconfig %s" % self.iface
 
1104
        if channel and str(channel).isdigit():
 
1105
            cmd = "%s channel %s" % (base, str(channel))
 
1106
            if self.verbose: print cmd
 
1107
            misc.Run(cmd)
 
1108
        if bssid:
 
1109
            cmd = "%s ap %s" % (base, bssid)
 
1110
            if self.verbose: print cmd
 
1111
            misc.Run(cmd)
 
1112
        
 
1113
    def GeneratePSK(self, network):
 
1114
        """ Generate a PSK using wpa_passphrase. 
 
1115
 
 
1116
        Keyword arguments:
 
1117
        network -- dictionary containing network info
 
1118
        
 
1119
        """
 
1120
        wpa_pass_path = misc.find_path('wpa_passphrase')
 
1121
        if not wpa_pass_path: return None
 
1122
        key_pattern = re.compile('network={.*?\spsk=(.*?)\n}.*',
 
1123
                                 re.I | re.M  | re.S)
 
1124
        cmd = [wpa_pass_path, str(network['essid']), str(network['key'])]
 
1125
        if self.verbose: print cmd
 
1126
        return misc.RunRegex(key_pattern, misc.Run(cmd))
 
1127
 
 
1128
    @neediface(False)
 
1129
    def Authenticate(self, network):
 
1130
        """ Authenticate with the specified wireless network.
 
1131
 
 
1132
        Keyword arguments:
 
1133
        network -- dictionary containing network info
 
1134
 
 
1135
        """
 
1136
        misc.ParseEncryption(network)
 
1137
        if self.wpa_driver == RALINK_DRIVER:
 
1138
            self._AuthenticateRalinkLegacy(network)
 
1139
        else:
 
1140
            cmd = ['wpa_supplicant', '-B', '-i', self.iface, '-c',
 
1141
                   os.path.join(wpath.networks, 
 
1142
                                network['bssid'].replace(':', '').lower()),
 
1143
                   '-D', self.wpa_driver]
 
1144
            if self.verbose: print cmd
 
1145
            misc.Run(cmd)
 
1146
 
 
1147
    def _AuthenticateRalinkLegacy(self, network):
 
1148
        """ Authenticate with the specified wireless network.
 
1149
 
 
1150
        This function handles Ralink legacy cards that cannot use
 
1151
        wpa_supplicant.
 
1152
 
 
1153
        Keyword arguments:
 
1154
        network -- dictionary containing network info
 
1155
 
 
1156
        """
 
1157
        if network.get('key') != None:
 
1158
            try:
 
1159
                info = self._GetRalinkInfo()[network.get('bssid')]
 
1160
            except KeyError:
 
1161
                print "Could not find current network in iwpriv " + \
 
1162
                      "get_site_survey results.  Cannot authenticate."
 
1163
                return
 
1164
            
 
1165
            if info['enctype'] == "WEP" and info['authtype'] == 'OPEN':
 
1166
                print 'Setting up WEP'
 
1167
                cmd = ''.join(['iwconfig ', self.iface, ' key ',
 
1168
                              network.get('key')])
 
1169
                if self.verbose: print cmd
 
1170
                misc.Run(cmd)
 
1171
            else:
 
1172
                cmd_list = []
 
1173
                cmd_list.append('NetworkType=' + info['nettype'])
 
1174
                cmd_list.append('AuthMode=' + info['authmode'])
 
1175
                cmd_list.append('EncrypType=' + info['enctype'])
 
1176
                cmd_list.append('SSID="%s"' % network['essid'])
 
1177
                cmd_list.append('%s="%s"' % (network['keyname'], network['key']))
 
1178
                if info['nettype'] == 'SHARED' and info['enctype'] == 'WEP':
 
1179
                    cmd_list.append('DefaultKeyID=1')
 
1180
    
 
1181
                for cmd in cmd_list:
 
1182
                    cmd = ['iwpriv', self.iface, 'set', cmd]
 
1183
                    if self.verbose: print ' '.join(cmd)
 
1184
                    misc.Run(cmd)
 
1185
 
 
1186
    @neediface([])
 
1187
    def GetNetworks(self):
 
1188
        """ Get a list of available wireless networks.
 
1189
 
 
1190
        Returns:
 
1191
        A list containing available wireless networks.
 
1192
 
 
1193
        """
 
1194
        cmd = 'iwlist ' + self.iface + ' scan'
 
1195
        if self.verbose: print cmd
 
1196
        results = misc.Run(cmd)
 
1197
        # Split the networks apart, using Cell as our split point
 
1198
        # this way we can look at only one network at a time.
 
1199
        # The spaces around '   Cell ' are to minimize the chance that someone
 
1200
        # has an essid named Cell...
 
1201
        networks = results.split( '   Cell ' )
 
1202
 
 
1203
        # Get available network info from iwpriv get_site_survey
 
1204
        # if we're using a ralink card (needed to get encryption info)
 
1205
        if self.wpa_driver == RALINK_DRIVER:
 
1206
            ralink_info = self._GetRalinkInfo()
 
1207
        else:
 
1208
            ralink_info = None
 
1209
 
 
1210
        # An array for the access points
 
1211
        access_points = []
 
1212
        access_points = {}
 
1213
        for cell in networks:
 
1214
            # Only use sections where there is an ESSID.
 
1215
            if 'ESSID:' in cell:
 
1216
                # Add this network to the list of networks
 
1217
                entry = self._ParseAccessPoint(cell, ralink_info)
 
1218
                if entry is not None:
 
1219
                    # Normally we only get duplicate bssids with hidden
 
1220
                    # networks.  If we hit this, we only want the entry
 
1221
                    # with the real essid to be in the network list.
 
1222
                    if (entry['bssid'] not in access_points 
 
1223
                        or not entry['hidden']):
 
1224
                        access_points[entry['bssid']] = entry
 
1225
 
 
1226
        return access_points.values()
 
1227
    
 
1228
    def _ParseAccessPoint(self, cell, ralink_info):
 
1229
        """ Parse a single cell from the output of iwlist.
 
1230
 
 
1231
        Keyword arguments:
 
1232
        cell -- string containing the cell information
 
1233
        ralink_info -- string contating network information needed
 
1234
                       for ralink cards.
 
1235
 
 
1236
        Returns:
 
1237
        A dictionary containing the cell networks properties.
 
1238
 
 
1239
        """
 
1240
        ap = {}
 
1241
        ap['essid'] = misc.RunRegex(essid_pattern, cell)
 
1242
        try:
 
1243
            ap['essid'] = misc.to_unicode(ap['essid'])
 
1244
        except (UnicodeDecodeError, UnicodeEncodeError):
 
1245
            print 'Unicode problem with current network essid, ignoring!!'
 
1246
            return None
 
1247
        if ap['essid'] in ['Hidden', '<hidden>', "", None]:
 
1248
            print 'hidden'
 
1249
            ap['hidden'] = True
 
1250
            ap['essid'] = "<hidden>"
 
1251
        else:
 
1252
            ap['hidden'] = False
 
1253
 
 
1254
        # Channel - For cards that don't have a channel number,
 
1255
        # convert the frequency.
 
1256
        ap['channel'] = misc.RunRegex(channel_pattern, cell)
 
1257
        if ap['channel'] == None:
 
1258
            freq = misc.RunRegex(freq_pattern, cell)
 
1259
            ap['channel'] = self._FreqToChannel(freq)
 
1260
 
 
1261
        # Bit Rate
 
1262
        ap['bitrates'] = misc.RunRegex(bitrates_pattern,
 
1263
                                       cell.split("Bit Rates")[-1])
 
1264
     
 
1265
        # BSSID
 
1266
        ap['bssid'] = misc.RunRegex(ap_mac_pattern, cell)
 
1267
 
 
1268
        # Mode
 
1269
        ap['mode'] = misc.RunRegex(mode_pattern, cell)
 
1270
 
 
1271
        # Break off here if we're using a ralink card
 
1272
        if self.wpa_driver == RALINK_DRIVER:
 
1273
            ap = self._ParseRalinkAccessPoint(ap, ralink_info, cell)
 
1274
        elif misc.RunRegex(wep_pattern, cell) == 'on':
 
1275
            # Encryption - Default to WEP
 
1276
            ap['encryption'] = True
 
1277
            ap['encryption_method'] = 'WEP'
 
1278
 
 
1279
            if misc.RunRegex(wpa1_pattern, cell) == 'WPA Version 1':
 
1280
                ap['encryption_method'] = 'WPA'
 
1281
 
 
1282
            if misc.RunRegex(altwpa_pattern, cell) == 'wpa_ie':
 
1283
                ap['encryption_method'] = 'WPA'
 
1284
 
 
1285
            if misc.RunRegex(wpa2_pattern, cell) == 'WPA2':
 
1286
                ap['encryption_method'] = 'WPA2'
 
1287
        else:
 
1288
            ap['encryption'] = False
 
1289
 
 
1290
        # Link Quality
 
1291
        # Set strength to -1 if the quality is not found
 
1292
        ap['quality'] = self._get_link_quality(cell)
 
1293
        if ap['quality'] is None:
 
1294
            ap['quality'] = -1
 
1295
 
 
1296
        # Signal Strength (only used if user doesn't want link
 
1297
        # quality displayed or it isn't found)
 
1298
        if misc.RunRegex(signaldbm_pattern, cell):
 
1299
            ap['strength'] = misc.RunRegex(signaldbm_pattern, cell)
 
1300
        elif self.wpa_driver != RALINK_DRIVER:  # This is already set for ralink
 
1301
            ap['strength'] = -1
 
1302
 
 
1303
        return ap
 
1304
 
 
1305
    def ValidateAuthentication(self, auth_time):
 
1306
        """ Validate WPA authentication.
 
1307
 
 
1308
            Validate that the wpa_supplicant authentication
 
1309
            process was successful.
 
1310
 
 
1311
            NOTE: It's possible this could return False,
 
1312
            though in reality wpa_supplicant just isn't
 
1313
            finished yet.
 
1314
            
 
1315
            Keyword arguments:
 
1316
            auth_time -- The time at which authentication began.
 
1317
            
 
1318
            Returns:
 
1319
            True if wpa_supplicant authenticated succesfully,
 
1320
            False otherwise.
 
1321
 
 
1322
        """
 
1323
        # Right now there's no way to do this for these drivers
 
1324
        if self.wpa_driver == RALINK_DRIVER or not self.wpa_cli_cmd:
 
1325
            return True
 
1326
 
 
1327
        MAX_TIME = 35
 
1328
        MAX_DISCONNECTED_TIME = 3
 
1329
        disconnected_time = 0
 
1330
        forced_rescan = False
 
1331
        while (time.time() - auth_time) < MAX_TIME:
 
1332
            cmd = '%s -i %s status' % (self.wpa_cli_cmd, self.iface)
 
1333
            output = misc.Run(cmd)
 
1334
            result = misc.RunRegex(auth_pattern, output)
 
1335
            if self.verbose:
 
1336
                print 'WPA_CLI RESULT IS', result
 
1337
 
 
1338
            if not result:
 
1339
                return False
 
1340
            if result == "COMPLETED":
 
1341
                return True
 
1342
            elif result == "DISCONNECTED" and not forced_rescan:
 
1343
                disconnected_time += 1
 
1344
                if disconnected_time > MAX_DISCONNECTED_TIME:
 
1345
                    disconnected_time = 0
 
1346
                    # Force a rescan to get wpa_supplicant moving again.
 
1347
                    forced_rescan = True
 
1348
                    self._ForceSupplicantScan()
 
1349
                    MAX_TIME += 5
 
1350
            else:
 
1351
                disconnected_time = 0
 
1352
            time.sleep(1)
 
1353
 
 
1354
        print 'wpa_supplicant authentication may have failed.'
 
1355
        return False
 
1356
        
 
1357
 
 
1358
    def _ForceSupplicantScan(self):
 
1359
        """ Force wpa_supplicant to rescan available networks.
 
1360
    
 
1361
        This function forces wpa_supplicant to rescan.
 
1362
        This works around authentication validation sometimes failing for
 
1363
        wpa_supplicant because it remains in a DISCONNECTED state for 
 
1364
        quite a while, after which a rescan is required, and then
 
1365
        attempting to authenticate.  This whole process takes a long
 
1366
        time, so we manually speed it up if we see it happening.
 
1367
        
 
1368
        """
 
1369
        print 'wpa_supplicant rescan forced...'
 
1370
        cmd = 'wpa_cli -i' + self.iface + ' scan'
 
1371
        misc.Run(cmd)
 
1372
        
 
1373
    @neediface(False)
 
1374
    def StopWPA(self):
 
1375
        """ Terminates wpa using wpa_cli"""
 
1376
        cmd = 'wpa_cli -i %s terminate' % self.iface
 
1377
        if self.verbose: print cmd
 
1378
        misc.Run(cmd)
 
1379
 
 
1380
    @neediface("")
 
1381
    def GetBSSID(self, iwconfig=None):
 
1382
        """ Get the MAC address for the interface. """
 
1383
        if not iwconfig:
 
1384
            output = self.GetIwconfig()
 
1385
        else:
 
1386
            output = iwconfig
 
1387
            
 
1388
        bssid = misc.RunRegex(bssid_pattern, output)
 
1389
        return bssid
 
1390
 
 
1391
    @neediface("")
 
1392
    def GetCurrentBitrate(self, iwconfig=None):
 
1393
        """ Get the current bitrate for the interface. """
 
1394
        if not iwconfig:
 
1395
            output = self.GetIwconfig()
 
1396
        else:
 
1397
            output = iwconfig
 
1398
            
 
1399
        bitrate = misc.RunRegex(bitrate_pattern, output)
 
1400
        return bitrate
 
1401
 
 
1402
    @neediface("")
 
1403
    def GetOperationalMode(self, iwconfig=None):
 
1404
        """ Get the operational mode for the interface. """
 
1405
        if not iwconfig:
 
1406
            output = self.GetIwconfig()
 
1407
        else:
 
1408
            output = iwconfig
 
1409
            
 
1410
        opmode = misc.RunRegex(opmode_pattern, output)
 
1411
        if opmode:
 
1412
            opmode = opmode.strip()
 
1413
        return opmode
 
1414
 
 
1415
    @neediface("")
 
1416
    def GetAvailableAuthMethods(self, iwlistauth=None):
 
1417
        """ Get the available authentication methods for the interface. """
 
1418
        if not iwlistauth:
 
1419
            cmd = 'iwlist ' + self.iface + ' auth'
 
1420
            if self.verbose: print cmd
 
1421
            output = misc.Run(cmd)
 
1422
        else:
 
1423
            output = iwlistauth
 
1424
            
 
1425
        authm = misc.RunRegex(authmethods_pattern, output)
 
1426
        authm_list = [m.strip() for m in authm.split('\n') if m.strip()]
 
1427
        return ';'.join(authm_list)
 
1428
 
 
1429
    def _get_link_quality(self, output):
 
1430
        """ Parse out the link quality from iwlist scan or iwconfig output. """
 
1431
        try:
 
1432
            [(strength, max_strength)] = strength_pattern.findall(output)
 
1433
        except ValueError:
 
1434
            (strength, max_strength) = (None, None)
 
1435
 
 
1436
        if strength in ['', None]:
 
1437
            try:
 
1438
                [(strength, max_strength)] = altstrength_pattern.findall(output)
 
1439
            except ValueError:
 
1440
                # if the pattern was unable to match anything
 
1441
                # we'll return 101, which will allow us to stay
 
1442
                # connected even though we don't know the strength
 
1443
                # it also allows us to tell if 
 
1444
                return 101
 
1445
        if strength not in ['', None] and max_strength:
 
1446
            return (100 * int(strength) // int(max_strength))
 
1447
        elif strength not in ["", None]:
 
1448
            return int(strength)
 
1449
        else:
 
1450
            return None
 
1451
 
 
1452
    @neediface(-1)
 
1453
    def GetSignalStrength(self, iwconfig=None):
 
1454
        """ Get the signal strength of the current network.
 
1455
 
 
1456
        Returns:
 
1457
        The signal strength.
 
1458
 
 
1459
        """
 
1460
        if not iwconfig:
 
1461
            output = self.GetIwconfig()
 
1462
        else:
 
1463
            output = iwconfig
 
1464
        return self._get_link_quality(output)
 
1465
    
 
1466
    @neediface(-100)
 
1467
    def GetDBMStrength(self, iwconfig=None):
 
1468
        """ Get the dBm signal strength of the current network.
 
1469
 
 
1470
        Returns:
 
1471
        The dBm signal strength.
 
1472
 
 
1473
        """
 
1474
        if not iwconfig:
 
1475
            output = self.GetIwconfig()
 
1476
        else:
 
1477
            output = iwconfig
 
1478
        signaldbm_pattern = re.compile('.*Signal level:?=? ?(-\d\d*)',
 
1479
                                       re.I | re.M | re.S)
 
1480
        dbm_strength = misc.RunRegex(signaldbm_pattern, output)
 
1481
        return dbm_strength
 
1482
 
 
1483
    @neediface("")
 
1484
    def GetCurrentNetwork(self, iwconfig=None):
 
1485
        """ Get the essid of the current network.
 
1486
 
 
1487
        Returns:
 
1488
        The current network essid.
 
1489
 
 
1490
        """
 
1491
        if not iwconfig:
 
1492
            output = self.GetIwconfig()
 
1493
        else:
 
1494
            output = iwconfig
 
1495
        network = misc.RunRegex(re.compile('.*ESSID:"(.*?)"',
 
1496
                                           re.I | re.M  | re.S), output)
 
1497
        if network:
 
1498
            network = misc.to_unicode(network)
 
1499
        return network