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