summaryrefslogtreecommitdiffstats
path: root/remoting/tools
diff options
context:
space:
mode:
authorlambroslambrou@chromium.org <lambroslambrou@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-11-17 23:33:35 +0000
committerlambroslambrou@chromium.org <lambroslambrou@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-11-17 23:33:35 +0000
commit1a98c25f1e91b843a73cc542e50cf78724f0f18a (patch)
tree4dabe99cc19ca5bb39305c9adbe7343c775287bd /remoting/tools
parent9ab22690e1ddb76c1de1a9191c72efcaf98f10c0 (diff)
downloadchromium_src-1a98c25f1e91b843a73cc542e50cf78724f0f18a.zip
chromium_src-1a98c25f1e91b843a73cc542e50cf78724f0f18a.tar.gz
chromium_src-1a98c25f1e91b843a73cc542e50cf78724f0f18a.tar.bz2
Initial check-in of Linux virtual Me2Me.
This CL includes a Python script, remoting.py, that sets up and runs a virtual X session and host process. It also includes a remoting_me2me_host executable target, which is a simplified version of remoting_simple_host, using the config files that remoting.py sets up. remoting.py uses Xvfb which needs to be installed (sudo apt-get install xvfb). BUG=None TEST=None Review URL: http://codereview.chromium.org/8511029 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@110597 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'remoting/tools')
-rwxr-xr-xremoting/tools/me2me_virtual_host.py317
1 files changed, 317 insertions, 0 deletions
diff --git a/remoting/tools/me2me_virtual_host.py b/remoting/tools/me2me_virtual_host.py
new file mode 100755
index 0000000..f8a8baa
--- /dev/null
+++ b/remoting/tools/me2me_virtual_host.py
@@ -0,0 +1,317 @@
+#!/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.
+
+# Virtual Me2Me implementation. This script runs and manages the processes
+# required for a Virtual Me2Me desktop, which are: X server, X desktop
+# session, and Host process.
+# This script is intended to run continuously as a background daemon
+# process, running under an ordinary (non-root) user account.
+
+import atexit
+import getpass
+import json
+import logging
+import os
+import random
+import signal
+import socket
+import subprocess
+import sys
+import time
+import urllib2
+import uuid
+
+# Local modules
+import gaia_auth
+import keygen
+
+REMOTING_COMMAND = "remoting_me2me_host"
+
+SCRIPT_PATH = os.path.dirname(sys.argv[0])
+if not SCRIPT_PATH:
+ SCRIPT_PATH = os.getcwd()
+
+# These are relative to SCRIPT_PATH.
+EXE_PATHS_TO_TRY = [
+ ".",
+ "../../out/Debug",
+ "../../out/Release"
+]
+
+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
+
+def locate_executable(exe_name):
+ for path in EXE_PATHS_TO_TRY:
+ exe_path = os.path.join(SCRIPT_PATH, path, exe_name)
+ if os.path.exists(exe_path):
+ return exe_path
+ raise Exception("Could not locate executable '%s'" % exe_name)
+
+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 save_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:
+ """This manages the configuration for a host.
+
+ Callers should instantiate a Host object (passing in a filename where the
+ config will be kept), then should call either of the methods:
+
+ * create_config(auth): Create a new Host configuration and register it with
+ the Directory Service (the "auth" parameter is used to authenticate with the
+ Service).
+ * load_config(): Load a config from disk, with details of an existing Host
+ registration.
+
+ After calling create_config() (or making any config changes) the method
+ save_config() should be called to save the details to disk.
+ """
+
+ server = 'www.googleapis.com'
+ url = 'https://' + server + '/chromoting/v1/@me/hosts'
+
+ def __init__(self, config_file):
+ self.config_file = config_file
+
+ def create_config(self, auth):
+ self.host_id = str(uuid.uuid1())
+ 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 save_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:
+ raise Exception("xauth failed with code %d" % ret_code)
+
+ 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:
+ raise Exception("Could not start Xvfb.")
+
+ # 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",
+ "LOGNAME",
+ "PATH",
+ "SHELL",
+ "USER",
+ "USERNAME"]:
+ if os.environ.has_key(key):
+ 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:
+ raise Exception("Could not connect to Xvfb.")
+ 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:
+ raise Exception("Could not start X session")
+
+ def launch_host(self):
+ # Start remoting host
+ command = locate_executable(REMOTING_COMMAND)
+ self.host_proc = subprocess.Popen(command, env=self.child_env)
+ if not self.host_proc.pid:
+ raise Exception("Could not start remoting host")
+
+
+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():
+ try:
+ auth.refresh_tokens()
+ except:
+ logging.error("Authentication failed.")
+ return 1
+ auth.save_config()
+
+ host = Host(os.path.join(CONFIG_DIR, "host.json"))
+
+ if not host.load_config():
+ host.create_config(auth)
+ host.save_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())