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.
This commit is contained in:
parent
58dc1947de
commit
96bc3d3088
|
@ -137,7 +137,7 @@ There a few reasons why a proxy is required:
|
||||||
|
|
||||||
`./utils/wsproxy.py -f 8787 localhost:5901`
|
`./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`
|
`./utils/web.py PORT`
|
||||||
|
|
||||||
|
|
|
@ -5,26 +5,25 @@ usage() {
|
||||||
echo "$*"
|
echo "$*"
|
||||||
echo
|
echo
|
||||||
fi
|
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
|
||||||
echo "Starts a mini-webserver and the WebSockets proxy and"
|
echo "Starts a mini-webserver and the WebSockets proxy and"
|
||||||
echo "provides a cut and paste URL to go to."
|
echo "provides a cut and paste URL to go to."
|
||||||
echo
|
echo
|
||||||
echo " --web WEB_PORT Port to serve web pages at"
|
echo " --listen PORT Port for webserver/proxy to listen on"
|
||||||
echo " Default: 8080"
|
echo " Default: 6080"
|
||||||
echo " --proxy PROXY_PORT Port for proxy to listen on"
|
|
||||||
echo " Default: 8081"
|
|
||||||
echo " --vnc VNC_HOST:PORT VNC server host:port proxy target"
|
echo " --vnc VNC_HOST:PORT VNC server host:port proxy target"
|
||||||
echo " Default: localhost:5900"
|
echo " Default: localhost:5900"
|
||||||
|
echo " --cert CERT Path to combined cert/key file"
|
||||||
|
echo " Default: self.pem"
|
||||||
exit 2
|
exit 2
|
||||||
}
|
}
|
||||||
|
|
||||||
NAME="$(basename $0)"
|
NAME="$(basename $0)"
|
||||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||||
WEB_PORT="6080"
|
PORT="6080"
|
||||||
PROXY_PORT="6081"
|
|
||||||
VNC_DEST="localhost:5900"
|
VNC_DEST="localhost:5900"
|
||||||
web_pid=""
|
CERT=""
|
||||||
proxy_pid=""
|
proxy_pid=""
|
||||||
|
|
||||||
die() {
|
die() {
|
||||||
|
@ -36,10 +35,6 @@ cleanup() {
|
||||||
trap - TERM QUIT INT EXIT
|
trap - TERM QUIT INT EXIT
|
||||||
trap "true" CHLD # Ignore cleanup messages
|
trap "true" CHLD # Ignore cleanup messages
|
||||||
echo
|
echo
|
||||||
if [ -n "${web_pid}" ]; then
|
|
||||||
echo "Terminating webserver (${web_pid})"
|
|
||||||
kill ${web_pid}
|
|
||||||
fi
|
|
||||||
if [ -n "${proxy_pid}" ]; then
|
if [ -n "${proxy_pid}" ]; then
|
||||||
echo "Terminating WebSockets proxy (${proxy_pid})"
|
echo "Terminating WebSockets proxy (${proxy_pid})"
|
||||||
kill ${proxy_pid}
|
kill ${proxy_pid}
|
||||||
|
@ -52,12 +47,12 @@ cleanup() {
|
||||||
while [ "$*" ]; do
|
while [ "$*" ]; do
|
||||||
param=$1; shift; OPTARG=$1
|
param=$1; shift; OPTARG=$1
|
||||||
case $param in
|
case $param in
|
||||||
--web) WEB_PORT="${OPTARG}"; shift ;;
|
--listen) PORT="${OPTARG}"; shift ;;
|
||||||
--proxy) PROXY_PORT="${OPTARG}"; shift ;;
|
--vnc) VNC_DEST="${OPTARG}"; shift ;;
|
||||||
--vnc) VNC_DEST="${OPTARG}"; shift ;;
|
--cert) CERT="${OPTARG}"; shift ;;
|
||||||
-h|--help) usage ;;
|
-h|--help) usage ;;
|
||||||
-*) usage "Unknown chrooter option: ${param}" ;;
|
-*) usage "Unknown chrooter option: ${param}" ;;
|
||||||
*) break ;;
|
*) break ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
|
@ -65,40 +60,39 @@ done
|
||||||
which netstat >/dev/null 2>&1 \
|
which netstat >/dev/null 2>&1 \
|
||||||
|| die "Must have netstat installed"
|
|| die "Must have netstat installed"
|
||||||
|
|
||||||
netstat -ltn | grep -qs "${WEB_PORT}.*LISTEN" \
|
netstat -ltn | grep -qs "${PORT}.*LISTEN" \
|
||||||
&& die "Port ${WEB_PORT} in use. Try --web WEB_PORT"
|
&& die "Port ${PORT} in use. Try --listen PORT"
|
||||||
|
|
||||||
netstat -ltn | grep -qs "${PROXY_PORT}.*LISTEN" \
|
|
||||||
&& die "Port ${PROXY_PORT} in use. Try --proxy PROXY_PORT"
|
|
||||||
|
|
||||||
trap "cleanup" TERM QUIT INT EXIT
|
trap "cleanup" TERM QUIT INT EXIT
|
||||||
|
|
||||||
# Find vnc.html
|
# Find vnc.html
|
||||||
if [ -e "$(pwd)/vnc.html" ]; then
|
if [ -e "$(pwd)/vnc.html" ]; then
|
||||||
TOP=$(pwd)
|
WEB=$(pwd)
|
||||||
elif [ -e "${HERE}/../vnc.html" ]; then
|
elif [ -e "${HERE}/../vnc.html" ]; then
|
||||||
TOP=${HERE}/../
|
WEB=${HERE}/../
|
||||||
elif [ -e "${HERE}/vnc.html" ]; then
|
elif [ -e "${HERE}/vnc.html" ]; then
|
||||||
TOP=${HERE}
|
WEB=${HERE}
|
||||||
else
|
else
|
||||||
die "Could not find vnc.html"
|
die "Could not find vnc.html"
|
||||||
fi
|
fi
|
||||||
cd ${TOP}
|
|
||||||
|
|
||||||
echo "Starting webserver on port ${WEB_PORT}"
|
# Find self.pem
|
||||||
${HERE}/web.py ${WEB_PORT} >/dev/null &
|
if [ -n "${CERT}" ]; then
|
||||||
web_pid="$!"
|
if [ ! -e "${CERT}" ]; then
|
||||||
sleep 1
|
die "Could not find ${CERT}"
|
||||||
if ps -p ${web_pid} >/dev/null; then
|
fi
|
||||||
echo "Started webserver (pid: ${web_pid})"
|
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
|
else
|
||||||
web_pid=
|
echo "Warning: could not find self.pem"
|
||||||
echo "Failed to start webserver"
|
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Starting WebSockets proxy on port ${PROXY_PORT}"
|
echo "Starting webserver and WebSockets proxy on port ${PORT}"
|
||||||
${HERE}/wsproxy.py -f ${PROXY_PORT} ${VNC_DEST} &
|
${HERE}/wsproxy.py -f --web ${WEB} ${CERT:+--cert ${CERT}} ${PORT} ${VNC_DEST} &
|
||||||
proxy_pid="$!"
|
proxy_pid="$!"
|
||||||
sleep 1
|
sleep 1
|
||||||
if ps -p ${proxy_pid} >/dev/null; then
|
if ps -p ${proxy_pid} >/dev/null; then
|
||||||
|
@ -110,8 +104,7 @@ else
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "\n\nNavigate to to this URL:\n"
|
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"
|
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 sys, socket, ssl, struct, traceback
|
||||||
import os, resource, errno, signal # daemonizing
|
import os, resource, errno, signal # daemonizing
|
||||||
|
from SimpleHTTPServer import SimpleHTTPRequestHandler
|
||||||
|
from cStringIO import StringIO
|
||||||
from base64 import b64encode, b64decode
|
from base64 import b64encode, b64decode
|
||||||
try:
|
try:
|
||||||
from hashlib import md5
|
from hashlib import md5
|
||||||
|
@ -31,7 +33,8 @@ settings = {
|
||||||
'key' : None,
|
'key' : None,
|
||||||
'ssl_only' : False,
|
'ssl_only' : False,
|
||||||
'daemon' : True,
|
'daemon' : True,
|
||||||
'record' : None, }
|
'record' : None,
|
||||||
|
'web' : False, }
|
||||||
|
|
||||||
server_handshake = """HTTP/1.1 101 Web Socket Protocol Handshake\r
|
server_handshake = """HTTP/1.1 101 Web Socket Protocol Handshake\r
|
||||||
Upgrade: WebSocket\r
|
Upgrade: WebSocket\r
|
||||||
|
@ -47,6 +50,29 @@ policy_response = """<cross-domain-policy><allow-access-from domain="*" to-ports
|
||||||
class EClose(Exception):
|
class EClose(Exception):
|
||||||
pass
|
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="."):
|
def traffic(token="."):
|
||||||
if settings['verbose'] and not settings['daemon']:
|
if settings['verbose'] and not settings['daemon']:
|
||||||
sys.stdout.write(token)
|
sys.stdout.write(token)
|
||||||
|
@ -54,7 +80,10 @@ def traffic(token="."):
|
||||||
|
|
||||||
def handler_msg(msg):
|
def handler_msg(msg):
|
||||||
if not settings['daemon']:
|
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):
|
def encode(buf):
|
||||||
buf = b64encode(buf)
|
buf = b64encode(buf)
|
||||||
|
@ -96,56 +125,77 @@ def gen_md5(keys):
|
||||||
return md5(struct.pack('>II8s', num1, num2, key3)).digest()
|
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
|
# Peek, but don't read the data
|
||||||
handshake = sock.recv(1024, socket.MSG_PEEK)
|
handshake = sock.recv(1024, socket.MSG_PEEK)
|
||||||
#handler_msg("Handshake [%s]" % repr(handshake))
|
#handler_msg("Handshake [%s]" % repr(handshake))
|
||||||
if handshake == "":
|
if handshake == "":
|
||||||
handler_msg("ignoring empty handshake")
|
raise EClose("ignoring empty handshake")
|
||||||
sock.close()
|
|
||||||
return False
|
|
||||||
elif handshake.startswith("<policy-file-request/>"):
|
elif handshake.startswith("<policy-file-request/>"):
|
||||||
handshake = sock.recv(1024)
|
handshake = sock.recv(1024)
|
||||||
handler_msg("Sending flash policy response")
|
|
||||||
sock.send(policy_response)
|
sock.send(policy_response)
|
||||||
sock.close()
|
raise EClose("Sending flash policy response")
|
||||||
return False
|
|
||||||
elif handshake[0] in ("\x16", "\x80"):
|
elif handshake[0] in ("\x16", "\x80"):
|
||||||
if not os.path.exists(settings['cert']):
|
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'])
|
% settings['cert'])
|
||||||
sock.close()
|
try:
|
||||||
return False
|
retsock = ssl.wrap_socket(
|
||||||
retsock = ssl.wrap_socket(
|
sock,
|
||||||
sock,
|
server_side=True,
|
||||||
server_side=True,
|
certfile=settings['cert'],
|
||||||
certfile=settings['cert'],
|
keyfile=settings['key'])
|
||||||
keyfile=settings['key'])
|
except ssl.SSLError, x:
|
||||||
|
if x.args[0] == ssl.SSL_ERROR_EOF:
|
||||||
|
raise EClose("")
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
scheme = "wss"
|
scheme = "wss"
|
||||||
handler_msg("using SSL/TLS")
|
stype = "SSL/TLS (wss://)"
|
||||||
elif settings['ssl_only']:
|
elif settings['ssl_only']:
|
||||||
handler_msg("non-SSL connection disallowed")
|
raise EClose("non-SSL connection received but disallowed")
|
||||||
sock.close()
|
|
||||||
return False
|
|
||||||
else:
|
else:
|
||||||
retsock = sock
|
retsock = sock
|
||||||
scheme = "ws"
|
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)
|
handshake = retsock.recv(4096)
|
||||||
#handler_msg("handshake: " + repr(handshake))
|
#handler_msg("handshake: " + repr(handshake))
|
||||||
|
|
||||||
if len(handshake) == 0:
|
if len(handshake) == 0:
|
||||||
raise EClose("Client closed during handshake")
|
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)
|
h = parse_handshake(handshake)
|
||||||
|
|
||||||
if h.get('key3'):
|
if h.get('key3'):
|
||||||
trailer = gen_md5(h)
|
trailer = gen_md5(h)
|
||||||
pre = "Sec-"
|
pre = "Sec-"
|
||||||
handler_msg("using protocol version 76")
|
ver = 76
|
||||||
else:
|
else:
|
||||||
trailer = ""
|
trailer = ""
|
||||||
pre = ""
|
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,
|
response = server_handshake % (pre, h['Origin'], pre, scheme,
|
||||||
h['Host'], h['path'], pre, trailer)
|
h['Host'], h['path'], pre, trailer)
|
||||||
|
@ -177,8 +227,8 @@ def daemonize(keepfd=None):
|
||||||
try:
|
try:
|
||||||
if fd != keepfd:
|
if fd != keepfd:
|
||||||
os.close(fd)
|
os.close(fd)
|
||||||
elif settings['verbose']:
|
else:
|
||||||
print "Keeping fd: %d" % fd
|
handler_vmsg("Keeping fd: %d" % fd)
|
||||||
except OSError, exc:
|
except OSError, exc:
|
||||||
if exc.errno != errno.EBADF: raise
|
if exc.errno != errno.EBADF: raise
|
||||||
|
|
||||||
|
@ -209,25 +259,25 @@ def start_server():
|
||||||
csock = startsock = None
|
csock = startsock = None
|
||||||
pid = 0
|
pid = 0
|
||||||
startsock, address = lsock.accept()
|
startsock, address = lsock.accept()
|
||||||
handler_msg('got client connection from %s' % address[0])
|
handler_vmsg('%s: forking handler' % address[0])
|
||||||
|
|
||||||
handler_msg("forking handler process")
|
|
||||||
pid = os.fork()
|
pid = os.fork()
|
||||||
|
|
||||||
if pid == 0: # handler process
|
if pid == 0: # handler process
|
||||||
csock = do_handshake(startsock)
|
csock = do_handshake(startsock, address)
|
||||||
if not csock:
|
|
||||||
handler_msg("No connection after handshake");
|
|
||||||
break
|
|
||||||
settings['handler'](csock)
|
settings['handler'](csock)
|
||||||
else: # parent process
|
else: # parent process
|
||||||
settings['handler_id'] += 1
|
settings['handler_id'] += 1
|
||||||
|
|
||||||
except EClose, exc:
|
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:
|
except Exception, exc:
|
||||||
handler_msg("handler exception: %s" % str(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 pid == 0:
|
||||||
if csock: csock.close()
|
if csock: csock.close()
|
||||||
|
|
|
@ -143,6 +143,8 @@ if __name__ == '__main__':
|
||||||
help="SSL key file (if separate from cert)")
|
help="SSL key file (if separate from cert)")
|
||||||
parser.add_option("--ssl-only", action="store_true",
|
parser.add_option("--ssl-only", action="store_true",
|
||||||
help="disallow non-encrypted connections")
|
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()
|
(options, args) = parser.parse_args()
|
||||||
|
|
||||||
if len(args) > 2: parser.error("Too many arguments")
|
if len(args) > 2: parser.error("Too many arguments")
|
||||||
|
@ -176,4 +178,7 @@ if __name__ == '__main__':
|
||||||
settings['daemon'] = options.daemon
|
settings['daemon'] = options.daemon
|
||||||
if options.record:
|
if options.record:
|
||||||
settings['record'] = os.path.abspath(options.record)
|
settings['record'] = os.path.abspath(options.record)
|
||||||
|
if options.web:
|
||||||
|
os.chdir = options.web
|
||||||
|
settings['web'] = options.web
|
||||||
start_server()
|
start_server()
|
||||||
|
|
Loading…
Reference in New Issue