Index: remoting/tools/remoting.py |
diff --git a/remoting/tools/remoting.py b/remoting/tools/remoting.py |
new file mode 100755 |
index 0000000000000000000000000000000000000000..2bb135551cb48a0df960c0b7913bf3a915af6310 |
--- /dev/null |
+++ b/remoting/tools/remoting.py |
@@ -0,0 +1,279 @@ |
+#!/usr/bin/env python |
+# Copyright (c) 2011 The Chromium Authors. All rights reserved. |
+# Use of this source code is governed by a BSD-style license that can be |
+# found in the LICENSE file. |
+ |
+import atexit |
+import getpass |
+import json |
+import logging |
+import os |
+import random |
+import signal |
+import socket |
+import subprocess |
+import sys |
+import time |
+import urllib2 |
+ |
+# Local modules |
+import gaia_auth |
+import keygen |
+ |
+REMOTING_COMMAND = ["../../out/Debug/remoting_me2me_host"] |
+ |
+CONFIG_DIR = os.path.expanduser("~/.config/chrome-remote-desktop") |
+ |
+X_LOCK_FILE_TEMPLATE = "/tmp/.X%d-lock" |
+FIRST_X_DISPLAY_NUMBER = 20 |
+ |
+X_AUTH_FILE = os.path.expanduser("~/.Xauthority") |
+os.environ["XAUTHORITY"] = X_AUTH_FILE |
+ |
+g_desktops = [] |
+ |
+class Authentication: |
+ """Manage authentication tokens for Chromoting/xmpp""" |
+ def __init__(self, config_file): |
+ self.config_file = config_file |
+ |
+ def refresh_tokens(self): |
+ print "Email:", |
+ self.login = raw_input() |
+ password = getpass.getpass("Password: ") |
+ |
+ chromoting_auth = gaia_auth.GaiaAuthenticator('chromoting') |
+ self.chromoting_auth_token = chromoting_auth.authenticate(self.login, |
+ password) |
+ |
+ xmpp_authenticator = gaia_auth.GaiaAuthenticator('chromiumsync') |
+ self.xmpp_auth_token = xmpp_authenticator.authenticate(self.login, |
+ password) |
+ |
+ def load_config(self): |
+ try: |
+ settings_file = open(self.config_file, 'r') |
+ data = json.load(settings_file) |
+ settings_file.close() |
+ self.login = data["xmpp_login"] |
+ self.chromoting_auth_token = data["chromoting_auth_token"] |
+ self.xmpp_auth_token = data["xmpp_auth_token"] |
+ except: |
+ return False |
+ return True |
+ |
+ def write_config(self): |
+ data = { |
+ "xmpp_login": self.login, |
+ "chromoting_auth_token": self.chromoting_auth_token, |
+ "xmpp_auth_token": self.xmpp_auth_token, |
+ } |
+ os.umask(0066) # Set permission mask for created file. |
+ settings_file = open(self.config_file, 'w') |
+ settings_file.write(json.dumps(data, indent=2)) |
+ settings_file.close() |
+ |
+ |
+class Host: |
+ """Create a new, or manage an existing, host registration.""" |
+ |
+ server = 'www.googleapis.com' |
+ url = 'https://' + server + '/chromoting/v1/@me/hosts' |
+ |
+ def __init__(self, config_file): |
+ self.config_file = config_file |
+ |
+ @staticmethod |
+ def random_uuid(): |
+ return ("%04x%04x-%04x-%04x-%04x-%04x%04x%04x" % |
+ tuple(map(lambda x: random.randrange(0, 0x10000), range(8)))) |
+ |
+ def register_new(self, auth): |
+ self.host_id = self.random_uuid() |
+ logging.info("HostId: " + self.host_id) |
+ self.host_name = socket.gethostname() |
+ logging.info("HostName: " + self.host_name) |
+ |
+ logging.info("Generating RSA key pair...") |
+ (self.private_key, public_key) = keygen.generateRSAKeyPair() |
+ logging.info("Done") |
+ |
+ json_data = { |
+ "data": { |
+ "hostId": self.host_id, |
+ "hostName": self.host_name, |
+ "publicKey": public_key, |
+ } |
+ } |
+ params = json.dumps(json_data) |
+ headers = { |
+ "Authorization": "GoogleLogin auth=" + auth.chromoting_auth_token, |
+ "Content-Type": "application/json", |
+ } |
+ |
+ request = urllib2.Request(self.url, params, headers) |
+ opener = urllib2.OpenerDirector() |
+ opener.add_handler(urllib2.HTTPDefaultErrorHandler()) |
+ |
+ logging.info("Registering host with directory service...") |
+ try: |
+ res = urllib2.urlopen(request) |
+ data = res.read() |
+ except urllib2.HTTPError, err: |
+ logging.error("Directory returned error: " + str(err)) |
+ logging.error(err.read()) |
+ sys.exit(1) |
+ logging.info("Done") |
+ |
+ def load_config(self): |
+ try: |
+ settings_file = open(self.config_file, 'r') |
+ data = json.load(settings_file) |
+ settings_file.close() |
+ self.host_id = data["host_id"] |
+ self.host_name = data["host_name"] |
+ self.private_key = data["private_key"] |
+ except: |
+ return False |
+ return True |
+ |
+ def write_config(self): |
+ data = { |
+ "host_id": self.host_id, |
+ "host_name": self.host_name, |
+ "private_key": self.private_key, |
+ } |
+ os.umask(0066) # Set permission mask for created file. |
+ settings_file = open(self.config_file, 'w') |
+ settings_file.write(json.dumps(data, indent=2)) |
+ settings_file.close() |
+ |
+ |
+def cleanup(): |
+ logging.info("Cleanup.") |
+ |
+ for desktop in g_desktops: |
+ if desktop.x_proc: |
+ logging.info("Terminating Xvfb") |
+ desktop.x_proc.terminate() |
+ |
+def signal_handler(signum, stackframe): |
+ # Exit cleanly so the atexit handler, cleanup(), gets called. |
+ raise SystemExit |
+ |
+ |
+class Desktop: |
+ """Manage a single virtual desktop""" |
+ def __init__(self): |
+ self.x_proc = None |
+ g_desktops.append(self) |
+ |
+ @staticmethod |
+ def get_unused_display_number(): |
+ """Return a candidate display number for which there is currently no |
+ X Server lock file""" |
+ display = FIRST_X_DISPLAY_NUMBER |
+ while os.path.exists(X_LOCK_FILE_TEMPLATE % display): |
+ display += 1 |
+ return display |
+ |
+ def launch_x_server(self): |
+ display = self.get_unused_display_number() |
+ ret_code = subprocess.call("xauth add :%d . `mcookie`" % display, |
+ shell=True) |
+ if ret_code != 0: |
+ logging.error("xauth failed with code %d" % ret_code) |
+ return 1 |
+ logging.info("Starting Xvfb on display :%d" % display); |
+ self.x_proc = subprocess.Popen(["Xvfb", ":%d" % display, |
+ "-auth", X_AUTH_FILE, |
+ "-nolisten", "tcp", |
+ "-screen", "0", "1024x768x24", |
+ ]) |
+ if not self.x_proc.pid: |
+ logging.info("Could not start Xvfb.") |
+ return 1 |
+ |
+ # Create clean environment for new session, so it is cleanly separated from |
+ # the user's console X session. |
+ self.child_env = {"DISPLAY": ":%d" % display} |
+ for key in ["HOME", "PATH"]: |
+ self.child_env[key] = os.environ[key] |
+ |
+ # Wait for X to be active. |
+ for test in range(5): |
+ proc = subprocess.Popen("xdpyinfo > /dev/null", env=self.child_env, |
+ shell=True) |
+ pid, retcode = os.waitpid(proc.pid, 0) |
+ if retcode == 0: |
+ break |
+ time.sleep(0.5) |
+ if retcode != 0: |
+ logging.info("Could not connect to Xvfb.") |
+ return 1 |
+ else: |
+ logging.info("Xvfb is active.") |
+ |
+ def launch_x_session(self): |
+ # Start desktop session |
+ session_proc = subprocess.Popen("/etc/X11/Xsession", |
+ cwd=os.environ["HOME"], |
+ env=self.child_env) |
+ if not session_proc.pid: |
+ logging.info("Could not start X session") |
+ return 1 |
+ |
+ def launch_host(self): |
+ # Start remoting host |
+ self.host_proc = subprocess.Popen(REMOTING_COMMAND, env=self.child_env) |
+ if not self.host_proc.pid: |
+ logging.info("Could not start remoting host") |
+ return 1 |
+ |
+ |
+def main(): |
+ atexit.register(cleanup) |
+ |
+ for s in ( |
+ signal.SIGINT, |
+ signal.SIGTERM, |
+ ): |
+ signal.signal(s, signal_handler) |
+ |
+ # Ensure full path to config directory exists. |
+ if not os.path.exists(CONFIG_DIR): |
+ os.makedirs(CONFIG_DIR, mode=0700) |
+ |
+ auth = Authentication(os.path.join(CONFIG_DIR, "auth.json")) |
+ if not auth.load_config(): |
+ auth.refresh_tokens() |
+ auth.write_config() |
+ |
+ host = Host(os.path.join(CONFIG_DIR, "host.json")) |
+ |
+ if not host.load_config(): |
+ host.register_new(auth) |
+ host.write_config() |
+ |
+ logging.info("Using host_id: " + host.host_id) |
+ |
+ desktop = Desktop() |
+ desktop.launch_x_server() |
+ desktop.launch_x_session() |
+ desktop.launch_host() |
+ |
+ while True: |
+ pid, status = os.wait() |
+ logging.info("wait() returned (%s,%s)" % (pid, status)) |
+ |
+ if pid == desktop.x_proc.pid: |
+ logging.info("X server process terminated with code %d", status) |
+ break |
+ |
+ if pid == desktop.host_proc.pid: |
+ logging.info("Host process terminated, relaunching") |
+ desktop.launch_host() |
+ |
+if __name__ == "__main__": |
+ logging.basicConfig(level=logging.DEBUG) |
+ sys.exit(main()) |