Commit f2538f33 authored by Joel Martin's avatar Joel Martin

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.
parent 86725f9b
......@@ -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
......
......@@ -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:
......
This diff is collapsed.
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
......
## 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:
<table>
<tr>
<th>Program</th>
<th>Language</th>
<th>Proxy or Interposer</th>
<th>Multiprocess</th>
<th>Daemonize</th>
<th>SSL/wss</th>
<th>Flash Policy Server</th>
<th>Session Record</th>
<th>Web Server</th>
<th>Program Wrap</th>
</tr> <tr>
<td>wsproxy.py</td>
<td>python</td>
<td>proxy</td>
<td>yes</td>
<td>yes</td>
<td>yes 1</td>
<td>yes</td>
<td>yes</td>
<td>yes</td>
<td>yes</td>
</tr> <tr>
<td>wsproxy</td>
<td>C</td>
<td>proxy</td>
<td>yes</td>
<td>yes</td>
<td>yes</td>
<td>yes</td>
<td>no</td>
<td>no</td>
<td>no</td>
</tr>
</tr> <tr>
<td>wsproxy.js</td>
<td>Node (node.js)</td>
<td>proxy</td>
<td>yes</td>
<td>no</td>
<td>no</td>
<td>no</td>
<td>no</td>
<td>no</td>
</tr>
</tr> <tr>
<td>wswrap/wswrapper.so</td>
<td>shell/C</td>
<td>interposer</td>
<td>indirectly</td>
<td>indirectly</td>
<td>no</td>
<td>no</td>
<td>no</td>
<td>no</td>
</tr>
</table>
......@@ -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:
......
VT100-orig.js
\ No newline at end of file
../include
\ No newline at end of file
......@@ -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
......
#!/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 "${@}"
/*
* 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 <stdio.h>
#include <stdlib.h>
#define __USE_GNU 1 // Pull in RTLD_NEXT
#include <dlfcn.h>
#include <string.h>
#include <netinet/in.h>
#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;
}
......@@ -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
......
#!/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 <listen_port>" % sys.argv[0]
sys.exit(1)
server = WebSocketEcho(
listen_port=listen_port,
verbose=True,
cert='self.pem',
web='.')
server.start_server()
......@@ -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");
......
......@@ -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__)
......
<html>
<head>
<title>WebSockets Telnet</title>
<script src="include/base64.js"></script>
<script src="include/util.js"></script>
<script src="include/webutil.js"></script>
<script src="include/canvas.js"></script>
<script src="VT100.js"></script>
<script src="wstelnet.js"></script>
<!-- Uncomment to activate firebug lite -->
<!--
<script type='text/javascript'
src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script>
-->
</head>
<body>
Host: <input id='host' style='width:100'>&nbsp;
Port: <input id='port' style='width:50'>&nbsp;
Encrypt: <input id='encrypt' type='checkbox'>&nbsp;
<input id='connectButton' type='button' value='Connect' style='width:100px'
onclick="connect();">&nbsp;
<br><br>
<pre id="terminal"></pre>
<script>
var telnet;
function connect() {
telnet.connect($D('host').value,
$D('port').value,
$D('encrypt').checked);
$D('connectButton').disabled = true;
$D('connectButton').value = "Connecting";
}
function disconnect() {
$D('connectButton').disabled = true;
$D('connectButton').value = "Disconnecting";
telnet.disconnect();
}
function connected() {
$D('connectButton').disabled = false;
$D('connectButton').value = "Disconnect";
$D('connectButton').onclick = disconnect;
}
function disconnected() {
$D('connectButton').disabled = false;
$D('connectButton').value = "Connect";
$D('connectButton').onclick = connect;
}
/* If no builtin websockets then load web_socket.js */
if (window.WebSocket) {
VNC_native_ws = true;
} else {
VNC_native_ws = false;
console.log("Loading web-socket-js flash bridge");
var extra = "<script src='include/web-socket-js/swfobject.js'><\/script>";
extra += "<script src='include/web-socket-js/FABridge.js'><\/script>";
extra += "<script src='include/web-socket-js/web_socket.js'><\/script>";
document.write(extra);
}
window.onload = function() {
console.log("onload");
if (!VNC_native_ws) {
console.log("initializing web-socket-js flash bridge");
WebSocket.__swfLocation = "include/web-socket-js/WebSocketMain.swf";
WebSocket.__initialize();
}
var url = document.location.href;
$D('host').value = (url.match(/host=([^&#]*)/) || ['',''])[1];
$D('port').value = (url.match(/port=([^&#]*)/) || ['',''])[1];
telnet = Telnet('terminal', connected, disconnected);
}
</script>
</body>
</html>
/*
* 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()
......@@ -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
......
......@@ -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;
}
......@@ -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
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment