diff --git a/include/web-socket-js/WebSocketMain.swf b/include/web-socket-js/WebSocketMain.swf index 244c445b..81744669 100644 Binary files a/include/web-socket-js/WebSocketMain.swf and b/include/web-socket-js/WebSocketMain.swf differ diff --git a/include/web-socket-js/web_socket.js b/include/web-socket-js/web_socket.js index a1330133..06cc5d02 100644 --- a/include/web-socket-js/web_socket.js +++ b/include/web-socket-js/web_socket.js @@ -1,49 +1,69 @@ // Copyright: Hiroshi Ichikawa // License: New BSD License // Reference: http://dev.w3.org/html5/websockets/ -// Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol +// Reference: http://tools.ietf.org/html/rfc6455 (function() { - if (window.WebSocket && !window.WEB_SOCKET_FORCE_FLASH) return; - - var console = window.console; - if (!console || !console.log || !console.error) { - console = {log: function(){ }, error: function(){ }}; + if (window.WEB_SOCKET_FORCE_FLASH) { + // Keeps going. + } else if (window.WebSocket) { + return; + } else if (window.MozWebSocket) { + // Firefox. + window.WebSocket = MozWebSocket; + return; } - if (!swfobject.hasFlashPlayerVersion("10.0.0")) { - console.error("Flash Player >= 10.0.0 is required."); + var logger; + if (window.WEB_SOCKET_LOGGER) { + logger = WEB_SOCKET_LOGGER; + } else if (window.console && window.console.log && window.console.error) { + // In some environment, console is defined but console.log or console.error is missing. + logger = window.console; + } else { + logger = {log: function(){ }, error: function(){ }}; + } + + // swfobject.hasFlashPlayerVersion("10.0.0") doesn't work with Gnash. + if (swfobject.getFlashPlayerVersion().major < 10) { + logger.error("Flash Player >= 10.0.0 is required."); return; } if (location.protocol == "file:") { - console.error( + logger.error( "WARNING: web-socket-js doesn't work in file:///... URL " + "unless you set Flash Security Settings properly. " + "Open the page via Web server i.e. http://..."); } /** - * This class represents a faux web socket. + * Our own implementation of WebSocket class using Flash. * @param {string} url - * @param {string} protocol + * @param {array or string} protocols * @param {string} proxyHost * @param {int} proxyPort * @param {string} headers */ - WebSocket = function(url, protocol, proxyHost, proxyPort, headers) { + window.WebSocket = function(url, protocols, proxyHost, proxyPort, headers) { var self = this; self.__id = WebSocket.__nextId++; WebSocket.__instances[self.__id] = self; self.readyState = WebSocket.CONNECTING; self.bufferedAmount = 0; self.__events = {}; + if (!protocols) { + protocols = []; + } else if (typeof protocols == "string") { + protocols = [protocols]; + } // Uses setTimeout() to make sure __createFlash() runs after the caller sets ws.onopen etc. // Otherwise, when onopen fires immediately, onopen is called before it is set. - setTimeout(function() { + self.__createTask = setTimeout(function() { WebSocket.__addTask(function() { + self.__createTask = null; WebSocket.__flash.create( - self.__id, url, protocol, proxyHost || null, proxyPort || 0, headers || null); + self.__id, url, protocols, proxyHost || null, proxyPort || 0, headers || null); }); }, 0); }; @@ -78,6 +98,12 @@ * Close this web socket gracefully. */ WebSocket.prototype.close = function() { + if (this.__createTask) { + clearTimeout(this.__createTask); + this.__createTask = null; + this.readyState = WebSocket.CLOSED; + return; + } if (this.readyState == WebSocket.CLOSED || this.readyState == WebSocket.CLOSING) { return; } @@ -131,7 +157,7 @@ events[i](event); } var handler = this["on" + event.type]; - if (handler) handler(event); + if (handler) handler.apply(this, [event]); }; /** @@ -139,16 +165,22 @@ * @param {Object} flashEvent */ WebSocket.prototype.__handleEvent = function(flashEvent) { + if ("readyState" in flashEvent) { this.readyState = flashEvent.readyState; } + if ("protocol" in flashEvent) { + this.protocol = flashEvent.protocol; + } var jsEvent; if (flashEvent.type == "open" || flashEvent.type == "error") { jsEvent = this.__createSimpleEvent(flashEvent.type); } else if (flashEvent.type == "close") { - // TODO implement jsEvent.wasClean jsEvent = this.__createSimpleEvent("close"); + jsEvent.wasClean = flashEvent.wasClean ? true : false; + jsEvent.code = flashEvent.code; + jsEvent.reason = flashEvent.reason; } else if (flashEvent.type == "message") { var data = decodeURIComponent(flashEvent.message); jsEvent = this.__createMessageEvent("message", data); @@ -157,6 +189,7 @@ } this.dispatchEvent(jsEvent); + }; WebSocket.prototype.__createSimpleEvent = function(type) { @@ -188,6 +221,9 @@ WebSocket.CLOSING = 2; WebSocket.CLOSED = 3; + // Field to check implementation of WebSocket. + WebSocket.__isFlashImplementation = true; + WebSocket.__initialized = false; WebSocket.__flash = null; WebSocket.__instances = {}; WebSocket.__tasks = []; @@ -207,16 +243,31 @@ * Loads WebSocketMain.swf and creates WebSocketMain object in Flash. */ WebSocket.__initialize = function() { - if (WebSocket.__flash) return; + + if (WebSocket.__initialized) return; + WebSocket.__initialized = true; if (WebSocket.__swfLocation) { // For backword compatibility. window.WEB_SOCKET_SWF_LOCATION = WebSocket.__swfLocation; } if (!window.WEB_SOCKET_SWF_LOCATION) { - console.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf"); + logger.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf"); return; } + if (!window.WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR && + !WEB_SOCKET_SWF_LOCATION.match(/(^|\/)WebSocketMainInsecure\.swf(\?.*)?$/) && + WEB_SOCKET_SWF_LOCATION.match(/^\w+:\/\/([^\/]+)/)) { + var swfHost = RegExp.$1; + if (location.host != swfHost) { + logger.error( + "[WebSocket] You must host HTML and WebSocketMain.swf in the same host " + + "('" + location.host + "' != '" + swfHost + "'). " + + "See also 'How to host HTML file and SWF file in different domains' section " + + "in README.md. If you use WebSocketMainInsecure.swf, you can suppress this message " + + "by WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR = true;"); + } + } var container = document.createElement("div"); container.id = "webSocketContainer"; // Hides Flash box. We cannot use display: none or visibility: hidden because it prevents @@ -250,9 +301,11 @@ null, function(e) { if (!e.success) { - console.error("[WebSocket] swfobject.embedSWF failed"); + logger.error("[WebSocket] swfobject.embedSWF failed"); } - }); + } + ); + }; /** @@ -287,7 +340,7 @@ WebSocket.__instances[events[i].webSocketId].__handleEvent(events[i]); } } catch (e) { - console.error(e); + logger.error(e); } }, 0); return true; @@ -295,12 +348,12 @@ // Called by Flash. WebSocket.__log = function(message) { - console.log(decodeURIComponent(message)); + logger.log(decodeURIComponent(message)); }; // Called by Flash. WebSocket.__error = function(message) { - console.error(decodeURIComponent(message)); + logger.error(decodeURIComponent(message)); }; WebSocket.__addTask = function(task) { @@ -327,15 +380,12 @@ }; if (!window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION) { - if (window.addEventListener) { - window.addEventListener("load", function(){ - WebSocket.__initialize(); - }, false); - } else { - window.attachEvent("onload", function(){ - WebSocket.__initialize(); - }); - } + // NOTE: + // This fires immediately if web_socket.js is dynamically loaded after + // the document is loaded. + swfobject.addDomLoadEvent(function() { + WebSocket.__initialize(); + }); } })(); diff --git a/include/websock.js b/include/websock.js index 935e3a1e..ccb7d4c1 100644 --- a/include/websock.js +++ b/include/websock.js @@ -128,9 +128,7 @@ function rQshiftStr(len) { if (typeof(len) === 'undefined') { len = rQlen(); } var arr = rQ.slice(rQi, rQi + len); rQi += len; - return arr.map(function (num) { - return String.fromCharCode(num); } ).join(''); - + return String.fromCharCode.apply(null, arr); } function rQshiftBytes(len) { if (typeof(len) === 'undefined') { len = rQlen(); } @@ -313,11 +311,19 @@ function init(protocols) { throw("WebSocket binary sub-protocol requested but not supported"); } if (typeof(protocols) === "object") { + var new_protocols = []; for (var i = 0; i < protocols.length; i++) { if (protocols[i] === 'binary') { - throw("WebSocket binary sub-protocol requested but not supported"); + Util.Error("Skipping unsupported WebSocket binary sub-protocol"); + } else { + new_protocols.push(protocols[i]); } } + if (new_protocols.length > 0) { + protocols = new_protocols; + } else { + throw("Only WebSocket binary sub-protocol was requested and not supported."); + } } } diff --git a/utils/websocket.py b/utils/websocket.py index d3bb48cb..4c0cacc9 100644 --- a/utils/websocket.py +++ b/utils/websocket.py @@ -18,7 +18,6 @@ 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 @@ -36,8 +35,6 @@ 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 @@ -75,6 +72,7 @@ class WebSocketServer(object): buffer_size = 65536 + server_handshake_hixie = """HTTP/1.1 101 Web Socket Protocol Handshake\r Upgrade: WebSocket\r Connection: Upgrade\r @@ -103,17 +101,19 @@ Sec-WebSocket-Accept: %s\r def __init__(self, listen_host='', listen_port=None, source_is_ipv6=False, verbose=False, cert='', key='', ssl_only=None, daemon=False, record='', web='', - run_once=False, timeout=0): + run_once=False, timeout=0, idle_timeout=0): # settings 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.idle_timeout = idle_timeout + self.launch_time = time.time() self.ws_connection = False self.handler_id = 1 @@ -163,7 +163,7 @@ Sec-WebSocket-Accept: %s\r # @staticmethod - def socket(host, port=None, connect=False, prefer_ipv6=False): + def socket(host, port=None, connect=False, prefer_ipv6=False, unix_socket=None, use_ssl=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. @@ -171,24 +171,36 @@ Sec-WebSocket-Accept: %s\r flags = 0 if host == '': host = None - if connect and not port: + if connect and not (port or unix_socket): 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 - 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) + + 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) + return sock @staticmethod @@ -552,93 +564,9 @@ Sec-WebSocket-Accept: %s\r # No orderly close for 75 - 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 + def do_websocket_handshake(self, headers, path): + h = self.headers = headers + self.path = path prot = 'WebSocket-Protocol' protocols = h.get('Sec-'+prot, h.get(prot, '')).split(',') @@ -691,7 +619,7 @@ Sec-WebSocket-Accept: %s\r self.base64 = True response = self.server_handshake_hixie % (pre, - h['Origin'], pre, scheme, h['Host'], path) + h['Origin'], pre, self.scheme, h['Host'], path) if 'base64' in protocols: response += "%sWebSocket-Protocol: base64\r\n" % pre @@ -699,6 +627,96 @@ 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)) @@ -750,7 +768,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) @@ -801,7 +819,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) + lsock = self.socket(self.listen_host, self.listen_port, False, self.prefer_ipv6) if self.daemon: self.daemonize(keepfd=lsock.fileno(), chdir=self.web) @@ -814,12 +832,17 @@ Sec-WebSocket-Accept: %s\r # os.fork() (python 2.4) child reaper signal.signal(signal.SIGCHLD, self.fallback_SIGCHLD) + last_active_time = self.launch_time while True: try: try: self.client = None startsock = None pid = err = 0 + child_count = 0 + + if multiprocessing and self.idle_timeout: + child_count = len(multiprocessing.active_children()) time_elapsed = time.time() - self.launch_time if self.timeout and time_elapsed > self.timeout: @@ -827,6 +850,19 @@ Sec-WebSocket-Accept: %s\r % self.timeout) break + if self.idle_timeout: + idle_time = 0 + if child_count == 0: + idle_time = time.time() - last_active_time + else: + idle_time = 0 + last_active_time = time.time() + + if idle_time > self.idle_timeout and child_count == 0: + self.msg('listener exit due to --idle-timeout %s' + % self.idle_timeout) + break + try: self.poll() @@ -848,7 +884,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) @@ -927,4 +963,3 @@ 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 550dff7a..13f65826 100755 --- a/utils/websockify +++ b/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) @@ -230,22 +308,37 @@ def websockify_init(): help="handle a single WebSocket connection and exit") parser.add_option("--timeout", type=int, default=0, help="after TIMEOUT seconds exit when not connected") + parser.add_option("--idle-timeout", type=int, default=0, + help="server exits after TIMEOUT seconds if there are no " + "active connections") parser.add_option("--cert", default="self.pem", help="SSL certificate file") 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 +347,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)