Home | History | Annotate | Download | only in chromeos
      1 #!/usr/bin/env python
      2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
      3 # Use of this source code is governed by a BSD-style license that can be
      4 # found in the LICENSE file.
      5 
      6 """Helper script to perform actions as a super-user on ChromeOS.
      7 
      8 Needs to be run with superuser privileges, typically using the
      9 suid_python binary.
     10 
     11 Usage:
     12   sudo python suid_actions.py --action=CleanFlimflamDirs
     13 """
     14 
     15 import optparse
     16 import os
     17 import shutil
     18 import subprocess
     19 import sys
     20 import time
     21 
     22 sys.path.append('/usr/local')  # to import autotest libs.
     23 from autotest.cros import constants
     24 from autotest.cros import cryptohome
     25 
     26 TEMP_BACKCHANNEL_FILE = '/tmp/pyauto_network_backchannel_file'
     27 
     28 
     29 class SuidAction(object):
     30   """Helper to perform some super-user actions on ChromeOS."""
     31 
     32   def _ParseArgs(self):
     33     parser = optparse.OptionParser()
     34     parser.add_option(
     35         '-a', '--action', help='Action to perform.')
     36     self._options = parser.parse_args()[0]
     37     if not self._options.action:
     38       raise RuntimeError('No action specified.')
     39 
     40   def Run(self):
     41     self._ParseArgs()
     42     assert os.geteuid() == 0, 'Needs superuser privileges.'
     43     handler = getattr(self, self._options.action)
     44     assert handler and callable(handler), \
     45         'No handler for %s' % self._options.action
     46     handler()
     47     return 0
     48 
     49   ## Actions ##
     50   def CleanFlimflamDirs(self):
     51     """Clean the contents of all connection manager (shill/flimflam) profiles.
     52     """
     53     flimflam_dirs = ['/home/chronos/user/flimflam',
     54                      '/home/chronos/user/shill',
     55                      '/var/cache/flimflam',
     56                      '/var/cache/shill']
     57 
     58     # The stop/start flimflam command should stop/start shill respectivly if
     59     # enabled.
     60     os.system('stop flimflam')
     61     try:
     62       for flimflam_dir in flimflam_dirs:
     63         if not os.path.exists(flimflam_dir):
     64           continue
     65         for item in os.listdir(flimflam_dir):
     66           path = os.path.join(flimflam_dir, item)
     67           if os.path.isdir(path):
     68             shutil.rmtree(path)
     69           else:
     70             os.remove(path)
     71     finally:
     72       os.system('start flimflam')
     73       # TODO(stanleyw): crosbug.com/29421 This method should wait until
     74       # flimflam/shill is fully initialized and accessible via DBus again.
     75       # Otherwise, there is a race conditions and subsequent accesses to
     76       # flimflam/shill may fail. Until this is fixed, waiting for the
     77       # resolv.conf file to be created is better than nothing.
     78       begin = time.time()
     79       while not os.path.exists(constants.RESOLV_CONF_FILE):
     80         if time.time() - begin > 10:
     81           raise RuntimeError('Timeout while waiting for flimflam/shill start.')
     82         time.sleep(.25)
     83 
     84   def RemoveAllCryptohomeVaults(self):
     85     """Remove any existing cryptohome vaults."""
     86     cryptohome.remove_all_vaults()
     87 
     88   def _GetEthInterfaces(self):
     89     """Returns a list of the eth* interfaces detected by the device."""
     90     # Assumes ethernet interfaces all have "eth" in the name.
     91     import pyudev
     92     return sorted([iface.sys_name for iface in
     93                    pyudev.Context().list_devices(subsystem='net')
     94                    if 'eth' in iface.sys_name])
     95 
     96   def _Renameif(self, old_iface, new_iface, mac_address):
     97     """Renames the interface with mac_address from old_iface to new_iface.
     98 
     99     Args:
    100       old_iface: The name of the interface you want to change.
    101       new_iface: The name of the interface you want to change to.
    102       mac_address:  The mac address of the interface being changed.
    103     """
    104     subprocess.call(['stop', 'flimflam'])
    105     subprocess.call(['ifconfig', old_iface, 'down'])
    106     subprocess.call(['nameif', new_iface, mac_address])
    107     subprocess.call(['ifconfig', new_iface, 'up'])
    108     subprocess.call(['start', 'flimflam'])
    109 
    110     # Check and make sure interfaces have been renamed
    111     eth_ifaces = self._GetEthInterfaces()
    112     if new_iface not in eth_ifaces:
    113       raise RuntimeError('Interface %s was not renamed to %s' %
    114                          (old_iface, new_iface))
    115     elif old_iface in eth_ifaces:
    116       raise RuntimeError('Old iface %s is still present' % old_iface)
    117 
    118   def SetupBackchannel(self):
    119     """Renames the connected ethernet interface to eth_test for offline mode
    120        testing.  Does nothing if no connected interface is found.
    121     """
    122     # Return the interface with ethernet connected or returns if none found.
    123     for iface in self._GetEthInterfaces():
    124       with open('/sys/class/net/%s/operstate' % iface, 'r') as fp:
    125         if 'up' in fp.read():
    126           eth_iface = iface
    127           break
    128     else:
    129       return
    130 
    131     # Write backup file to be used by TeardownBackchannel to restore the
    132     # interface names.
    133     with open(TEMP_BACKCHANNEL_FILE, 'w') as fpw:
    134       with open('/sys/class/net/%s/address' % eth_iface) as fp:
    135         mac_address = fp.read().strip()
    136         fpw.write('%s, %s' % (eth_iface, mac_address))
    137 
    138     self._Renameif(eth_iface, 'eth_test', mac_address)
    139 
    140   def TeardownBackchannel(self):
    141     """Restores the eth interface names if SetupBackchannel was called."""
    142     if not os.path.isfile(TEMP_BACKCHANNEL_FILE):
    143       return
    144 
    145     with open(TEMP_BACKCHANNEL_FILE, 'r') as fp:
    146       eth_iface, mac_address = fp.read().split(',')
    147 
    148     self._Renameif('eth_test', eth_iface, mac_address)
    149     os.remove(TEMP_BACKCHANNEL_FILE)
    150 
    151 
    152 if __name__ == '__main__':
    153   sys.exit(SuidAction().Run())
    154