aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--poetry.lock194
-rw-r--r--pyproject.toml6
-rw-r--r--service.py81
-rw-r--r--session.py91
-rw-r--r--test_client.py10
-rw-r--r--vnc.py32
6 files changed, 412 insertions, 2 deletions
diff --git a/poetry.lock b/poetry.lock
index 12fbad9..0e68b69 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,7 +1,197 @@
-package = []
+[[package]]
+category = "main"
+description = "Python package for providing Mozilla's CA Bundle."
+name = "certifi"
+optional = false
+python-versions = "*"
+version = "2020.6.20"
+
+[[package]]
+category = "main"
+description = "Universal encoding detector for Python 2 and 3"
+name = "chardet"
+optional = false
+python-versions = "*"
+version = "3.0.4"
+
+[[package]]
+category = "main"
+description = "Python bindings for libdbus"
+name = "dbus-python"
+optional = false
+python-versions = "*"
+version = "1.2.16"
+
+[[package]]
+category = "main"
+description = "Internationalized Domain Names in Applications (IDNA)"
+name = "idna"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "2.10"
+
+[[package]]
+category = "main"
+description = "NumPy is the fundamental package for array computing with Python."
+name = "numpy"
+optional = false
+python-versions = ">=3.5"
+version = "1.18.5"
+
+[[package]]
+category = "main"
+description = "Utility that helps with local TCP ports managment. It can find an unused TCP localhost port and remember the association."
+name = "port-for"
+optional = false
+python-versions = "*"
+version = "0.4"
+
+[[package]]
+category = "main"
+description = "Cross-platform lib for process and system monitoring in Python."
+name = "psutil"
+optional = false
+python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "5.7.2"
+
+[package.extras]
+test = ["ipaddress", "mock", "unittest2", "enum34", "pywin32", "wmi"]
+
+[[package]]
+category = "main"
+description = "Python interface for cairo"
+name = "pycairo"
+optional = false
+python-versions = ">=3.5, <4"
+version = "1.19.1"
+
+[[package]]
+category = "main"
+description = "Python bindings for GObject Introspection"
+name = "pygobject"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
+version = "3.36.1"
+
+[package.dependencies]
+pycairo = ">=1.11.1"
+
+[[package]]
+category = "main"
+description = "Python HTTP for Humans."
+name = "requests"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+version = "2.24.0"
+
+[package.dependencies]
+certifi = ">=2017.4.17"
+chardet = ">=3.0.2,<4"
+idna = ">=2.5,<3"
+urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26"
+
+[package.extras]
+security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"]
+socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"]
+
+[[package]]
+category = "main"
+description = "HTTP library with thread-safe connection pooling, file post, and more."
+name = "urllib3"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
+version = "1.25.9"
+
+[package.extras]
+brotli = ["brotlipy (>=0.6.0)"]
+secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)", "ipaddress"]
+socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"]
+
+[[package]]
+category = "main"
+description = "Websockify."
+name = "websockify"
+optional = false
+python-versions = "*"
+version = "0.9.0"
+
+[package.dependencies]
+numpy = "*"
[metadata]
-content-hash = "8165d934e932435bf4742b9198674202413b43524911713d5c7c55cb8d314618"
+content-hash = "647d3ef2f88b9c37462804e88709259cfabccd8b431cb5d998100c86e611819a"
python-versions = "^3.5"
[metadata.files]
+certifi = [
+ {file = "certifi-2020.6.20-py2.py3-none-any.whl", hash = "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"},
+ {file = "certifi-2020.6.20.tar.gz", hash = "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3"},
+]
+chardet = [
+ {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"},
+ {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"},
+]
+dbus-python = [
+ {file = "dbus-python-1.2.16.tar.gz", hash = "sha256:11238f1d86c995d8aed2e22f04a1e3779f0d70e587caffeab4857f3c662ed5a4"},
+]
+idna = [
+ {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"},
+ {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"},
+]
+numpy = [
+ {file = "numpy-1.18.5-cp35-cp35m-macosx_10_9_intel.whl", hash = "sha256:e91d31b34fc7c2c8f756b4e902f901f856ae53a93399368d9a0dc7be17ed2ca0"},
+ {file = "numpy-1.18.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:7d42ab8cedd175b5ebcb39b5208b25ba104842489ed59fbb29356f671ac93583"},
+ {file = "numpy-1.18.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:a78e438db8ec26d5d9d0e584b27ef25c7afa5a182d1bf4d05e313d2d6d515271"},
+ {file = "numpy-1.18.5-cp35-cp35m-win32.whl", hash = "sha256:a87f59508c2b7ceb8631c20630118cc546f1f815e034193dc72390db038a5cb3"},
+ {file = "numpy-1.18.5-cp35-cp35m-win_amd64.whl", hash = "sha256:965df25449305092b23d5145b9bdaeb0149b6e41a77a7d728b1644b3c99277c1"},
+ {file = "numpy-1.18.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ac792b385d81151bae2a5a8adb2b88261ceb4976dbfaaad9ce3a200e036753dc"},
+ {file = "numpy-1.18.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:ef627986941b5edd1ed74ba89ca43196ed197f1a206a3f18cc9faf2fb84fd675"},
+ {file = "numpy-1.18.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:f718a7949d1c4f622ff548c572e0c03440b49b9531ff00e4ed5738b459f011e8"},
+ {file = "numpy-1.18.5-cp36-cp36m-win32.whl", hash = "sha256:4064f53d4cce69e9ac613256dc2162e56f20a4e2d2086b1956dd2fcf77b7fac5"},
+ {file = "numpy-1.18.5-cp36-cp36m-win_amd64.whl", hash = "sha256:b03b2c0badeb606d1232e5f78852c102c0a7989d3a534b3129e7856a52f3d161"},
+ {file = "numpy-1.18.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a7acefddf994af1aeba05bbbafe4ba983a187079f125146dc5859e6d817df824"},
+ {file = "numpy-1.18.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:cd49930af1d1e49a812d987c2620ee63965b619257bd76eaaa95870ca08837cf"},
+ {file = "numpy-1.18.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:b39321f1a74d1f9183bf1638a745b4fd6fe80efbb1f6b32b932a588b4bc7695f"},
+ {file = "numpy-1.18.5-cp37-cp37m-win32.whl", hash = "sha256:cae14a01a159b1ed91a324722d746523ec757357260c6804d11d6147a9e53e3f"},
+ {file = "numpy-1.18.5-cp37-cp37m-win_amd64.whl", hash = "sha256:0172304e7d8d40e9e49553901903dc5f5a49a703363ed756796f5808a06fc233"},
+ {file = "numpy-1.18.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e15b382603c58f24265c9c931c9a45eebf44fe2e6b4eaedbb0d025ab3255228b"},
+ {file = "numpy-1.18.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:3676abe3d621fc467c4c1469ee11e395c82b2d6b5463a9454e37fe9da07cd0d7"},
+ {file = "numpy-1.18.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:4674f7d27a6c1c52a4d1aa5f0881f1eff840d2206989bae6acb1c7668c02ebfb"},
+ {file = "numpy-1.18.5-cp38-cp38-win32.whl", hash = "sha256:9c9d6531bc1886454f44aa8f809268bc481295cf9740827254f53c30104f074a"},
+ {file = "numpy-1.18.5-cp38-cp38-win_amd64.whl", hash = "sha256:3dd6823d3e04b5f223e3e265b4a1eae15f104f4366edd409e5a5e413a98f911f"},
+ {file = "numpy-1.18.5.zip", hash = "sha256:34e96e9dae65c4839bd80012023aadd6ee2ccb73ce7fdf3074c62f301e63120b"},
+]
+port-for = [
+ {file = "port-for-0.4.tar.gz", hash = "sha256:47b5cb48f8e036497cd73b96de305cecb4070e9ecbc908724afcbd2224edccde"},
+ {file = "port_for-0.4-py2.py3-none-any.whl", hash = "sha256:247b4db1901aa3d9906258308e40dfbadf65275b27ca77faa0b9a876b7284970"},
+]
+psutil = [
+ {file = "psutil-5.7.2-cp27-none-win32.whl", hash = "sha256:f2018461733b23f308c298653c8903d32aaad7873d25e1d228765e91ae42c3f2"},
+ {file = "psutil-5.7.2-cp27-none-win_amd64.whl", hash = "sha256:66c18ca7680a31bf16ee22b1d21b6397869dda8059dbdb57d9f27efa6615f195"},
+ {file = "psutil-5.7.2-cp35-cp35m-win32.whl", hash = "sha256:5e9d0f26d4194479a13d5f4b3798260c20cecf9ac9a461e718eb59ea520a360c"},
+ {file = "psutil-5.7.2-cp35-cp35m-win_amd64.whl", hash = "sha256:4080869ed93cce662905b029a1770fe89c98787e543fa7347f075ade761b19d6"},
+ {file = "psutil-5.7.2-cp36-cp36m-win32.whl", hash = "sha256:d8a82162f23c53b8525cf5f14a355f5d1eea86fa8edde27287dd3a98399e4fdf"},
+ {file = "psutil-5.7.2-cp36-cp36m-win_amd64.whl", hash = "sha256:0ee3c36428f160d2d8fce3c583a0353e848abb7de9732c50cf3356dd49ad63f8"},
+ {file = "psutil-5.7.2-cp37-cp37m-win32.whl", hash = "sha256:ff1977ba1a5f71f89166d5145c3da1cea89a0fdb044075a12c720ee9123ec818"},
+ {file = "psutil-5.7.2-cp37-cp37m-win_amd64.whl", hash = "sha256:a5b120bb3c0c71dfe27551f9da2f3209a8257a178ed6c628a819037a8df487f1"},
+ {file = "psutil-5.7.2-cp38-cp38-win32.whl", hash = "sha256:10512b46c95b02842c225f58fa00385c08fa00c68bac7da2d9a58ebe2c517498"},
+ {file = "psutil-5.7.2-cp38-cp38-win_amd64.whl", hash = "sha256:68d36986ded5dac7c2dcd42f2682af1db80d4bce3faa126a6145c1637e1b559f"},
+ {file = "psutil-5.7.2.tar.gz", hash = "sha256:90990af1c3c67195c44c9a889184f84f5b2320dce3ee3acbd054e3ba0b4a7beb"},
+]
+pycairo = [
+ {file = "pycairo-1.19.1.tar.gz", hash = "sha256:2c143183280feb67f5beb4e543fd49990c28e7df427301ede04fc550d3562e84"},
+]
+pygobject = [
+ {file = "PyGObject-3.36.1.tar.gz", hash = "sha256:012a589aec687bfa809a1ff9f5cd775dc7f6fcec1a6bc7fe88e1002a68f8ba34"},
+]
+requests = [
+ {file = "requests-2.24.0-py2.py3-none-any.whl", hash = "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"},
+ {file = "requests-2.24.0.tar.gz", hash = "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b"},
+]
+urllib3 = [
+ {file = "urllib3-1.25.9-py2.py3-none-any.whl", hash = "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115"},
+ {file = "urllib3-1.25.9.tar.gz", hash = "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527"},
+]
+websockify = [
+ {file = "websockify-0.9.0.tar.gz", hash = "sha256:c35b5b79ebc517d3b784dacfb993be413a93cda5222c6f382443ce29c1a6cada"},
+]
diff --git a/pyproject.toml b/pyproject.toml
index 8fffb40..acdfc6b 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -7,6 +7,12 @@ license = "GPL-2.0-or-later"
[tool.poetry.dependencies]
python = "^3.5"
+dbus-python = "^1.2.16"
+PyGObject = "^3.36.1"
+port_for = "^0.4"
+requests = "^2.24.0"
+websockify = "^0.9.0"
+psutil = "^5.7.2"
[tool.poetry.dev-dependencies]
diff --git a/service.py b/service.py
new file mode 100644
index 0000000..6125a75
--- /dev/null
+++ b/service.py
@@ -0,0 +1,81 @@
+import json
+import time
+from threading import Thread
+
+import dbus
+import dbus.service
+
+from session import Session
+
+
+class RWAService(dbus.service.Object):
+ def __init__(self):
+ self.bus = dbus.SessionBus()
+ name = dbus.service.BusName("de.rwa.rwa", bus=self.bus)
+
+ self.update_service_running = False
+ self.sessions = {}
+ super().__init__(name, "/RWA")
+
+ @dbus.service.method("de.rwa.rwa", out_signature="s")
+ def start(self):
+ """Start a new remote session."""
+ # Start session
+ session = Session()
+
+ # Add session to sessions list
+ self.sessions[session.pid] = session
+
+ # Start session update service
+ self._ensure_update_service()
+
+ return json.dumps(session.client_meta)
+
+ @dbus.service.method("de.rwa.rwa", in_signature="i")
+ def stop(self, pid: int):
+ """Stop a remote session."""
+ session = self.sessions[pid]
+ session.stop()
+
+ def _ensure_update_service(self):
+ """Start session update thread if it isn't already running."""
+ if not self.update_service_running:
+ self.update_thread = Thread(target=self._update_sessions)
+ self.update_thread.start()
+
+ def _update_sessions(self):
+ """Go through all running sessions and update their status.
+
+ Things that this function will do:
+ - Check if VNC is still running
+ - Kill websockify if VNC process is dead
+ """
+ while len(self.sessions.values()) > 0:
+ for session in list(self.sessions.values()):
+ print(f"Session #{session.pid}")
+
+ # Check if VNC process is still running
+ running = session.vnc_process_running
+ if running:
+ print("Session is running")
+ else:
+ print("Session is dead.")
+
+ session.stop()
+ del self.sessions[session.pid]
+
+ time.sleep(2)
+
+ self.update_service_running = False
+ # TODO Probably kill daemon here (quit main loop)
+
+
+if __name__ == "__main__":
+ import dbus.mainloop.glib
+ from gi.repository import GLib
+
+ dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+
+ loop = GLib.MainLoop()
+ object = RWAService()
+ loop.run()
diff --git a/session.py b/session.py
new file mode 100644
index 0000000..3c8eeb9
--- /dev/null
+++ b/session.py
@@ -0,0 +1,91 @@
+import os
+import secrets
+import signal
+
+import psutil
+import requests
+
+from vnc import run_vnc, save_password
+
+API_SERVER = "http://127.0.0.1:8000"
+REGISTER_URL = API_SERVER + "/app/rwa/api/register/"
+
+
+class Session:
+ def __init__(self):
+ self._generate_password()
+ self._start_vnc()
+ self._register_session()
+
+ @property
+ def pid(self) -> int:
+ return self.vnc_pid
+
+ @property
+ def port(self) -> int:
+ return self.ws_port
+
+ def _generate_password(self):
+ """Generate password for x11vnc and save it."""
+ self.password = secrets.token_urlsafe(20)
+ self.pw_filename = save_password(self.password)
+
+ def _start_vnc(self):
+ """Start x11vnc server."""
+ process_info = run_vnc(self.pw_filename)
+
+ self.vnc_pid = process_info["vnc"]["pid"]
+ self.vnc_port = process_info["vnc"]["port"]
+ self.ws_pid = process_info["ws"]["pid"]
+ self.ws_port = process_info["ws"]["port"]
+
+ def _register_session(self):
+ """Register session in RWA."""
+ r = requests.post(
+ REGISTER_URL,
+ json={
+ "port": self.ws_port,
+ "password": self.password,
+ "pid": self.vnc_pid,
+ },
+ )
+ print(r)
+ self.meta = r.json()
+ self.session_id = self.meta["session_id"]
+ self.web_url = self.meta["url"]
+ self.api_token = self.meta["token"]
+ self.pin = self.meta["pin"]
+
+ def stop(self):
+ """Stop session and clean up."""
+ # Kill VNC
+ if self.vnc_pid in psutil.pids():
+ print("Kill VNC.")
+ os.kill(self.vnc_pid, signal.SIGTERM)
+
+ # Kill websockify
+ if self.ws_pid in psutil.pids():
+ print("Kill websockify.")
+ os.kill(self.ws_pid, signal.SIGTERM)
+
+ # Delete PW file
+ if os.path.exists(self.pw_filename):
+ print("Delete password file")
+ os.remove(self.pw_filename)
+
+ # Delete self
+ del self
+
+ @property
+ def vnc_process_running(self):
+ """Check if the VNC process is still running."""
+ if self.vnc_pid in psutil.pids():
+ p = psutil.Process(self.vnc_pid)
+ if p.status() == "zombie":
+ return False
+ return True
+ return False
+
+ @property
+ def client_meta(self):
+ return {"id": self.pid, "url": self.web_url, "pin": self.pin}
diff --git a/test_client.py b/test_client.py
new file mode 100644
index 0000000..cd7d338
--- /dev/null
+++ b/test_client.py
@@ -0,0 +1,10 @@
+import dbus
+
+bus = dbus.SessionBus()
+
+
+time = bus.get_object("de.rwa.rwa", "/RWA")
+
+
+curr = time.start()
+print("Your VNC session is", curr)
diff --git a/vnc.py b/vnc.py
new file mode 100644
index 0000000..066eda1
--- /dev/null
+++ b/vnc.py
@@ -0,0 +1,32 @@
+import os
+import subprocess
+from typing import Dict
+from uuid import uuid4
+
+import port_for
+
+
+def save_password(pw: str) -> str:
+ """Save password in x11vnc format in temporary directory."""
+ filename = f"/tmp/rwa/{uuid4()}.pw"
+ os.makedirs("/tmp/rwa/", exist_ok=True)
+ p = subprocess.Popen(["x11vnc", "-storepasswd", f"{pw}", filename])
+ p.communicate()
+ return filename
+
+
+def run_vnc(pw_filename: str) -> Dict[str, Dict[str, int]]:
+ """Run x11vnc and websockify with random, unique ports in background."""
+ port = port_for.select_random()
+ port_vnc = port_for.select_random()
+
+ # Start VNC process
+ p = subprocess.Popen(["x11vnc", "-rfbauth", pw_filename, "-rfbport", f"{port_vnc}"])
+
+ # Start websockify
+ p2 = subprocess.Popen(f"websockify {port} 127.0.0.1:{port_vnc}", shell=True,)
+
+ return {
+ "ws": {"pid": p2.pid, "port": port},
+ "vnc": {"port": port_vnc, "pid": p.pid},
+ }