diff --git a/README.md b/README.md index ead2abd6..3e1eb60b 100644 --- a/README.md +++ b/README.md @@ -133,20 +133,20 @@ There a few reasons why a proxy is required: * To run the python proxy directly without using launch script (to pass additional options for example): - `./utils/wsproxy.py -f source_port target_addr:target_port` + `./utils/wsproxy.py source_port target_addr:target_port` - `./utils/wsproxy.py -f 8787 localhost:5901` + `./utils/wsproxy.py 8787 localhost:5901` -* To run a mini python web server without the launch script: +* To activate the mini-webserver in wsproxy.py use the `--web DIR` + option: - `./utils/web.py PORT` + `./utils/wsproxy.py --web ./ 8787 localhost:5901` - `./utils/web.py 8080` -* Point your web browser at http://localhost:8080/vnc.html - (or whatever port you used above to run the web server). Specify the - host and port where the proxy is running and the password that the - vnc server is using (if any). Hit the Connect button. +* Point your web browser at http://localhost:8787/vnc.html. On the + page enter the location where the proxy is running (localhost and + 8787) and the password that the vnc server is using (if any). Hit + the Connect button. * If you are using python 2.3 or 2.4 and you want wsproxy to support 'wss://' (TLS) then see the diff --git a/docs/TODO b/docs/TODO index 87f786f5..c64cace7 100644 --- a/docs/TODO +++ b/docs/TODO @@ -20,11 +20,7 @@ Short Term: - Clipboard button -> popup: - text, clear and send buttons -- wswrapper: - - Flash policy request support. - - SSL/TLS support. - - Tests suite: - - test pselect/poll/ppoll +- wstelnet: support CSI L and CSI M Medium Term: diff --git a/include/canvas.js b/include/canvas.js index e28c3b04..0e15a651 100644 --- a/include/canvas.js +++ b/include/canvas.js @@ -10,7 +10,7 @@ /*jslint browser: true, white: false, bitwise: false */ /*global window, Util, Base64 */ -function Canvas(conf) { +Canvas = function(conf) { conf = conf || {}; // Configuration var that = {}, // Public API interface @@ -216,99 +216,6 @@ function constructor() { return that ; } -/* Translate DOM key down/up event to keysym value */ -that.getKeysym = function(e) { - var evt, keysym; - evt = (e ? e : window.event); - - /* Remap modifier and special keys */ - switch ( evt.keyCode ) { - case 8 : keysym = 0xFF08; break; // BACKSPACE - case 9 : keysym = 0xFF09; break; // TAB - case 13 : keysym = 0xFF0D; break; // ENTER - case 27 : keysym = 0xFF1B; break; // ESCAPE - case 45 : keysym = 0xFF63; break; // INSERT - case 46 : keysym = 0xFFFF; break; // DELETE - case 36 : keysym = 0xFF50; break; // HOME - case 35 : keysym = 0xFF57; break; // END - case 33 : keysym = 0xFF55; break; // PAGE_UP - case 34 : keysym = 0xFF56; break; // PAGE_DOWN - case 37 : keysym = 0xFF51; break; // LEFT - case 38 : keysym = 0xFF52; break; // UP - case 39 : keysym = 0xFF53; break; // RIGHT - case 40 : keysym = 0xFF54; break; // DOWN - case 112 : keysym = 0xFFBE; break; // F1 - case 113 : keysym = 0xFFBF; break; // F2 - case 114 : keysym = 0xFFC0; break; // F3 - case 115 : keysym = 0xFFC1; break; // F4 - case 116 : keysym = 0xFFC2; break; // F5 - case 117 : keysym = 0xFFC3; break; // F6 - case 118 : keysym = 0xFFC4; break; // F7 - case 119 : keysym = 0xFFC5; break; // F8 - case 120 : keysym = 0xFFC6; break; // F9 - case 121 : keysym = 0xFFC7; break; // F10 - case 122 : keysym = 0xFFC8; break; // F11 - case 123 : keysym = 0xFFC9; break; // F12 - case 16 : keysym = 0xFFE1; break; // SHIFT - case 17 : keysym = 0xFFE3; break; // CONTROL - //case 18 : keysym = 0xFFE7; break; // Left Meta (Mac Option) - case 18 : keysym = 0xFFE9; break; // Left ALT (Mac Command) - default : keysym = evt.keyCode; break; - } - - /* Remap symbols */ - switch (keysym) { - case 186 : keysym = 59; break; // ; (IE) - case 187 : keysym = 61; break; // = (IE) - case 188 : keysym = 44; break; // , (Mozilla, IE) - case 109 : // - (Mozilla) - if (Util.Engine.gecko) { - keysym = 45; } - break; - case 189 : keysym = 45; break; // - (IE) - case 190 : keysym = 46; break; // . (Mozilla, IE) - case 191 : keysym = 47; break; // / (Mozilla, IE) - case 192 : keysym = 96; break; // ` (Mozilla, IE) - case 219 : keysym = 91; break; // [ (Mozilla, IE) - case 220 : keysym = 92; break; // \ (Mozilla, IE) - case 221 : keysym = 93; break; // ] (Mozilla, IE) - case 222 : keysym = 39; break; // ' (Mozilla, IE) - } - - /* Remap shifted and unshifted keys */ - if (!!evt.shiftKey) { - switch (keysym) { - case 48 : keysym = 41 ; break; // ) (shifted 0) - case 49 : keysym = 33 ; break; // ! (shifted 1) - case 50 : keysym = 64 ; break; // @ (shifted 2) - case 51 : keysym = 35 ; break; // # (shifted 3) - case 52 : keysym = 36 ; break; // $ (shifted 4) - case 53 : keysym = 37 ; break; // % (shifted 5) - case 54 : keysym = 94 ; break; // ^ (shifted 6) - case 55 : keysym = 38 ; break; // & (shifted 7) - case 56 : keysym = 42 ; break; // * (shifted 8) - case 57 : keysym = 40 ; break; // ( (shifted 9) - - case 59 : keysym = 58 ; break; // : (shifted `) - case 61 : keysym = 43 ; break; // + (shifted ;) - case 44 : keysym = 60 ; break; // < (shifted ,) - case 45 : keysym = 95 ; break; // _ (shifted -) - case 46 : keysym = 62 ; break; // > (shifted .) - case 47 : keysym = 63 ; break; // ? (shifted /) - case 96 : keysym = 126; break; // ~ (shifted `) - case 91 : keysym = 123; break; // { (shifted [) - case 92 : keysym = 124; break; // | (shifted \) - case 93 : keysym = 125; break; // } (shifted ]) - case 39 : keysym = 34 ; break; // " (shifted ') - } - } else if ((keysym >= 65) && (keysym <=90)) { - /* Remap unshifted A-Z */ - keysym += 32; - } - - return keysym; -} - function onMouseButton(e, down) { var evt, pos, bmask; if (! conf.focused) { @@ -363,24 +270,24 @@ function onMouseMove(e) { } function onKeyDown(e) { - //Util.Debug("keydown: " + that.getKeysym(e)); + //Util.Debug("keydown: " + getKeysym(e)); if (! conf.focused) { return true; } if (c_keyPress) { - c_keyPress(that.getKeysym(e), 1); + c_keyPress(getKeysym(e), 1, e.ctrlKey, e.shiftKey, e.altKey); } Util.stopEvent(e); return false; } function onKeyUp(e) { - //Util.Debug("keyup: " + that.getKeysym(e)); + //Util.Debug("keyup: " + getKeysym(e)); if (! conf.focused) { return true; } if (c_keyPress) { - c_keyPress(that.getKeysym(e), 0); + c_keyPress(getKeysym(e), 0, e.ctrlKey, e.shiftKey, e.altKey); } Util.stopEvent(e); return false; @@ -780,3 +687,97 @@ return constructor(); // Return the public API interface } // End of Canvas() + +/* Translate DOM key down/up event to keysym value */ +function getKeysym(e) { + var evt, keysym; + evt = (e ? e : window.event); + + /* Remap modifier and special keys */ + switch ( evt.keyCode ) { + case 8 : keysym = 0xFF08; break; // BACKSPACE + case 9 : keysym = 0xFF09; break; // TAB + case 13 : keysym = 0xFF0D; break; // ENTER + case 27 : keysym = 0xFF1B; break; // ESCAPE + case 45 : keysym = 0xFF63; break; // INSERT + case 46 : keysym = 0xFFFF; break; // DELETE + case 36 : keysym = 0xFF50; break; // HOME + case 35 : keysym = 0xFF57; break; // END + case 33 : keysym = 0xFF55; break; // PAGE_UP + case 34 : keysym = 0xFF56; break; // PAGE_DOWN + case 37 : keysym = 0xFF51; break; // LEFT + case 38 : keysym = 0xFF52; break; // UP + case 39 : keysym = 0xFF53; break; // RIGHT + case 40 : keysym = 0xFF54; break; // DOWN + case 112 : keysym = 0xFFBE; break; // F1 + case 113 : keysym = 0xFFBF; break; // F2 + case 114 : keysym = 0xFFC0; break; // F3 + case 115 : keysym = 0xFFC1; break; // F4 + case 116 : keysym = 0xFFC2; break; // F5 + case 117 : keysym = 0xFFC3; break; // F6 + case 118 : keysym = 0xFFC4; break; // F7 + case 119 : keysym = 0xFFC5; break; // F8 + case 120 : keysym = 0xFFC6; break; // F9 + case 121 : keysym = 0xFFC7; break; // F10 + case 122 : keysym = 0xFFC8; break; // F11 + case 123 : keysym = 0xFFC9; break; // F12 + case 16 : keysym = 0xFFE1; break; // SHIFT + case 17 : keysym = 0xFFE3; break; // CONTROL + //case 18 : keysym = 0xFFE7; break; // Left Meta (Mac Option) + case 18 : keysym = 0xFFE9; break; // Left ALT (Mac Command) + default : keysym = evt.keyCode; break; + } + + /* Remap symbols */ + switch (keysym) { + case 186 : keysym = 59; break; // ; (IE) + case 187 : keysym = 61; break; // = (IE) + case 188 : keysym = 44; break; // , (Mozilla, IE) + case 109 : // - (Mozilla) + if (Util.Engine.gecko) { + keysym = 45; } + break; + case 189 : keysym = 45; break; // - (IE) + case 190 : keysym = 46; break; // . (Mozilla, IE) + case 191 : keysym = 47; break; // / (Mozilla, IE) + case 192 : keysym = 96; break; // ` (Mozilla, IE) + case 219 : keysym = 91; break; // [ (Mozilla, IE) + case 220 : keysym = 92; break; // \ (Mozilla, IE) + case 221 : keysym = 93; break; // ] (Mozilla, IE) + case 222 : keysym = 39; break; // ' (Mozilla, IE) + } + + /* Remap shifted and unshifted keys */ + if (!!evt.shiftKey) { + switch (keysym) { + case 48 : keysym = 41 ; break; // ) (shifted 0) + case 49 : keysym = 33 ; break; // ! (shifted 1) + case 50 : keysym = 64 ; break; // @ (shifted 2) + case 51 : keysym = 35 ; break; // # (shifted 3) + case 52 : keysym = 36 ; break; // $ (shifted 4) + case 53 : keysym = 37 ; break; // % (shifted 5) + case 54 : keysym = 94 ; break; // ^ (shifted 6) + case 55 : keysym = 38 ; break; // & (shifted 7) + case 56 : keysym = 42 ; break; // * (shifted 8) + case 57 : keysym = 40 ; break; // ( (shifted 9) + + case 59 : keysym = 58 ; break; // : (shifted `) + case 61 : keysym = 43 ; break; // + (shifted ;) + case 44 : keysym = 60 ; break; // < (shifted ,) + case 45 : keysym = 95 ; break; // _ (shifted -) + case 46 : keysym = 62 ; break; // > (shifted .) + case 47 : keysym = 63 ; break; // ? (shifted /) + case 96 : keysym = 126; break; // ~ (shifted `) + case 91 : keysym = 123; break; // { (shifted [) + case 92 : keysym = 124; break; // | (shifted \) + case 93 : keysym = 125; break; // } (shifted ]) + case 39 : keysym = 34 ; break; // " (shifted ') + } + } else if ((keysym >= 65) && (keysym <=90)) { + /* Remap unshifted A-Z */ + keysym += 32; + } + + return keysym; +} + diff --git a/utils/Makefile b/utils/Makefile index 646adcb4..008d45c2 100644 --- a/utils/Makefile +++ b/utils/Makefile @@ -1,4 +1,4 @@ -TARGETS=wsproxy wswrapper.so +TARGETS=wsproxy wswrapper.so rebind.so CFLAGS += -fPIC all: $(TARGETS) @@ -10,6 +10,9 @@ wswrapper.o: wswrapper.h wswrapper.so: wswrapper.o md5.o $(CC) $(LDFLAGS) $^ -shared -fPIC -ldl -lresolv -o $@ +rebind.so: rebind.o + $(CC) $(LDFLAGS) $^ -shared -fPIC -ldl -o $@ + websocket.o: websocket.c websocket.h md5.h wsproxy.o: wsproxy.c websocket.h wswrapper.o: wswrapper.c diff --git a/utils/README.md b/utils/README.md index 93fdfb32..7aeebe68 100644 --- a/utils/README.md +++ b/utils/README.md @@ -1,17 +1,4 @@ -## WebSockets Utilities: wswrapper and wsproxy - - -### wswrapper - -wswrapper is an LD_PRELOAD library that converts a TCP listen socket -of an existing program to a be a WebSockets socket. The `wswrap` -script can be used to easily launch a program using wswrapper. Here is -an example of using wswrapper with vncserver. wswrapper will convert -the socket listening on port 5901 to be a WebSockets port: - - `cd noVNC/utils` - - `./wswrap 5901 vncserver -geometry 640x480 :1` +## WebSockets Proxy ### wsproxy @@ -32,7 +19,7 @@ does not end in 255). These are not necessary for the basic operation. -* Daemonizing: When the `-f` option is not specified, wsproxy runs +* Daemonizing: When the `-D` option is specified, wsproxy runs in the background as a daemon process. * SSL (the wss:// WebSockets URI): This is detected automatically by @@ -50,67 +37,63 @@ These are not necessary for the basic operation. sent and received from the client to a file using the `--record` option. +* Mini-webserver: wsproxy can detect and respond to normal web + requests on the same port as the WebSockets proxy and Flash security + policy. This functionality is activate with the `--web DIR` option + where DIR is the root of the web directory to serve. + +* Wrap a program: see the "Wrap a Program" section below. + #### Implementations of wsproxy There are three implementations of wsproxy: python, C, and Node (node.js). wswrapper is only implemented in C. -Here is the feature support matrix for the the wsproxy implementations -and wswrapper: +Here is the feature support matrix for the the wsproxy +implementations: - + - + - + - - - - - - - - - - -
Program LanguageProxy or Interposer Multiprocess Daemonize SSL/wss Flash Policy Server Session Record Web ServerProgram Wrap
wsproxy.py pythonproxy yes yes yes 1 yes yes yesyes
wsproxy Cproxy yes yes yes yes no nono
wsproxy.js Node (node.js)proxy yes no no no no no
wswrap/wswrapper.soshell/Cinterposerindirectlyindirectlynonono no
@@ -120,6 +103,42 @@ and wswrapper: section on *Building the Python ssl module*. +### Wrap a Program + +In addition to proxying from a source address to a target address +(which may be on a different system), wsproxy has the ability to +launch a program on the local system and proxy WebSockets traffic to +a normal TCP port owned/bound by the program. + +The is accomplished with a small LD_PRELOAD library (`rebind.so`) +which intercepts bind() system calls by the program. The specified +port is moved to a new localhost/loopback free high port. wsproxy +then proxies WebSockets traffic directed to the original port to the +new (moved) port of the program. + +The program wrap mode is invoked by replacing the target with `--` +followed by the program command line to wrap. + + `./utils/wsproxy.py 2023 -- PROGRAM ARGS` + +The `--wrap-mode` option can be used to indicate what action to take +when the wrapped program exits or daemonizes. + +Here is an example of using wsproxy to wrap the vncserver command +(which backgrounds itself): + + `./utils/wsproxy.py 5901 --wrap-mode=ignore -- vncserver -geometry 1024x768 :1` + +Here is an example of wrapping telnetd (from krb5-telnetd).telnetd +exits after the connection closes so the wrap mode is set to respawn +the command: + + `sudo ./utils/wsproxy.py 2023 --wrap-mode=respawn -- telnetd -debug 2023` + +The `utils/wstelnet.html` page demonstrates a simple WebSockets based +telnet client. + + ### Building the Python ssl module (for python 2.5 and older) * Install the build dependencies. On Ubuntu use this command: diff --git a/utils/VT100.js b/utils/VT100.js new file mode 120000 index 00000000..41785643 --- /dev/null +++ b/utils/VT100.js @@ -0,0 +1 @@ +VT100-orig.js \ No newline at end of file diff --git a/utils/include b/utils/include new file mode 120000 index 00000000..f5030fe8 --- /dev/null +++ b/utils/include @@ -0,0 +1 @@ +../include \ No newline at end of file diff --git a/utils/launch.sh b/utils/launch.sh index 4ed9f92d..c21e2baa 100755 --- a/utils/launch.sh +++ b/utils/launch.sh @@ -7,10 +7,10 @@ usage() { fi echo "Usage: ${NAME} [--listen PORT] [--vnc VNC_HOST:PORT] [--cert CERT]" echo - echo "Starts a mini-webserver and the WebSockets proxy and" - echo "provides a cut and paste URL to go to." + echo "Starts the WebSockets proxy and a mini-webserver and " + echo "provides a cut-and-paste URL to go to." echo - echo " --listen PORT Port for webserver/proxy to listen on" + echo " --listen PORT Port for proxy/webserver to listen on" echo " Default: 6080" echo " --vnc VNC_HOST:PORT VNC server host:port proxy target" echo " Default: localhost:5900" @@ -92,12 +92,10 @@ else fi echo "Starting webserver and WebSockets proxy on port ${PORT}" -${HERE}/wsproxy.py -f --web ${WEB} ${CERT:+--cert ${CERT}} ${PORT} ${VNC_DEST} & +${HERE}/wsproxy.py --web ${WEB} ${CERT:+--cert ${CERT}} ${PORT} ${VNC_DEST} & proxy_pid="$!" sleep 1 -if ps -p ${proxy_pid} >/dev/null; then - echo "Started WebSockets proxy (pid: ${proxy_pid})" -else +if ! ps -p ${proxy_pid} >/dev/null; then proxy_pid= echo "Failed to start WebSockets proxy" exit 1 diff --git a/utils/rebind b/utils/rebind new file mode 100755 index 00000000..6912d200 --- /dev/null +++ b/utils/rebind @@ -0,0 +1,18 @@ +#!/bin/bash + +usage() { + echo "Usage: $(basename $0) OLD_PORT NEW_PORT COMMAND_LINE" + echo + echo "Launch COMMAND_LINE, but intercept system calls to bind" + echo "to OLD_PORT and instead bind them to localhost:NEW_PORT" + exit 2 +} + +# Parameter defaults +mydir=$(readlink -f $(dirname ${0})) + +export REBIND_PORT_OLD="${1}"; shift +export REBIND_PORT_NEW="${1}"; shift + +LD_PRELOAD=${mydir}/rebind.so "${@}" + diff --git a/utils/rebind.c b/utils/rebind.c new file mode 100644 index 00000000..c7e83ded --- /dev/null +++ b/utils/rebind.c @@ -0,0 +1,94 @@ +/* + * rebind: Intercept bind calls and bind to a different port + * Copyright 2010 Joel Martin + * Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3) + * + * Overload (LD_PRELOAD) bind system call. If REBIND_PORT_OLD and + * REBIND_PORT_NEW environment variables are set then bind on the new + * port (of localhost) instead of the old port. + * + * This allows a proxy (such as wsproxy) to run on the old port and translate + * traffic to/from the new port. + * + * Usage: + * LD_PRELOAD=./rebind.so \ + * REBIND_PORT_OLD=23 \ + * REBIND_PORT_NEW=2023 \ + * program + */ + +//#define DO_DEBUG 1 + +#include +#include + +#define __USE_GNU 1 // Pull in RTLD_NEXT +#include + +#include +#include + + +#if defined(DO_DEBUG) +#define DEBUG(...) \ + fprintf(stderr, "wswrapper: "); \ + fprintf(stderr, __VA_ARGS__); +#else +#define DEBUG(...) +#endif + + +int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen) +{ + static void * (*func)(); + int do_move = 0; + struct sockaddr_in * addr_in = (struct sockaddr_in *)addr; + struct sockaddr_in addr_tmp; + socklen_t addrlen_tmp; + char * PORT_OLD, * PORT_NEW, * end1, * end2; + int ret, oldport, newport, askport = htons(addr_in->sin_port); + uint32_t askaddr = htons(addr_in->sin_addr.s_addr); + if (!func) func = (void *(*)()) dlsym(RTLD_NEXT, "bind"); + + DEBUG(">> bind(%d, _, %d), askaddr %d, askport %d\n", + sockfd, addrlen, askaddr, askport); + + /* Determine if we should move this socket */ + if (addr_in->sin_family == AF_INET) { + // TODO: support IPv6 + PORT_OLD = getenv("REBIND_OLD_PORT"); + PORT_NEW = getenv("REBIND_NEW_PORT"); + if (PORT_OLD && (*PORT_OLD != '\0') && + PORT_NEW && (*PORT_NEW != '\0')) { + oldport = strtol(PORT_OLD, &end1, 10); + newport = strtol(PORT_NEW, &end2, 10); + if (oldport && (*end1 == '\0') && + newport && (*end2 == '\0') && + (oldport == askport)) { + do_move = 1; + } + } + } + + if (! do_move) { + /* Just pass everything right through to the real bind */ + ret = (int) func(sockfd, addr, addrlen); + DEBUG("<< bind(%d, _, %d) ret %d\n", sockfd, addrlen, ret); + return ret; + } + + DEBUG("binding fd %d on localhost:%d instead of 0x%x:%d\n", + sockfd, newport, ntohl(addr_in->sin_addr.s_addr), oldport); + + /* Use a temporary location for the new address information */ + addrlen_tmp = sizeof(addr_tmp); + memcpy(&addr_tmp, addr, addrlen_tmp); + + /* Bind to other port on the loopback instead */ + addr_tmp.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + addr_tmp.sin_port = htons(newport); + ret = (int) func(sockfd, &addr_tmp, addrlen_tmp); + + DEBUG("<< bind(%d, _, %d) ret %d\n", sockfd, addrlen, ret); + return ret; +} diff --git a/utils/websocket.py b/utils/websocket.py index 48eb15ac..45205084 100755 --- a/utils/websocket.py +++ b/utils/websocket.py @@ -11,7 +11,7 @@ as taken from http://docs.python.org/dev/library/ssl.html#certificates ''' -import sys, socket, ssl, struct, traceback +import sys, socket, ssl, struct, traceback, select import os, resource, errno, signal # daemonizing from SimpleHTTPServer import SimpleHTTPRequestHandler from cStringIO import StringIO @@ -26,7 +26,7 @@ from cgi import parse_qsl class WebSocketServer(): """ WebSockets server class. - Must be sub-classed with handler method definition. + Must be sub-classed with new_client method definition. """ server_handshake = """HTTP/1.1 101 Web Socket Protocol Handshake\r @@ -70,6 +70,21 @@ Connection: Upgrade\r self.handler_id = 1 + print "WebSocket server settings:" + print " - Listen on %s:%s" % ( + self.listen_host, self.listen_port) + print " - Flash security policy server" + if self.web: + print " - Web server" + if os.path.exists(self.cert): + print " - SSL/TLS support" + if self.ssl_only: + print " - Deny non-SSL/TLS connections" + else: + print " - No SSL/TLS support (no cert file)" + if self.daemon: + print " - Backgrounding (daemon)" + # # WebSocketServer static methods # @@ -284,16 +299,34 @@ Connection: Upgrade\r return retsock - def handler(self, client): + # + # Events that can/should be overridden in sub-classes + # + def started(self): + """ Called after WebSockets startup """ + self.vmsg("WebSockets server started") + + def poll(self): + """ Run periodically while waiting for connections. """ + self.msg("Running poll()") + + def do_SIGCHLD(self, sig, stack): + self.vmsg("Got SIGCHLD, ignoring") + + def do_SIGINT(self, sig, stack): + self.msg("Got SIGINT, exiting") + sys.exit(0) + + def new_client(self, client): """ Do something with a WebSockets client connection. """ - raise("WebSocketServer.handler() must be overloaded") + raise("WebSocketServer.new_client() must be overloaded") def start_server(self): """ Daemonize if requested. Listen for for connections. Run do_handshake() method for each connection. If the connection - is a WebSockets client then call handler() method (which must - be overridden) for each connection. + is a WebSockets client then call new_client() method (which must + be overridden) for each new client connection. """ lsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) @@ -301,37 +334,46 @@ Connection: Upgrade\r lsock.bind((self.listen_host, self.listen_port)) lsock.listen(100) - print "WebSocket server settings:" - print " - Listening on %s:%s" % ( - self.listen_host, self.listen_port) - if self.daemon: - print " - Backgrounding (daemon)" - print " - Flash security policy server" - if self.web: - print " - Web server" - if os.path.exists(self.cert): - print " - SSL/TLS support" - if self.ssl_only: - print " - Deny non-SSL/TLS connections" - if self.daemon: self.daemonize(self, keepfd=lsock.fileno()) + self.started() # Some things need to happen after daemonizing + # Reep zombies - signal.signal(signal.SIGCHLD, signal.SIG_IGN) + signal.signal(signal.SIGCHLD, self.do_SIGCHLD) + signal.signal(signal.SIGINT, self.do_SIGINT) while True: try: csock = startsock = None - pid = 0 - startsock, address = lsock.accept() + pid = err = 0 + + try: + self.poll() + + ready = select.select([lsock], [], [], 1)[0]; + if lsock in ready: + startsock, address = lsock.accept() + else: + continue + except Exception, exc: + if hasattr(exc, 'errno'): + err = exc.errno + elif type(exc) == select.error: + err = exc[0] + if err == errno.EINTR: + self.vmsg("Ignoring interrupted syscall()") + continue + else: + raise + self.vmsg('%s: forking handler' % address[0]) pid = os.fork() if pid == 0: # handler process csock = self.do_handshake(startsock, address) - self.handler(csock) + self.new_client(csock) else: # parent process self.handler_id += 1 diff --git a/utils/wsecho.py b/utils/wsecho.py new file mode 100755 index 00000000..15e2ef75 --- /dev/null +++ b/utils/wsecho.py @@ -0,0 +1,90 @@ +#!/usr/bin/python + +''' +A WebSocket server that echos back whatever it receives from the client. +Copyright 2010 Joel Martin +Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3) + +You can make a cert/key with openssl using: +openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem +as taken from http://docs.python.org/dev/library/ssl.html#certificates + +''' + +import sys, socket, select +from websocket import WebSocketServer + +class WebSocketEcho(WebSocketServer): + """ + WebSockets server that echo back whatever is received from the + client. All traffic to/from the client is base64 + encoded/decoded. + """ + buffer_size = 8096 + + def new_client(self, client): + """ + Echo back whatever is received. + """ + + cqueue = [] + cpartial = "" + rlist = [client] + + while True: + wlist = [] + + if cqueue: wlist.append(client) + ins, outs, excepts = select.select(rlist, wlist, [], 1) + if excepts: raise Exception("Socket exception") + + if client in outs: + # Send queued target data to the client + dat = cqueue.pop(0) + sent = client.send(dat) + self.vmsg("Sent %s/%s bytes of frame: '%s'" % ( + sent, len(dat), self.decode(dat)[0])) + if sent != len(dat): + # requeue the remaining data + cqueue.insert(0, dat[sent:]) + + + if client in ins: + # Receive client data, decode it, and send it back + buf = client.recv(self.buffer_size) + if len(buf) == 0: raise self.EClose("Client closed") + + if buf == '\xff\x00': + raise self.EClose("Client sent orderly close frame") + elif buf[-1] == '\xff': + if cpartial: + # Prepend saved partial and decode frame(s) + frames = self.decode(cpartial + buf) + cpartial = "" + else: + # decode frame(s) + frames = self.decode(buf) + + for frame in frames: + self.vmsg("Received frame: %s" % repr(frame)) + cqueue.append(self.encode(frame)) + else: + # Save off partial WebSockets frame + self.vmsg("Received partial frame") + cpartial = cpartial + buf + +if __name__ == '__main__': + try: + if len(sys.argv) < 1: raise + listen_port = int(sys.argv[1]) + except: + print "Usage: %s " % sys.argv[0] + sys.exit(1) + + server = WebSocketEcho( + listen_port=listen_port, + verbose=True, + cert='self.pem', + web='.') + server.start_server() + diff --git a/utils/wsproxy.c b/utils/wsproxy.c index 5ba22063..42bb45e7 100644 --- a/utils/wsproxy.c +++ b/utils/wsproxy.c @@ -34,7 +34,7 @@ Traffic Legend:\n\ char USAGE[] = "Usage: [options] " \ "[source_addr:]source_port target_addr:target_port\n\n" \ " --verbose|-v verbose messages and per frame traffic\n" \ - " --foreground|-f stay in foreground, do not daemonize\n" \ + " --daemon|-D become a daemon (background process)\n" \ " --cert CERT SSL certificate file\n" \ " --key KEY SSL key file (if separate from cert)\n" \ " --ssl-only disallow non-encrypted connections"; @@ -244,12 +244,12 @@ void proxy_handler(ws_ctx_t *ws_ctx) { int main(int argc, char *argv[]) { int fd, c, option_index = 0; - static int ssl_only = 0, foreground = 0, verbose = 0; + static int ssl_only = 0, daemon = 0, verbose = 0; char *found; static struct option long_options[] = { {"verbose", no_argument, &verbose, 'v'}, {"ssl-only", no_argument, &ssl_only, 1 }, - {"foreground", no_argument, &foreground, 'f'}, + {"daemon", no_argument, &daemon, 'D'}, /* ---- */ {"cert", required_argument, 0, 'c'}, {"key", required_argument, 0, 'k'}, @@ -264,7 +264,7 @@ int main(int argc, char *argv[]) settings.key = ""; while (1) { - c = getopt_long (argc, argv, "vfc:k:", + c = getopt_long (argc, argv, "vDc:k:", long_options, &option_index); /* Detect the end */ @@ -278,8 +278,8 @@ int main(int argc, char *argv[]) case 'v': verbose = 1; break; - case 'f': - foreground = 1; + case 'D': + daemon = 1; break; case 'c': settings.cert = realpath(optarg, NULL); @@ -299,7 +299,7 @@ int main(int argc, char *argv[]) } settings.verbose = verbose; settings.ssl_only = ssl_only; - settings.daemon = foreground ? 0: 1; + settings.daemon = daemon; if ((argc-optind) != 2) { usage("Invalid number of arguments\n"); diff --git a/utils/wsproxy.py b/utils/wsproxy.py index 660a2a6b..fed4c042 100755 --- a/utils/wsproxy.py +++ b/utils/wsproxy.py @@ -11,7 +11,7 @@ as taken from http://docs.python.org/dev/library/ssl.html#certificates ''' -import socket, optparse, time, os +import socket, optparse, time, os, sys, subprocess from select import select from websocket import WebSocketServer @@ -38,12 +38,102 @@ Traffic Legend: """ def __init__(self, *args, **kwargs): - # Save off the target host:port - self.target_host = kwargs.pop('target_host') - self.target_port = kwargs.pop('target_port') + # Save off proxy specific options + self.target_host = kwargs.pop('target_host') + self.target_port = kwargs.pop('target_port') + self.wrap_cmd = kwargs.pop('wrap_cmd') + self.wrap_mode = kwargs.pop('wrap_mode') + # Last 3 timestamps command was run + self.wrap_times = [0, 0, 0] + + if self.wrap_cmd: + rebinder_path = ['./', os.path.dirname(sys.argv[0])] + self.rebinder = None + + for rdir in rebinder_path: + rpath = os.path.join(rdir, "rebind.so") + if os.path.exists(rpath): + self.rebinder = rpath + break + + if not self.rebinder: + raise Exception("rebind.so not found, perhaps you need to run make") + + self.target_host = "127.0.0.1" # Loopback + # Find a free high port + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.bind(('', 0)) + self.target_port = sock.getsockname()[1] + sock.close() + + os.environ.update({ + "LD_PRELOAD": self.rebinder, + "REBIND_OLD_PORT": str(kwargs['listen_port']), + "REBIND_NEW_PORT": str(self.target_port)}) + WebSocketServer.__init__(self, *args, **kwargs) - def handler(self, client): + def run_wrap_cmd(self): + print "Starting '%s'" % " ".join(self.wrap_cmd) + self.wrap_times.append(time.time()) + self.wrap_times.pop(0) + self.cmd = subprocess.Popen( + self.wrap_cmd, env=os.environ) + self.spawn_message = True + + def started(self): + """ + Called after Websockets server startup (i.e. after daemonize) + """ + # 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() + 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 + + if self.wrap_cmd and self.cmd: + ret = self.cmd.poll() + if ret != None: + self.vmsg("Wrapped command exited (or daemon). Returned %s" % ret) + self.cmd = None + + if self.wrap_cmd and self.cmd == None: + # Response to wrapped command being gone + if self.wrap_mode == "ignore": + pass + elif self.wrap_mode == "exit": + sys.exit(ret) + elif self.wrap_mode == "respawn": + now = time.time() + avg = sum(self.wrap_times)/len(self.wrap_times) + if (now - avg) < 10: + # 3 times in the last 10 seconds + if self.spawn_message: + print "Command respawning too fast" + self.spawn_message = False + else: + self.run_wrap_cmd() + + # + # Routines above this point are run in the master listener + # process. + # + + # + # Routines below this point are connection handler routines and + # will be run in a separate forked process for each connection. + # + + def new_client(self, client): """ Called after a new WebSocket connection has been established. """ @@ -155,16 +245,18 @@ Traffic Legend: cpartial = cpartial + buf if __name__ == '__main__': - usage = "%prog [--record FILE]" + usage = "\n %prog [options]" 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) parser.add_option("--verbose", "-v", action="store_true", help="verbose messages and per frame traffic") parser.add_option("--record", help="record sessions to FILE.[session_number]", metavar="FILE") - parser.add_option("--foreground", "-f", - dest="daemon", default=True, action="store_false", - help="stay in foreground, do not daemonize") + parser.add_option("--daemon", "-D", + dest="daemon", action="store_true", + help="become a daemon (background process)") parser.add_option("--cert", default="self.pem", help="SSL certificate file") parser.add_option("--key", default=None, @@ -173,30 +265,43 @@ if __name__ == '__main__': 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") (opts, args) = parser.parse_args() # Sanity checks - if len(args) > 2: parser.error("Too many arguments") - if len(args) < 2: parser.error("Too few arguments") + if len(args) < 2: + parser.error("Too few arguments") + if sys.argv.count('--'): + opts.wrap_cmd = args[1:] + else: + opts.wrap_cmd = None + if len(args) > 2: + parser.error("Too many arguments") if opts.ssl_only and not os.path.exists(opts.cert): parser.error("SSL only and %s not found" % opts.cert) - elif not os.path.exists(opts.cert): - print "Warning: %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].split(':') else: opts.listen_host, opts.listen_port = '', args[0] - if args[1].count(':') > 0: - opts.target_host, opts.target_port = args[1].split(':') - else: - parser.error("Error parsing target") try: opts.listen_port = int(opts.listen_port) except: parser.error("Error parsing listen port") - try: opts.target_port = int(opts.target_port) - except: parser.error("Error parsing target port") + + 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].split(':') + else: + parser.error("Error parsing target") + try: opts.target_port = int(opts.target_port) + except: parser.error("Error parsing target port") # Create and start the WebSockets proxy server = WebSocketProxy(**opts.__dict__) diff --git a/utils/wstelnet.html b/utils/wstelnet.html new file mode 100644 index 00000000..01e987b7 --- /dev/null +++ b/utils/wstelnet.html @@ -0,0 +1,90 @@ + + + + WebSockets Telnet + + + + + + + + + + + + + + + Host:   + Port:   + Encrypt:   +   + +

