From 96bc3d308835b8ef76a10ba3dcdfab06356563af Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Thu, 6 Jan 2011 18:26:54 -0600 Subject: [PATCH] wsproxy.py: add web serving capability. - Added ability to respond to normal web requests. This is basically integrating web.py functionality into wsproxy. This is only in the python version and it is off by default when calling wsproxy. Turn it on with --web DIR where DIR is the web root directory. Next task is to clean up wsproxy.py. It's gotten unwieldy and it really no longer needs to be parallel to the C version. --- README.md | 2 +- utils/launch.sh | 73 ++++++++++++--------------- utils/websocket.py | 122 ++++++++++++++++++++++++++++++++------------- utils/wsproxy.py | 5 ++ 4 files changed, 125 insertions(+), 77 deletions(-) diff --git a/README.md b/README.md index 4d1b1e51..ead2abd6 100644 --- a/README.md +++ b/README.md @@ -137,7 +137,7 @@ There a few reasons why a proxy is required: `./utils/wsproxy.py -f 8787 localhost:5901` -* To run the mini python web server without the launch script: +* To run a mini python web server without the launch script: `./utils/web.py PORT` diff --git a/utils/launch.sh b/utils/launch.sh index 1ee7d15b..4ed9f92d 100755 --- a/utils/launch.sh +++ b/utils/launch.sh @@ -5,26 +5,25 @@ usage() { echo "$*" echo fi - echo "Usage: ${NAME} [--web WEB_PORT] [--proxy PROXY_PORT] [--vnc VNC_HOST:PORT]" + 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 - echo " --web WEB_PORT Port to serve web pages at" - echo " Default: 8080" - echo " --proxy PROXY_PORT Port for proxy to listen on" - echo " Default: 8081" + echo " --listen PORT Port for webserver/proxy to listen on" + echo " Default: 6080" echo " --vnc VNC_HOST:PORT VNC server host:port proxy target" echo " Default: localhost:5900" + echo " --cert CERT Path to combined cert/key file" + echo " Default: self.pem" exit 2 } NAME="$(basename $0)" HERE="$(cd "$(dirname "$0")" && pwd)" -WEB_PORT="6080" -PROXY_PORT="6081" +PORT="6080" VNC_DEST="localhost:5900" -web_pid="" +CERT="" proxy_pid="" die() { @@ -36,10 +35,6 @@ cleanup() { trap - TERM QUIT INT EXIT trap "true" CHLD # Ignore cleanup messages echo - if [ -n "${web_pid}" ]; then - echo "Terminating webserver (${web_pid})" - kill ${web_pid} - fi if [ -n "${proxy_pid}" ]; then echo "Terminating WebSockets proxy (${proxy_pid})" kill ${proxy_pid} @@ -52,12 +47,12 @@ cleanup() { while [ "$*" ]; do param=$1; shift; OPTARG=$1 case $param in - --web) WEB_PORT="${OPTARG}"; shift ;; - --proxy) PROXY_PORT="${OPTARG}"; shift ;; - --vnc) VNC_DEST="${OPTARG}"; shift ;; - -h|--help) usage ;; + --listen) PORT="${OPTARG}"; shift ;; + --vnc) VNC_DEST="${OPTARG}"; shift ;; + --cert) CERT="${OPTARG}"; shift ;; + -h|--help) usage ;; -*) usage "Unknown chrooter option: ${param}" ;; - *) break ;; + *) break ;; esac done @@ -65,40 +60,39 @@ done which netstat >/dev/null 2>&1 \ || die "Must have netstat installed" -netstat -ltn | grep -qs "${WEB_PORT}.*LISTEN" \ - && die "Port ${WEB_PORT} in use. Try --web WEB_PORT" - -netstat -ltn | grep -qs "${PROXY_PORT}.*LISTEN" \ - && die "Port ${PROXY_PORT} in use. Try --proxy PROXY_PORT" +netstat -ltn | grep -qs "${PORT}.*LISTEN" \ + && die "Port ${PORT} in use. Try --listen PORT" trap "cleanup" TERM QUIT INT EXIT # Find vnc.html if [ -e "$(pwd)/vnc.html" ]; then - TOP=$(pwd) + WEB=$(pwd) elif [ -e "${HERE}/../vnc.html" ]; then - TOP=${HERE}/../ + WEB=${HERE}/../ elif [ -e "${HERE}/vnc.html" ]; then - TOP=${HERE} + WEB=${HERE} else die "Could not find vnc.html" fi -cd ${TOP} -echo "Starting webserver on port ${WEB_PORT}" -${HERE}/web.py ${WEB_PORT} >/dev/null & -web_pid="$!" -sleep 1 -if ps -p ${web_pid} >/dev/null; then - echo "Started webserver (pid: ${web_pid})" +# Find self.pem +if [ -n "${CERT}" ]; then + if [ ! -e "${CERT}" ]; then + die "Could not find ${CERT}" + fi +elif [ -e "$(pwd)/self.pem" ]; then + CERT="$(pwd)/self.pem" +elif [ -e "${HERE}/../self.pem" ]; then + CERT="${HERE}/../self.pem" +elif [ -e "${HERE}/self.pem" ]; then + CERT="${HERE}/self.pem" else - web_pid= - echo "Failed to start webserver" - exit 1 + echo "Warning: could not find self.pem" fi -echo "Starting WebSockets proxy on port ${PROXY_PORT}" -${HERE}/wsproxy.py -f ${PROXY_PORT} ${VNC_DEST} & +echo "Starting webserver and WebSockets proxy on port ${PORT}" +${HERE}/wsproxy.py -f --web ${WEB} ${CERT:+--cert ${CERT}} ${PORT} ${VNC_DEST} & proxy_pid="$!" sleep 1 if ps -p ${proxy_pid} >/dev/null; then @@ -110,8 +104,7 @@ else fi echo -e "\n\nNavigate to to this URL:\n" -echo -e " http://$(hostname):${WEB_PORT}/vnc.html?host=$(hostname)&port=${PROXY_PORT}\n" +echo -e " http://$(hostname):${PORT}/vnc.html?host=$(hostname)&port=${PORT}\n" echo -e "Press Ctrl-C to exit\n\n" -wait ${web_pid} - +wait ${proxy_pid} diff --git a/utils/websocket.py b/utils/websocket.py index 70748c1d..7efd01a3 100755 --- a/utils/websocket.py +++ b/utils/websocket.py @@ -13,6 +13,8 @@ as taken from http://docs.python.org/dev/library/ssl.html#certificates import sys, socket, ssl, struct, traceback import os, resource, errno, signal # daemonizing +from SimpleHTTPServer import SimpleHTTPRequestHandler +from cStringIO import StringIO from base64 import b64encode, b64decode try: from hashlib import md5 @@ -31,7 +33,8 @@ settings = { 'key' : None, 'ssl_only' : False, 'daemon' : True, - 'record' : None, } + 'record' : None, + 'web' : False, } server_handshake = """HTTP/1.1 101 Web Socket Protocol Handshake\r Upgrade: WebSocket\r @@ -47,6 +50,29 @@ policy_response = """II8s', num1, num2, key3)).digest() -def do_handshake(sock): +def do_handshake(sock, address): + stype = "" # Peek, but don't read the data handshake = sock.recv(1024, socket.MSG_PEEK) #handler_msg("Handshake [%s]" % repr(handshake)) if handshake == "": - handler_msg("ignoring empty handshake") - sock.close() - return False + raise EClose("ignoring empty handshake") elif handshake.startswith(""): handshake = sock.recv(1024) - handler_msg("Sending flash policy response") sock.send(policy_response) - sock.close() - return False + raise EClose("Sending flash policy response") elif handshake[0] in ("\x16", "\x80"): if not os.path.exists(settings['cert']): - handler_msg("SSL connection but '%s' not found" - % settings['cert']) - sock.close() - return False - retsock = ssl.wrap_socket( - sock, - server_side=True, - certfile=settings['cert'], - keyfile=settings['key']) + raise EClose("SSL connection but '%s' not found" + % settings['cert']) + try: + retsock = ssl.wrap_socket( + sock, + server_side=True, + certfile=settings['cert'], + keyfile=settings['key']) + except ssl.SSLError, x: + if x.args[0] == ssl.SSL_ERROR_EOF: + raise EClose("") + else: + raise + scheme = "wss" - handler_msg("using SSL/TLS") + stype = "SSL/TLS (wss://)" elif settings['ssl_only']: - handler_msg("non-SSL connection disallowed") - sock.close() - return False + raise EClose("non-SSL connection received but disallowed") else: retsock = sock scheme = "ws" - handler_msg("using plain (not SSL) socket") + stype = "Plain non-SSL (ws://)" + + # Now get the data from the socket handshake = retsock.recv(4096) #handler_msg("handshake: " + repr(handshake)) + if len(handshake) == 0: raise EClose("Client closed during handshake") + + # Handle normal web requests + if handshake.startswith('GET ') and \ + handshake.find('Upgrade: WebSocket\r\n') == -1: + if not settings['web']: + raise EClose("Normal web request received but disallowed") + sh = SplitHTTPHandler(handshake, retsock, address) + if sh.last_code < 200 or sh.last_code >= 300: + raise EClose(sh.last_message) + elif settings['verbose']: + raise EClose(sh.last_message) + else: + raise EClose("") + + # Do WebSockets handshake and return the socket h = parse_handshake(handshake) if h.get('key3'): trailer = gen_md5(h) pre = "Sec-" - handler_msg("using protocol version 76") + ver = 76 else: trailer = "" pre = "" - handler_msg("using protocol version 75") + ver = 75 + + handler_msg("%s WebSocket connection (version %s) from %s" + % (stype, ver, address[0])) response = server_handshake % (pre, h['Origin'], pre, scheme, h['Host'], h['path'], pre, trailer) @@ -177,8 +227,8 @@ def daemonize(keepfd=None): try: if fd != keepfd: os.close(fd) - elif settings['verbose']: - print "Keeping fd: %d" % fd + else: + handler_vmsg("Keeping fd: %d" % fd) except OSError, exc: if exc.errno != errno.EBADF: raise @@ -209,25 +259,25 @@ def start_server(): csock = startsock = None pid = 0 startsock, address = lsock.accept() - handler_msg('got client connection from %s' % address[0]) - - handler_msg("forking handler process") + handler_vmsg('%s: forking handler' % address[0]) pid = os.fork() if pid == 0: # handler process - csock = do_handshake(startsock) - if not csock: - handler_msg("No connection after handshake"); - break + csock = do_handshake(startsock, address) settings['handler'](csock) else: # parent process settings['handler_id'] += 1 except EClose, exc: - handler_msg("handler exit: %s" % exc.args) + if csock and csock != startsock: + csock.close() + startsock.close() + if exc.args[0]: + handler_msg("%s: %s" % (address[0], exc.args[0])) except Exception, exc: handler_msg("handler exception: %s" % str(exc)) - #handler_msg(traceback.format_exc()) + if settings['verbose']: + handler_msg(traceback.format_exc()) if pid == 0: if csock: csock.close() diff --git a/utils/wsproxy.py b/utils/wsproxy.py index 378e4743..32a4021e 100755 --- a/utils/wsproxy.py +++ b/utils/wsproxy.py @@ -143,6 +143,8 @@ if __name__ == '__main__': help="SSL key file (if separate from cert)") parser.add_option("--ssl-only", action="store_true", help="disallow non-encrypted connections") + parser.add_option("--web", default=None, metavar="DIR", + help="run webserver on same port. Serve files from DIR.") (options, args) = parser.parse_args() if len(args) > 2: parser.error("Too many arguments") @@ -176,4 +178,7 @@ if __name__ == '__main__': settings['daemon'] = options.daemon if options.record: settings['record'] = os.path.abspath(options.record) + if options.web: + os.chdir = options.web + settings['web'] = options.web start_server()