mirror of
https://github.com/novnc/noVNC.git
synced 2026-06-09 05:44:38 +00:00
Pull in latest websockify.
Pull in version 376872d99. Several changes including: - binary/typed array support in websock.js - unix socket support - multiple target support via config file(s) - prefer IPv6 option
This commit is contained in:
131
utils/websockify
131
utils/websockify
@@ -13,9 +13,11 @@ as taken from http://docs.python.org/dev/library/ssl.html#certificates
|
||||
|
||||
import socket, optparse, time, os, sys, subprocess
|
||||
from select import select
|
||||
from websocket import WebSocketServer
|
||||
import websocket
|
||||
try: from urllib.parse import parse_qs, urlparse
|
||||
except: from urlparse import parse_qs, urlparse
|
||||
|
||||
class WebSocketProxy(WebSocketServer):
|
||||
class WebSocketProxy(websocket.WebSocketServer):
|
||||
"""
|
||||
Proxy traffic to and from a WebSockets client to a normal TCP
|
||||
socket server target. All traffic to/from the client is base64
|
||||
@@ -43,6 +45,9 @@ Traffic Legend:
|
||||
self.target_port = kwargs.pop('target_port')
|
||||
self.wrap_cmd = kwargs.pop('wrap_cmd')
|
||||
self.wrap_mode = kwargs.pop('wrap_mode')
|
||||
self.unix_target = kwargs.pop('unix_target')
|
||||
self.ssl_target = kwargs.pop('ssl_target')
|
||||
self.target_cfg = kwargs.pop('target_cfg')
|
||||
# Last 3 timestamps command was run
|
||||
self.wrap_times = [0, 0, 0]
|
||||
|
||||
@@ -58,6 +63,7 @@ Traffic Legend:
|
||||
|
||||
if not self.rebinder:
|
||||
raise Exception("rebind.so not found, perhaps you need to run make")
|
||||
self.rebinder = os.path.abspath(self.rebinder)
|
||||
|
||||
self.target_host = "127.0.0.1" # Loopback
|
||||
# Find a free high port
|
||||
@@ -71,7 +77,10 @@ Traffic Legend:
|
||||
"REBIND_OLD_PORT": str(kwargs['listen_port']),
|
||||
"REBIND_NEW_PORT": str(self.target_port)})
|
||||
|
||||
WebSocketServer.__init__(self, *args, **kwargs)
|
||||
if self.target_cfg:
|
||||
self.target_cfg = os.path.abspath(self.target_cfg)
|
||||
|
||||
websocket.WebSocketServer.__init__(self, *args, **kwargs)
|
||||
|
||||
def run_wrap_cmd(self):
|
||||
print("Starting '%s'" % " ".join(self.wrap_cmd))
|
||||
@@ -88,14 +97,26 @@ Traffic Legend:
|
||||
# Need to call wrapped command after daemonization so we can
|
||||
# know when the wrapped command exits
|
||||
if self.wrap_cmd:
|
||||
print(" - proxying from %s:%s to '%s' (port %s)\n" % (
|
||||
self.listen_host, self.listen_port,
|
||||
" ".join(self.wrap_cmd), self.target_port))
|
||||
self.run_wrap_cmd()
|
||||
dst_string = "'%s' (port %s)" % (" ".join(self.wrap_cmd), self.target_port)
|
||||
elif self.unix_target:
|
||||
dst_string = self.unix_target
|
||||
else:
|
||||
print(" - proxying from %s:%s to %s:%s\n" % (
|
||||
self.listen_host, self.listen_port,
|
||||
self.target_host, self.target_port))
|
||||
dst_string = "%s:%s" % (self.target_host, self.target_port)
|
||||
|
||||
if self.target_cfg:
|
||||
msg = " - proxying from %s:%s to targets in %s" % (
|
||||
self.listen_host, self.listen_port, self.target_cfg)
|
||||
else:
|
||||
msg = " - proxying from %s:%s to %s" % (
|
||||
self.listen_host, self.listen_port, dst_string)
|
||||
|
||||
if self.ssl_target:
|
||||
msg += " (using SSL)"
|
||||
|
||||
print(msg + "\n")
|
||||
|
||||
if self.wrap_cmd:
|
||||
self.run_wrap_cmd()
|
||||
|
||||
def poll(self):
|
||||
# If we are wrapping a command, check it's status
|
||||
@@ -137,12 +158,26 @@ Traffic Legend:
|
||||
"""
|
||||
Called after a new WebSocket connection has been established.
|
||||
"""
|
||||
# Checks if we receive a token, and look
|
||||
# for a valid target for it then
|
||||
if self.target_cfg:
|
||||
(self.target_host, self.target_port) = self.get_target(self.target_cfg, self.path)
|
||||
|
||||
# Connect to the target
|
||||
self.msg("connecting to: %s:%s" % (
|
||||
self.target_host, self.target_port))
|
||||
if self.wrap_cmd:
|
||||
msg = "connecting to command: %s" % (" ".join(self.wrap_cmd), self.target_port)
|
||||
elif self.unix_target:
|
||||
msg = "connecting to unix socket: %s" % self.unix_target
|
||||
else:
|
||||
msg = "connecting to: %s:%s" % (
|
||||
self.target_host, self.target_port)
|
||||
|
||||
if self.ssl_target:
|
||||
msg += " (using SSL)"
|
||||
self.msg(msg)
|
||||
|
||||
tsock = self.socket(self.target_host, self.target_port,
|
||||
connect=True)
|
||||
connect=True, use_ssl=self.ssl_target, unix_socket=self.unix_target)
|
||||
|
||||
if self.verbose and not self.daemon:
|
||||
print(self.traffic_legend)
|
||||
@@ -154,10 +189,49 @@ Traffic Legend:
|
||||
if tsock:
|
||||
tsock.shutdown(socket.SHUT_RDWR)
|
||||
tsock.close()
|
||||
self.vmsg("%s:%s: Target closed" %(
|
||||
self.vmsg("%s:%s: Closed target" %(
|
||||
self.target_host, self.target_port))
|
||||
raise
|
||||
|
||||
def get_target(self, target_cfg, path):
|
||||
"""
|
||||
Parses the path, extracts a token, and looks for a valid
|
||||
target for that token in the configuration file(s). Sets
|
||||
target_host and target_port if successful
|
||||
"""
|
||||
# The files in targets contain the lines
|
||||
# in the form of token: host:port
|
||||
|
||||
# Extract the token parameter from url
|
||||
args = parse_qs(urlparse(path)[4]) # 4 is the query from url
|
||||
|
||||
if not len(args['token']):
|
||||
raise self.EClose("Token not present")
|
||||
|
||||
token = args['token'][0].rstrip('\n')
|
||||
|
||||
# target_cfg can be a single config file or directory of
|
||||
# config files
|
||||
if os.path.isdir(target_cfg):
|
||||
cfg_files = [os.path.join(target_cfg, f)
|
||||
for f in os.listdir(target_cfg)]
|
||||
else:
|
||||
cfg_files = [target_cfg]
|
||||
|
||||
targets = {}
|
||||
for f in cfg_files:
|
||||
for line in [l.strip() for l in file(f).readlines()]:
|
||||
if line and not line.startswith('#'):
|
||||
ttoken, target = line.split(': ')
|
||||
targets[ttoken] = target.strip()
|
||||
|
||||
self.vmsg("Target config: %s" % repr(targets))
|
||||
|
||||
if targets.has_key(token):
|
||||
return targets[token].split(':')
|
||||
else:
|
||||
raise self.EClose("Token '%s' not found" % token)
|
||||
|
||||
def do_proxy(self, target):
|
||||
"""
|
||||
Proxy client WebSocket to normal target socket.
|
||||
@@ -191,6 +265,8 @@ Traffic Legend:
|
||||
# Receive target data, encode it and queue for client
|
||||
buf = target.recv(self.buffer_size)
|
||||
if len(buf) == 0:
|
||||
self.vmsg("%s:%s: Target closed connection" %(
|
||||
self.target_host, self.target_port))
|
||||
raise self.CClose(1000, "Target closed")
|
||||
|
||||
cqueue.append(buf)
|
||||
@@ -211,11 +287,13 @@ Traffic Legend:
|
||||
|
||||
if closed:
|
||||
# TODO: What about blocking on client socket?
|
||||
self.vmsg("%s:%s: Client closed connection" %(
|
||||
self.target_host, self.target_port))
|
||||
raise self.CClose(closed['code'], closed['reason'])
|
||||
|
||||
def websockify_init():
|
||||
usage = "\n %prog [options]"
|
||||
usage += " [source_addr:]source_port target_addr:target_port"
|
||||
usage += " [source_addr:]source_port [target_addr:target_port]"
|
||||
usage += "\n %prog [options]"
|
||||
usage += " [source_addr:]source_port -- WRAP_COMMAND_LINE"
|
||||
parser = optparse.OptionParser(usage=usage)
|
||||
@@ -235,17 +313,29 @@ def websockify_init():
|
||||
parser.add_option("--key", default=None,
|
||||
help="SSL key file (if separate from cert)")
|
||||
parser.add_option("--ssl-only", action="store_true",
|
||||
help="disallow non-encrypted connections")
|
||||
help="disallow non-encrypted client connections")
|
||||
parser.add_option("--ssl-target", action="store_true",
|
||||
help="connect to SSL target as SSL client")
|
||||
parser.add_option("--unix-target",
|
||||
help="connect to unix socket target", metavar="FILE")
|
||||
parser.add_option("--web", default=None, metavar="DIR",
|
||||
help="run webserver on same port. Serve files from DIR.")
|
||||
parser.add_option("--wrap-mode", default="exit", metavar="MODE",
|
||||
choices=["exit", "ignore", "respawn"],
|
||||
help="action to take when the wrapped program exits "
|
||||
"or daemonizes: exit (default), ignore, respawn")
|
||||
parser.add_option("--prefer-ipv6", "-6",
|
||||
action="store_true", dest="source_is_ipv6",
|
||||
help="prefer IPv6 when resolving source_addr")
|
||||
parser.add_option("--target-config", metavar="FILE",
|
||||
dest="target_cfg",
|
||||
help="Configuration file containing valid targets "
|
||||
"in the form 'token: host:port' or, alternatively, a "
|
||||
"directory containing configuration files of this form")
|
||||
(opts, args) = parser.parse_args()
|
||||
|
||||
# Sanity checks
|
||||
if len(args) < 2:
|
||||
if len(args) < 2 and not opts.target_cfg:
|
||||
parser.error("Too few arguments")
|
||||
if sys.argv.count('--'):
|
||||
opts.wrap_cmd = args[1:]
|
||||
@@ -254,24 +344,29 @@ def websockify_init():
|
||||
if len(args) > 2:
|
||||
parser.error("Too many arguments")
|
||||
|
||||
if not websocket.ssl and opts.ssl_target:
|
||||
parser.error("SSL target requested and Python SSL module not loaded.");
|
||||
|
||||
if opts.ssl_only and not os.path.exists(opts.cert):
|
||||
parser.error("SSL only and %s not found" % opts.cert)
|
||||
|
||||
# Parse host:port and convert ports to numbers
|
||||
if args[0].count(':') > 0:
|
||||
opts.listen_host, opts.listen_port = args[0].rsplit(':', 1)
|
||||
opts.listen_host = opts.listen_host.strip('[]')
|
||||
else:
|
||||
opts.listen_host, opts.listen_port = '', args[0]
|
||||
|
||||
try: opts.listen_port = int(opts.listen_port)
|
||||
except: parser.error("Error parsing listen port")
|
||||
|
||||
if opts.wrap_cmd:
|
||||
if opts.wrap_cmd or opts.unix_target or opts.target_cfg:
|
||||
opts.target_host = None
|
||||
opts.target_port = None
|
||||
else:
|
||||
if args[1].count(':') > 0:
|
||||
opts.target_host, opts.target_port = args[1].rsplit(':', 1)
|
||||
opts.target_host = opts.target_host.strip('[]')
|
||||
else:
|
||||
parser.error("Error parsing target")
|
||||
try: opts.target_port = int(opts.target_port)
|
||||
|
||||
Reference in New Issue
Block a user