Skip to content

Commit

Permalink
proof of concept auth proxy server: can be used for listening on one …
Browse files Browse the repository at this point in the history
…port (ie: 80, 443 - typically a port that firewalls will allow) and once the client has connected it forwards the client connection to the real xpra server (currently hardcoded)

git-svn-id: https://xpra.org/svn/Xpra/trunk@4326 3bb7dfac-3a0b-4e04-842a-767bc560f471
  • Loading branch information
totaam committed Sep 11, 2013
1 parent 8744a53 commit 3d61cc7
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 20 deletions.
4 changes: 2 additions & 2 deletions src/xpra/platform/darwin/shadow_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ def __init__(self):
ShadowServerBase.__init__(self)
GTKServerBase.__init__(self)

def init(self, sockets, opts):
GTKServerBase.init(self, sockets, opts)
def init(self, opts):
GTKServerBase.init(self, opts)
self.keycodes = {}

def makeRootWindowModel(self):
Expand Down
2 changes: 1 addition & 1 deletion src/xpra/scripts/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,7 @@ def sigusr2(*args):
if mode=="start" and len(args)>0 and (args[0].startswith("ssh/") or args[0].startswith("ssh:")):
#ie: "xpra start ssh:HOST:DISPLAY --start-child=xterm"
return run_remote_server(parser, options, args)
elif (mode in ("start", "upgrade") and supports_server) or (mode=="shadow" and supports_shadow):
elif (mode in ("start", "upgrade", "proxy") and supports_server) or (mode=="shadow" and supports_shadow):
nox()
from xpra.scripts.server import run_server
return run_server(parser, options, mode, script_file, args)
Expand Down
25 changes: 16 additions & 9 deletions src/xpra/scripts/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,11 +215,12 @@ def run_server(parser, opts, mode, xpra_file, extra_args):
from xpra.server.server_base import SERVER_ENCODINGS
print("server supports the following encodings:\n * %s" % ("\n * ".join(encodings_help(SERVER_ENCODINGS))))
return 0
assert mode in ("start", "upgrade", "shadow")
assert mode in ("start", "upgrade", "shadow", "proxy")
upgrading = mode == "upgrade"
shadowing = mode == "shadow"
proxying = mode == "proxy"
display_name = extra_args.pop(0)
if display_name.startswith(":") and not shadowing:
if display_name.startswith(":") and not shadowing and not proxying:
n = display_name[1:]
p = n.find(".")
if p>0:
Expand All @@ -234,7 +235,7 @@ def run_server(parser, opts, mode, xpra_file, extra_args):
except:
pass

if not shadowing and opts.exit_with_children and not opts.start_child:
if not shadowing and not proxying and opts.exit_with_children and not opts.start_child:
sys.stderr.write("--exit-with-children specified without any children to spawn; exiting immediately")
return 1

Expand Down Expand Up @@ -381,7 +382,7 @@ def unsetenv(*varnames):
os.environ["IMSETTINGS_MODULE"] = "none" #or "xim"?
os.environ["XMODIFIERS"] = ""

if not clobber and not shadowing:
if not clobber and not shadowing and not proxying:
# We need to set up a new server environment
xauthority = os.environ.get("XAUTHORITY", os.path.expanduser("~/.Xauthority"))
if not os.path.exists(xauthority):
Expand Down Expand Up @@ -420,7 +421,7 @@ def setsid():
sys.stderr.write("Error running \"%s\": %s\n" % (" ".join(xauth_cmd), e))

def xvfb_error(instance_exists=False):
if clobber or shadowing:
if clobber or shadowing or proxying:
return False
if xvfb.poll() is None:
return False
Expand All @@ -438,7 +439,7 @@ def xvfb_error(instance_exists=False):
if xvfb_error():
return 1

if not sys.platform.startswith("win") and not sys.platform.startswith("darwin"):
if not sys.platform.startswith("win") and not sys.platform.startswith("darwin") and not proxying:
from xpra.x11.bindings.wait_for_x_server import wait_for_x_server #@UnresolvedImport
# Whether we spawned our server or not, it is now running -- or at least
# starting. First wait for it to start up:
Expand All @@ -459,7 +460,7 @@ def xvfb_error(instance_exists=False):
if default_display is not None:
default_display.close()
manager.set_default_display(display)
else:
elif not proxying:
assert "gtk" not in sys.modules
import gtk #@Reimport

Expand All @@ -469,6 +470,12 @@ def xvfb_error(instance_exists=False):
app = ShadowServer()
app.init(opts)
app.init_sockets(sockets)
elif proxying:
xvfb_pid = None
from xpra.server.auth_proxy import ProxyServer
app = ProxyServer()
app.init(opts)
app.init_sockets(sockets)
else:
from xpra.x11.gtk_x11 import gdk_display_source
assert gdk_display_source
Expand Down Expand Up @@ -523,7 +530,7 @@ def reaper_quit():
gobject.idle_add(app.clean_quit)

procs = []
if os.name=="posix":
if os.name=="posix" and not proxying:
child_reaper = ChildReaper(reaper_quit, children_pids)
old_python = sys.version_info < (2, 7) or sys.version_info[:2] == (3, 0)
if old_python:
Expand Down Expand Up @@ -573,7 +580,7 @@ def cleanup_pa():
_cleanups.append(cleanup_pa)
if opts.exit_with_children:
assert opts.start_child
if opts.start_child:
if opts.start_child and not proxying:
assert os.name=="posix"
#disable ubuntu's global menu using env vars:
env = os.environ.copy()
Expand Down
114 changes: 114 additions & 0 deletions src/xpra/server/auth_proxy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# coding=utf8
# This file is part of Xpra.
# Copyright (C) 2013 Antoine Martin <[email protected]>
# Copyright (C) 2008 Nathaniel Smith <[email protected]>
# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
# later version. See the file COPYING for details.