+ +

+
+        
+
+    
+
+
diff --git a/utils/wstelnet.js b/utils/wstelnet.js
new file mode 100644
index 00000000..4e1bd0dd
--- /dev/null
+++ b/utils/wstelnet.js
@@ -0,0 +1,333 @@
+/*
+ * WebSockets telnet client
+ * Copyright (C) 2011 Joel Martin
+ * Licensed under LGPL-3 (see LICENSE.txt)
+ *
+ * Incorporates VT100.js from:
+ *   http://code.google.com/p/sshconsole
+ * Which was modified from:
+ *   http://fzort.org/bi/o.php#vt100_js
+ *
+ * Telnet protocol:
+ *   http://www.networksorcery.com/enp/protocol/telnet.htm
+ *   http://www.networksorcery.com/enp/rfc/rfc1091.txt
+ *
+ * ANSI escape sequeneces:
+ *   http://en.wikipedia.org/wiki/ANSI_escape_code
+ *   http://ascii-table.com/ansi-escape-sequences-vt-100.php
+ *   http://www.termsys.demon.co.uk/vtansi.htm
+ *   http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
+ *
+ * ASCII codes:
+ *   http://en.wikipedia.org/wiki/ASCII
+ *   http://www.hobbyprojects.com/ascii-table/ascii-table.html
+ *
+ * Other web consoles:
+ *   http://stackoverflow.com/questions/244750/ajax-console-window-with-ansi-vt100-support
+ */
+
+
+
+
+function Telnet(target, connect_callback, disconnect_callback) {
+
+var that = {},  // Public API interface
+    vt100, ws, sQ = [];
+    termType = "VT100";
+
+
+Array.prototype.pushStr = function (str) {
+    var n = str.length;
+    for (var i=0; i < n; i++) {
+        this.push(str.charCodeAt(i));
+    }
+}
+
+function do_send() {
+    if (sQ.length > 0) {
+        Util.Debug("Sending " + sQ);
+        ws.send(Base64.encode(sQ));
+        sQ = [];
+    }
+}
+
+function do_recv(e) {
+    //console.log(">> do_recv");
+    var arr = Base64.decode(e.data), str = "",
+        chr, cmd, code, value;
+
+    Util.Debug("Received array '" + arr + "'");
+    while (arr.length > 0) {
+        chr = arr.shift();
+        switch (chr) {
+        case 255:   // IAC
+            cmd = chr;
+            code = arr.shift();
+            value = arr.shift();
+            switch (code) {
+            case 254: // DONT
+                Util.Debug("Got Cmd DONT '" + value + "', ignoring");
+                break;
+            case 253: // DO
+                Util.Debug("Got Cmd DO '" + value + "'");
+                if (value === 24) {
+                    // Terminal type
+                    Util.Info("Send WILL '" + value + "' (TERM-TYPE)");
+                    sQ.push(255, 251, value);
+                } else {
+                    // Refuse other DO requests with a WONT
+                    Util.Debug("Send WONT '" + value + "'");
+                    sQ.push(255, 252, value);
+                }
+                break;
+            case 252: // WONT
+                Util.Debug("Got Cmd WONT '" + value + "', ignoring");
+                break;
+            case 251: // WILL
+                Util.Debug("Got Cmd WILL '" + value + "'");
+                if (value === 1) {
+                    // Affirm echo with DO
+                    Util.Info("Send Cmd DO '" + value + "' (echo)");
+                    sQ.push(255, 253, value);
+                } else {
+                    // Reject other WILL offers with a DONT
+                    Util.Debug("Send Cmd DONT '" + value + "'");
+                    sQ.push(255, 254, value);
+                }
+                break;
+            case 250: // SB (subnegotiation)
+                if (value === 24) {
+                    Util.Info("Got IAC SB TERM-TYPE SEND(1) IAC SE");
+                    // TERM-TYPE subnegotiation
+                    if (arr[0] === 1 &&
+                        arr[1] === 255 &&
+                        arr[2] === 240) {
+                        arr.shift(); arr.shift(); arr.shift();
+                        Util.Info("Send IAC SB TERM-TYPE IS(0) '" + 
+                                  termType + "' IAC SE");
+                        sQ.push(255, 250, 24, 0); 
+                        sQ.pushStr(termType);
+                        sQ.push(255, 240);
+                    } else {
+                        Util.Info("Invalid subnegotiation received" + arr);
+                    }
+                } else {
+                    Util.Info("Ignoring SB " + value);
+                }
+                break;
+            default:
+                Util.Info("Got Cmd " + cmd + " " + value + ", ignoring"); }
+            continue;
+        case 242:   // Data Mark (Synch)
+            cmd = chr;
+            code = arr.shift();
+            value = arr.shift();
+            Util.Info("Ignoring Data Mark (Synch)");
+            break;
+        default:   // everything else
+            str += String.fromCharCode(chr);
+        }
+    }
+
+    if (sQ) {
+        do_send();
+    }
+
+    if (str) {
+        vt100.write(str);
+    }
+
+    //console.log("<< do_recv");
+}
+
+
+
+that.connect = function(host, port, encrypt) {
+    var host = host,
+        port = port,
+        scheme = "ws://", uri;
+
+    Util.Debug(">> connect");
+    if ((!host) || (!port)) {
+        console.log("must set host and port");
+        return;
+    }
+
+    if (ws) {
+        ws.close();
+    }
+
+    if (encrypt) {
+        scheme = "wss://";
+    }
+    uri = scheme + host + ":" + port;
+    Util.Info("connecting to " + uri);
+    ws = new WebSocket(uri);
+
+    ws.onmessage = do_recv;
+
+    ws.onopen = function(e) {
+        Util.Info(">> WebSockets.onopen");
+        vt100.curs_set(true, true);
+        connect_callback();
+        Util.Info("<< WebSockets.onopen");
+    };
+    ws.onclose = function(e) {
+        Util.Info(">> WebSockets.onclose");
+        that.disconnect();
+        Util.Info("<< WebSockets.onclose");
+    };
+    ws.onerror = function(e) {
+        Util.Info(">> WebSockets.onerror");
+        that.disconnect();
+        Util.Info("<< WebSockets.onerror");
+    };
+
+    Util.Debug("<< connect");
+}
+
+that.disconnect = function() {
+    Util.Debug(">> disconnect");
+    if (ws) {
+        ws.close();
+    }
+    vt100.curs_set(true, false);
+
+    disconnect_callback();
+    Util.Debug("<< disconnect");
+}
+
+
+function constructor() {
+    /* Initialize the terminal emulator/renderer */
+
+    vt100 = new VT100(80, 24, target);
+
+    // Turn off local echo
+    vt100.noecho();
+
+
+    /*
+     * Override VT100 I/O routines
+     */
+
+    // Set handler for sending characters
+    vt100.getch(
+        function send_chr(chr, vt) {
+            var i;
+            Util.Debug(">> send_chr: " + chr);
+            for (i = 0; i < chr.length; i++) {
+                sQ.push(chr.charCodeAt(i));
+            }
+            do_send();
+            vt100.getch(send_chr);
+        }
+    );
+
+    vt100.debug = function(message) {
+        Util.Debug(message + "\n");
+    }
+
+    vt100.warn = function(message) {
+        Util.Warn(message + "\n");
+    }
+
+    vt100.curs_set = function(vis, grab, eventist)
+    {
+        this.debug("curs_set:: vis: " + vis + ", grab: " + grab);
+        if (vis !== undefined)
+            this.cursor_vis_ = (vis > 0);
+        if (eventist === undefined)
+            eventist = window;
+        if (grab === true || grab === false) {
+            if (grab === this.grab_events_)
+                return;
+            if (grab) {
+                this.grab_events_ = true;
+                VT100.the_vt_ = this;
+                Util.addEvent(eventist, 'keydown', vt100.key_down);
+                Util.addEvent(eventist, 'keyup', vt100.key_up);
+            } else {
+                Util.removeEvent(eventist, 'keydown', vt100.key_down);
+                Util.removeEvent(eventist, 'keyup', vt100.key_up);
+                this.grab_events_ = false;
+                VT100.the_vt_ = undefined;
+            }
+        }
+    }
+
+    vt100.key_down = function(e) {
+        var vt = VT100.the_vt_, keysym, ch, str = "";
+
+        if (vt === undefined)
+            return true;
+
+        keysym = getKeysym(e);
+
+        if (keysym < 128) {
+            if (e.ctrlKey) {
+                if (keysym == 64) {
+                    // control 0
+                    ch = 0;
+                } else if ((keysym >= 97) && (keysym <= 122)) {
+                    // control codes 1-26
+                    ch = keysym - 96;
+                } else if ((keysym >= 91) && (keysym <= 95)) {
+                    // control codes 27-31
+                    ch = keysym - 64;
+                } else {
+                    Util.Info("Debug unknown control keysym: " + keysym);
+                }
+            } else {
+                ch = keysym;
+            }
+            str = String.fromCharCode(ch);
+        } else {
+            switch (keysym) {
+            case 65505: // Shift, do not send directly
+                break;
+            case 65507: // Ctrl, do not send directly
+                break;
+            case 65293: // Carriage return, line feed
+                str = '\n'; break;
+            case 65288: // Backspace
+                str = '\b'; break;
+            case 65307: // Escape
+                str = '\x1b'; break;
+            case 65361: // Left arrow 
+                str = '\x1b[D'; break;
+            case 65362: // Up arrow 
+                str = '\x1b[A'; break;
+            case 65363: // Right arrow 
+                str = '\x1b[C'; break;
+            case 65364: // Down arrow 
+                str = '\x1b[B'; break;
+            default:
+                Util.Info("Unrecoginized keysym " + keysym);
+            }
+        }
+
+        if (str) {
+            vt.key_buf_.push(str);
+            setTimeout(VT100.go_getch_, 0);
+        }
+
+        Util.stopEvent(e);
+        return false;
+    }
+
+    vt100.key_up = function(e) {
+        var vt = VT100.the_vt_;
+        if (vt === undefined)
+            return true;
+        Util.stopEvent(e);
+        return false;
+    }
+
+
+    return that;
+}
+
+return constructor(); // Return the public API interface
+
+} // End of Telnet()
diff --git a/utils/wstest.py b/utils/wstest.py
index 0d005fa2..9442b04c 100755
--- a/utils/wstest.py
+++ b/utils/wstest.py
@@ -32,7 +32,7 @@ class WebSocketTest(WebSocketServer):
 
         WebSocketServer.__init__(self, *args, **kwargs)
 
-    def handler(self, client):
+    def new_client(self, client):
         self.send_cnt = 0
         self.recv_cnt = 0
 
diff --git a/utils/wswrapper.c b/utils/wswrapper.c
index 76e931e1..bd4e6f06 100644
--- a/utils/wswrapper.c
+++ b/utils/wswrapper.c
@@ -3,15 +3,27 @@
  * Copyright 2010 Joel Martin
  * Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
  *
- * wswrapper.so is meant to be LD preloaded. Use wswrap to run a program using
- * wswrapper.so.
- */
-
-/* 
- * Limitations:
+ * wswrapper is an LD_PRELOAD library that converts a TCP listen socket of an
+ * existing program to a be a WebSockets socket. The `wswrap` script can be
+ * used to easily launch a program using wswrapper. Here is an example of
+ * using wswrapper with vncserver. wswrapper will convert the socket listening
+ * on port 5901 to be a WebSockets port:
+ *
+ *  cd noVNC/utils
+ *  ./wswrap 5901 vncserver -geometry 640x480 :1
+ *
+ * This is tricky a subtle process so there are some serious limitations:
  * - multi-threaded programs may not work
+ * - programs that fork may behave in strange and mysterious ways (such as
+ *   fork bombing your system)
  * - programs using ppoll or epoll will not work correctly
  * - doesn't support fopencookie, streams, putc, etc.
+ *
+ * **********************************************************************
+ * WARNING:
+ * Due to the above limitations, this code should be considered an experiment
+ * only. Consider using the program wrap mode of wsproxy.py instead.
+ * **********************************************************************
  */
 
 #define DO_MSG 1
