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
7import abc
8import os
9import shutil
10import sys
11import subprocess
12
13
14class 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
29class 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
161class 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
175def 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
199if __name__ == '__main__':
200  Main()