]> git.proxmox.com Git - mirror_novnc.git/commitdiff
Update websockify/websock.js.
authorJoel Martin <github@martintribe.org>
Mon, 17 Sep 2012 22:00:01 +0000 (17:00 -0500)
committerJoel Martin <github@martintribe.org>
Mon, 17 Sep 2012 22:00:01 +0000 (17:00 -0500)
This change pulls websockify 6d9deda9c5.

Most note worthy changes:
- Pulls in web-socket-js 7677e7a954 which updates to IETF 6455 (from
  Hixie)
- Binary support detection and use in include/websock.js
- Add ssl and unix target support
- Add multiple target support via config file/dir.
- Idle timeout exit

include/web-socket-js/WebSocketMain.swf
include/web-socket-js/web_socket.js
include/websock.js
utils/websocket.py
utils/websockify

index 244c445bad43199306bc668e1bd34ff639606bf8..8174466912475a494681e9436844f4bf90d909f9 100644 (file)
Binary files a/include/web-socket-js/WebSocketMain.swf and b/include/web-socket-js/WebSocketMain.swf differ
index a13301339b6ec37a3084adbb27a3dcda720f700c..06cc5d02707f33b53de284b82dd11232827555b5 100644 (file)
@@ -1,49 +1,69 @@
 // Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
 // 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);
   };
    * 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;
     }
       events[i](event);
     }
     var handler = this["on" + event.type];
-    if (handler) handler(event);
+    if (handler) handler.apply(this, [event]);
   };
 
   /**
    * @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);
     }
     
     this.dispatchEvent(jsEvent);
+    
   };
   
   WebSocket.prototype.__createSimpleEvent = function(type) {
   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 = [];
    * 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
       null,
       function(e) {
         if (!e.success) {
-          console.error("[WebSocket] swfobject.embedSWF failed");
+          logger.error("[WebSocket] swfobject.embedSWF failed");
         }
-      });
+      }
+    );
+    
   };
   
   /**
           WebSocket.__instances[events[i].webSocketId].__handleEvent(events[i]);
         }
       } catch (e) {
-        console.error(e);
+        logger.error(e);
       }
     }, 0);
     return true;
   
   // 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) {
   };
   
   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();
+    });
   }
   
 })();
index 935e3a1ea7b278c9d7c50378b4349df72d06cb56..ccb7d4c12190a46ddff0809d404e813b94fa3d98 100644 (file)
@@ -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.");
+            }
         }
     }
 
index d3bb48cbf8cc1fc4bd3a0c016a25f7ae084fea2d..4c0cacc9e306913ac3d5aa16fcb0bd30b4ae0da6 100644 (file)
@@ -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,6 +564,72 @@ Sec-WebSocket-Accept: %s\r
 
         # No orderly close for 75
 
+    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(',')
+
+        ver = h.get('Sec-WebSocket-Version')
+        if ver:
+            # HyBi/IETF version of the protocol
+
+            # HyBi-07 report version 7
+            # HyBi-08 - HyBi-12 report version 8
+            # HyBi-13 reports version 13
+            if ver in ['7', '8', '13']:
+                self.version = "hybi-%02d" % int(ver)
+            else:
+                raise self.EClose('Unsupported protocol version %s' % ver)
+
+            key = h['Sec-WebSocket-Key']
+
+            # Choose binary if client supports it
+            if 'binary' in protocols:
+                self.base64 = False
+            elif 'base64' in protocols:
+                self.base64 = True
+            else:
+                raise self.EClose("Client must support 'binary' or 'base64' protocol")
+
+            # Generate the hash value for the accept header
+            accept = b64encode(sha1(s2b(key + self.GUID)).digest())
+
+            response = self.server_handshake_hybi % b2s(accept)
+            if self.base64:
+                response += "Sec-WebSocket-Protocol: base64\r\n"
+            else:
+                response += "Sec-WebSocket-Protocol: binary\r\n"
+            response += "\r\n"
+
+        else:
+            # Hixie version of the protocol (75 or 76)
+
+            if h.get('key3'):
+                trailer = self.gen_md5(h)
+                pre = "Sec-"
+                self.version = "hixie-76"
+            else:
+                trailer = ""
+                pre = ""
+                self.version = "hixie-75"
+
+            # We only support base64 in Hixie era
+            self.base64 = True
+
+            response = self.server_handshake_hixie % (pre,
+                    h['Origin'], pre, self.scheme, h['Host'], path)
+
+            if 'base64' in protocols:
+                response += "%sWebSocket-Protocol: base64\r\n" % pre
+            else:
+                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:
@@ -569,10 +647,10 @@ Sec-WebSocket-Accept: %s\r
         - 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
@@ -613,7 +691,7 @@ Sec-WebSocket-Accept: %s\r
                 else:
                     raise
 
-            scheme = "wss"
+            self.scheme = "wss"
             stype = "SSL/TLS (wss://)"
 
         elif self.ssl_only:
@@ -621,7 +699,7 @@ Sec-WebSocket-Accept: %s\r
 
         else:
             retsock = sock
-            scheme = "ws"
+            self.scheme = "ws"
             stype = "Plain non-SSL (ws://)"
 
         wsh = WSRequestHandler(retsock, address, not self.web)
@@ -637,67 +715,7 @@ Sec-WebSocket-Accept: %s\r
         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(',')
-
-        ver = h.get('Sec-WebSocket-Version')
-        if ver:
-            # HyBi/IETF version of the protocol
-
-            # HyBi-07 report version 7
-            # HyBi-08 - HyBi-12 report version 8
-            # HyBi-13 reports version 13
-            if ver in ['7', '8', '13']:
-                self.version = "hybi-%02d" % int(ver)
-            else:
-                raise self.EClose('Unsupported protocol version %s' % ver)
-
-            key = h['Sec-WebSocket-Key']
-
-            # Choose binary if client supports it
-            if 'binary' in protocols:
-                self.base64 = False
-            elif 'base64' in protocols:
-                self.base64 = True
-            else:
-                raise self.EClose("Client must support 'binary' or 'base64' protocol")
-
-            # Generate the hash value for the accept header
-            accept = b64encode(sha1(s2b(key + self.GUID)).digest())
-
-            response = self.server_handshake_hybi % b2s(accept)
-            if self.base64:
-                response += "Sec-WebSocket-Protocol: base64\r\n"
-            else:
-                response += "Sec-WebSocket-Protocol: binary\r\n"
-            response += "\r\n"
-
-        else:
-            # Hixie version of the protocol (75 or 76)
-
-            if h.get('key3'):
-                trailer = self.gen_md5(h)
-                pre = "Sec-"
-                self.version = "hixie-76"
-            else:
-                trailer = ""
-                pre = ""
-                self.version = "hixie-75"
-
-            # We only support base64 in Hixie era
-            self.base64 = True
-
-            response = self.server_handshake_hixie % (pre,
-                    h['Origin'], pre, scheme, h['Host'], path)
-
-            if 'base64' in protocols:
-                response += "%sWebSocket-Protocol: base64\r\n" % pre
-            else:
-                self.msg("Warning: client does not report 'base64' protocol support")
-            response += "\r\n" + trailer
+        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],
@@ -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
-
index 550dff7ad2cf13c01177551c37dc4530a47a5305..13f65826ece42923cbf08a475f1df899ff930917 100755 (executable)
@@ -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)