@@ -322,8 +334,8 @@ ssize_t _WS_ready(int sockfd, int nonblock)
     while (1) {
         len = (int) rfunc(sockfd, buf, count, flags);
         if (len < 1) {
-            TRACE("<< _WS_ready(%d, %d) len < 1, errno: %d\n",
-                  sockfd, nonblock, errno);
+            TRACE("<< _WS_ready(%d, %d) len %d, errno: %d\n",
+                  sockfd, nonblock, len, errno);
             return len;
         }
         if (len >= 2 && buf[0] == '\x00' && buf[1] == '\xff') {
@@ -668,7 +680,21 @@ int _WS_select(int mode, int nfds, fd_set *readfds,
         return ret;
     }
 
+#ifdef DO_TRACE
     TRACE(">> _WS_select(%d, %d, _, _, _, _)\n", mode, nfds);
+    for (i = 0; i < _WS_nfds; i++) {
+        fd = _WS_fds[i];
+        if (readfds && (FD_ISSET(fd, readfds))) {
+            TRACE("   WS %d is in readfds\n", fd, nfds);
+        }
+        if (writefds && (FD_ISSET(fd, writefds))) {
+            TRACE("   WS %d is in writefds\n", fd, nfds);
+        }
+        if (exceptfds && (FD_ISSET(fd, exceptfds))) {
+            TRACE("   WS %d is in exceptfds\n", fd, nfds);
+        }
+    }
+#endif
     if (timeptr) {
         memcpy(&savetv, timeptr, sizeof(savetv));
         gettimeofday(&starttv, NULL);
@@ -763,12 +789,26 @@ int _WS_select(int mode, int nfds, fd_set *readfds,
     } while (ret == 0);
 
     /* Restore original time value for pselect glibc does */
-    if (mode == 1) {
+    if (timeptr && mode == 1) {
         memcpy(timeptr, &savetv, sizeof(savetv));
     }
 
+#ifdef DO_TRACE
     TRACE("<< _WS_select(%d, %d, _, _, _, _) ret %d, errno %d\n",
           mode, nfds, ret, errno);
+    for (i = 0; i < _WS_nfds; i++) {
+        fd = _WS_fds[i];
+        if (readfds && (FD_ISSET(fd, readfds))) {
+            TRACE("   WS %d is set in readfds\n", fd, nfds);
+        }
+        if (writefds && (FD_ISSET(fd, writefds))) {
+            TRACE("   WS %d is set in writefds\n", fd, nfds);
+        }
+        if (exceptfds && (FD_ISSET(fd, exceptfds))) {
+            TRACE("   WS %d is set in exceptfds\n", fd, nfds);
+        }
+    }
+#endif
     return ret;
 }
 
@@ -1045,30 +1085,48 @@ int ppoll(struct pollfd *fds, nfds_t nfds,
                     (sigset_t *)sigmask);
 }
 
+int dup(int oldfd) {
+    int ret;
+    static void * (*func)();
+    if (!func) func = (void *(*)()) dlsym(RTLD_NEXT, "dup");
+
+    TRACE(">> dup(%d) called\n", oldfd);
+
+    ret = (int) func(oldfd);
+
+    TRACE("<< dup(%d) ret %d\n", oldfd, ret);
+    return ret;
+}
+
 int dup2(int oldfd, int newfd) {
     int ret;
     static void * (*func)();
     if (!func) func = (void *(*)()) dlsym(RTLD_NEXT, "dup2");
 
-    TRACE("dup2(%d, %d) called\n", oldfd, newfd);
+    TRACE(">> dup2(%d, %d) called\n", oldfd, newfd);
 
     ret = (int) func(oldfd, newfd);
-    if (! _WS_connections[oldfd]) {
+    if ((! _WS_connections[oldfd]) && (! _WS_connections[newfd])) {
         return ret;
     }
 
-    if (ret < 0) {
+    if ((ret < 0) || (oldfd == newfd) ||
+        (_WS_connections[oldfd] == _WS_connections[newfd])) {
+        TRACE("<< dup2(%d, %d) ret %d\n", oldfd, newfd, ret);
         return ret;
     }
-    if (oldfd == newfd) {
-        return newfd;
-    }
     
     /* dup2 behavior is to close newfd if it's open */
     if (_WS_connections[newfd]) {
         _WS_free(newfd);
     }
 
+    if (! _WS_connections[oldfd]) {
+        TRACE("<< dup2(%d, %d) ret %d\n", oldfd, newfd, ret);
+        return ret;
+    }
+
+    MSG("interposing on duplicated fd %d\n", newfd);
     /* oldfd and newfd are now descriptors for the same socket,
      * re-use the same context memory area */
     _WS_connections[newfd] = _WS_connections[oldfd];
@@ -1078,6 +1136,21 @@ int dup2(int oldfd, int newfd) {
     _WS_fds[_WS_nfds] = newfd;
     _WS_nfds++;
 
+    TRACE("<< dup2(%d, %d) ret %d\n", oldfd, newfd, ret);
     return ret;
 
 }
+
+int dup3(int oldfd, int newfd, int flags) {
+    int ret;
+    static void * (*func)();
+    if (!func) func = (void *(*)()) dlsym(RTLD_NEXT, "dup3");
+
+    TRACE(">> dup3(%d, %d, %d) called\n", oldfd, newfd, flags);
+
+    ret = (int) func(oldfd, newfd, flags);
+
+    TRACE("<< dup3(%d, %d, %d) ret %d\n", oldfd, newfd, flags, ret);
+    return ret;
+}
+
diff --git a/utils/wswrapper.h b/utils/wswrapper.h
index b69e9cf1..412b17a3 100644
--- a/utils/wswrapper.h
+++ b/utils/wswrapper.h
@@ -2,9 +2,6 @@
  * wswrap/wswrapper: Add WebSockets support to any service.
  * Copyright 2010 Joel Martin
  * Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
- *
- * wswrapper.so is meant to be LD preloaded. Use wswrap to run a program using
- * wswrapper.so.
  */
 
 #ifdef DO_MSG