import gobject
gobject.threads_init()

from xpra.log import Logger
log = Logger()

from xpra.server.server_core import ServerCore
from xpra.scripts.config import make_defaults_struct
from xpra.scripts.main import parse_display_name, connect_to
from xpra.net.protocol import Protocol
from xpra.net.protocol import set_scheduler
from xpra.os_util import Queue
set_scheduler(gobject)


class ProxyServer(ServerCore):

def __init__(self):
self.main_loop = None
self.client_to_server = Queue(10)
self.server_to_client = Queue(10)
self.client_protocol = None
self.server_protocol = None
log("AuthProxy.__init__()")
ServerCore.__init__(self)
self.idle_add = gobject.idle_add
self.timeout_add = gobject.timeout_add
self.source_remove = gobject.source_remove

def do_run(self):
self.main_loop = gobject.MainLoop()
self.main_loop.run()

def do_quit(self):
for x in (self.client_protocol, self.server_protocol):
if x:
x.close()
self.client_protocol = None
self.server_protocol = None
self.main_loop.quit()

def add_listen_socket(self, socktype, sock):
sock.listen(5)
gobject.io_add_watch(sock, gobject.IO_IN, self._new_connection, sock)
self.socket_types[sock] = socktype

def verify_connection_accepted(self, protocol):
pass

def hello_oked(self, proto, packet, c, auth_caps):
if c.boolget("info_request"):
log.info("sending response to info request")
self.send_info(proto)
return
self.start_proxy(proto, packet)

def send_info(self, proto):
caps = self.make_hello()
caps["server_type"] = "proxy"
proto.send_now(["hello", caps])

def start_proxy(self, proto, packet):
log.info("start_proxy(%s, %s)", proto, packet)
#from now on, we forward client packets:
self.client_protocol = proto
self.client_protocol.set_packet_source(self.get_client_packet)
proto._process_packet_cb = self.process_client_packet
#figure out where the real server lives:
#FIXME: hardcoded
#FIXME: forward hello to server: need to remove auth and encoding params
target = "tcp:192.168.1.100:10000"
def parse_error(*args):
log.warn("parse error on %s: %s", target, args)
opts = make_defaults_struct()
disp_desc = parse_display_name(parse_error, opts, target)
log.info("display description(%s) = %s", target, disp_desc)
conn = connect_to(disp_desc)
log.info("server connection=%s", conn)
self.server_protocol = Protocol(conn, self.process_server_packet, self.get_server_packet)
log.info("server protocol=%s", self.server_protocol)
self.server_protocol.large_packets.append("keymap-changed")
self.server_protocol.large_packets.append("server-settings")
self.server_protocol.set_compression_level(0)
self.server_protocol.start()
#forward the hello packet:
self.client_to_server.put(packet)
self.server_protocol.source_has_more()

def get_server_packet(self):
#server wants a packet
return self.client_to_server.get(),

def get_client_packet(self):
#server wants a packet
return self.server_to_client.get(),

def process_server_packet(self, proto, packet):
log.info("process_server_packet: %s", packet[0])
#forward packet received from server to the client
self.server_to_client.put(packet)
self.client_protocol.source_has_more()

def process_client_packet(self, proto, packet):
log.info("process_client_packet: %s", packet[0])
#forward packet received from client to the server
self.client_to_server.put(packet)
self.server_protocol.source_has_more()
7 changes: 6 additions & 1 deletion src/xpra/server/server_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,11 @@ def cleanup_source(self, protocol):
self._potential_protocols.remove(protocol)
return source

def verify_connection_accepted(self, protocol):
if not protocol._closed and protocol in self._potential_protocols and protocol not in self._server_sources:
log.error("connection timedout: %s", protocol)
self.send_disconnect(protocol, "login timeout")


def no_more_clients(self):
#so it is now safe to clear them:
Expand All @@ -376,7 +381,7 @@ def _process_connection_lost(self, proto, packet):
sys.stdout.flush()


def hello_oked(self, auth_caps, proto, c):
def hello_oked(self, proto, packet, c, auth_caps):
screenshot_req = c.boolget("screenshot_request")
info_req = c.boolget("info_request", False)
if not screenshot_req and not info_req:
Expand Down
13 changes: 6 additions & 7 deletions src/xpra/server/server_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,13 +173,12 @@ def _new_connection(self, listener, *args):
protocol.set_compression_level(self.compression_level)
self._potential_protocols.append(protocol)
protocol.start()
def verify_connection_accepted(protocol):
if not protocol._closed and protocol in self._potential_protocols and protocol not in self._server_sources:
log.error("connection timedout: %s", protocol)
self.send_disconnect(protocol, "login timeout")
self.timeout_add(10*1000, verify_connection_accepted, protocol)
self.timeout_add(10*1000, self.verify_connection_accepted, protocol)
return True

def verify_connection_accepted(self, protocol):
raise NotImplementedError()

def send_disconnect(self, proto, reason):
log("send_disconnect(%s, %s)", proto, reason)
if proto._closed:
Expand Down Expand Up @@ -269,7 +268,7 @@ def _process_hello(self, proto, packet):
auth_caps = self.verify_hello(proto, c)
if auth_caps is not False:
#continue processing hello packet:
self.hello_oked(auth_caps, proto, c)
self.hello_oked(proto, packet, c, auth_caps)


def verify_hello(self, proto, c):
Expand Down Expand Up @@ -323,7 +322,7 @@ def verify_hello(self, proto, c):
return False
return auth_caps

def hello_oked(self, auth_caps, proto, c):
def hello_oked(self, proto, packet, c, auth_caps):
pass


Expand Down

0 comments on commit 3d61cc7

Please sign in to comment.