1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 # Use of this source code is governed by a BSD-style license that can be 3 # found in the LICENSE file. 4 5 """Chromoting helper to install/uninstall host and replace pref pane.""" 6 7 import abc 8 import os 9 import shutil 10 import sys 11 import subprocess 12 13 14 class ChromotingHelper(object): 15 """Chromoting helper base class.""" 16 __metaclass__ = abc.ABCMeta 17 18 @abc.abstractmethod 19 def InstallHost(self, bin_dir): 20 """Installs the chromoting host""" 21 return 22 23 @abc.abstractmethod 24 def UninstallHost(self, bin_dir): 25 """Uninstalls the chromoting host""" 26 return 27 28 29 class ChromotingHelperMac(ChromotingHelper): 30 """Chromoting Helper class for Mac. 31 32 Installs/uninstalls host and replace the pref pane for testing purpose. 33 """ 34 35 def InstallHost(self, bin_dir): 36 """Installs host on Mac.""" 37 assert os.geteuid() == 0, 'Need superuser privileges' 38 39 # Run most of the steps here with login user 40 login_uid = os.getuid() 41 os.seteuid(login_uid) 42 43 # Change the working dir to the dir that has the host zip file 44 current_dir = os.getcwd() 45 pyautolib_dir = os.path.dirname(os.path.abspath(__file__)) 46 os.chdir(bin_dir) 47 host_dir = 'remoting-me2me-host-mac' 48 output_dir = os.path.join(host_dir, 'output') 49 50 # Remove remoting-me2me-host-mac dir just in case 51 shutil.rmtree(host_dir, True) 52 53 # Unzip the host archive and prepare the files/dirs 54 subprocess.call('unzip remoting-me2me-host-mac.zip', shell=True) 55 subprocess.call('mkdir ' + output_dir, shell=True) 56 57 # Prepare security identity for code signing purpose 58 os.seteuid(0) 59 key_chain = '/Library/Keychains/ChromotingTest' 60 password = '1111' 61 key = os.path.join(pyautolib_dir, 'chromoting_key.p12') 62 cert = os.path.join(pyautolib_dir, 'chromoting_cert.p12') 63 subprocess.call(['security', 'delete-keychain', key_chain]) 64 subprocess.call(['security', 'create-keychain', '-p', 65 password, key_chain]) 66 subprocess.call(['security', 'import', key, 67 '-k', key_chain, '-P', password, '-A']) 68 subprocess.call(['security', 'import', cert, 69 '-k', key_chain, '-P', password]) 70 os.seteuid(login_uid) 71 72 # Sign the host 73 do_signing = os.path.join(host_dir, 'do_signing.sh') 74 subprocess.call(do_signing + ' ' + output_dir + ' ' + host_dir + ' ' + 75 key_chain + ' "Chromoting Test"', shell=True) 76 77 # Remove security identify 78 os.seteuid(0) 79 subprocess.call(['security', 'delete-keychain', key_chain]) 80 os.seteuid(login_uid) 81 82 # Figure out the dmg name 83 version = "" 84 for output_file in os.listdir(output_dir): 85 if output_file.endswith('.dmg'): 86 version = os.path.basename(output_file)[len('ChromotingHost-'):-4] 87 88 # Mount before installation 89 dmg = os.path.join(output_dir, 'ChromotingHost-' + version + '.dmg') 90 subprocess.call('hdiutil' + ' mount ' + dmg, shell=True) 91 92 # Install host 93 os.seteuid(0) 94 mpkg = os.path.join('/Volumes', 'Chromoting Host ' + version, 95 'Chromoting Host.pkg') 96 subprocess.call(['/usr/sbin/installer', '-pkg', 97 mpkg, '-target', '/']) 98 os.seteuid(login_uid) 99 100 # Unmount after installation 101 mounted = os.path.join('/Volumes', 'Chromoting Host ' + version) 102 subprocess.call('hdiutil detach "' + mounted + '"', shell=True) 103 104 # Clean up remoting-me2me-host-mac dir 105 shutil.rmtree(host_dir, True) 106 107 # Resume the original working dir 108 os.chdir(current_dir) 109 110 def UninstallHost(self, bin_dir): 111 """Uninstalls host on Mac.""" 112 assert os.geteuid() == 0, 'Need superuser privileges' 113 uninstall_app = os.path.join('/', 'Applications', 114 'Chromoting Host Uninstaller.app', 115 'Contents', 'MacOS', 116 'remoting_host_uninstaller') 117 subprocess.call([uninstall_app, '--no-ui']) 118 119 def ReplacePrefPaneMac(self, operation): 120 """Constructs mock pref pane to replace the actual pref pane on Mac.""" 121 assert os.geteuid() == 0, 'Need superuser privileges' 122 123 pref_pane_dir = os.path.join('/Library', 'PreferencePanes') 124 125 mock_pref_pane = os.path.join(pref_pane_dir, 'mock_pref_pane') 126 pref_pane = os.path.join(pref_pane_dir, 127 'org.chromium.chromoting.prefPane') 128 mock_pref_pane_python = os.path.join( 129 os.path.dirname(os.path.abspath(__file__)), 130 'mock_pref_pane.py') 131 132 # When the symlink from real pref pane to mock pref pane exists, 133 # mock pref pane will be modified to be a dir when the host is installed. 134 # After the host is installed and mock pref pane is modified to be a file, 135 # it will be a file until next host installation. 136 if os.path.isdir(mock_pref_pane): 137 shutil.rmtree(mock_pref_pane, True) 138 elif os.path.isfile(mock_pref_pane): 139 os.remove(mock_pref_pane) 140 141 mock_pref_pane_file = open(mock_pref_pane, 'w') 142 mock_pref_pane_file.write('#!/bin/bash\n') 143 mock_pref_pane_file.write('\n') 144 mock_pref_pane_file.write('suid-python' + 145 ' ' + mock_pref_pane_python + ' ' + operation) 146 mock_pref_pane_file.close() 147 148 subprocess.call(['chmod', 'a+x', mock_pref_pane]) 149 150 # The real pref pane is a dir if the host is installed on a clean machine. 151 # Once the test is run on the machine, real pref pane will be replaced to 152 # a symlink. 153 if os.path.isdir(pref_pane): 154 shutil.rmtree(pref_pane, True) 155 elif os.path.isfile(pref_pane): 156 os.remove(pref_pane) 157 158 subprocess.call(['ln', '-s', mock_pref_pane, pref_pane]) 159 160 161 class ChromotingHelperWindows(ChromotingHelper): 162 """Chromoting Helper class for Windows for installing/uninstalling host.""" 163 164 def InstallHost(self, bin_dir): 165 """Installs host on Windows.""" 166 host_msi = os.path.join(bin_dir, 'chromoting.msi') 167 subprocess.Popen(['msiexec', '/i', host_msi, '/passive']).wait() 168 169 def UninstallHost(self, bin_dir): 170 """Uninstalls host on Windows.""" 171 host_msi = os.path.join(bin_dir, 'chromoting.msi') 172 subprocess.Popen(['msiexec', '/x', host_msi, '/passive']).wait() 173 174 175 def Main(): 176 """Main function to dispatch operations.""" 177 assert sys.platform.startswith('win') or \ 178 sys.platform.startswith('darwin'), \ 179 'Only support Windows and Mac' 180 181 if sys.platform.startswith('win'): 182 helper = ChromotingHelperWindows() 183 elif sys.platform.startswith('darwin'): 184 helper = ChromotingHelperMac() 185 186 if sys.argv[1] == 'install': 187 helper.InstallHost(sys.argv[2]) 188 elif sys.argv[1] == 'uninstall': 189 helper.UninstallHost(sys.argv[2]) 190 elif sys.argv[1] in ['enable', 'disable', 'changepin']: 191 assert sys.platform.startswith('darwin'), \ 192 'Replacing pref pane is Mac specific' 193 helper.ReplacePrefPaneMac(sys.argv[1]) 194 else: 195 print >>sys.stderr, 'Invalid syntax' 196 return 1 197 198 199 if __name__ == '__main__': 200 Main()