| 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())
|
|
|