diff --git a/utils/websocket.py b/utils/websocket.py index 8194914ae3ccdf11360c886b22c301ff78458368..37b69c868a59e50ba95ed28c86dbcf05efea68c0 100644 --- a/utils/websocket.py +++ b/utils/websocket.py @@ -16,7 +16,8 @@ as taken from http://docs.python.org/dev/library/ssl.html#certificates ''' -import os, sys, time, errno, signal, socket, struct, traceback, select +import os, sys, time, errno, signal, socket, traceback, select +import struct, array from cgi import parse_qsl from base64 import b64encode, b64decode @@ -28,6 +29,7 @@ if sys.hexversion > 0x3000000: from urllib.parse import urlsplit b2s = lambda buf: buf.decode('latin_1') s2b = lambda s: s.encode('latin_1') + s2a = lambda s: s else: # python 2.X from cStringIO import StringIO @@ -36,6 +38,7 @@ else: # No-ops b2s = lambda buf: buf s2b = lambda s: s + s2a = lambda s: [ord(c) for c in s] if sys.hexversion >= 0x2060000: # python >= 2.6 @@ -54,7 +57,7 @@ for mod, sup in [('numpy', 'HyBi protocol'), globals()[mod] = __import__(mod) except ImportError: globals()[mod] = None - print("WARNING: no '%s' module, %s support disabled" % ( + print("WARNING: no '%s' module, %s decode may be slower" % ( mod, sup)) @@ -88,7 +91,8 @@ Sec-WebSocket-Accept: %s\r def __init__(self, listen_host='', listen_port=None, source_is_ipv6=False, verbose=False, cert='', key='', ssl_only=None, - daemon=False, record='', web=''): + daemon=False, record='', web='', + run_once=False, timeout=0): # settings self.verbose = verbose @@ -96,6 +100,11 @@ Sec-WebSocket-Accept: %s\r self.listen_port = listen_port self.ssl_only = ssl_only self.daemon = daemon + self.run_once = run_once + self.timeout = timeout + + self.launch_time = time.time() + self.ws_connection = False self.handler_id = 1 # Make paths settings absolute @@ -207,6 +216,38 @@ Sec-WebSocket-Accept: %s\r os.dup2(os.open(os.devnull, os.O_RDWR), sys.stdout.fileno()) os.dup2(os.open(os.devnull, os.O_RDWR), sys.stderr.fileno()) + @staticmethod + def unmask(buf, f): + pstart = f['hlen'] + 4 + pend = pstart + f['length'] + if numpy: + b = c = s2b('') + if f['length'] >= 4: + mask = numpy.frombuffer(buf, dtype=numpy.dtype('<u4'), + offset=f['hlen'], count=1) + data = numpy.frombuffer(buf, dtype=numpy.dtype('<u4'), + offset=pstart, count=int(f['length'] / 4)) + #b = numpy.bitwise_xor(data, mask).data + b = numpy.bitwise_xor(data, mask).tostring() + + if f['length'] % 4: + #print("Partial unmask") + mask = numpy.frombuffer(buf, dtype=numpy.dtype('B'), + offset=f['hlen'], count=(f['length'] % 4)) + data = numpy.frombuffer(buf, dtype=numpy.dtype('B'), + offset=pend - (f['length'] % 4), + count=(f['length'] % 4)) + c = numpy.bitwise_xor(data, mask).tostring() + return b + c + else: + # Slower fallback + data = array.array('B') + mask = s2a(f['mask']) + data.fromstring(buf[pstart:pend]) + for i in range(len(data)): + data[i] ^= mask[i % 4] + return data.tostring() + @staticmethod def encode_hybi(buf, opcode, base64=False): """ Encode a HyBi style WebSocket frame. @@ -295,24 +336,7 @@ Sec-WebSocket-Accept: %s\r if has_mask: # unmask payload f['mask'] = buf[f['hlen']:f['hlen']+4] - b = c = s2b('') - if f['length'] >= 4: - mask = numpy.frombuffer(buf, dtype=numpy.dtype('<u4'), - offset=f['hlen'], count=1) - data = numpy.frombuffer(buf, dtype=numpy.dtype('<u4'), - offset=f['hlen'] + 4, count=int(f['length'] / 4)) - #b = numpy.bitwise_xor(data, mask).data - b = numpy.bitwise_xor(data, mask).tostring() - - if f['length'] % 4: - #print("Partial unmask") - mask = numpy.frombuffer(buf, dtype=numpy.dtype('B'), - offset=f['hlen'], count=(f['length'] % 4)) - data = numpy.frombuffer(buf, dtype=numpy.dtype('B'), - offset=full_len - (f['length'] % 4), - count=(f['length'] % 4)) - c = numpy.bitwise_xor(data, mask).tostring() - f['payload'] = b + c + f['payload'] = WebSocketServer.unmask(buf, f) else: print("Unmasked frame: %s" % repr(buf)) f['payload'] = buf[(f['hlen'] + has_mask * 4):full_len] @@ -468,11 +492,11 @@ Sec-WebSocket-Accept: %s\r break else: - if buf[0:2] == '\xff\x00': + if buf[0:2] == s2b('\xff\x00'): closed = "Client sent orderly close frame" break - elif buf[0:2] == '\x00\xff': + elif buf[0:2] == s2b('\x00\xff'): buf = buf[2:] continue # No-op @@ -611,9 +635,6 @@ Sec-WebSocket-Accept: %s\r if ver: # HyBi/IETF version of the protocol - if sys.hexversion < 0x2060000 or not numpy: - raise self.EClose("Python >= 2.6 and numpy module is required for HyBi-07 or greater") - # HyBi-07 report version 7 # HyBi-08 - HyBi-12 report version 8 # HyBi-13 reports version 13 @@ -669,6 +690,9 @@ Sec-WebSocket-Accept: %s\r self.msg("%s: %s WebSocket connection" % (address[0], stype)) self.msg("%s: Version %s, base64: '%s'" % (address[0], self.version, self.base64)) + if self.path != '/': + self.msg("%s: Path: '%s'" % (address[0], self.path)) + # Send server WebSockets handshake response #self.msg("sending response [%s]" % response) @@ -727,6 +751,7 @@ Sec-WebSocket-Accept: %s\r self.rec = open(fname, 'w+') self.rec.write("var VNC_frame_data = [\n") + self.ws_connection = True self.new_client() except self.EClose: _, exc, _ = sys.exc_info() @@ -777,6 +802,12 @@ Sec-WebSocket-Accept: %s\r startsock = None pid = err = 0 + time_elapsed = time.time() - self.launch_time + if self.timeout and time_elapsed > self.timeout: + self.msg('listener exit due to --timeout %s' + % self.timeout) + break + try: self.poll() @@ -799,7 +830,14 @@ Sec-WebSocket-Accept: %s\r else: raise - if Process: + if self.run_once: + # Run in same process if run_once + self.top_new_client(startsock, address) + if self.ws_connection : + self.msg('%s: exiting due to --run-once' + % address[0]) + break + elif Process: self.vmsg('%s: new handler Process' % address[0]) p = Process(target=self.top_new_client, args=(startsock, address)) diff --git a/utils/websockify b/utils/websockify index 59b6e0de3f5fb6d9b208a8a78b22fc109cbda48a..4521a96351032e39d6af83b82a9dfce9c5c674f6 100755 --- a/utils/websockify +++ b/utils/websockify @@ -226,6 +226,10 @@ if __name__ == '__main__': parser.add_option("--daemon", "-D", dest="daemon", action="store_true", help="become a daemon (background process)") + parser.add_option("--run-once", action="store_true", + help="handle a single WebSocket connection and exit") + parser.add_option("--timeout", type=int, default=0, + help="after TIMEOUT seconds exit when not connected") parser.add_option("--cert", default="self.pem", help="SSL certificate file") parser.add_option("--key", default=None,