From fc003a13e7fcbbad6cb871d3ed6f2543078a35d7 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 15 Aug 2012 13:48:03 -0500 Subject: [PATCH] Revert "Pull in latest websockify." This reverts commit 4dd1bb1ecbd49c112c2f7ff4bd66016ebe38d063. --- include/websock.js | 103 ++++--------------- utils/websocket.py | 240 +++++++++++++++++++++------------------------ utils/websockify | 131 ++++--------------------- 3 files changed, 150 insertions(+), 324 deletions(-) diff --git a/include/websock.js b/include/websock.js index e3f9d2af..20d51d6a 100644 --- a/include/websock.js +++ b/include/websock.js @@ -61,8 +61,6 @@ function Websock() { var api = {}, // Public API websocket = null, // WebSocket object - protocols, // Protocols to request in priority order - mode = 'base64', rQ = [], // Receive queue rQi = 0, // Receive queue index rQmax = 10000, // Max receive queue size before compacting @@ -125,46 +123,26 @@ function rQshift32() { (rQ[rQi++] << 8) + (rQ[rQi++] ); } -function rQslice(start, end) { - if (mode === 'binary') { - if (end) { - return rQ.subarray(rQi + start, rQi + end); - } else { - return rQ.subarray(rQi + start); - } - } else { - if (end) { - return rQ.slice(rQi + start, rQi + end); - } else { - return rQ.slice(rQi + start); - } - } -} - function rQshiftStr(len) { if (typeof(len) === 'undefined') { len = rQlen(); } - var arr = rQslice(0, len); + var arr = rQ.slice(rQi, rQi + len); rQi += len; - return String.fromCharCode.apply(null, arr); + return arr.map(function (num) { + return String.fromCharCode(num); } ).join(''); + } function rQshiftBytes(len) { if (typeof(len) === 'undefined') { len = rQlen(); } - var a = rQslice(0, len), b = []; - if (mode === 'binary') { - // Convert to plain array - b.push.apply(b, a); - } else { - // Already plain array, just return the original - b = a - } rQi += len; - return b; + return rQ.slice(rQi-len, rQi); } -function rQshiftArray(len) { - if (typeof(len) === 'undefined') { len = rQlen(); } - var a = rQslice(0, len); - rQi += len; - return a; + +function rQslice(start, end) { + if (end) { + return rQ.slice(rQi + start, rQi + end); + } else { + return rQ.slice(rQi + start); + } } // Check to see if we must wait for 'num' bytes (default to FBU.bytes) @@ -192,26 +170,13 @@ function rQwait(msg, num, goback) { function encode_message() { /* base64 encode */ - if (mode === 'binary') { - return (new Uint8Array(sQ)).buffer; - } else { - return Base64.encode(sQ); - } + return Base64.encode(sQ); } function decode_message(data) { //Util.Debug(">> decode_message: " + data); - if (mode === 'binary') { - // Create new arraybuffer and dump old and new data into it - // TODO: this could be far more efficient and re-use the array - var new_rQ = new Uint8Array(rQ.length + data.byteLength); - new_rQ.set(rQ); - new_rQ.set(new Uint8Array(data), rQ.length); - rQ = new_rQ; - } else { - /* base64 decode and concat to the end */ - rQ = rQ.concat(Base64.decode(data, 0)); - } + /* base64 decode */ + rQ = rQ.concat(Base64.decode(data, 0)); //Util.Debug(">> decode_message, rQ: " + rQ); } @@ -265,7 +230,7 @@ function recv_message(e) { // Compact the receive queue if (rQ.length > rQmax) { //Util.Debug("Compacting receive queue"); - rQ = rQslice(rQi); + rQ = rQ.slice(rQi); rQi = 0; } } else { @@ -298,32 +263,7 @@ function init() { rQ = []; rQi = 0; sQ = []; - websocket = null, - protocols = "base64"; - - var bt = false, - wsbt = false; - - if (('Uint8Array' in window) && - ('set' in Uint8Array.prototype)) { - bt = true; - } - // TODO: this sucks, the property should exist on the prototype - // but it does not. - try { - if (bt && ('binaryType' in (new WebSocket("ws://localhost:17523")))) { - wsbt = true; - } - } catch (exc) { - // Just ignore failed test localhost connections - } - if (bt && wsbt) { - Util.Info("Detected binaryType support in WebSockets"); - protocols = ['binary', 'base64']; - } else { - Util.Info("No binaryType support in WebSockets, using base64 encoding"); - protocols = 'base64'; - } + websocket = null; } function open(uri) { @@ -332,22 +272,19 @@ function open(uri) { if (test_mode) { websocket = {}; } else { - websocket = new WebSocket(uri, protocols); + websocket = new WebSocket(uri, 'base64'); + // TODO: future native binary support + //websocket = new WebSocket(uri, ['binary', 'base64']); } websocket.onmessage = recv_message; websocket.onopen = function() { Util.Debug(">> WebSock.onopen"); if (websocket.protocol) { - mode = websocket.protocol; Util.Info("Server chose sub-protocol: " + websocket.protocol); } else { - mode = 'base64'; Util.Error("Server select no sub-protocol!: " + websocket.protocol); } - if (mode === 'binary') { - websocket.binaryType = 'arraybuffer'; - } eventHandlers.open(); Util.Debug("<< WebSock.onopen"); }; diff --git a/utils/websocket.py b/utils/websocket.py index 11f718cd..d3bb48cb 100644 --- a/utils/websocket.py +++ b/utils/websocket.py @@ -18,6 +18,7 @@ as taken from http://docs.python.org/dev/library/ssl.html#certificates import os, sys, time, errno, signal, socket, traceback, select import array, struct +from cgi import parse_qsl from base64 import b64encode, b64decode # Imports that vary by python version @@ -35,6 +36,8 @@ try: from io import StringIO except: from cStringIO import StringIO try: from http.server import SimpleHTTPRequestHandler except: from SimpleHTTPServer import SimpleHTTPRequestHandler +try: from urllib.parse import urlsplit +except: from urlparse import urlsplit # python 2.6 differences try: from hashlib import md5, sha1 @@ -72,7 +75,6 @@ class WebSocketServer(object): buffer_size = 65536 - server_handshake_hixie = """HTTP/1.1 101 Web Socket Protocol Handshake\r Upgrade: WebSocket\r Connection: Upgrade\r @@ -107,12 +109,11 @@ Sec-WebSocket-Accept: %s\r self.verbose = verbose self.listen_host = listen_host self.listen_port = listen_port - self.prefer_ipv6 = source_is_ipv6 self.ssl_only = ssl_only self.daemon = daemon self.run_once = run_once self.timeout = timeout - + self.launch_time = time.time() self.ws_connection = False self.handler_id = 1 @@ -162,7 +163,7 @@ Sec-WebSocket-Accept: %s\r # @staticmethod - def socket(host, port=None, connect=False, prefer_ipv6=False, unix_socket=None, use_ssl=False): + def socket(host, port=None, connect=False, prefer_ipv6=False): """ Resolve a host (and optional port) to an IPv4 or IPv6 address. Create a socket. Bind to it if listen is set, otherwise connect to it. Return the socket. @@ -170,36 +171,24 @@ Sec-WebSocket-Accept: %s\r flags = 0 if host == '': host = None - if connect and not (port or unix_socket): + if connect and not port: raise Exception("Connect mode requires a port") - if use_ssl and not ssl: - raise Exception("SSL socket requested but Python SSL module not loaded."); - if not connect and use_ssl: - raise Exception("SSL only supported in connect mode (for now)") if not connect: flags = flags | socket.AI_PASSIVE - - if not unix_socket: - addrs = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM, - socket.IPPROTO_TCP, flags) - if not addrs: - raise Exception("Could not resolve host '%s'" % host) - addrs.sort(key=lambda x: x[0]) - if prefer_ipv6: - addrs.reverse() - sock = socket.socket(addrs[0][0], addrs[0][1]) - if connect: - sock.connect(addrs[0][4]) - if use_ssl: - sock = ssl.wrap_socket(sock) - else: - sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - sock.bind(addrs[0][4]) - sock.listen(100) - else: - sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - sock.connect(unix_socket) - + addrs = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM, + socket.IPPROTO_TCP, flags) + if not addrs: + raise Exception("Could resolve host '%s'" % host) + addrs.sort(key=lambda x: x[0]) + if prefer_ipv6: + addrs.reverse() + sock = socket.socket(addrs[0][0], addrs[0][1]) + if connect: + sock.connect(addrs[0][4]) + else: + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + sock.bind(addrs[0][4]) + sock.listen(100) return sock @staticmethod @@ -563,9 +552,93 @@ Sec-WebSocket-Accept: %s\r # No orderly close for 75 - def do_websocket_handshake(self, headers, path): - h = self.headers = headers - self.path = path + def do_handshake(self, sock, address): + """ + do_handshake does the following: + - Peek at the first few bytes from the socket. + - If the connection is Flash policy request then answer it, + close the socket and return. + - If the connection is an HTTPS/SSL/TLS connection then SSL + wrap the socket. + - Read from the (possibly wrapped) socket. + - If we have received a HTTP GET request and the webserver + functionality is enabled, answer it, close the socket and + return. + - Assume we have a WebSockets connection, parse the client + handshake data. + - Send a WebSockets handshake server response. + - Return the socket for this WebSocket client. + """ + + stype = "" + + ready = select.select([sock], [], [], 3)[0] + if not ready: + raise self.EClose("ignoring socket not ready") + # Peek, but do not read the data so that we have a opportunity + # to SSL wrap the socket first + handshake = sock.recv(1024, socket.MSG_PEEK) + #self.msg("Handshake [%s]" % handshake) + + if handshake == "": + raise self.EClose("ignoring empty handshake") + + elif handshake.startswith(s2b("")): + # Answer Flash policy request + handshake = sock.recv(1024) + sock.send(s2b(self.policy_response)) + raise self.EClose("Sending flash policy response") + + elif handshake[0] in ("\x16", "\x80", 22, 128): + # SSL wrap the connection + if not ssl: + raise self.EClose("SSL connection but no 'ssl' module") + if not os.path.exists(self.cert): + raise self.EClose("SSL connection but '%s' not found" + % self.cert) + retsock = None + try: + retsock = ssl.wrap_socket( + sock, + server_side=True, + certfile=self.cert, + keyfile=self.key) + except ssl.SSLError: + _, x, _ = sys.exc_info() + if x.args[0] == ssl.SSL_ERROR_EOF: + if len(x.args) > 1: + raise self.EClose(x.args[1]) + else: + raise self.EClose("Got SSL_ERROR_EOF") + else: + raise + + scheme = "wss" + stype = "SSL/TLS (wss://)" + + elif self.ssl_only: + raise self.EClose("non-SSL connection received but disallowed") + + else: + retsock = sock + scheme = "ws" + stype = "Plain non-SSL (ws://)" + + wsh = WSRequestHandler(retsock, address, not self.web) + if wsh.last_code == 101: + # Continue on to handle WebSocket upgrade + pass + elif wsh.last_code == 405: + raise self.EClose("Normal web request received but disallowed") + elif wsh.last_code < 200 or wsh.last_code >= 300: + raise self.EClose(wsh.last_message) + elif self.verbose: + raise self.EClose(wsh.last_message) + else: + raise self.EClose("") + + h = self.headers = wsh.headers + path = self.path = wsh.path prot = 'WebSocket-Protocol' protocols = h.get('Sec-'+prot, h.get(prot, '')).split(',') @@ -618,7 +691,7 @@ Sec-WebSocket-Accept: %s\r self.base64 = True response = self.server_handshake_hixie % (pre, - h['Origin'], pre, self.scheme, h['Host'], path) + h['Origin'], pre, scheme, h['Host'], path) if 'base64' in protocols: response += "%sWebSocket-Protocol: base64\r\n" % pre @@ -626,96 +699,6 @@ Sec-WebSocket-Accept: %s\r self.msg("Warning: client does not report 'base64' protocol support") response += "\r\n" + trailer - return response - - - def do_handshake(self, sock, address): - """ - do_handshake does the following: - - Peek at the first few bytes from the socket. - - If the connection is Flash policy request then answer it, - close the socket and return. - - If the connection is an HTTPS/SSL/TLS connection then SSL - wrap the socket. - - Read from the (possibly wrapped) socket. - - If we have received a HTTP GET request and the webserver - functionality is enabled, answer it, close the socket and - return. - - Assume we have a WebSockets connection, parse the client - handshake data. - - Send a WebSockets handshake server response. - - Return the socket for this WebSocket client. - """ - stype = "" - ready = select.select([sock], [], [], 3)[0] - - - if not ready: - raise self.EClose("ignoring socket not ready") - # Peek, but do not read the data so that we have a opportunity - # to SSL wrap the socket first - handshake = sock.recv(1024, socket.MSG_PEEK) - #self.msg("Handshake [%s]" % handshake) - - if handshake == "": - raise self.EClose("ignoring empty handshake") - - elif handshake.startswith(s2b("")): - # Answer Flash policy request - handshake = sock.recv(1024) - sock.send(s2b(self.policy_response)) - raise self.EClose("Sending flash policy response") - - elif handshake[0] in ("\x16", "\x80", 22, 128): - # SSL wrap the connection - if not ssl: - raise self.EClose("SSL connection but no 'ssl' module") - if not os.path.exists(self.cert): - raise self.EClose("SSL connection but '%s' not found" - % self.cert) - retsock = None - try: - retsock = ssl.wrap_socket( - sock, - server_side=True, - certfile=self.cert, - keyfile=self.key) - except ssl.SSLError: - _, x, _ = sys.exc_info() - if x.args[0] == ssl.SSL_ERROR_EOF: - if len(x.args) > 1: - raise self.EClose(x.args[1]) - else: - raise self.EClose("Got SSL_ERROR_EOF") - else: - raise - - self.scheme = "wss" - stype = "SSL/TLS (wss://)" - - elif self.ssl_only: - raise self.EClose("non-SSL connection received but disallowed") - - else: - retsock = sock - self.scheme = "ws" - stype = "Plain non-SSL (ws://)" - - wsh = WSRequestHandler(retsock, address, not self.web) - if wsh.last_code == 101: - # Continue on to handle WebSocket upgrade - pass - elif wsh.last_code == 405: - raise self.EClose("Normal web request received but disallowed") - elif wsh.last_code < 200 or wsh.last_code >= 300: - raise self.EClose(wsh.last_message) - elif self.verbose: - raise self.EClose(wsh.last_message) - else: - raise self.EClose("") - - response = self.do_websocket_handshake(wsh.headers, wsh.path) - self.msg("%s: %s WebSocket connection" % (address[0], stype)) self.msg("%s: Version %s, base64: '%s'" % (address[0], self.version, self.base64)) @@ -767,7 +750,7 @@ Sec-WebSocket-Accept: %s\r self.rec = None self.start_time = int(time.time()*1000) - # handler process + # handler process try: try: self.client = self.do_handshake(startsock, address) @@ -818,7 +801,7 @@ Sec-WebSocket-Accept: %s\r is a WebSockets client then call new_client() method (which must be overridden) for each new client connection. """ - lsock = self.socket(self.listen_host, self.listen_port, False, self.prefer_ipv6) + lsock = self.socket(self.listen_host, self.listen_port) if self.daemon: self.daemonize(keepfd=lsock.fileno(), chdir=self.web) @@ -865,7 +848,7 @@ Sec-WebSocket-Accept: %s\r continue else: raise - + if self.run_once: # Run in same process if run_once self.top_new_client(startsock, address) @@ -944,3 +927,4 @@ class WSRequestHandler(SimpleHTTPRequestHandler): def log_message(self, f, *args): # Save instead of printing self.last_message = f % args + diff --git a/utils/websockify b/utils/websockify index 965ce13c..550dff7a 100755 --- a/utils/websockify +++ b/utils/websockify @@ -13,11 +13,9 @@ as taken from http://docs.python.org/dev/library/ssl.html#certificates import socket, optparse, time, os, sys, subprocess from select import select -import websocket -try: from urllib.parse import parse_qs, urlparse -except: from urlparse import parse_qs, urlparse +from websocket import WebSocketServer -class WebSocketProxy(websocket.WebSocketServer): +class WebSocketProxy(WebSocketServer): """ Proxy traffic to and from a WebSockets client to a normal TCP socket server target. All traffic to/from the client is base64 @@ -45,9 +43,6 @@ 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] @@ -63,7 +58,6 @@ 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 @@ -77,10 +71,7 @@ Traffic Legend: "REBIND_OLD_PORT": str(kwargs['listen_port']), "REBIND_NEW_PORT": str(self.target_port)}) - if self.target_cfg: - self.target_cfg = os.path.abspath(self.target_cfg) - - websocket.WebSocketServer.__init__(self, *args, **kwargs) + WebSocketServer.__init__(self, *args, **kwargs) def run_wrap_cmd(self): print("Starting '%s'" % " ".join(self.wrap_cmd)) @@ -97,26 +88,14 @@ Traffic Legend: # Need to call wrapped command after daemonization so we can # know when the wrapped command exits if self.wrap_cmd: - dst_string = "'%s' (port %s)" % (" ".join(self.wrap_cmd), self.target_port) - elif self.unix_target: - dst_string = self.unix_target - else: - 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: + 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() + else: + print(" - proxying from %s:%s to %s:%s\n" % ( + self.listen_host, self.listen_port, + self.target_host, self.target_port)) def poll(self): # If we are wrapping a command, check it's status @@ -158,26 +137,12 @@ 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 - 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) - + self.msg("connecting to: %s:%s" % ( + self.target_host, self.target_port)) tsock = self.socket(self.target_host, self.target_port, - connect=True, use_ssl=self.ssl_target, unix_socket=self.unix_target) + connect=True) if self.verbose and not self.daemon: print(self.traffic_legend) @@ -189,49 +154,10 @@ Traffic Legend: if tsock: tsock.shutdown(socket.SHUT_RDWR) tsock.close() - self.vmsg("%s:%s: Closed target" %( + self.vmsg("%s:%s: Target closed" %( 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. @@ -265,8 +191,6 @@ 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) @@ -287,13 +211,11 @@ 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) @@ -313,29 +235,17 @@ 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 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") + help="disallow non-encrypted connections") 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 and not opts.target_cfg: + if len(args) < 2: parser.error("Too few arguments") if sys.argv.count('--'): opts.wrap_cmd = args[1:] @@ -344,29 +254,24 @@ 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 or opts.unix_target or opts.target_cfg: + if opts.wrap_cmd: 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)