http://git-wip-us.apache.org/repos/asf/cloudstack/blob/a0571d1e/patches/systemvm/debian/config/root/lswcp/utils/websocket.py ---------------------------------------------------------------------- diff --git a/patches/systemvm/debian/config/root/lswcp/utils/websocket.py b/patches/systemvm/debian/config/root/lswcp/utils/websocket.py new file mode 100644 index 0000000..82bb56c --- /dev/null +++ b/patches/systemvm/debian/config/root/lswcp/utils/websocket.py @@ -0,0 +1,925 @@ +#!/usr/bin/env python + +''' +Python WebSocket library with support for "wss://" encryption. +Copyright 2011 Joel Martin +Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3) + +Supports following protocol versions: + - http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75 + - http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76 + - http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10 + +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 os, sys, time, errno, signal, socket, traceback, select, re +import array, struct +from cgi import parse_qsl +from base64 import b64encode, b64decode + +# Imports that vary by python version + +# python 3.0 differences +if sys.hexversion > 0x3000000: + b2s = lambda buf: buf.decode('latin_1') + s2b = lambda s: s.encode('latin_1') + s2a = lambda s: s +else: + b2s = lambda buf: buf # No-op + s2b = lambda s: s # No-op + s2a = lambda s: [ord(c) for c in s] +try: from io import StringIO +except: from cStringIO import StringIO +try: from http.server import SimpleHTTPRequestHandler +except: from SimpleHTTPServer import SimpleHTTPRequestHandler +try: from urllib.parse import urlsplit +except: from urlparse import urlsplit + +# python 2.6 differences +try: from hashlib import md5, sha1 +except: from md5 import md5; from sha import sha as sha1 + +# python 2.5 differences +try: + from struct import pack, unpack_from +except: + from struct import pack + def unpack_from(fmt, buf, offset=0): + slice = buffer(buf, offset, struct.calcsize(fmt)) + return struct.unpack(fmt, slice) + +# Degraded functionality if these imports are missing +for mod, sup in [('numpy', 'HyBi protocol'), ('ssl', 'TLS/SSL/wss'), + ('multiprocessing', 'Multi-Processing'), + ('resource', 'daemonizing')]: + try: + globals()[mod] = __import__(mod) + except ImportError: + globals()[mod] = None + print("WARNING: no '%s' module, %s is slower or disabled" % ( + mod, sup)) +if multiprocessing and sys.platform == 'win32': + # make sockets pickle-able/inheritable + import multiprocessing.reduction + + +class WebSocketServer(object): + """ + WebSockets server class. + Must be sub-classed with new_client method definition. + """ + + buffer_size = 65536 + + server_handshake_hixie = """HTTP/1.1 101 Web Socket Protocol Handshake\r +Upgrade: WebSocket\r +Connection: Upgrade\r +%sWebSocket-Origin: %s\r +%sWebSocket-Location: %s://%s%s\r +""" + + server_handshake_hybi = """HTTP/1.1 101 Switching Protocols\r +Upgrade: websocket\r +Connection: Upgrade\r +Sec-WebSocket-Accept: %s\r +""" + + GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" + + policy_response = """<cross-domain-policy><allow-access-from domain="*" to-ports="*" /></cross-domain-policy>\n""" + + class EClose(Exception): + pass + + def __init__(self, listen_host='', listen_port=None, source_is_ipv6=False, + verbose=False, cert='', key='', ssl_only=None, + daemon=False, record='', web='', + run_once=False, timeout=0): + + # settings + self.verbose = verbose + self.listen_host = listen_host + 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 + self.cert = os.path.abspath(cert) + self.key = self.web = self.record = '' + if key: + self.key = os.path.abspath(key) + if web: + self.web = os.path.abspath(web) + if record: + self.record = os.path.abspath(record) + + if self.web: + os.chdir(self.web) + + # Sanity checks + if not ssl and self.ssl_only: + raise Exception("No 'ssl' module and SSL-only specified") + if self.daemon and not resource: + raise Exception("Module 'resource' required to daemonize") + + # Show configuration + 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. Web root: %s" % self.web) + if ssl: + 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)") + else: + print(" - No SSL/TLS support (no 'ssl' module)") + if self.daemon: + print(" - Backgrounding (daemon)") + if self.record: + print(" - Recording to '%s.*'" % self.record) + + # + # WebSocketServer static methods + # + + @staticmethod + def socket(host, port=None, connect=False, prefer_ipv6=False): + """ Resolve a host (and optional port) to an IPv4 or IPv6 + address. Create a socket. Bind to it if listen is set, + otherwise connect to it. Return the socket. + """ + flags = 0 + if host == '': + host = None + if connect and not port: + raise Exception("Connect mode requires a port") + if not connect: + flags = flags | socket.AI_PASSIVE + addrs = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM, + socket.IPPROTO_TCP, flags) + if not addrs: + raise Exception("Could resolve host '%s'" % host) + addrs.sort(key=lambda x: x[0]) + if prefer_ipv6: + addrs.reverse() + sock = socket.socket(addrs[0][0], addrs[0][1]) + if connect: + sock.connect(addrs[0][4]) + else: + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + sock.bind(addrs[0][4]) + sock.listen(100) + return sock + + @staticmethod + def daemonize(keepfd=None, chdir='/'): + os.umask(0) + if chdir: + os.chdir(chdir) + else: + os.chdir('/') + os.setgid(os.getgid()) # relinquish elevations + os.setuid(os.getuid()) # relinquish elevations + + # Double fork to daemonize + if os.fork() > 0: os._exit(0) # Parent exits + os.setsid() # Obtain new process group + if os.fork() > 0: os._exit(0) # Parent exits + + # Signal handling + def terminate(a,b): os._exit(0) + signal.signal(signal.SIGTERM, terminate) + signal.signal(signal.SIGINT, signal.SIG_IGN) + + # Close open files + maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1] + if maxfd == resource.RLIM_INFINITY: maxfd = 256 + for fd in reversed(range(maxfd)): + try: + if fd != keepfd: + os.close(fd) + except OSError: + _, exc, _ = sys.exc_info() + if exc.errno != errno.EBADF: raise + + # Redirect I/O to /dev/null + os.dup2(os.open(os.devnull, os.O_RDWR), sys.stdin.fileno()) + 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. + Optional opcode: + 0x0 - continuation + 0x1 - text frame (base64 encode buf) + 0x2 - binary frame (use raw buf) + 0x8 - connection close + 0x9 - ping + 0xA - pong + """ + if base64: + buf = b64encode(buf) + + b1 = 0x80 | (opcode & 0x0f) # FIN + opcode + payload_len = len(buf) + if payload_len <= 125: + header = pack('>BB', b1, payload_len) + elif payload_len > 125 and payload_len < 65536: + header = pack('>BBH', b1, 126, payload_len) + elif payload_len >= 65536: + header = pack('>BBQ', b1, 127, payload_len) + + #print("Encoded: %s" % repr(header + buf)) + + return header + buf, len(header), 0 + + @staticmethod + def decode_hybi(buf, base64=False): + """ Decode HyBi style WebSocket packets. + Returns: + {'fin' : 0_or_1, + 'opcode' : number, + 'mask' : 32_bit_number, + 'hlen' : header_bytes_number, + 'length' : payload_bytes_number, + 'payload' : decoded_buffer, + 'left' : bytes_left_number, + 'close_code' : number, + 'close_reason' : string} + """ + + f = {'fin' : 0, + 'opcode' : 0, + 'mask' : 0, + 'hlen' : 2, + 'length' : 0, + 'payload' : None, + 'left' : 0, + 'close_code' : None, + 'close_reason' : None} + + blen = len(buf) + f['left'] = blen + + if blen < f['hlen']: + return f # Incomplete frame header + + b1, b2 = unpack_from(">BB", buf) + f['opcode'] = b1 & 0x0f + f['fin'] = (b1 & 0x80) >> 7 + has_mask = (b2 & 0x80) >> 7 + + f['length'] = b2 & 0x7f + + if f['length'] == 126: + f['hlen'] = 4 + if blen < f['hlen']: + return f # Incomplete frame header + (f['length'],) = unpack_from('>xxH', buf) + elif f['length'] == 127: + f['hlen'] = 10 + if blen < f['hlen']: + return f # Incomplete frame header + (f['length'],) = unpack_from('>xxQ', buf) + + full_len = f['hlen'] + has_mask * 4 + f['length'] + + if blen < full_len: # Incomplete frame + return f # Incomplete frame header + + # Number of bytes that are part of the next frame(s) + f['left'] = blen - full_len + + # Process 1 frame + if has_mask: + # unmask payload + f['mask'] = buf[f['hlen']:f['hlen']+4] + f['payload'] = WebSocketServer.unmask(buf, f) + else: + print("Unmasked frame: %s" % repr(buf)) + f['payload'] = buf[(f['hlen'] + has_mask * 4):full_len] + + if base64 and f['opcode'] in [1, 2]: + try: + f['payload'] = b64decode(f['payload']) + except: + print("Exception while b64decoding buffer: %s" % + repr(buf)) + raise + + if f['opcode'] == 0x08: + if f['length'] >= 2: + f['close_code'] = unpack_from(">H", f['payload']) + if f['length'] > 3: + f['close_reason'] = f['payload'][2:] + + return f + + @staticmethod + def encode_hixie(buf): + return s2b("\x00" + b2s(b64encode(buf)) + "\xff"), 1, 1 + + @staticmethod + def decode_hixie(buf): + end = buf.find(s2b('\xff')) + return {'payload': b64decode(buf[1:end]), + 'hlen': 1, + 'length': end - 1, + 'left': len(buf) - (end + 1)} + + + @staticmethod + def gen_md5(keys): + """ Generate hash value for WebSockets hixie-76. """ + key1 = keys['Sec-WebSocket-Key1'] + key2 = keys['Sec-WebSocket-Key2'] + key3 = keys['key3'] + spaces1 = key1.count(" ") + spaces2 = key2.count(" ") + num1 = int("".join([c for c in key1 if c.isdigit()])) / spaces1 + num2 = int("".join([c for c in key2 if c.isdigit()])) / spaces2 + + return b2s(md5(pack('>II8s', + int(num1), int(num2), key3)).digest()) + + # + # WebSocketServer logging/output functions + # + + def traffic(self, token="."): + """ Show traffic flow in verbose mode. """ + if self.verbose and not self.daemon: + sys.stdout.write(token) + sys.stdout.flush() + + def msg(self, msg): + """ Output message with handler_id prefix. """ + if not self.daemon: + print("% 3d: %s" % (self.handler_id, msg)) + + def vmsg(self, msg): + """ Same as msg() but only if verbose. """ + if self.verbose: + self.msg(msg) + + # + # Main WebSocketServer methods + # + def send_frames(self, bufs=None): + """ Encode and send WebSocket frames. Any frames already + queued will be sent first. If buf is not set then only queued + frames will be sent. Returns the number of pending frames that + could not be fully sent. If returned pending frames is greater + than 0, then the caller should call again when the socket is + ready. """ + + tdelta = int(time.time()*1000) - self.start_time + + if bufs: + for buf in bufs: + if self.version.startswith("hybi"): + if self.base64: + encbuf, lenhead, lentail = self.encode_hybi( + buf, opcode=1, base64=True) + else: + encbuf, lenhead, lentail = self.encode_hybi( + buf, opcode=2, base64=False) + + else: + encbuf, lenhead, lentail = self.encode_hixie(buf) + + if self.rec: + self.rec.write("%s,\n" % + repr("{%s{" % tdelta + + encbuf[lenhead:-lentail])) + + self.send_parts.append(encbuf) + + while self.send_parts: + # Send pending frames + buf = self.send_parts.pop(0) + sent = self.client.send(buf) + + if sent == len(buf): + self.traffic("<") + else: + self.traffic("<.") + self.send_parts.insert(0, buf[sent:]) + break + + return len(self.send_parts) + + def recv_frames(self): + """ Receive and decode WebSocket frames. + + Returns: + (bufs_list, closed_string) + """ + + closed = False + bufs = [] + tdelta = int(time.time()*1000) - self.start_time + + buf = self.client.recv(self.buffer_size) + if len(buf) == 0: + closed = "Client closed abruptly" + return bufs, closed + + if self.recv_part: + # Add partially received frames to current read buffer + buf = self.recv_part + buf + self.recv_part = None + + while buf: + if self.version.startswith("hybi"): + + frame = self.decode_hybi(buf, base64=self.base64) + #print("Received buf: %s, frame: %s" % (repr(buf), frame)) + + if frame['payload'] == None: + # Incomplete/partial frame + self.traffic("}.") + if frame['left'] > 0: + self.recv_part = buf[-frame['left']:] + break + else: + if frame['opcode'] == 0x8: # connection close + closed = "Client closed, reason: %s - %s" % ( + frame['close_code'], + frame['close_reason']) + break + + else: + if buf[0:2] == s2b('\xff\x00'): + closed = "Client sent orderly close frame" + break + + elif buf[0:2] == s2b('\x00\xff'): + buf = buf[2:] + continue # No-op + + elif buf.count(s2b('\xff')) == 0: + # Partial frame + self.traffic("}.") + self.recv_part = buf + break + + frame = self.decode_hixie(buf) + + self.traffic("}") + + if self.rec: + start = frame['hlen'] + end = frame['hlen'] + frame['length'] + self.rec.write("%s,\n" % + repr("}%s}" % tdelta + buf[start:end])) + + + bufs.append(frame['payload']) + + if frame['left']: + buf = buf[-frame['left']:] + else: + buf = '' + + return bufs, closed + + def send_close(self, code=None, reason=''): + """ Send a WebSocket orderly close frame. """ + + if self.version.startswith("hybi"): + msg = s2b('') + if code != None: + msg = pack(">H%ds" % (len(reason)), code) + + buf, h, t = self.encode_hybi(msg, opcode=0x08, base64=False) + self.client.send(buf) + + elif self.version == "hixie-76": + buf = s2b('\xff\x00') + self.client.send(buf) + + # No orderly close for 75 + + def do_handshake(self, sock, address): + """ + do_handshake does the following: + - Peek at the first few bytes from the socket. + - If the connection is Flash policy request then answer it, + close the socket and return. + - If the connection is an HTTPS/SSL/TLS connection then SSL + wrap the socket. + - Read from the (possibly wrapped) socket. + - If we have received a HTTP GET request and the webserver + functionality is enabled, answer it, close the socket and + return. + - Assume we have a WebSockets connection, parse the client + handshake data. + - Send a WebSockets handshake server response. + - Return the socket for this WebSocket client. + """ + + stype = "" + + ready = select.select([sock], [], [], 3)[0] + if not ready: + raise self.EClose("ignoring socket not ready") + # Peek, but do not read the data so that we have a opportunity + # to SSL wrap the socket first + handshake = sock.recv(1024, socket.MSG_PEEK) + #self.msg("Handshake [%s]" % handshake) + + if handshake == "": + raise self.EClose("ignoring empty handshake") + + elif handshake.startswith(s2b("<policy-file-request/>")): + # Answer Flash policy request + handshake = sock.recv(1024) + sock.send(s2b(self.policy_response)) + raise self.EClose("Sending flash policy response") + + elif handshake[0] in ("\x16", "\x80", 22, 128): + # SSL wrap the connection + if not ssl: + raise self.EClose("SSL connection but no 'ssl' module") + if not os.path.exists(self.cert): + raise self.EClose("SSL connection but '%s' not found" + % self.cert) + retsock = None + try: + retsock = ssl.wrap_socket( + sock, + server_side=True, + certfile=self.cert, + keyfile=self.key) + except ssl.SSLError: + _, x, _ = sys.exc_info() + if x.args[0] == ssl.SSL_ERROR_EOF: + if len(x.args) > 1: + raise self.EClose(x.args[1]) + else: + raise self.EClose("Got SSL_ERROR_EOF") + else: + raise + + scheme = "wss" + stype = "SSL/TLS (wss://)" + + elif self.ssl_only: + raise self.EClose("non-SSL connection received but disallowed") + + else: + retsock = sock + scheme = "ws" + stype = "Plain non-SSL (ws://)" + + wsh = WSRequestHandler(retsock, address, not self.web) + if wsh.last_code == 101: + # Continue on to handle WebSocket upgrade + pass + elif wsh.last_code == 405: + raise self.EClose("Normal web request received but disallowed") + elif wsh.last_code < 200 or wsh.last_code >= 300: + raise self.EClose(wsh.last_message) + elif self.verbose: + raise self.EClose(wsh.last_message) + else: + raise self.EClose("") + + h = self.headers = wsh.headers + path = self.path = wsh.path + + prot = 'WebSocket-Protocol' + protocols = h.get('Sec-'+prot, h.get(prot, '')).split(',') + + ver = h.get('Sec-WebSocket-Version') + if ver: + # HyBi/IETF version of the protocol + + # HyBi-07 report version 7 + # HyBi-08 - HyBi-12 report version 8 + # HyBi-13 reports version 13 + if ver in ['7', '8', '13']: + self.version = "hybi-%02d" % int(ver) + else: + raise self.EClose('Unsupported protocol version %s' % ver) + + key = h['Sec-WebSocket-Key'] + + # Choose binary if client supports it + if 'binary' in protocols: + self.base64 = False + elif 'base64' in protocols: + self.base64 = True + else: + raise self.EClose("Client must support 'binary' or 'base64' protocol") + + # Generate the hash value for the accept header + accept = b64encode(sha1(s2b(key + self.GUID)).digest()) + + response = self.server_handshake_hybi % b2s(accept) + if self.base64: + response += "Sec-WebSocket-Protocol: base64\r\n" + else: + response += "Sec-WebSocket-Protocol: binary\r\n" + response += "\r\n" + + else: + # Hixie version of the protocol (75 or 76) + + if h.get('key3'): + trailer = self.gen_md5(h) + pre = "Sec-" + self.version = "hixie-76" + else: + trailer = "" + pre = "" + self.version = "hixie-75" + + # We only support base64 in Hixie era + self.base64 = True + + response = self.server_handshake_hixie % (pre, + h['Origin'], pre, scheme, h['Host'], path) + + if 'base64' in protocols: + response += "%sWebSocket-Protocol: base64\r\n" % pre + else: + self.msg("Warning: client does not report 'base64' protocol support") + response += "\r\n" + trailer + + 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) + retsock.send(s2b(response)) + + # Return the WebSockets socket which may be SSL wrapped + return retsock + + + # + # 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.vmsg("Running poll()") + pass + + def fallback_SIGCHLD(self, sig, stack): + # Reap zombies when using os.fork() (python 2.4) + self.vmsg("Got SIGCHLD, reaping zombies") + try: + result = os.waitpid(-1, os.WNOHANG) + while result[0]: + self.vmsg("Reaped child process %s" % result[0]) + result = os.waitpid(-1, os.WNOHANG) + except (OSError): + pass + + def do_SIGINT(self, sig, stack): + self.msg("Got SIGINT, exiting") + sys.exit(0) + + def top_new_client(self, startsock, address): + """ Do something with a WebSockets client connection. """ + # Initialize per client settings + self.send_parts = [] + self.recv_part = None + self.base64 = False + self.rec = None + self.start_time = int(time.time()*1000) + + # handler process + try: + try: + self.client = self.do_handshake(startsock, address) + + if self.record: + # Record raw frame data as JavaScript array + fname = "%s.%s" % (self.record, + self.handler_id) + self.msg("opening record file: %s" % fname) + 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() + # Connection was not a WebSockets connection + if exc.args[0]: + self.msg("%s: %s" % (address[0], exc.args[0])) + except Exception: + _, exc, _ = sys.exc_info() + self.msg("handler exception: %s" % str(exc)) + if self.verbose: + self.msg(traceback.format_exc()) + finally: + if self.rec: + self.rec.write("'EOF']\n") + self.rec.close() + + if self.client and self.client != startsock: + self.client.close() + + def new_client(self): + """ Do something with a WebSockets client connection. """ + 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 new_client() method (which must + be overridden) for each new client connection. + """ + lsock = self.socket(self.listen_host, self.listen_port) + + if self.daemon: + self.daemonize(keepfd=lsock.fileno(), chdir=self.web) + + self.started() # Some things need to happen after daemonizing + + # Allow override of SIGINT + signal.signal(signal.SIGINT, self.do_SIGINT) + if not multiprocessing: + # os.fork() (python 2.4) child reaper + signal.signal(signal.SIGCHLD, self.fallback_SIGCHLD) + + while True: + try: + try: + self.client = None + 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() + + ready = select.select([lsock], [], [], 1)[0] + if lsock in ready: + startsock, address = lsock.accept() + else: + continue + except Exception: + _, exc, _ = sys.exc_info() + if hasattr(exc, 'errno'): + err = exc.errno + elif hasattr(exc, 'args'): + err = exc.args[0] + else: + err = exc[0] + if err == errno.EINTR: + self.vmsg("Ignoring interrupted syscall") + continue + else: + raise + + 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 multiprocessing: + self.vmsg('%s: new handler Process' % address[0]) + p = multiprocessing.Process( + target=self.top_new_client, + args=(startsock, address)) + p.start() + # child will not return + else: + # python 2.4 + self.vmsg('%s: forking handler' % address[0]) + pid = os.fork() + if pid == 0: + # child handler process + self.top_new_client(startsock, address) + break # child process exits + + # parent process + self.handler_id += 1 + + except KeyboardInterrupt: + _, exc, _ = sys.exc_info() + print("In KeyboardInterrupt") + pass + except SystemExit: + _, exc, _ = sys.exc_info() + print("In SystemExit") + break + except Exception: + _, exc, _ = sys.exc_info() + self.msg("handler exception: %s" % str(exc)) + if self.verbose: + self.msg(traceback.format_exc()) + + finally: + if startsock: + startsock.close() + + +# HTTP handler with WebSocket upgrade support +class WSRequestHandler(SimpleHTTPRequestHandler): + def __init__(self, req, addr, only_upgrade=False): + self.only_upgrade = only_upgrade # only allow upgrades + SimpleHTTPRequestHandler.__init__(self, req, addr, object()) + + def do_GET(self): + if (self.headers.get('upgrade') and + self.headers.get('upgrade').lower() == 'websocket'): + + if (self.headers.get('sec-websocket-key1') or + self.headers.get('websocket-key1')): + # For Hixie-76 read out the key hash + self.headers.__setitem__('key3', self.rfile.read(8)) + + # Just indicate that an WebSocket upgrade is needed + self.last_code = 101 + self.last_message = "101 Switching Protocols" + elif self.only_upgrade: + # Normal web request responses are disabled + self.last_code = 405 + self.last_message = "405 Method Not Allowed" + else: + if not re.match('^\/((images|include|include\/web-socket-js)\/)?[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+(\?.*)?$', self.path): + self.last_code = 400 + self.last_message = "400 Bad Request" + SimpleHTTPRequestHandler.send_response(self,self.last_code,self.last_message) + else: + SimpleHTTPRequestHandler.do_GET(self) + + 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 +
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/a0571d1e/patches/systemvm/debian/config/root/lswcp/utils/websockify ---------------------------------------------------------------------- diff --git a/patches/systemvm/debian/config/root/lswcp/utils/websockify b/patches/systemvm/debian/config/root/lswcp/utils/websockify new file mode 100644 index 0000000..497d550 --- /dev/null +++ b/patches/systemvm/debian/config/root/lswcp/utils/websockify @@ -0,0 +1,206 @@ +#!/usr/bin/env python + +''' +A WebSocket to TCP socket proxy with support for "wss://" encryption. +Copyright 2011 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 socket, optparse, time, os, sys, subprocess +from rijndael_lsw import decrypt +from select import select +from websocket import WebSocketServer +from hashlib import sha256 +from time import time +try: from urllib.parse import unquote_plus +except: from urllib import unquote_plus + +class WebSocketProxy(WebSocketServer): + """ + Proxy traffic to and from a WebSockets client to a normal TCP + socket server target. All traffic to/from the client is base64 + encoded/decoded to allow binary data to be sent/received to/from + the target. + """ + + buffer_size = 65536 + + traffic_legend = """ +Traffic Legend: + } - Client receive + }. - Client receive partial + { - Target receive + + > - Target send + >. - Target send partial + < - Client send + <. - Client send partial +""" + + def started(self): + """ + Called after Websockets server startup (i.e. after daemonize) + """ + print(" - proxy listenin on %s:%s\n" % ( + self.listen_host, self.listen_port)) + + # + # 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): + """ + Called after a new WebSocket connection has been established. + """ + self.key = 'daP8TeiMJierui9euug7aiTi' + arguments = decrypt(self.key, self.path.lstrip('/')) + arguments, signature = arguments.split('|') + self.target_host, self.target_port, self.client_ip, self.timestamp = arguments.rsplit(':', 3) + + # Check signature + if sha256(self.key+arguments).hexdigest() != signature: + self.send_close() + raise self.EClose('Not accepted: Invalid signature') + # Check client ip address + if self.client_ip != self.client.getpeername()[0]: + self.send_close() + raise self.EClose('Not accepted: Invalid client ip address') + # Check timestamp + if self.timestamp < time()-300: + self.send_close() + raise self.EClose('Not accepted: Timestamp older than 5 minutes') + + # Connect to the target + self.msg("connecting to: %s:%s" % (self.target_host, self.target_port)) + tsock = self.socket(self.target_host, self.target_port, + connect=True) + + if self.verbose and not self.daemon: + print(self.traffic_legend) + + # Start proxying + try: + self.do_proxy(tsock) + except: + if tsock: + tsock.shutdown(socket.SHUT_RDWR) + tsock.close() + self.vmsg("%s:%s: Target closed" % (self.target_host, self.target_port)) + raise + + def do_proxy(self, target): + """ + Proxy client WebSocket to normal target socket. + """ + + cqueue = [] + c_pend = 0 + tqueue = [] + rlist = [self.client, target] + + while True: + wlist = [] + + if tqueue: wlist.append(target) + if cqueue or c_pend: wlist.append(self.client) + ins, outs, excepts = select(rlist, wlist, [], 1) + if excepts: raise Exception("Socket exception") + + if target in outs: + # Send queued client data to the target + dat = tqueue.pop(0) + sent = target.send(dat) + if sent == len(dat): + self.traffic(">") + else: + # requeue the remaining data + tqueue.insert(0, dat[sent:]) + self.traffic(".>") + + + if target in ins: + # Receive target data, encode it and queue for client + buf = target.recv(self.buffer_size) + if len(buf) == 0: raise self.EClose("Target closed") + + cqueue.append(buf) + self.traffic("{") + + + if self.client in outs: + # Send queued target data to the client + c_pend = self.send_frames(cqueue) + + cqueue = [] + + + if self.client in ins: + # Receive client data, decode it, and queue for target + bufs, closed = self.recv_frames() + tqueue.extend(bufs) + + if closed: + # TODO: What about blocking on client socket? + self.send_close() + raise self.EClose(closed) + +if __name__ == '__main__': + usage = "\n %prog [options]" + usage += " [source_addr:]source_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("--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, + 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.") + (opts, args) = parser.parse_args() + + # Sanity checks + if len(args) < 1: + parser.error("Too few arguments") + else: + if len(args) > 1: + 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) + + # Parse host:port and convert ports to numbers + if args[0].count(':') > 0: + opts.listen_host, opts.listen_port = args[0].rsplit(':', 1) + else: + opts.listen_host, opts.listen_port = '', args[0] + + try: opts.listen_port = int(opts.listen_port) + except: parser.error("Error parsing listen port") + + # Create and start the WebSockets proxy + server = WebSocketProxy(**opts.__dict__) + server.start_server() http://git-wip-us.apache.org/repos/asf/cloudstack/blob/a0571d1e/patches/systemvm/debian/config/root/lswcp/utils/websockify.py ---------------------------------------------------------------------- diff --git a/patches/systemvm/debian/config/root/lswcp/utils/websockify.py b/patches/systemvm/debian/config/root/lswcp/utils/websockify.py new file mode 100644 index 0000000..05b5af4 --- /dev/null +++ b/patches/systemvm/debian/config/root/lswcp/utils/websockify.py @@ -0,0 +1 @@ +websockify \ No newline at end of file http://git-wip-us.apache.org/repos/asf/cloudstack/blob/a0571d1e/patches/systemvm/debian/config/root/lswcp/utils/wsproxy.py ---------------------------------------------------------------------- diff --git a/patches/systemvm/debian/config/root/lswcp/utils/wsproxy.py b/patches/systemvm/debian/config/root/lswcp/utils/wsproxy.py new file mode 100644 index 0000000..05b5af4 --- /dev/null +++ b/patches/systemvm/debian/config/root/lswcp/utils/wsproxy.py @@ -0,0 +1 @@ +websockify \ No newline at end of file http://git-wip-us.apache.org/repos/asf/cloudstack/blob/a0571d1e/patches/systemvm/debian/config/root/lswcp/vnc.html ---------------------------------------------------------------------- diff --git a/patches/systemvm/debian/config/root/lswcp/vnc.html b/patches/systemvm/debian/config/root/lswcp/vnc.html new file mode 100644 index 0000000..b6cf85b --- /dev/null +++ b/patches/systemvm/debian/config/root/lswcp/vnc.html @@ -0,0 +1,198 @@ +<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.1//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile11.dtd"> +<html> +<head> + + <!-- + noVNC example: simple example using default UI + Copyright (C) 2011 Joel Martin + Licensed under LGPL-3 (see LICENSE.txt) + --> + <title>noVNC</title> + + <meta charset="utf-8"> + + <!-- Always force latest IE rendering engine (even in intranet) & Chrome Frame + Remove this if you use the .htaccess --> + <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> + + <!-- Apple iOS Safari settings --> + <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> + <meta name="apple-mobile-web-app-capable" content="yes" /> + <meta names="apple-mobile-web-app-status-bar-style" content="black-translucent" /> + <!-- App Start Icon --> + <link rel="apple-touch-startup-image" href="images/screen_320x460.png" /> + <!-- For iOS devices set the icon to use if user bookmarks app on their homescreen --> + <link rel="apple-touch-icon" href="images/screen_57x57.png"> + <!-- + <link rel="apple-touch-icon-precomposed" href="images/screen_57x57.png" /> + --> + + + <!-- Stylesheets --> + <link rel="stylesheet" href="include/base.css" /> + <link rel="alternate stylesheet" href="include/black.css" TITLE="Black" /> + <link rel="alternate stylesheet" href="include/blue.css" TITLE="Blue" /> + + <!-- + <script type='text/javascript' + src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script> + --> + + <script src="include/vnc.js"></script> + <script src="include/ui.js"></script> + +</head> + +<body> + <div id="noVNC-control-bar"> + <!--noVNC Mobile Device only Buttons--> + <div class="noVNC-buttons-left"> + <input type="image" src="images/drag.png" + id="noVNC_view_drag_button" class="noVNC_status_button" + title="Move/Drag Viewport" + onclick="UI.setViewDrag();"> + <div id="noVNC_mobile_buttons"> + <input type="image" src="images/mouse_none.png" + id="noVNC_mouse_button0" class="noVNC_status_button" + onclick="UI.setMouseButton(1);"> + <input type="image" src="images/mouse_left.png" + id="noVNC_mouse_button1" class="noVNC_status_button" + onclick="UI.setMouseButton(2);"> + <input type="image" src="images/mouse_middle.png" + id="noVNC_mouse_button2" class="noVNC_status_button" + onclick="UI.setMouseButton(4);"> + <input type="image" src="images/mouse_right.png" + id="noVNC_mouse_button4" class="noVNC_status_button" + onclick="UI.setMouseButton(0);"> + <input type="image" src="images/keyboard.png" + id="showKeyboard" class="noVNC_status_button" + value="Keyboard" title="Show Keyboard" + onclick="UI.showKeyboard()"/> + <input type="email" + autocapitalize="off" autocorrect="off" + id="keyboardinput" class="noVNC_status_button" + onKeyDown="onKeyDown(event);" onblur="UI.keyInputBlur();"/> + </div> + </div> + + <!--noVNC Buttons--> + <div class="noVNC-buttons-right"> + <input type="image" src="images/ctrlaltdel.png" + id="sendCtrlAltDelButton" class="noVNC_status_button" + title="Send Ctrl-Alt-Del" + onclick="UI.sendCtrlAltDel();" /> + <input type="image" src="images/clipboard.png" + id="clipboardButton" class="noVNC_status_button" + title="Clipboard" + onclick="UI.toggleClipboardPanel();" /> + <input type="image" src="images/settings.png" + id="settingsButton" class="noVNC_status_button" + title="Settings" + onclick="UI.toggleSettingsPanel();" /> + <input type="image" src="images/connect.png" + id="connectButton" class="noVNC_status_button" + title="Connect" + onclick="UI.toggleConnectPanel()" /> + <input type="image" src="images/disconnect.png" + id="disconnectButton" class="noVNC_status_button" + title="Disconnect" + onclick="UI.disconnect()" /> + </div> + + <!-- Description Panel --> + <!-- Shown by default when hosted at for kanaka.github.com --> + <div id="noVNC_description" style="display:none;" class=""> + noVNC is a browser based VNC client implemented using HTML5 Canvas + and WebSockets. You will either need a VNC server with WebSockets + support (such as <a href="http://libvncserver.sourceforge.net/">libvncserver</a>) + or you will need to use + <a href="https://github.com/kanaka/websockify">websockify</a> + to bridge between your browser and VNC server. See the noVNC + <a href="https://github.com/kanaka/noVNC">README</a> + and <a href="http://kanaka.github.com/noVNC">website</a> + for more information. + <br /> + <input type="button" value="Close" + onclick="UI.toggleConnectPanel();"> + </div> + + <!-- Clipboard Panel --> + <div id="noVNC_clipboard" class="triangle-right top"> + <textarea id="noVNC_clipboard_text" rows=5 + onfocus="UI.displayBlur();" onblur="UI.displayFocus();" + onchange="UI.clipSend();"> + </textarea> + <br /> + <input id="noVNC_clipboard_clear_button" type="button" + value="Clear" onclick="UI.clipClear();"> + </div> + + <!-- Settings Panel --> + <div id="noVNC_settings" class="triangle-right top"> + <span id="noVNC_settings_menu" onmouseover="UI.displayBlur();" + onmouseout="UI.displayFocus();"> + <ul> + <li><input id="noVNC_encrypt" type="checkbox"> Encrypt</li> + <li><input id="noVNC_true_color" type="checkbox" checked> True Color</li> + <li><input id="noVNC_cursor" type="checkbox"> Local Cursor</li> + <li><input id="noVNC_clip" type="checkbox"> Clip to Window</li> + <li><input id="noVNC_shared" type="checkbox"> Shared Mode</li> + <li><input id="noVNC_view_only" type="checkbox"> View Only</li> + <li><input id="noVNC_connectTimeout" type="input"> Connect Timeout (s)</li> + <li><input id="noVNC_path" type="input" value="websockify"> Path</li> + <hr> + <!-- Stylesheet selection dropdown --> + <li><label><strong>Style: </strong> + <select id="noVNC_stylesheet" name="vncStyle"> + <option value="default">default</option> + </select></label> + </li> + + <!-- Logging selection dropdown --> + <li><label><strong>Logging: </strong> + <select id="noVNC_logging" name="vncLogging"> + </select></label> + </li> + <hr> + <li><input type="button" id="noVNC_apply" value="Apply" + onclick="UI.settingsApply()"></li> + </ul> + </span> + </div> + + <!-- Connection Panel --> + <div id="noVNC_controls" class="triangle-right top"> + <ul> + <li><label><strong>Host: </strong><input id="noVNC_host" /></label></li> + <li><label><strong>Port: </strong><input id="noVNC_port" /></label></li> + <li><label><strong>Password: </strong><input id="noVNC_password" type="password" /></label></li> + <li><input id="noVNC_connect_button" type="button" value="Connect" onclick="UI.connect();"></li> + </ul> + </div> + + </div> <!-- End of noVNC-control-bar --> + + + <div id="noVNC_screen"> + <div id="noVNC_screen_pad"></div> + + <div id="noVNC_status_bar" class="noVNC_status_bar"> + <div id="noVNC_status">Loading</div> + </div> + + <h1 id="noVNC_logo"><span>no</span><br />VNC</h1> + + <!-- HTML5 Canvas --> + <div id="noVNC_container"> + <canvas id="noVNC_canvas" width="640px" height="20px"> + Canvas not supported. + </canvas> + </div> + + </div> + + <script> + window.onload = UI.load; + </script> + </body> +</html> http://git-wip-us.apache.org/repos/asf/cloudstack/blob/a0571d1e/patches/systemvm/debian/config/root/lswcp/vnc_auto.html ---------------------------------------------------------------------- diff --git a/patches/systemvm/debian/config/root/lswcp/vnc_auto.html b/patches/systemvm/debian/config/root/lswcp/vnc_auto.html new file mode 100644 index 0000000..2c0c962 --- /dev/null +++ b/patches/systemvm/debian/config/root/lswcp/vnc_auto.html @@ -0,0 +1,120 @@ +<!DOCTYPE html> +<html> + <!-- + noVNC Example: Automatically connect on page load. + Copyright (C) 2011 Joel Martin + Licensed under LGPL-3 (see LICENSE.txt) + + Connect parameters are provided in query string: + http://example.com/?host=HOST&port=PORT&encrypt=1&true_color=1 + --> + <head> + <title>noVNC</title> + <meta http-equiv="X-UA-Compatible" content="chrome=1"> + <link rel="stylesheet" href="include/base.css" title="plain"> + <link href="include/bootstrap.css" rel="stylesheet"> + <script src="include/vnc.js"></script> + <script src="include/keymap.js"></script> + <script type="text/javascript"> + window.addEventListener('load', function () { document.getElementById('noVNC_focus').focus(); } ); + </script> + </head> + + <body style="margin: 0px;"> + <div id="noVNC_screen"> + <input id="noVNC_focus" style="position:absolute; top:-1000px;"/> + <div id="noVNC_status_bar" class="noVNC_status_bar" style="margin-top: 0px;"> + <div id="noVNC_buttons"> + <a href="#" onClick="return false;" class="btn" id="clipboardButton">Clipboard</a> + <a href="#" onClick="return false;" class="btn" id="sendCtrlAltDelButton">CtrlAltDel</a> + </div> + <div id="noVNC_status">Loading</div> + </div> + <canvas id="noVNC_canvas" width="640px" height="20px"> + <p style="color:white">This browser does not fully support required functionality, please use latest Chrome, Firefox or Internet Explorer 10. </p> + </canvas> + </div> + + <script> + /*jslint white: false */ + /*global window, $, Util, RFB, */ + "use strict"; + + var rfb; + + function passwordRequired(rfb) { + var msg; + msg = '<form onsubmit="return setPassword();"'; + msg += ' style="margin-bottom: 0px">'; + msg += 'Password Required: '; + msg += '<input type=password size=10 id="password_input" class="noVNC_status">'; + msg += '<\/form>'; + $D('noVNC_status_bar').setAttribute("class", "noVNC_status_warn"); + $D('noVNC_status').innerHTML = msg; + } + function setPassword() { + rfb.sendPassword($D('password_input').value); + return false; + } + function updateState(rfb, state, oldstate, msg) { //rfb, state, oldstate, msg + var s, sb, cad, level, clss; + s = $D('noVNC_status'); + sb = $D('noVNC_status_bar'); + cad = $D('sendCtrlAltDelButton'); + switch (state) { + case 'failed': level = "error"; break; + case 'fatal': level = "error"; break; + case 'normal': level = "normal"; break; + case 'disconnected': level = "warn"; break; + case 'loaded': level = "normal"; break; + default: level = "warn"; break; + } + + if (state === "normal") { cad.disabled = false; } + else { cad.disabled = true; } + + if (typeof(msg) !== 'undefined') { + s.setAttribute("class", "noVNC_status_" + level); + s.innerHTML ='<span></span>'+ msg; + } + } + + window.onload = function () { + var host, port, password, path; + + $D('sendCtrlAltDelButton').style.display = "inline"; + $D('sendCtrlAltDelButton').onclick = function() { + if (confirm("Are you sure?")) rfb.sendCtrlAltDel(); + }; + + $D('clipboardButton').style.display = "inline"; + $D('clipboardButton').onclick = function() { + rfb.sendString(prompt("Enter text to paste:")); + }; + + document.title = unescape(WebUtil.getQueryVar('title', 'noVNC')); + host = WebUtil.getQueryVar('host', null); + port = WebUtil.getQueryVar('port', null); + password = WebUtil.getQueryVar('password', ''); + path = WebUtil.getQueryVar('path', 'websockify'); + if ((!host) || (!port)) { + updateState('failed', + "Must specify host and port in URL"); + return; + } + + rfb = new RFB({'target': $D('noVNC_canvas'), + 'encrypt': WebUtil.getQueryVar('encrypt', + (window.location.protocol === "https:")), + 'true_color': WebUtil.getQueryVar('true_color', true), + 'local_cursor': WebUtil.getQueryVar('cursor', true), + 'shared': WebUtil.getQueryVar('shared', true), + 'view_only': WebUtil.getQueryVar('view_only', false), + 'updateState': updateState, + 'onPasswordRequired': passwordRequired}); + rfb.connect(host, port, password, path); + }; + </script> + + </body> +</html> http://git-wip-us.apache.org/repos/asf/cloudstack/blob/a0571d1e/scripts/vm/systemvm/injectkeys.sh ---------------------------------------------------------------------- diff --git a/scripts/vm/systemvm/injectkeys.sh b/scripts/vm/systemvm/injectkeys.sh index 9f94678..ff58ef7 100755 --- a/scripts/vm/systemvm/injectkeys.sh +++ b/scripts/vm/systemvm/injectkeys.sh @@ -24,7 +24,7 @@ #set -x TMP=/tmp -MOUNTPATH=${HOME}/systemvm_mnt +MOUNTPATH=${TMP}/systemvm_mnt TMPDIR=${TMP}/cloud/systemvm
