Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(260)

Side by Side Diff: remoting/tools/me2me_virtual_host.py

Issue 10830225: Don't use auth.json in me2me_virtual_host. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 8 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 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 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 # Virtual Me2Me implementation. This script runs and manages the processes 6 # Virtual Me2Me implementation. This script runs and manages the processes
7 # required for a Virtual Me2Me desktop, which are: X server, X desktop 7 # required for a Virtual Me2Me desktop, which are: X server, X desktop
8 # session, and Host process. 8 # session, and Host process.
9 # This script is intended to run continuously as a background daemon 9 # This script is intended to run continuously as a background daemon
10 # process, running under an ordinary (non-root) user account. 10 # process, running under an ordinary (non-root) user account.
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after
67 FIRST_X_DISPLAY_NUMBER = 20 67 FIRST_X_DISPLAY_NUMBER = 20
68 68
69 X_AUTH_FILE = os.path.expanduser("~/.Xauthority") 69 X_AUTH_FILE = os.path.expanduser("~/.Xauthority")
70 os.environ["XAUTHORITY"] = X_AUTH_FILE 70 os.environ["XAUTHORITY"] = X_AUTH_FILE
71 71
72 72
73 # Globals needed by the atexit cleanup() handler. 73 # Globals needed by the atexit cleanup() handler.
74 g_desktops = [] 74 g_desktops = []
75 g_pidfile = None 75 g_pidfile = None
76 76
77 class Config:
78 def __init__(self, path):
79 self.path = path
80 self.data = {}
81 self.changed = False
82
83 def load(self):
84 try:
85 settings_file = open(self.path, 'r')
86 self.data = json.load(settings_file)
87 self.changed = False
88 settings_file.close()
89 except:
90 return False
91 return True
92
93 def save(self):
94 if not self.changed:
95 return True
96 try:
97 old_umask = os.umask(0066)
98 settings_file = open(self.path, 'w')
99 settings_file.write(json.dumps(self.data, indent=2))
100 settings_file.close()
101 os.umask(old_umask)
102 except:
103 return False
104 self.changed = False
105 return True
106
107 def get(self, key):
108 return self.data.get(key)
109
110 def __getitem__(self, key):
111 return self.data[key]
112
113 def __setitem__(self, key, value):
114 self.data[key] = value
115 self.changed = True
116
117 def clear_auth(self):
118 del self.data["xmpp_login"]
119 del self.data["chromoting_auth_token"]
120 del self.data["xmpp_auth_token"]
121
122 def clear_host_info(self):
123 del self.data["host_id"]
124 del self.data["host_name"]
125 del self.data["host_secret_hash"]
126 del self.data["private_key"]
77 127
78 class Authentication: 128 class Authentication:
79 """Manage authentication tokens for Chromoting/xmpp""" 129 """Manage authentication tokens for Chromoting/xmpp"""
80 130
81 def __init__(self, config_file): 131 def __init__(self):
82 self.config_file = config_file 132 pass
83 133
84 def generate_tokens(self): 134 def generate_tokens(self):
85 """Prompt for username/password and use them to generate new authentication 135 """Prompt for username/password and use them to generate new authentication
86 tokens. 136 tokens.
87 137
88 Raises: 138 Raises:
89 Exception: Failed to get new authentication tokens. 139 Exception: Failed to get new authentication tokens.
90 """ 140 """
91 print "Email:", 141 print "Email:",
92 self.login = raw_input() 142 self.login = raw_input()
93 password = getpass.getpass("App-specific password: ") 143 password = getpass.getpass("App-specific password: ")
94 144
95 chromoting_auth = gaia_auth.GaiaAuthenticator('chromoting') 145 chromoting_auth = gaia_auth.GaiaAuthenticator('chromoting')
96 self.chromoting_auth_token = chromoting_auth.authenticate(self.login, 146 self.chromoting_auth_token = chromoting_auth.authenticate(self.login,
97 password) 147 password)
98 148
99 xmpp_authenticator = gaia_auth.GaiaAuthenticator('chromiumsync') 149 xmpp_authenticator = gaia_auth.GaiaAuthenticator('chromiumsync')
100 self.xmpp_auth_token = xmpp_authenticator.authenticate(self.login, 150 self.xmpp_auth_token = xmpp_authenticator.authenticate(self.login,
101 password) 151 password)
102 152
103 def load_config(self): 153 def load_config(self, config):
104 try: 154 try:
105 settings_file = open(self.config_file, 'r') 155 self.login = config["xmpp_login"]
106 data = json.load(settings_file) 156 self.chromoting_auth_token = config["chromoting_auth_token"]
107 settings_file.close() 157 self.xmpp_auth_token = config["xmpp_auth_token"]
108 self.login = data["xmpp_login"] 158 except KeyError:
109 self.chromoting_auth_token = data["chromoting_auth_token"]
110 self.xmpp_auth_token = data["xmpp_auth_token"]
111 except:
112 return False 159 return False
113 return True 160 return True
114 161
115 def save_config(self): 162 def save_config(self, config):
116 data = { 163 config["xmpp_login"] = self.login
117 "xmpp_login": self.login, 164 config["chromoting_auth_token"] = self.chromoting_auth_token
118 "chromoting_auth_token": self.chromoting_auth_token, 165 config["xmpp_auth_token"] = self.xmpp_auth_token
119 "xmpp_auth_token": self.xmpp_auth_token,
120 }
121 # File will contain private keys, so deny read/write access to others.
122 old_umask = os.umask(0066)
123 settings_file = open(self.config_file, 'w')
124 settings_file.write(json.dumps(data, indent=2))
125 settings_file.close()
126 os.umask(old_umask)
127
128 166
129 class Host: 167 class Host:
130 """This manages the configuration for a host. 168 """This manages the configuration for a host.
131 169
132 Callers should instantiate a Host object (passing in a filename where the 170 Callers should instantiate a Host object (passing in a filename where the
133 config will be kept), then should call either of the methods: 171 config will be kept), then should call either of the methods:
134 172
135 * register(auth): Create a new Host configuration and register it 173 * register(auth): Create a new Host configuration and register it
136 with the Directory Service (the "auth" parameter is used to 174 with the Directory Service (the "auth" parameter is used to
137 authenticate with the Service). 175 authenticate with the Service).
138 * load_config(): Load a config from disk, with details of an existing Host 176 * load_config(): Load a config from disk, with details of an existing Host
139 registration. 177 registration.
140 178
141 After calling register() (or making any config changes) the method 179 After calling register() (or making any config changes) the method
142 save_config() should be called to save the details to disk. 180 save_config() should be called to save the details to disk.
143 """ 181 """
144 182
145 server = 'www.googleapis.com' 183 server = 'www.googleapis.com'
146 url = 'https://' + server + '/chromoting/v1/@me/hosts' 184 url = 'https://' + server + '/chromoting/v1/@me/hosts'
147 185
148 def __init__(self, config_file, auth): 186 def __init__(self, auth):
149 """ 187 """
150 Args: 188 Args:
151 config_file: Host configuration file path 189 config: Host configuration object
152 auth: Authentication object with credentials for authenticating with the 190 auth: Authentication object with credentials for authenticating with the
153 Directory service. 191 Directory service.
154 """ 192 """
155 self.config_file = config_file
156 self.auth = auth 193 self.auth = auth
157 self.host_id = str(uuid.uuid1()) 194 self.host_id = str(uuid.uuid1())
158 self.host_name = socket.gethostname() 195 self.host_name = socket.gethostname()
159 self.host_secret_hash = None 196 self.host_secret_hash = None
160 self.private_key = None 197 self.private_key = None
161 198
162 def register(self): 199 def register(self):
163 """Generates a private key for the stored |host_id|, and registers it with 200 """Generates a private key for the stored |host_id|, and registers it with
164 the Directory service. 201 the Directory service.
165 202
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after
215 def set_pin(self, pin): 252 def set_pin(self, pin):
216 if pin == "": 253 if pin == "":
217 self.host_secret_hash = "plain:" 254 self.host_secret_hash = "plain:"
218 else: 255 else:
219 self.host_secret_hash = "hmac:" + base64.b64encode( 256 self.host_secret_hash = "hmac:" + base64.b64encode(
220 hmac.new(str(self.host_id), pin, hashlib.sha256).digest()) 257 hmac.new(str(self.host_id), pin, hashlib.sha256).digest())
221 258
222 def is_pin_set(self): 259 def is_pin_set(self):
223 return self.host_secret_hash 260 return self.host_secret_hash
224 261
225 def load_config(self): 262 def load_config(self, config):
226 try: 263 try:
227 settings_file = open(self.config_file, 'r') 264 self.host_id = config["host_id"]
228 data = json.load(settings_file) 265 self.host_name = config["host_name"]
229 settings_file.close() 266 self.host_secret_hash = config.get("host_secret_hash")
230 except: 267 self.private_key = config["private_key"]
231 logging.info("Failed to load: " + self.config_file) 268 except KeyError:
232 return False 269 return False
233 self.host_id = data["host_id"]
234 self.host_name = data["host_name"]
235 self.host_secret_hash = data.get("host_secret_hash")
236 self.private_key = data["private_key"]
237 return True 270 return True
238 271
239 def save_config(self): 272 def save_config(self, config):
240 data = { 273 config["host_id"] = self.host_id
241 "host_id": self.host_id, 274 config["host_name"] = self.host_name
242 "host_name": self.host_name, 275 config["host_secret_hash"] = self.host_secret_hash
243 "host_secret_hash": self.host_secret_hash, 276 config["private_key"] = self.private_key
244 "private_key": self.private_key,
245 }
246 if self.host_secret_hash:
247 data["host_secret_hash"] = self.host_secret_hash
248
249 old_umask = os.umask(0066)
250 settings_file = open(self.config_file, 'w')
251 settings_file.write(json.dumps(data, indent=2))
252 settings_file.close()
253 os.umask(old_umask)
254
255 277
256 class Desktop: 278 class Desktop:
257 """Manage a single virtual desktop""" 279 """Manage a single virtual desktop"""
258 280
259 def __init__(self, sizes): 281 def __init__(self, sizes):
260 self.x_proc = None 282 self.x_proc = None
261 self.session_proc = None 283 self.session_proc = None
262 self.host_proc = None 284 self.host_proc = None
263 self.sizes = sizes 285 self.sizes = sizes
264 g_desktops.append(self) 286 g_desktops.append(self)
(...skipping 108 matching lines...) Expand 10 before | Expand all | Expand 10 after
373 # Daemonization would solve this problem by separating the process from the 395 # Daemonization would solve this problem by separating the process from the
374 # controlling terminal. 396 # controlling terminal.
375 logging.info("Launching X session: %s" % XSESSION_COMMAND) 397 logging.info("Launching X session: %s" % XSESSION_COMMAND)
376 self.session_proc = subprocess.Popen(XSESSION_COMMAND, 398 self.session_proc = subprocess.Popen(XSESSION_COMMAND,
377 stdin=open(os.devnull, "r"), 399 stdin=open(os.devnull, "r"),
378 cwd=HOME_DIR, 400 cwd=HOME_DIR,
379 env=self.child_env) 401 env=self.child_env)
380 if not self.session_proc.pid: 402 if not self.session_proc.pid:
381 raise Exception("Could not start X session") 403 raise Exception("Could not start X session")
382 404
383 def launch_host(self, host): 405 def launch_host(self, host_config):
384 # Start remoting host 406 # Start remoting host
385 args = [locate_executable(REMOTING_COMMAND), 407 args = [locate_executable(REMOTING_COMMAND),
386 "--host-config=%s" % (host.config_file)] 408 "--host-config=%s" % (host_config.path)]
387 if host.auth.config_file != host.config_file:
388 args.append("--auth-config=%s" % (host.auth.config_file))
389 self.host_proc = subprocess.Popen(args, env=self.child_env) 409 self.host_proc = subprocess.Popen(args, env=self.child_env)
390 logging.info(args) 410 logging.info(args)
391 if not self.host_proc.pid: 411 if not self.host_proc.pid:
392 raise Exception("Could not start remoting host") 412 raise Exception("Could not start remoting host")
393 413
394 414
395 class PidFile: 415 class PidFile:
396 """Class to allow creating and deleting a file which holds the PID of the 416 """Class to allow creating and deleting a file which holds the PID of the
397 running process. This is used to detect if a process is already running, and 417 running process. This is used to detect if a process is already running, and
398 inform the user of the PID. On process termination, the PID file is 418 inform the user of the PID. On process termination, the PID file is
(...skipping 296 matching lines...) Expand 10 before | Expand all | Expand 10 after
695 715
696 atexit.register(cleanup) 716 atexit.register(cleanup)
697 717
698 for s in [signal.SIGHUP, signal.SIGINT, signal.SIGTERM, signal.SIGUSR1]: 718 for s in [signal.SIGHUP, signal.SIGINT, signal.SIGTERM, signal.SIGUSR1]:
699 signal.signal(s, signal_handler) 719 signal.signal(s, signal_handler)
700 720
701 # Ensure full path to config directory exists. 721 # Ensure full path to config directory exists.
702 if not os.path.exists(CONFIG_DIR): 722 if not os.path.exists(CONFIG_DIR):
703 os.makedirs(CONFIG_DIR, mode=0700) 723 os.makedirs(CONFIG_DIR, mode=0700)
704 724
705 host_config_file = os.path.join(CONFIG_DIR, "host#%s.json" % host_hash) 725 host_config = Config(os.path.join(CONFIG_DIR, "host#%s.json" % host_hash))
726 host_config.load()
706 727
707 # --silent option is specified when we are started from WebApp UI. Don't use 728 auth = Authentication()
708 # separate auth file in that case. 729 auth_config_loaded = auth.load_config(host_config)
709 # TODO(sergeyu): Always use host config for auth parameters. 730 if not auth_config_loaded and not options.silent:
710 if options.silent: 731 # If we failed to load authentication parameters from the host config
711 auth_config_file = host_config_file 732 # then try loading them from the legacy auth.json file.
712 else: 733 auth_config = Config(os.path.join(CONFIG_DIR, "auth.json"))
713 auth_config_file = os.path.join(CONFIG_DIR, "auth.json") 734 if auth_config.load():
735 auth_config_loaded = auth.load_config(auth_config)
736 # If we were able to read auth.json then copy its content to the host
737 # config.
738 if auth_config_loaded:
739 auth.save_config(host_config)
740 host_config.save()
714 741
715 auth = Authentication(auth_config_file) 742 host = Host(auth)
716 auth_config_loaded = auth.load_config() 743 host_config_loaded = host.load_config(host_config)
717
718 host = Host(host_config_file, auth)
719 host_config_loaded = host.load_config()
720 744
721 if options.silent: 745 if options.silent:
722 if not host_config_loaded or not auth_config_loaded: 746 if not host_config_loaded or not auth_config_loaded:
723 logging.error("Failed to load host configuration.") 747 logging.error("Failed to load host configuration.")
724 return 1 748 return 1
725 else: 749 else:
726 need_auth_tokens = not auth_config_loaded 750 need_auth_tokens = not auth_config_loaded
727 need_register_host = not host_config_loaded 751 need_register_host = not host_config_loaded
728 # Outside the loop so user doesn't get asked twice. 752 # Outside the loop so user doesn't get asked twice.
729 if need_register_host: 753 if need_register_host:
730 host.ask_pin() 754 host.ask_pin()
731 elif options.new_pin or not host.is_pin_set(): 755 elif options.new_pin or not host.is_pin_set():
732 host.ask_pin() 756 host.ask_pin()
733 host.save_config() 757 host.save_config(host_config)
734 running, pid = PidFile(pid_filename).check() 758 running, pid = PidFile(pid_filename).check()
735 if running and pid != 0: 759 if running and pid != 0:
736 os.kill(pid, signal.SIGUSR1) 760 os.kill(pid, signal.SIGUSR1)
737 print "The running instance has been updated with the new PIN." 761 print "The running instance has been updated with the new PIN."
738 return 0 762 return 0
739 763
740 # The loop is to deal with the case of registering a new Host with 764 # The loop is to deal with the case of registering a new Host with
741 # previously-saved auth tokens (from a previous run of this script), which 765 # previously-saved auth tokens (from a previous run of this script), which
742 # may require re-prompting for username & password. 766 # may require re-prompting for username & password.
743 while True: 767 while True:
744 try: 768 if need_auth_tokens:
745 if need_auth_tokens: 769 try:
746 auth.generate_tokens() 770 auth.generate_tokens()
747 auth.save_config()
748 need_auth_tokens = False 771 need_auth_tokens = False
749 except Exception: 772 except Exception:
750 logging.error("Authentication failed") 773 logging.error("Authentication failed")
751 return 1 774 return 1
775 # Save the new auth tokens.
776 auth.save_config(host_config)
777 if not host_config.save():
778 logging.error("Faled to save host configuration.")
Lambros 2012/08/09 01:20:27 s/Faled/Failed
Sergey Ulanov 2012/08/09 02:13:10 Done.
779 return 1
752 780
753 try: 781 if need_register_host:
754 if need_register_host: 782 try:
755 host.register() 783 host.register()
756 host.save_config() 784 host.save_config(host_config)
757 except urllib2.HTTPError, err: 785 except urllib2.HTTPError, err:
758 if err.getcode() == 401: 786 if err.getcode() == 401:
759 # Authentication failed - re-prompt for username & password. 787 # Authentication failed - re-prompt for username & password.
760 need_auth_tokens = True 788 need_auth_tokens = True
761 continue 789 continue
762 else: 790 else:
763 # Not an authentication error. 791 # Not an authentication error.
764 logging.error("Directory returned error: " + str(err)) 792 logging.error("Directory returned error: " + str(err))
765 logging.error(err.read()) 793 logging.error(err.read())
766 return 1 794 return 1
767 795
768 # |auth| and |host| are both set up, so break out of the loop. 796 # |auth| and |host| are both set up, so break out of the loop.
769 break 797 break
770 798
799 if not host_config.save():
800 logging.error("Faled to save host configuration.")
Lambros 2012/08/09 01:20:27 s/Faled/Failed
Sergey Ulanov 2012/08/09 02:13:10 Done.
801 return 1
802
771 global g_pidfile 803 global g_pidfile
772 g_pidfile = PidFile(pid_filename) 804 g_pidfile = PidFile(pid_filename)
773 running, pid = g_pidfile.check() 805 running, pid = g_pidfile.check()
774 806
775 if running: 807 if running:
776 print "An instance of this script is already running." 808 print "An instance of this script is already running."
777 print "Use the -k flag to terminate the running instance." 809 print "Use the -k flag to terminate the running instance."
778 print "If this isn't the case, delete '%s' and try again." % pid_filename 810 print "If this isn't the case, delete '%s' and try again." % pid_filename
779 return 1 811 return 1
780 812
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after
822 "before starting new session.") 854 "before starting new session.")
823 time.sleep(60 - elapsed) 855 time.sleep(60 - elapsed)
824 856
825 logging.info("Launching X server and X session") 857 logging.info("Launching X server and X session")
826 last_launch_time = time.time() 858 last_launch_time = time.time()
827 desktop.launch_x_server(args) 859 desktop.launch_x_server(args)
828 desktop.launch_x_session() 860 desktop.launch_x_session()
829 861
830 if desktop.host_proc is None: 862 if desktop.host_proc is None:
831 logging.info("Launching host process") 863 logging.info("Launching host process")
832 desktop.launch_host(host) 864 desktop.launch_host(host_config)
833 865
834 try: 866 try:
835 pid, status = os.wait() 867 pid, status = os.wait()
836 except OSError, e: 868 except OSError, e:
837 if e.errno == errno.EINTR: 869 if e.errno == errno.EINTR:
838 # Retry on EINTR, which can happen if a signal such as SIGUSR1 is 870 # Retry on EINTR, which can happen if a signal such as SIGUSR1 is
839 # received. 871 # received.
840 continue 872 continue
841 else: 873 else:
842 # Anything else is an unexpected error. 874 # Anything else is an unexpected error.
(...skipping 16 matching lines...) Expand all
859 logging.info("Host process terminated") 891 logging.info("Host process terminated")
860 desktop.host_proc = None 892 desktop.host_proc = None
861 893
862 # These exit-codes must match the ones used by the host. 894 # These exit-codes must match the ones used by the host.
863 # See remoting/host/constants.h. 895 # See remoting/host/constants.h.
864 # Delete the host or auth configuration depending on the returned error 896 # Delete the host or auth configuration depending on the returned error
865 # code, so the next time this script is run, a new configuration 897 # code, so the next time this script is run, a new configuration
866 # will be created and registered. 898 # will be created and registered.
867 if os.WEXITSTATUS(status) == 2: 899 if os.WEXITSTATUS(status) == 2:
868 logging.info("Host configuration is invalid - exiting.") 900 logging.info("Host configuration is invalid - exiting.")
869 try: 901 host_config.clear_auth()
870 os.remove(host.config_file) 902 host_config.clear_host_info()
871 os.remove(auth.config_file) 903 host_config.save()
872 except:
873 pass
874 return 0 904 return 0
875 elif os.WEXITSTATUS(status) == 3: 905 elif os.WEXITSTATUS(status) == 3:
876 logging.info("Host ID has been deleted - exiting.") 906 logging.info("Host ID has been deleted - exiting.")
877 try: 907 host_config.clear_host_info()
878 os.remove(host.config_file) 908 host_config.save()
879 except:
880 pass
881 return 0 909 return 0
882 elif os.WEXITSTATUS(status) == 4: 910 elif os.WEXITSTATUS(status) == 4:
883 logging.info("OAuth credentials are invalid - exiting.") 911 logging.info("OAuth credentials are invalid - exiting.")
884 try: 912 host_config.clear_auth()
885 os.remove(auth.config_file) 913 host_config.save()
886 except:
887 pass
888 return 0 914 return 0
889 elif os.WEXITSTATUS(status) == 5: 915 elif os.WEXITSTATUS(status) == 5:
890 logging.info("Host domain is blocked by policy - exiting.") 916 logging.info("Host domain is blocked by policy - exiting.")
891 os.remove(host.config_file) 917 os.remove(host.config_file)
892 return 0 918 return 0
893 919
894 if __name__ == "__main__": 920 if __name__ == "__main__":
895 logging.basicConfig(level=logging.DEBUG) 921 logging.basicConfig(level=logging.DEBUG)
896 sys.exit(main()) 922 sys.exit(main())
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698