From f2538f337d85695cd6bfa183195ca3189d45d58c Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 12 Jan 2011 13:15:11 -0600 Subject: [PATCH] wsproxy, wstelnet: wrap command, WS telnet client. wswrapper: Getting the wswrapper.c LD_PRELOAD model working has turned out to involve too many dark corners of the glibc/POSIX file descriptor space. I realized that 95% of what I want can be accomplished by adding a "wrap command" mode to wsproxy. The code is still there for now, but consider it experimental at best. Minor fix to dup2 and add dup and dup3 logging. wsproxy Wrap Command: In wsproxy wrap command mode, a command line is specified instead of a target address and port. wsproxy then uses a much simpler LD_PRELOAD library, rebind.so, to move intercept any bind() system calls made by the program. If the bind() call is for the wsproxy listen port number then the real bind() system call is issued for an alternate (free high) port on loopback/localhost. wsproxy then forwards from the listen address/port to the moved port. The --wrap-mode argument takes three options that determine the behavior of wsproxy when the wrapped command returns an exit code (exit or daemonizing): ignore, exit, respawn. For example, this runs vncserver on turns port 5901 into a WebSockets port (rebind.so must be built first): ./utils/wsproxy.py --wrap-mode=ignore 5901 -- vncserver :1 The vncserver command backgrounds itself so the wrap mode is set to "ignore" so that wsproxy keeps running even after it receives an exit code from vncserver. wstelnet: To demonstrate the wrap command mode, I added WebSockets telnet client. For example, this runs telnetd (krb5-telnetd) on turns port 2023 into a WebSockets port (using "respawn" mode since telnetd exits after each connection closes): sudo ./utils/wsproxy.py --wrap-mode=respawn 2023 -- telnetd -debug 2023 Then the utils/wstelnet.html page can be used to connect to the telnetd server on port 2023. The telnet client includes VT100.js (from http://code.google.com/p/sshconsole) which handles the terminal emulation and rendering. rebind: The rebind LD_PRELOAD library is used by wsproxy in wrap command mode to intercept bind() system calls and move the port to a different port on loopback/localhost. The rebind.so library can be built by running make in the utils directory. The rebind library can be used separately from wsproxy by setting the REBIND_OLD_PORT and REBIND_NEW_PORT environment variables prior to executing a command. For example: export export REBIND_PORT_OLD="23" export export REBIND_PORT_NEW="65023" LD_PRELOAD=./rebind.so telnetd -debug 23 Alternately, the rebind script does the same thing: rebind 23 65023 telnetd -debug 23 Other changes/notes: - wsproxy no longer daemonizes by default. Remove -f/--foreground option and add -D/--deamon option. - When wsproxy is used to wrap a command in "respawn" mode, the command will not be respawn more often than 3 times within 10 seconds. - Move getKeysym routine out of Canvas object so that it can be called directly. --- README.md | 18 +-- docs/TODO | 6 +- include/canvas.js | 197 +++++++++++++------------- utils/Makefile | 5 +- utils/README.md | 81 ++++++----- utils/VT100.js | 1 + utils/include | 1 + utils/launch.sh | 12 +- utils/rebind | 18 +++ utils/rebind.c | 94 +++++++++++++ utils/websocket.py | 88 +++++++++--- utils/wsecho.py | 90 ++++++++++++ utils/wsproxy.c | 14 +- utils/wsproxy.py | 143 ++++++++++++++++--- utils/wstelnet.html | 90 ++++++++++++ utils/wstelnet.js | 333 ++++++++++++++++++++++++++++++++++++++++++++ utils/wstest.py | 2 +- utils/wswrapper.c | 103 ++++++++++++-- utils/wswrapper.h | 3 - 19 files changed, 1080 insertions(+), 219 deletions(-) create mode 120000 utils/VT100.js create mode 120000 utils/include create mode 100755 utils/rebind create mode 100644 utils/rebind.c create mode 100755 utils/wsecho.py create mode 100644 utils/wstelnet.html create mode 100644 utils/wstelnet.js diff --git a/README.md b/README.md index ead2abd..3e1eb60 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 87f786f..c64cace 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 e28c3b0..0e15a65 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 646adcb..008d45c 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 93fdfb3..7aeebe6 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 0000000..4178564 --- /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 0000000..f5030fe --- /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 4ed9f92..c21e2ba 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 0000000..6912d20 --- /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 0000000..c7e83de --- /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 48eb15a..4520508 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 0000000..15e2ef7 --- /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 5ba2206..42bb45e 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 660a2a6..fed4c04 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 0000000..01e987b --- /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 0000000..4e1bd0d
--- /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 0d005fa..9442b04 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 76e931e..bd4e6f0 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 b69e9cf..412b17a 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