Commit 96bc3d30 authored by Joel Martin's avatar Joel Martin

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.
parent 58dc1947
......@@ -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`
......
......@@ -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,9 +47,9 @@ cleanup() {
while [ "$*" ]; do
param=$1; shift; OPTARG=$1
case $param in
--web) WEB_PORT="${OPTARG}"; shift ;;
--proxy) PROXY_PORT="${OPTARG}"; shift ;;
--listen) PORT="${OPTARG}"; shift ;;
--vnc) VNC_DEST="${OPTARG}"; shift ;;
--cert) CERT="${OPTARG}"; shift ;;
-h|--help) usage ;;
-*) usage "Unknown chrooter option: ${param}" ;;
*) break ;;
......@@ -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}
......@@ -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 = """<cross-domain-policy><allow-access-from domain="*" to-ports
class EClose(Exception):
pass
# HTTP handler with request from a string and response to a socket
class SplitHTTPHandler(SimpleHTTPRequestHandler):
def __init__(self, req, resp, addr):
# Save the response socket
self.response = resp
SimpleHTTPRequestHandler.__init__(self, req, addr, object())
def setup(self):
self.connection = self.response
# Duck type request string to file object
self.rfile = StringIO(self.request)
self.wfile = self.connection.makefile('wb', self.wbufsize)
def send_response(self, code, message=None):
# Save the status code
self.last_code = code
SimpleHTTPRequestHandler.send_response(self, code, message)
def log_message(self, f, *args):
# Save instead of printing
self.last_message = f % args
def traffic(token="."):
if settings['verbose'] and not settings['daemon']:
sys.stdout.write(token)
......@@ -54,7 +80,10 @@ def traffic(token="."):
def handler_msg(msg):
if not settings['daemon']:
print " %d: %s" % (settings['handler_id'], msg)
print "% 3d: %s" % (settings['handler_id'], msg)
def handler_vmsg(msg):
if settings['verbose']: handler_msg(msg)
def encode(buf):
buf = b64encode(buf)
......@@ -96,56 +125,77 @@ def gen_md5(keys):
return md5(struct.pack('>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("<policy-file-request/>"):
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"
raise EClose("SSL connection but '%s' not found"
% settings['cert'])
sock.close()
return False
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()
......
......@@ -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()
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