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 |
Language |
- Proxy or Interposer |
Multiprocess |
Daemonize |
SSL/wss |
Flash Policy Server |
Session Record |
Web Server |
+ Program Wrap |
wsproxy.py |
python |
- proxy |
yes |
yes |
yes 1 |
yes |
yes |
yes |
+ yes |
wsproxy |
C |
- proxy |
yes |
yes |
yes |
yes |
no |
no |
+ no |
wsproxy.js |
Node (node.js) |
- proxy |
yes |
no |
no |
no |
no |
no |
-
-
- wswrap/wswrapper.so |
- shell/C |
- interposer |
- indirectly |
- indirectly |
- no |
- no |
- no |
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