6 |
6 |
Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
|
7 |
7 |
|
8 |
8 |
Supports following protocol versions:
|
9 |
|
- http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75
|
10 |
|
- http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
|
|
9 |
- http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-07
|
11 |
10 |
- http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10
|
|
11 |
- http://tools.ietf.org/html/rfc6455
|
12 |
12 |
|
13 |
13 |
You can make a cert/key with openssl using:
|
14 |
14 |
openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem
|
... | ... | |
16 |
16 |
|
17 |
17 |
'''
|
18 |
18 |
|
19 |
|
import os, sys, time, errno, signal, socket, traceback, select
|
|
19 |
import os, sys, time, errno, signal, socket, select, logging
|
20 |
20 |
import array, struct
|
21 |
21 |
from base64 import b64encode, b64decode
|
22 |
22 |
|
... | ... | |
37 |
37 |
except: from SimpleHTTPServer import SimpleHTTPRequestHandler
|
38 |
38 |
|
39 |
39 |
# python 2.6 differences
|
40 |
|
try: from hashlib import md5, sha1
|
41 |
|
except: from md5 import md5; from sha import sha as sha1
|
|
40 |
try: from hashlib import sha1
|
|
41 |
except: from sha import sha as sha1
|
42 |
42 |
|
43 |
43 |
# python 2.5 differences
|
44 |
44 |
try:
|
... | ... | |
50 |
50 |
return struct.unpack(fmt, slice)
|
51 |
51 |
|
52 |
52 |
# Degraded functionality if these imports are missing
|
53 |
|
for mod, sup in [('numpy', 'HyBi protocol'), ('ssl', 'TLS/SSL/wss'),
|
54 |
|
('multiprocessing', 'Multi-Processing'),
|
55 |
|
('resource', 'daemonizing')]:
|
|
53 |
for mod, msg in [('numpy', 'HyBi protocol will be slower'),
|
|
54 |
('ssl', 'TLS/SSL/wss is disabled'),
|
|
55 |
('multiprocessing', 'Multi-Processing is disabled'),
|
|
56 |
('resource', 'daemonizing is disabled')]:
|
56 |
57 |
try:
|
57 |
58 |
globals()[mod] = __import__(mod)
|
58 |
59 |
except ImportError:
|
59 |
60 |
globals()[mod] = None
|
60 |
|
print("WARNING: no '%s' module, %s is slower or disabled" % (
|
61 |
|
mod, sup))
|
|
61 |
print("WARNING: no '%s' module, %s" % (mod, msg))
|
|
62 |
|
62 |
63 |
if multiprocessing and sys.platform == 'win32':
|
63 |
64 |
# make sockets pickle-able/inheritable
|
64 |
65 |
import multiprocessing.reduction
|
65 |
66 |
|
66 |
67 |
|
67 |
|
class WebSocketServer(object):
|
|
68 |
# HTTP handler with WebSocket upgrade support
|
|
69 |
class WebSocketRequestHandler(SimpleHTTPRequestHandler):
|
68 |
70 |
"""
|
69 |
|
WebSockets server class.
|
70 |
|
Must be sub-classed with new_client method definition.
|
|
71 |
WebSocket Request Handler Class, derived from SimpleHTTPRequestHandler.
|
|
72 |
Must be sub-classed with new_websocket_client method definition.
|
|
73 |
The request handler can be configured by setting optional
|
|
74 |
attributes on the server object:
|
|
75 |
|
|
76 |
* only_upgrade: If true, SimpleHTTPRequestHandler will not be enabled,
|
|
77 |
only websocket is allowed.
|
|
78 |
* verbose: If true, verbose logging is activated.
|
|
79 |
* daemon: Running as daemon, do not write to console etc
|
|
80 |
* record: Record raw frame data as JavaScript array into specified filename
|
|
81 |
* run_once: Handle a single request
|
|
82 |
* handler_id: A sequence number for this connection, appended to record filename
|
71 |
83 |
"""
|
72 |
|
|
73 |
84 |
buffer_size = 65536
|
74 |
85 |
|
75 |
|
|
76 |
|
server_handshake_hixie = """HTTP/1.1 101 Web Socket Protocol Handshake\r
|
77 |
|
Upgrade: WebSocket\r
|
78 |
|
Connection: Upgrade\r
|
79 |
|
%sWebSocket-Origin: %s\r
|
80 |
|
%sWebSocket-Location: %s://%s%s\r
|
81 |
|
"""
|
82 |
|
|
83 |
|
server_handshake_hybi = """HTTP/1.1 101 Switching Protocols\r
|
84 |
|
Upgrade: websocket\r
|
85 |
|
Connection: Upgrade\r
|
86 |
|
Sec-WebSocket-Accept: %s\r
|
87 |
|
"""
|
88 |
|
|
89 |
86 |
GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
90 |
87 |
|
91 |
|
policy_response = """<cross-domain-policy><allow-access-from domain="*" to-ports="*" /></cross-domain-policy>\n"""
|
|
88 |
server_version = "WebSockify"
|
92 |
89 |
|
93 |
|
# An exception before the WebSocket connection was established
|
94 |
|
class EClose(Exception):
|
95 |
|
pass
|
|
90 |
protocol_version = "HTTP/1.1"
|
96 |
91 |
|
97 |
92 |
# An exception while the WebSocket client was connected
|
98 |
93 |
class CClose(Exception):
|
99 |
94 |
pass
|
100 |
95 |
|
101 |
|
def __init__(self, listen_host='', listen_port=None, source_is_ipv6=False,
|
102 |
|
verbose=False, cert='', key='', ssl_only=None,
|
103 |
|
daemon=False, record='', web='',
|
104 |
|
run_once=False, timeout=0, idle_timeout=0):
|
105 |
|
|
106 |
|
# settings
|
107 |
|
self.verbose = verbose
|
108 |
|
self.listen_host = listen_host
|
109 |
|
self.listen_port = listen_port
|
110 |
|
self.prefer_ipv6 = source_is_ipv6
|
111 |
|
self.ssl_only = ssl_only
|
112 |
|
self.daemon = daemon
|
113 |
|
self.run_once = run_once
|
114 |
|
self.timeout = timeout
|
115 |
|
self.idle_timeout = idle_timeout
|
116 |
|
|
117 |
|
self.launch_time = time.time()
|
118 |
|
self.ws_connection = False
|
119 |
|
self.handler_id = 1
|
120 |
|
|
121 |
|
# Make paths settings absolute
|
122 |
|
self.cert = os.path.abspath(cert)
|
123 |
|
self.key = self.web = self.record = ''
|
124 |
|
if key:
|
125 |
|
self.key = os.path.abspath(key)
|
126 |
|
if web:
|
127 |
|
self.web = os.path.abspath(web)
|
128 |
|
if record:
|
129 |
|
self.record = os.path.abspath(record)
|
130 |
|
|
131 |
|
if self.web:
|
132 |
|
os.chdir(self.web)
|
133 |
|
|
134 |
|
# Sanity checks
|
135 |
|
if not ssl and self.ssl_only:
|
136 |
|
raise Exception("No 'ssl' module and SSL-only specified")
|
137 |
|
if self.daemon and not resource:
|
138 |
|
raise Exception("Module 'resource' required to daemonize")
|
139 |
|
|
140 |
|
# Show configuration
|
141 |
|
print("WebSocket server settings:")
|
142 |
|
print(" - Listen on %s:%s" % (
|
143 |
|
self.listen_host, self.listen_port))
|
144 |
|
print(" - Flash security policy server")
|
145 |
|
if self.web:
|
146 |
|
print(" - Web server. Web root: %s" % self.web)
|
147 |
|
if ssl:
|
148 |
|
if os.path.exists(self.cert):
|
149 |
|
print(" - SSL/TLS support")
|
150 |
|
if self.ssl_only:
|
151 |
|
print(" - Deny non-SSL/TLS connections")
|
152 |
|
else:
|
153 |
|
print(" - No SSL/TLS support (no cert file)")
|
154 |
|
else:
|
155 |
|
print(" - No SSL/TLS support (no 'ssl' module)")
|
156 |
|
if self.daemon:
|
157 |
|
print(" - Backgrounding (daemon)")
|
158 |
|
if self.record:
|
159 |
|
print(" - Recording to '%s.*'" % self.record)
|
160 |
|
|
161 |
|
#
|
162 |
|
# WebSocketServer static methods
|
163 |
|
#
|
164 |
|
|
165 |
|
@staticmethod
|
166 |
|
def socket(host, port=None, connect=False, prefer_ipv6=False, unix_socket=None, use_ssl=False):
|
167 |
|
""" Resolve a host (and optional port) to an IPv4 or IPv6
|
168 |
|
address. Create a socket. Bind to it if listen is set,
|
169 |
|
otherwise connect to it. Return the socket.
|
170 |
|
"""
|
171 |
|
flags = 0
|
172 |
|
if host == '':
|
173 |
|
host = None
|
174 |
|
if connect and not (port or unix_socket):
|
175 |
|
raise Exception("Connect mode requires a port")
|
176 |
|
if use_ssl and not ssl:
|
177 |
|
raise Exception("SSL socket requested but Python SSL module not loaded.");
|
178 |
|
if not connect and use_ssl:
|
179 |
|
raise Exception("SSL only supported in connect mode (for now)")
|
180 |
|
if not connect:
|
181 |
|
flags = flags | socket.AI_PASSIVE
|
182 |
|
|
183 |
|
if not unix_socket:
|
184 |
|
addrs = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM,
|
185 |
|
socket.IPPROTO_TCP, flags)
|
186 |
|
if not addrs:
|
187 |
|
raise Exception("Could not resolve host '%s'" % host)
|
188 |
|
addrs.sort(key=lambda x: x[0])
|
189 |
|
if prefer_ipv6:
|
190 |
|
addrs.reverse()
|
191 |
|
sock = socket.socket(addrs[0][0], addrs[0][1])
|
192 |
|
if connect:
|
193 |
|
sock.connect(addrs[0][4])
|
194 |
|
if use_ssl:
|
195 |
|
sock = ssl.wrap_socket(sock)
|
196 |
|
else:
|
197 |
|
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
198 |
|
sock.bind(addrs[0][4])
|
199 |
|
sock.listen(100)
|
200 |
|
else:
|
201 |
|
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
202 |
|
sock.connect(unix_socket)
|
203 |
|
|
204 |
|
return sock
|
205 |
|
|
206 |
|
@staticmethod
|
207 |
|
def daemonize(keepfd=None, chdir='/'):
|
208 |
|
os.umask(0)
|
209 |
|
if chdir:
|
210 |
|
os.chdir(chdir)
|
211 |
|
else:
|
212 |
|
os.chdir('/')
|
213 |
|
os.setgid(os.getgid()) # relinquish elevations
|
214 |
|
os.setuid(os.getuid()) # relinquish elevations
|
215 |
|
|
216 |
|
# Double fork to daemonize
|
217 |
|
if os.fork() > 0: os._exit(0) # Parent exits
|
218 |
|
os.setsid() # Obtain new process group
|
219 |
|
if os.fork() > 0: os._exit(0) # Parent exits
|
220 |
|
|
221 |
|
# Signal handling
|
222 |
|
def terminate(a,b): os._exit(0)
|
223 |
|
signal.signal(signal.SIGTERM, terminate)
|
224 |
|
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
|
96 |
def __init__(self, req, addr, server):
|
|
97 |
# Retrieve a few configuration variables from the server
|
|
98 |
self.only_upgrade = getattr(server, "only_upgrade", False)
|
|
99 |
self.verbose = getattr(server, "verbose", False)
|
|
100 |
self.daemon = getattr(server, "daemon", False)
|
|
101 |
self.record = getattr(server, "record", False)
|
|
102 |
self.run_once = getattr(server, "run_once", False)
|
|
103 |
self.rec = None
|
|
104 |
self.handler_id = getattr(server, "handler_id", False)
|
|
105 |
self.file_only = getattr(server, "file_only", False)
|
|
106 |
self.traffic = getattr(server, "traffic", False)
|
225 |
107 |
|
226 |
|
# Close open files
|
227 |
|
maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
|
228 |
|
if maxfd == resource.RLIM_INFINITY: maxfd = 256
|
229 |
|
for fd in reversed(range(maxfd)):
|
230 |
|
try:
|
231 |
|
if fd != keepfd:
|
232 |
|
os.close(fd)
|
233 |
|
except OSError:
|
234 |
|
_, exc, _ = sys.exc_info()
|
235 |
|
if exc.errno != errno.EBADF: raise
|
|
108 |
self.logger = getattr(server, "logger", None)
|
|
109 |
if self.logger is None:
|
|
110 |
self.logger = WebSocketServer.get_logger()
|
236 |
111 |
|
237 |
|
# Redirect I/O to /dev/null
|
238 |
|
os.dup2(os.open(os.devnull, os.O_RDWR), sys.stdin.fileno())
|
239 |
|
os.dup2(os.open(os.devnull, os.O_RDWR), sys.stdout.fileno())
|
240 |
|
os.dup2(os.open(os.devnull, os.O_RDWR), sys.stderr.fileno())
|
|
112 |
SimpleHTTPRequestHandler.__init__(self, req, addr, server)
|
241 |
113 |
|
242 |
114 |
@staticmethod
|
243 |
115 |
def unmask(buf, hlen, plen):
|
... | ... | |
254 |
126 |
b = numpy.bitwise_xor(data, mask).tostring()
|
255 |
127 |
|
256 |
128 |
if plen % 4:
|
257 |
|
#print("Partial unmask")
|
|
129 |
#self.msg("Partial unmask")
|
258 |
130 |
mask = numpy.frombuffer(buf, dtype=numpy.dtype('B'),
|
259 |
131 |
offset=hlen, count=(plen % 4))
|
260 |
132 |
data = numpy.frombuffer(buf, dtype=numpy.dtype('B'),
|
... | ... | |
295 |
167 |
elif payload_len >= 65536:
|
296 |
168 |
header = pack('>BBQ', b1, 127, payload_len)
|
297 |
169 |
|
298 |
|
#print("Encoded: %s" % repr(header + buf))
|
|
170 |
#self.msg("Encoded: %s", repr(header + buf))
|
299 |
171 |
|
300 |
172 |
return header + buf, len(header), 0
|
301 |
173 |
|
302 |
174 |
@staticmethod
|
303 |
|
def decode_hybi(buf, base64=False):
|
|
175 |
def decode_hybi(buf, base64=False, logger=None):
|
304 |
176 |
""" Decode HyBi style WebSocket packets.
|
305 |
177 |
Returns:
|
306 |
178 |
{'fin' : 0_or_1,
|
... | ... | |
324 |
196 |
'close_code' : 1000,
|
325 |
197 |
'close_reason' : ''}
|
326 |
198 |
|
|
199 |
if logger is None:
|
|
200 |
logger = WebSocketServer.get_logger()
|
|
201 |
|
327 |
202 |
blen = len(buf)
|
328 |
203 |
f['left'] = blen
|
329 |
204 |
|
... | ... | |
359 |
234 |
# Process 1 frame
|
360 |
235 |
if f['masked']:
|
361 |
236 |
# unmask payload
|
362 |
|
f['payload'] = WebSocketServer.unmask(buf, f['hlen'],
|
|
237 |
f['payload'] = WebSocketRequestHandler.unmask(buf, f['hlen'],
|
363 |
238 |
f['length'])
|
364 |
239 |
else:
|
365 |
|
print("Unmasked frame: %s" % repr(buf))
|
|
240 |
logger.debug("Unmasked frame: %s" % repr(buf))
|
366 |
241 |
f['payload'] = buf[(f['hlen'] + f['masked'] * 4):full_len]
|
367 |
242 |
|
368 |
243 |
if base64 and f['opcode'] in [1, 2]:
|
369 |
244 |
try:
|
370 |
245 |
f['payload'] = b64decode(f['payload'])
|
371 |
246 |
except:
|
372 |
|
print("Exception while b64decoding buffer: %s" %
|
373 |
|
repr(buf))
|
|
247 |
logger.exception("Exception while b64decoding buffer: %s" %
|
|
248 |
(repr(buf)))
|
374 |
249 |
raise
|
375 |
250 |
|
376 |
251 |
if f['opcode'] == 0x08:
|
... | ... | |
381 |
256 |
|
382 |
257 |
return f
|
383 |
258 |
|
384 |
|
@staticmethod
|
385 |
|
def encode_hixie(buf):
|
386 |
|
return s2b("\x00" + b2s(b64encode(buf)) + "\xff"), 1, 1
|
387 |
|
|
388 |
|
@staticmethod
|
389 |
|
def decode_hixie(buf):
|
390 |
|
end = buf.find(s2b('\xff'))
|
391 |
|
return {'payload': b64decode(buf[1:end]),
|
392 |
|
'hlen': 1,
|
393 |
|
'masked': False,
|
394 |
|
'length': end - 1,
|
395 |
|
'left': len(buf) - (end + 1)}
|
396 |
|
|
397 |
|
|
398 |
|
@staticmethod
|
399 |
|
def gen_md5(keys):
|
400 |
|
""" Generate hash value for WebSockets hixie-76. """
|
401 |
|
key1 = keys['Sec-WebSocket-Key1']
|
402 |
|
key2 = keys['Sec-WebSocket-Key2']
|
403 |
|
key3 = keys['key3']
|
404 |
|
spaces1 = key1.count(" ")
|
405 |
|
spaces2 = key2.count(" ")
|
406 |
|
num1 = int("".join([c for c in key1 if c.isdigit()])) / spaces1
|
407 |
|
num2 = int("".join([c for c in key2 if c.isdigit()])) / spaces2
|
408 |
|
|
409 |
|
return b2s(md5(pack('>II8s',
|
410 |
|
int(num1), int(num2), key3)).digest())
|
411 |
259 |
|
412 |
260 |
#
|
413 |
|
# WebSocketServer logging/output functions
|
|
261 |
# WebSocketRequestHandler logging/output functions
|
414 |
262 |
#
|
415 |
263 |
|
416 |
|
def traffic(self, token="."):
|
417 |
|
""" Show traffic flow in verbose mode. """
|
418 |
|
if self.verbose and not self.daemon:
|
|
264 |
def print_traffic(self, token="."):
|
|
265 |
""" Show traffic flow mode. """
|
|
266 |
if self.traffic:
|
419 |
267 |
sys.stdout.write(token)
|
420 |
268 |
sys.stdout.flush()
|
421 |
269 |
|
422 |
|
def msg(self, msg):
|
|
270 |
def msg(self, msg, *args, **kwargs):
|
423 |
271 |
""" Output message with handler_id prefix. """
|
424 |
|
if not self.daemon:
|
425 |
|
print("% 3d: %s" % (self.handler_id, msg))
|
|
272 |
prefix = "% 3d: " % self.handler_id
|
|
273 |
self.logger.log(logging.INFO, "%s%s" % (prefix, msg), *args, **kwargs)
|
426 |
274 |
|
427 |
|
def vmsg(self, msg):
|
428 |
|
""" Same as msg() but only if verbose. """
|
429 |
|
if self.verbose:
|
430 |
|
self.msg(msg)
|
|
275 |
def vmsg(self, msg, *args, **kwargs):
|
|
276 |
""" Same as msg() but as debug. """
|
|
277 |
prefix = "% 3d: " % self.handler_id
|
|
278 |
self.logger.log(logging.DEBUG, "%s%s" % (prefix, msg), *args, **kwargs)
|
|
279 |
|
|
280 |
def warn(self, msg, *args, **kwargs):
|
|
281 |
""" Same as msg() but as warning. """
|
|
282 |
prefix = "% 3d: " % self.handler_id
|
|
283 |
self.logger.log(logging.WARN, "%s%s" % (prefix, msg), *args, **kwargs)
|
431 |
284 |
|
432 |
285 |
#
|
433 |
|
# Main WebSocketServer methods
|
|
286 |
# Main WebSocketRequestHandler methods
|
434 |
287 |
#
|
435 |
288 |
def send_frames(self, bufs=None):
|
436 |
289 |
""" Encode and send WebSocket frames. Any frames already
|
... | ... | |
444 |
297 |
|
445 |
298 |
if bufs:
|
446 |
299 |
for buf in bufs:
|
447 |
|
if self.version.startswith("hybi"):
|
448 |
|
if self.base64:
|
449 |
|
encbuf, lenhead, lentail = self.encode_hybi(
|
450 |
|
buf, opcode=1, base64=True)
|
451 |
|
else:
|
452 |
|
encbuf, lenhead, lentail = self.encode_hybi(
|
453 |
|
buf, opcode=2, base64=False)
|
454 |
|
|
|
300 |
if self.base64:
|
|
301 |
encbuf, lenhead, lentail = self.encode_hybi(buf, opcode=1, base64=True)
|
455 |
302 |
else:
|
456 |
|
encbuf, lenhead, lentail = self.encode_hixie(buf)
|
|
303 |
encbuf, lenhead, lentail = self.encode_hybi(buf, opcode=2, base64=False)
|
457 |
304 |
|
458 |
305 |
if self.rec:
|
459 |
306 |
self.rec.write("%s,\n" %
|
... | ... | |
465 |
312 |
while self.send_parts:
|
466 |
313 |
# Send pending frames
|
467 |
314 |
buf = self.send_parts.pop(0)
|
468 |
|
sent = self.client.send(buf)
|
|
315 |
sent = self.request.send(buf)
|
469 |
316 |
|
470 |
317 |
if sent == len(buf):
|
471 |
|
self.traffic("<")
|
|
318 |
self.print_traffic("<")
|
472 |
319 |
else:
|
473 |
|
self.traffic("<.")
|
|
320 |
self.print_traffic("<.")
|
474 |
321 |
self.send_parts.insert(0, buf[sent:])
|
475 |
322 |
break
|
476 |
323 |
|
... | ... | |
487 |
334 |
bufs = []
|
488 |
335 |
tdelta = int(time.time()*1000) - self.start_time
|
489 |
336 |
|
490 |
|
buf = self.client.recv(self.buffer_size)
|
|
337 |
buf = self.request.recv(self.buffer_size)
|
491 |
338 |
if len(buf) == 0:
|
492 |
339 |
closed = {'code': 1000, 'reason': "Client closed abruptly"}
|
493 |
340 |
return bufs, closed
|
... | ... | |
498 |
345 |
self.recv_part = None
|
499 |
346 |
|
500 |
347 |
while buf:
|
501 |
|
if self.version.startswith("hybi"):
|
502 |
|
|
503 |
|
frame = self.decode_hybi(buf, base64=self.base64)
|
504 |
|
#print("Received buf: %s, frame: %s" % (repr(buf), frame))
|
505 |
|
|
506 |
|
if frame['payload'] == None:
|
507 |
|
# Incomplete/partial frame
|
508 |
|
self.traffic("}.")
|
509 |
|
if frame['left'] > 0:
|
510 |
|
self.recv_part = buf[-frame['left']:]
|
511 |
|
break
|
512 |
|
else:
|
513 |
|
if frame['opcode'] == 0x8: # connection close
|
514 |
|
closed = {'code': frame['close_code'],
|
515 |
|
'reason': frame['close_reason']}
|
516 |
|
break
|
517 |
|
|
|
348 |
frame = self.decode_hybi(buf, base64=self.base64,
|
|
349 |
logger=self.logger)
|
|
350 |
#self.msg("Received buf: %s, frame: %s", repr(buf), frame)
|
|
351 |
|
|
352 |
if frame['payload'] == None:
|
|
353 |
# Incomplete/partial frame
|
|
354 |
self.print_traffic("}.")
|
|
355 |
if frame['left'] > 0:
|
|
356 |
self.recv_part = buf[-frame['left']:]
|
|
357 |
break
|
518 |
358 |
else:
|
519 |
|
if buf[0:2] == s2b('\xff\x00'):
|
520 |
|
closed = {'code': 1000,
|
521 |
|
'reason': "Client sent orderly close frame"}
|
|
359 |
if frame['opcode'] == 0x8: # connection close
|
|
360 |
closed = {'code': frame['close_code'],
|
|
361 |
'reason': frame['close_reason']}
|
522 |
362 |
break
|
523 |
363 |
|
524 |
|
elif buf[0:2] == s2b('\x00\xff'):
|
525 |
|
buf = buf[2:]
|
526 |
|
continue # No-op
|
527 |
|
|
528 |
|
elif buf.count(s2b('\xff')) == 0:
|
529 |
|
# Partial frame
|
530 |
|
self.traffic("}.")
|
531 |
|
self.recv_part = buf
|
532 |
|
break
|
533 |
|
|
534 |
|
frame = self.decode_hixie(buf)
|
535 |
|
|
536 |
|
self.traffic("}")
|
|
364 |
self.print_traffic("}")
|
537 |
365 |
|
538 |
366 |
if self.rec:
|
539 |
367 |
start = frame['hlen']
|
540 |
368 |
end = frame['hlen'] + frame['length']
|
541 |
369 |
if frame['masked']:
|
542 |
|
recbuf = WebSocketServer.unmask(buf, frame['hlen'],
|
|
370 |
recbuf = WebSocketRequestHandler.unmask(buf, frame['hlen'],
|
543 |
371 |
frame['length'])
|
544 |
372 |
else:
|
545 |
373 |
recbuf = buf[frame['hlen']:frame['hlen'] +
|
... | ... | |
560 |
388 |
def send_close(self, code=1000, reason=''):
|
561 |
389 |
""" Send a WebSocket orderly close frame. """
|
562 |
390 |
|
563 |
|
if self.version.startswith("hybi"):
|
564 |
|
msg = pack(">H%ds" % len(reason), code, reason)
|
565 |
|
|
566 |
|
buf, h, t = self.encode_hybi(msg, opcode=0x08, base64=False)
|
567 |
|
self.client.send(buf)
|
568 |
|
|
569 |
|
elif self.version == "hixie-76":
|
570 |
|
buf = s2b('\xff\x00')
|
571 |
|
self.client.send(buf)
|
572 |
|
|
573 |
|
# No orderly close for 75
|
|
391 |
msg = pack(">H%ds" % len(reason), code, reason)
|
|
392 |
buf, h, t = self.encode_hybi(msg, opcode=0x08, base64=False)
|
|
393 |
self.request.send(buf)
|
574 |
394 |
|
575 |
|
def do_websocket_handshake(self, headers, path):
|
576 |
|
h = self.headers = headers
|
577 |
|
self.path = path
|
|
395 |
def do_websocket_handshake(self):
|
|
396 |
h = self.headers
|
578 |
397 |
|
579 |
398 |
prot = 'WebSocket-Protocol'
|
580 |
399 |
protocols = h.get('Sec-'+prot, h.get(prot, '')).split(',')
|
... | ... | |
589 |
408 |
if ver in ['7', '8', '13']:
|
590 |
409 |
self.version = "hybi-%02d" % int(ver)
|
591 |
410 |
else:
|
592 |
|
raise self.EClose('Unsupported protocol version %s' % ver)
|
|
411 |
self.send_error(400, "Unsupported protocol version %s" % ver)
|
|
412 |
return False
|
593 |
413 |
|
594 |
414 |
key = h['Sec-WebSocket-Key']
|
595 |
415 |
|
... | ... | |
599 |
419 |
elif 'base64' in protocols:
|
600 |
420 |
self.base64 = True
|
601 |
421 |
else:
|
602 |
|
raise self.EClose("Client must support 'binary' or 'base64' protocol")
|
|
422 |
self.send_error(400, "Client must support 'binary' or 'base64' protocol")
|
|
423 |
return False
|
603 |
424 |
|
604 |
425 |
# Generate the hash value for the accept header
|
605 |
426 |
accept = b64encode(sha1(s2b(key + self.GUID)).digest())
|
606 |
427 |
|
607 |
|
response = self.server_handshake_hybi % b2s(accept)
|
|
428 |
self.send_response(101, "Switching Protocols")
|
|
429 |
self.send_header("Upgrade", "websocket")
|
|
430 |
self.send_header("Connection", "Upgrade")
|
|
431 |
self.send_header("Sec-WebSocket-Accept", b2s(accept))
|
608 |
432 |
if self.base64:
|
609 |
|
response += "Sec-WebSocket-Protocol: base64\r\n"
|
|
433 |
self.send_header("Sec-WebSocket-Protocol", "base64")
|
|
434 |
else:
|
|
435 |
self.send_header("Sec-WebSocket-Protocol", "binary")
|
|
436 |
self.end_headers()
|
|
437 |
return True
|
|
438 |
else:
|
|
439 |
self.send_error(400, "Missing Sec-WebSocket-Version header. Hixie protocols not supported.")
|
|
440 |
|
|
441 |
return False
|
|
442 |
|
|
443 |
def handle_websocket(self):
|
|
444 |
"""Upgrade a connection to Websocket, if requested. If this succeeds,
|
|
445 |
new_websocket_client() will be called. Otherwise, False is returned.
|
|
446 |
"""
|
|
447 |
if (self.headers.get('upgrade') and
|
|
448 |
self.headers.get('upgrade').lower() == 'websocket'):
|
|
449 |
|
|
450 |
if not self.do_websocket_handshake():
|
|
451 |
return False
|
|
452 |
|
|
453 |
# Indicate to server that a Websocket upgrade was done
|
|
454 |
self.server.ws_connection = True
|
|
455 |
# Initialize per client settings
|
|
456 |
self.send_parts = []
|
|
457 |
self.recv_part = None
|
|
458 |
self.start_time = int(time.time()*1000)
|
|
459 |
|
|
460 |
# client_address is empty with, say, UNIX domain sockets
|
|
461 |
client_addr = ""
|
|
462 |
is_ssl = False
|
|
463 |
try:
|
|
464 |
client_addr = self.client_address[0]
|
|
465 |
is_ssl = self.client_address[2]
|
|
466 |
except IndexError:
|
|
467 |
pass
|
|
468 |
|
|
469 |
if is_ssl:
|
|
470 |
self.stype = "SSL/TLS (wss://)"
|
610 |
471 |
else:
|
611 |
|
response += "Sec-WebSocket-Protocol: binary\r\n"
|
612 |
|
response += "\r\n"
|
|
472 |
self.stype = "Plain non-SSL (ws://)"
|
|
473 |
|
|
474 |
self.log_message("%s: %s WebSocket connection", client_addr,
|
|
475 |
self.stype)
|
|
476 |
self.log_message("%s: Version %s, base64: '%s'", client_addr,
|
|
477 |
self.version, self.base64)
|
|
478 |
if self.path != '/':
|
|
479 |
self.log_message("%s: Path: '%s'", client_addr, self.path)
|
|
480 |
|
|
481 |
if self.record:
|
|
482 |
# Record raw frame data as JavaScript array
|
|
483 |
fname = "%s.%s" % (self.record,
|
|
484 |
self.handler_id)
|
|
485 |
self.log_message("opening record file: %s", fname)
|
|
486 |
self.rec = open(fname, 'w+')
|
|
487 |
encoding = "binary"
|
|
488 |
if self.base64: encoding = "base64"
|
|
489 |
self.rec.write("var VNC_frame_encoding = '%s';\n"
|
|
490 |
% encoding)
|
|
491 |
self.rec.write("var VNC_frame_data = [\n")
|
613 |
492 |
|
|
493 |
try:
|
|
494 |
self.new_websocket_client()
|
|
495 |
except self.CClose:
|
|
496 |
# Close the client
|
|
497 |
_, exc, _ = sys.exc_info()
|
|
498 |
self.send_close(exc.args[0], exc.args[1])
|
|
499 |
return True
|
614 |
500 |
else:
|
615 |
|
# Hixie version of the protocol (75 or 76)
|
|
501 |
return False
|
|
502 |
|
|
503 |
def do_GET(self):
|
|
504 |
"""Handle GET request. Calls handle_websocket(). If unsuccessful,
|
|
505 |
and web server is enabled, SimpleHTTPRequestHandler.do_GET will be called."""
|
|
506 |
if not self.handle_websocket():
|
|
507 |
if self.only_upgrade:
|
|
508 |
self.send_error(405, "Method Not Allowed")
|
|
509 |
else:
|
|
510 |
SimpleHTTPRequestHandler.do_GET(self)
|
|
511 |
|
|
512 |
def list_directory(self, path):
|
|
513 |
if self.file_only:
|
|
514 |
self.send_error(404, "No such file")
|
|
515 |
else:
|
|
516 |
return SimpleHTTPRequestHandler.list_directory(self, path)
|
|
517 |
|
|
518 |
def new_websocket_client(self):
|
|
519 |
""" Do something with a WebSockets client connection. """
|
|
520 |
raise Exception("WebSocketRequestHandler.new_websocket_client() must be overloaded")
|
|
521 |
|
|
522 |
def do_HEAD(self):
|
|
523 |
if self.only_upgrade:
|
|
524 |
self.send_error(405, "Method Not Allowed")
|
|
525 |
else:
|
|
526 |
SimpleHTTPRequestHandler.do_HEAD(self)
|
|
527 |
|
|
528 |
def finish(self):
|
|
529 |
if self.rec:
|
|
530 |
self.rec.write("'EOF'];\n")
|
|
531 |
self.rec.close()
|
|
532 |
|
|
533 |
def handle(self):
|
|
534 |
# When using run_once, we have a single process, so
|
|
535 |
# we cannot loop in BaseHTTPRequestHandler.handle; we
|
|
536 |
# must return and handle new connections
|
|
537 |
if self.run_once:
|
|
538 |
self.handle_one_request()
|
|
539 |
else:
|
|
540 |
SimpleHTTPRequestHandler.handle(self)
|
|
541 |
|
|
542 |
def log_request(self, code='-', size='-'):
|
|
543 |
if self.verbose:
|
|
544 |
SimpleHTTPRequestHandler.log_request(self, code, size)
|
|
545 |
|
616 |
546 |
|
617 |
|
if h.get('key3'):
|
618 |
|
trailer = self.gen_md5(h)
|
619 |
|
pre = "Sec-"
|
620 |
|
self.version = "hixie-76"
|
|
547 |
class WebSocketServer(object):
|
|
548 |
"""
|
|
549 |
WebSockets server class.
|
|
550 |
As an alternative, the standard library SocketServer can be used
|
|
551 |
"""
|
|
552 |
|
|
553 |
policy_response = """<cross-domain-policy><allow-access-from domain="*" to-ports="*" /></cross-domain-policy>\n"""
|
|
554 |
log_prefix = "websocket"
|
|
555 |
|
|
556 |
# An exception before the WebSocket connection was established
|
|
557 |
class EClose(Exception):
|
|
558 |
pass
|
|
559 |
|
|
560 |
class Terminate(Exception):
|
|
561 |
pass
|
|
562 |
|
|
563 |
def __init__(self, RequestHandlerClass, listen_host='',
|
|
564 |
listen_port=None, source_is_ipv6=False,
|
|
565 |
verbose=False, cert='', key='', ssl_only=None,
|
|
566 |
daemon=False, record='', web='',
|
|
567 |
file_only=False,
|
|
568 |
run_once=False, timeout=0, idle_timeout=0, traffic=False,
|
|
569 |
tcp_keepalive=True, tcp_keepcnt=None, tcp_keepidle=None,
|
|
570 |
tcp_keepintvl=None):
|
|
571 |
|
|
572 |
# settings
|
|
573 |
self.RequestHandlerClass = RequestHandlerClass
|
|
574 |
self.verbose = verbose
|
|
575 |
self.listen_host = listen_host
|
|
576 |
self.listen_port = listen_port
|
|
577 |
self.prefer_ipv6 = source_is_ipv6
|
|
578 |
self.ssl_only = ssl_only
|
|
579 |
self.daemon = daemon
|
|
580 |
self.run_once = run_once
|
|
581 |
self.timeout = timeout
|
|
582 |
self.idle_timeout = idle_timeout
|
|
583 |
self.traffic = traffic
|
|
584 |
|
|
585 |
self.launch_time = time.time()
|
|
586 |
self.ws_connection = False
|
|
587 |
self.handler_id = 1
|
|
588 |
|
|
589 |
self.logger = self.get_logger()
|
|
590 |
self.tcp_keepalive = tcp_keepalive
|
|
591 |
self.tcp_keepcnt = tcp_keepcnt
|
|
592 |
self.tcp_keepidle = tcp_keepidle
|
|
593 |
self.tcp_keepintvl = tcp_keepintvl
|
|
594 |
|
|
595 |
# Make paths settings absolute
|
|
596 |
self.cert = os.path.abspath(cert)
|
|
597 |
self.key = self.web = self.record = ''
|
|
598 |
if key:
|
|
599 |
self.key = os.path.abspath(key)
|
|
600 |
if web:
|
|
601 |
self.web = os.path.abspath(web)
|
|
602 |
if record:
|
|
603 |
self.record = os.path.abspath(record)
|
|
604 |
|
|
605 |
if self.web:
|
|
606 |
os.chdir(self.web)
|
|
607 |
self.only_upgrade = not self.web
|
|
608 |
|
|
609 |
# Sanity checks
|
|
610 |
if not ssl and self.ssl_only:
|
|
611 |
raise Exception("No 'ssl' module and SSL-only specified")
|
|
612 |
if self.daemon and not resource:
|
|
613 |
raise Exception("Module 'resource' required to daemonize")
|
|
614 |
|
|
615 |
# Show configuration
|
|
616 |
self.msg("WebSocket server settings:")
|
|
617 |
self.msg(" - Listen on %s:%s",
|
|
618 |
self.listen_host, self.listen_port)
|
|
619 |
self.msg(" - Flash security policy server")
|
|
620 |
if self.web:
|
|
621 |
self.msg(" - Web server. Web root: %s", self.web)
|
|
622 |
if ssl:
|
|
623 |
if os.path.exists(self.cert):
|
|
624 |
self.msg(" - SSL/TLS support")
|
|
625 |
if self.ssl_only:
|
|
626 |
self.msg(" - Deny non-SSL/TLS connections")
|
621 |
627 |
else:
|
622 |
|
trailer = ""
|
623 |
|
pre = ""
|
624 |
|
self.version = "hixie-75"
|
|
628 |
self.msg(" - No SSL/TLS support (no cert file)")
|
|
629 |
else:
|
|
630 |
self.msg(" - No SSL/TLS support (no 'ssl' module)")
|
|
631 |
if self.daemon:
|
|
632 |
self.msg(" - Backgrounding (daemon)")
|
|
633 |
if self.record:
|
|
634 |
self.msg(" - Recording to '%s.*'", self.record)
|
625 |
635 |
|
626 |
|
# We only support base64 in Hixie era
|
627 |
|
self.base64 = True
|
|
636 |
#
|
|
637 |
# WebSocketServer static methods
|
|
638 |
#
|
628 |
639 |
|
629 |
|
response = self.server_handshake_hixie % (pre,
|
630 |
|
h['Origin'], pre, self.scheme, h['Host'], path)
|
|
640 |
@staticmethod
|
|
641 |
def get_logger():
|
|
642 |
return logging.getLogger("%s.%s" % (
|
|
643 |
WebSocketServer.log_prefix,
|
|
644 |
WebSocketServer.__class__.__name__))
|
631 |
645 |
|
632 |
|
if 'base64' in protocols:
|
633 |
|
response += "%sWebSocket-Protocol: base64\r\n" % pre
|
|
646 |
@staticmethod
|
|
647 |
def socket(host, port=None, connect=False, prefer_ipv6=False,
|
|
648 |
unix_socket=None, use_ssl=False, tcp_keepalive=True,
|
|
649 |
tcp_keepcnt=None, tcp_keepidle=None, tcp_keepintvl=None):
|
|
650 |
""" Resolve a host (and optional port) to an IPv4 or IPv6
|
|
651 |
address. Create a socket. Bind to it if listen is set,
|
|
652 |
otherwise connect to it. Return the socket.
|
|
653 |
"""
|
|
654 |
flags = 0
|
|
655 |
if host == '':
|
|
656 |
host = None
|
|
657 |
if connect and not (port or unix_socket):
|
|
658 |
raise Exception("Connect mode requires a port")
|
|
659 |
if use_ssl and not ssl:
|
|
660 |
raise Exception("SSL socket requested but Python SSL module not loaded.");
|
|
661 |
if not connect and use_ssl:
|
|
662 |
raise Exception("SSL only supported in connect mode (for now)")
|
|
663 |
if not connect:
|
|
664 |
flags = flags | socket.AI_PASSIVE
|
|
665 |
|
|
666 |
if not unix_socket:
|
|
667 |
addrs = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM,
|
|
668 |
socket.IPPROTO_TCP, flags)
|
|
669 |
if not addrs:
|
|
670 |
raise Exception("Could not resolve host '%s'" % host)
|
|
671 |
addrs.sort(key=lambda x: x[0])
|
|
672 |
if prefer_ipv6:
|
|
673 |
addrs.reverse()
|
|
674 |
sock = socket.socket(addrs[0][0], addrs[0][1])
|
|
675 |
|
|
676 |
if tcp_keepalive:
|
|
677 |
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
|
|
678 |
if tcp_keepcnt:
|
|
679 |
sock.setsockopt(socket.SOL_TCP, socket.TCP_KEEPCNT,
|
|
680 |
tcp_keepcnt)
|
|
681 |
if tcp_keepidle:
|
|
682 |
sock.setsockopt(socket.SOL_TCP, socket.TCP_KEEPIDLE,
|
|
683 |
tcp_keepidle)
|
|
684 |
if tcp_keepintvl:
|
|
685 |
sock.setsockopt(socket.SOL_TCP, socket.TCP_KEEPINTVL,
|
|
686 |
tcp_keepintvl)
|
|
687 |
|
|
688 |
if connect:
|
|
689 |
sock.connect(addrs[0][4])
|
|
690 |
if use_ssl:
|
|
691 |
sock = ssl.wrap_socket(sock)
|
634 |
692 |
else:
|
635 |
|
self.msg("Warning: client does not report 'base64' protocol support")
|
636 |
|
response += "\r\n" + trailer
|
|
693 |
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
694 |
sock.bind(addrs[0][4])
|
|
695 |
sock.listen(100)
|
|
696 |
else:
|
|
697 |
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
698 |
sock.connect(unix_socket)
|
|
699 |
|
|
700 |
return sock
|
637 |
701 |
|
638 |
|
return response
|
|
702 |
@staticmethod
|
|
703 |
def daemonize(keepfd=None, chdir='/'):
|
|
704 |
os.umask(0)
|
|
705 |
if chdir:
|
|
706 |
os.chdir(chdir)
|
|
707 |
else:
|
|
708 |
os.chdir('/')
|
|
709 |
os.setgid(os.getgid()) # relinquish elevations
|
|
710 |
os.setuid(os.getuid()) # relinquish elevations
|
639 |
711 |
|
|
712 |
# Double fork to daemonize
|
|
713 |
if os.fork() > 0: os._exit(0) # Parent exits
|
|
714 |
os.setsid() # Obtain new process group
|
|
715 |
if os.fork() > 0: os._exit(0) # Parent exits
|
|
716 |
|
|
717 |
# Signal handling
|
|
718 |
signal.signal(signal.SIGTERM, signal.SIG_IGN)
|
|
719 |
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
|
720 |
|
|
721 |
# Close open files
|
|
722 |
maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
|
|
723 |
if maxfd == resource.RLIM_INFINITY: maxfd = 256
|
|
724 |
for fd in reversed(range(maxfd)):
|
|
725 |
try:
|
|
726 |
if fd != keepfd:
|
|
727 |
os.close(fd)
|
|
728 |
except OSError:
|
|
729 |
_, exc, _ = sys.exc_info()
|
|
730 |
if exc.errno != errno.EBADF: raise
|
|
731 |
|
|
732 |
# Redirect I/O to /dev/null
|
|
733 |
os.dup2(os.open(os.devnull, os.O_RDWR), sys.stdin.fileno())
|
|
734 |
os.dup2(os.open(os.devnull, os.O_RDWR), sys.stdout.fileno())
|
|
735 |
os.dup2(os.open(os.devnull, os.O_RDWR), sys.stderr.fileno())
|
640 |
736 |
|
641 |
737 |
def do_handshake(self, sock, address):
|
642 |
738 |
"""
|
... | ... | |
655 |
751 |
- Send a WebSockets handshake server response.
|
656 |
752 |
- Return the socket for this WebSocket client.
|
657 |
753 |
"""
|
658 |
|
stype = ""
|
659 |
754 |
ready = select.select([sock], [], [], 3)[0]
|
660 |
755 |
|
661 |
756 |
|
... | ... | |
699 |
794 |
else:
|
700 |
795 |
raise
|
701 |
796 |
|
702 |
|
self.scheme = "wss"
|
703 |
|
stype = "SSL/TLS (wss://)"
|
704 |
|
|
705 |
797 |
elif self.ssl_only:
|
706 |
798 |
raise self.EClose("non-SSL connection received but disallowed")
|
707 |
799 |
|
708 |
800 |
else:
|
709 |
801 |
retsock = sock
|
710 |
|
self.scheme = "ws"
|
711 |
|
stype = "Plain non-SSL (ws://)"
|
712 |
802 |
|
713 |
|
wsh = WSRequestHandler(retsock, address, not self.web)
|
714 |
|
if wsh.last_code == 101:
|
715 |
|
# Continue on to handle WebSocket upgrade
|
716 |
|
pass
|
717 |
|
elif wsh.last_code == 405:
|
718 |
|
raise self.EClose("Normal web request received but disallowed")
|
719 |
|
elif wsh.last_code < 200 or wsh.last_code >= 300:
|
720 |
|
raise self.EClose(wsh.last_message)
|
721 |
|
elif self.verbose:
|
722 |
|
raise self.EClose(wsh.last_message)
|
723 |
|
else:
|
724 |
|
raise self.EClose("")
|
|
803 |
# If the address is like (host, port), we are extending it
|
|
804 |
# with a flag indicating SSL. Not many other options
|
|
805 |
# available...
|
|
806 |
if len(address) == 2:
|
|
807 |
address = (address[0], address[1], (retsock != sock))
|
725 |
808 |
|
726 |
|
response = self.do_websocket_handshake(wsh.headers, wsh.path)
|
|
809 |
self.RequestHandlerClass(retsock, address, self)
|
727 |
810 |
|
728 |
|
self.msg("%s: %s WebSocket connection" % (address[0], stype))
|
729 |
|
self.msg("%s: Version %s, base64: '%s'" % (address[0],
|
730 |
|
self.version, self.base64))
|
731 |
|
if self.path != '/':
|
732 |
|
self.msg("%s: Path: '%s'" % (address[0], self.path))
|
|
811 |
# Return the WebSockets socket which may be SSL wrapped
|
|
812 |
return retsock
|
733 |
813 |
|
|
814 |
#
|
|
815 |
# WebSocketServer logging/output functions
|
|
816 |
#
|
734 |
817 |
|
735 |
|
# Send server WebSockets handshake response
|
736 |
|
#self.msg("sending response [%s]" % response)
|
737 |
|
retsock.send(s2b(response))
|
|
818 |
def msg(self, *args, **kwargs):
|
|
819 |
""" Output message as info """
|
|
820 |
self.logger.log(logging.INFO, *args, **kwargs)
|
738 |
821 |
|
739 |
|
# Return the WebSockets socket which may be SSL wrapped
|
740 |
|
return retsock
|
|
822 |
def vmsg(self, *args, **kwargs):
|
|
823 |
""" Same as msg() but as debug. """
|
|
824 |
self.logger.log(logging.DEBUG, *args, **kwargs)
|
|
825 |
|
|
826 |
def warn(self, *args, **kwargs):
|
|
827 |
""" Same as msg() but as warning. """
|
|
828 |
self.logger.log(logging.WARN, *args, **kwargs)
|
741 |
829 |
|
742 |
830 |
|
743 |
831 |
#
|
... | ... | |
752 |
840 |
#self.vmsg("Running poll()")
|
753 |
841 |
pass
|
754 |
842 |
|
|
843 |
def terminate(self):
|
|
844 |
raise self.Terminate()
|
|
845 |
|
|
846 |
def multiprocessing_SIGCHLD(self, sig, stack):
|
|
847 |
self.vmsg('Reaping zombies, active child count is %s', len(multiprocessing.active_children()))
|
|
848 |
|
755 |
849 |
def fallback_SIGCHLD(self, sig, stack):
|
756 |
850 |
# Reap zombies when using os.fork() (python 2.4)
|
757 |
851 |
self.vmsg("Got SIGCHLD, reaping zombies")
|
... | ... | |
765 |
859 |
|
766 |
860 |
def do_SIGINT(self, sig, stack):
|
767 |
861 |
self.msg("Got SIGINT, exiting")
|
768 |
|
sys.exit(0)
|
|
862 |
self.terminate()
|
|
863 |
|
|
864 |
def do_SIGTERM(self, sig, stack):
|
|
865 |
self.msg("Got SIGTERM, exiting")
|
|
866 |
self.terminate()
|
769 |
867 |
|
770 |
868 |
def top_new_client(self, startsock, address):
|
771 |
869 |
""" Do something with a WebSockets client connection. """
|
772 |
|
# Initialize per client settings
|
773 |
|
self.send_parts = []
|
774 |
|
self.recv_part = None
|
775 |
|
self.base64 = False
|
776 |
|
self.rec = None
|
777 |
|
self.start_time = int(time.time()*1000)
|
778 |
|
|
779 |
870 |
# handler process
|
|
871 |
client = None
|
780 |
872 |
try:
|
781 |
873 |
try:
|
782 |
|
self.client = self.do_handshake(startsock, address)
|
783 |
|
|
784 |
|
if self.record:
|
785 |
|
# Record raw frame data as JavaScript array
|
786 |
|
fname = "%s.%s" % (self.record,
|
787 |
|
self.handler_id)
|
788 |
|
self.msg("opening record file: %s" % fname)
|
789 |
|
self.rec = open(fname, 'w+')
|
790 |
|
encoding = "binary"
|
791 |
|
if self.base64: encoding = "base64"
|
792 |
|
self.rec.write("var VNC_frame_encoding = '%s';\n"
|
793 |
|
% encoding)
|
794 |
|
self.rec.write("var VNC_frame_data = [\n")
|
795 |
|
|
796 |
|
self.ws_connection = True
|
797 |
|
self.new_client()
|
798 |
|
except self.CClose:
|
799 |
|
# Close the client
|
800 |
|
_, exc, _ = sys.exc_info()
|
801 |
|
if self.client:
|
802 |
|
self.send_close(exc.args[0], exc.args[1])
|
|
874 |
client = self.do_handshake(startsock, address)
|
803 |
875 |
except self.EClose:
|
804 |
876 |
_, exc, _ = sys.exc_info()
|
805 |
877 |
# Connection was not a WebSockets connection
|
806 |
878 |
if exc.args[0]:
|
807 |
879 |
self.msg("%s: %s" % (address[0], exc.args[0]))
|
|
880 |
except WebSocketServer.Terminate:
|
|
881 |
raise
|
808 |
882 |
except Exception:
|
809 |
883 |
_, exc, _ = sys.exc_info()
|
810 |
884 |
self.msg("handler exception: %s" % str(exc))
|
811 |
|
if self.verbose:
|
812 |
|
self.msg(traceback.format_exc())
|
|
885 |
self.vmsg("exception", exc_info=True)
|
813 |
886 |
finally:
|
814 |
|
if self.rec:
|
815 |
|
self.rec.write("'EOF'];\n")
|
816 |
|
self.rec.close()
|
817 |
887 |
|
818 |
|
if self.client and self.client != startsock:
|
|
888 |
if client and client != startsock:
|
819 |
889 |
# Close the SSL wrapped socket
|
820 |
890 |
# Original socket closed by caller
|
821 |
|
self.client.close()
|
822 |
|
|
823 |
|
def new_client(self):
|
824 |
|
""" Do something with a WebSockets client connection. """
|
825 |
|
raise("WebSocketServer.new_client() must be overloaded")
|
|
891 |
client.close()
|
826 |
892 |
|
827 |
893 |
def start_server(self):
|
828 |
894 |
"""
|
829 |
895 |
Daemonize if requested. Listen for for connections. Run
|
830 |
896 |
do_handshake() method for each connection. If the connection
|
831 |
|
is a WebSockets client then call new_client() method (which must
|
|
897 |
is a WebSockets client then call new_websocket_client() method (which must
|
832 |
898 |
be overridden) for each new client connection.
|
833 |
899 |
"""
|
834 |
|
lsock = self.socket(self.listen_host, self.listen_port, False, self.prefer_ipv6)
|
|
900 |
lsock = self.socket(self.listen_host, self.listen_port, False,
|
|
901 |
self.prefer_ipv6,
|
|
902 |
tcp_keepalive=self.tcp_keepalive,
|
|
903 |
tcp_keepcnt=self.tcp_keepcnt,
|
|
904 |
tcp_keepidle=self.tcp_keepidle,
|
|
905 |
tcp_keepintvl=self.tcp_keepintvl)
|
835 |
906 |
|
836 |
907 |
if self.daemon:
|
837 |
908 |
self.daemonize(keepfd=lsock.fileno(), chdir=self.web)
|
838 |
909 |
|
839 |
910 |
self.started() # Some things need to happen after daemonizing
|
840 |
911 |
|
841 |
|
# Allow override of SIGINT
|
|
912 |
# Allow override of signals
|
|
913 |
original_signals = {
|
|
914 |
signal.SIGINT: signal.getsignal(signal.SIGINT),
|
|
915 |
signal.SIGTERM: signal.getsignal(signal.SIGTERM),
|
|
916 |
signal.SIGCHLD: signal.getsignal(signal.SIGCHLD),
|
|
917 |
}
|
842 |
918 |
signal.signal(signal.SIGINT, self.do_SIGINT)
|
|
919 |
signal.signal(signal.SIGTERM, self.do_SIGTERM)
|
843 |
920 |
if not multiprocessing:
|
844 |
921 |
# os.fork() (python 2.4) child reaper
|
845 |
922 |
signal.signal(signal.SIGCHLD, self.fallback_SIGCHLD)
|
|
923 |
else:
|
|
924 |
# make sure that _cleanup is called when children die
|
|
925 |
# by calling active_children on SIGCHLD
|
|
926 |
signal.signal(signal.SIGCHLD, self.multiprocessing_SIGCHLD)
|
846 |
927 |
|
847 |
928 |
last_active_time = self.launch_time
|
848 |
|
while True:
|
849 |
|
try:
|
|
929 |
try:
|
|
930 |
while True:
|
850 |
931 |
try:
|
851 |
|
self.client = None
|
852 |
|
startsock = None
|
853 |
|
pid = err = 0
|
854 |
|
child_count = 0
|
855 |
|
|
856 |
|
if multiprocessing and self.idle_timeout:
|
857 |
|
child_count = len(multiprocessing.active_children())
|
858 |
|
|
859 |
|
time_elapsed = time.time() - self.launch_time
|
860 |
|
if self.timeout and time_elapsed > self.timeout:
|
861 |
|
self.msg('listener exit due to --timeout %s'
|
862 |
|
% self.timeout)
|
863 |
|
break
|
864 |
|
|
865 |
|
if self.idle_timeout:
|
866 |
|
idle_time = 0
|
867 |
|
if child_count == 0:
|
868 |
|
idle_time = time.time() - last_active_time
|
869 |
|
else:
|
870 |
|
idle_time = 0
|
871 |
|
last_active_time = time.time()
|
872 |
|
|
873 |
|
if idle_time > self.idle_timeout and child_count == 0:
|
874 |
|
self.msg('listener exit due to --idle-timeout %s'
|
875 |
|
% self.idle_timeout)
|
876 |
|
break
|
877 |
|
|
878 |
932 |
try:
|
879 |
|
self.poll()
|
|
933 |
startsock = None
|
|
934 |
pid = err = 0
|
|
935 |
child_count = 0
|
|
936 |
|
|
937 |
if multiprocessing:
|
|
938 |
# Collect zombie child processes
|
|
939 |
child_count = len(multiprocessing.active_children())
|
|
940 |
|
|
941 |
time_elapsed = time.time() - self.launch_time
|
|
942 |
if self.timeout and time_elapsed > self.timeout:
|
|
943 |
self.msg('listener exit due to --timeout %s'
|
|
944 |
% self.timeout)
|
|
945 |
break
|
880 |
946 |
|
881 |
|
ready = select.select([lsock], [], [], 1)[0]
|
882 |
|
if lsock in ready:
|
883 |
|
startsock, address = lsock.accept()
|
884 |
|
else:
|
885 |
|
continue
|
886 |
|
except Exception:
|
887 |
|
_, exc, _ = sys.exc_info()
|
888 |
|
if hasattr(exc, 'errno'):
|
889 |
|
err = exc.errno
|
890 |
|
elif hasattr(exc, 'args'):
|
891 |
|
err = exc.args[0]
|
892 |
|
else:
|
893 |
|
err = exc[0]
|
894 |
|
if err == errno.EINTR:
|
895 |
|
self.vmsg("Ignoring interrupted syscall")
|
896 |
|
continue
|
897 |
|
else:
|
|
947 |
if self.idle_timeout:
|
|
948 |
idle_time = 0
|
|
949 |
if child_count == 0:
|
|
950 |
idle_time = time.time() - last_active_time
|
|
951 |
else:
|
|
952 |
idle_time = 0
|
|
953 |
last_active_time = time.time()
|
|
954 |
|
|
955 |
if idle_time > self.idle_timeout and child_count == 0:
|
|
956 |
self.msg('listener exit due to --idle-timeout %s'
|
|
957 |
% self.idle_timeout)
|
|
958 |
break
|
|
959 |
|
|
960 |
try:
|
|
961 |
self.poll()
|
|
962 |
|
|
963 |
ready = select.select([lsock], [], [], 1)[0]
|
|
964 |
if lsock in ready:
|
|
965 |
startsock, address = lsock.accept()
|
|
966 |
else:
|
|
967 |
continue
|
|
968 |
except self.Terminate:
|
898 |
969 |
raise
|
899 |
|
|
900 |
|
if self.run_once:
|
901 |
|
# Run in same process if run_once
|
902 |
|
self.top_new_client(startsock, address)
|
903 |
|
if self.ws_connection :
|
904 |
|
self.msg('%s: exiting due to --run-once'
|
905 |
|
% address[0])
|
906 |
|
break
|
907 |
|
elif multiprocessing:
|
908 |
|
self.vmsg('%s: new handler Process' % address[0])
|
909 |
|
p = multiprocessing.Process(
|
910 |
|
target=self.top_new_client,
|
911 |
|
args=(startsock, address))
|
912 |
|
p.start()
|
913 |
|
# child will not return
|
914 |
|
else:
|
915 |
|
# python 2.4
|
916 |
|
self.vmsg('%s: forking handler' % address[0])
|
917 |
|
pid = os.fork()
|
918 |
|
if pid == 0:
|
919 |
|
# child handler process
|
|
970 |
except Exception:
|
|
971 |
_, exc, _ = sys.exc_info()
|
|
972 |
if hasattr(exc, 'errno'):
|
|
973 |
err = exc.errno
|
|
974 |
elif hasattr(exc, 'args'):
|
|
975 |
err = exc.args[0]
|
|
976 |
else:
|
|
977 |
err = exc[0]
|
|
978 |
if err == errno.EINTR:
|
|
979 |
self.vmsg("Ignoring interrupted syscall")
|
|
980 |
continue
|
|
981 |
else:
|
|
982 |
raise
|
|
983 |
|
|
984 |
if self.run_once:
|
|
985 |
# Run in same process if run_once
|
920 |
986 |
self.top_new_client(startsock, address)
|
921 |
|
break # child process exits
|
922 |
|
|
923 |
|
# parent process
|
924 |
|
self.handler_id += 1
|
925 |
|
|
926 |
|
except KeyboardInterrupt:
|
927 |
|
_, exc, _ = sys.exc_info()
|
928 |
|
print("In KeyboardInterrupt")
|
929 |
|
pass
|
930 |
|
except SystemExit:
|
931 |
|
_, exc, _ = sys.exc_info()
|
932 |
|
print("In SystemExit")
|
933 |
|
break
|
934 |
|
except Exception:
|
935 |
|
_, exc, _ = sys.exc_info()
|
936 |
|
self.msg("handler exception: %s" % str(exc))
|
937 |
|
if self.verbose:
|
938 |
|
self.msg(traceback.format_exc())
|
939 |
|
|
940 |
|
finally:
|
941 |
|
if startsock:
|
942 |
|
startsock.close()
|
943 |
|
|
|
987 |
if self.ws_connection :
|
|
988 |
self.msg('%s: exiting due to --run-once'
|
|
989 |
% address[0])
|
|
990 |
break
|
|
991 |
elif multiprocessing:
|
|
992 |
self.vmsg('%s: new handler Process' % address[0])
|
|
993 |
p = multiprocessing.Process(
|
|
994 |
target=self.top_new_client,
|
|
995 |
args=(startsock, address))
|
|
996 |
p.start()
|
|
997 |
# child will not return
|
|
998 |
else:
|
|
999 |
# python 2.4
|
|
1000 |
self.vmsg('%s: forking handler' % address[0])
|
|
1001 |
pid = os.fork()
|
|
1002 |
if pid == 0:
|
|
1003 |
# child handler process
|
|
1004 |
self.top_new_client(startsock, address)
|
|
1005 |
break # child process exits
|
|
1006 |
|
|
1007 |
# parent process
|
|
1008 |
self.handler_id += 1
|
|
1009 |
|
|
1010 |
except (self.Terminate, SystemExit, KeyboardInterrupt):
|
|
1011 |
self.msg("In exit")
|
|
1012 |
break
|
|
1013 |
except Exception:
|
|
1014 |
self.msg("handler exception: %s", str(exc))
|
|
1015 |
self.vmsg("exception", exc_info=True)
|
944 |
1016 |
|
945 |
|
# HTTP handler with WebSocket upgrade support
|
946 |
|
class WSRequestHandler(SimpleHTTPRequestHandler):
|
947 |
|
def __init__(self, req, addr, only_upgrade=False):
|
948 |
|
self.only_upgrade = only_upgrade # only allow upgrades
|
949 |
|
SimpleHTTPRequestHandler.__init__(self, req, addr, object())
|
|
1017 |
finally:
|
|
1018 |
if startsock:
|
|
1019 |
startsock.close()
|
|
1020 |
finally:
|
|
1021 |
# Close listen port
|
|
1022 |
self.vmsg("Closing socket listening at %s:%s",
|
|
1023 |
self.listen_host, self.listen_port)
|
|
1024 |
lsock.close()
|
950 |
1025 |
|
951 |
|
def do_GET(self):
|
952 |
|
if (self.headers.get('upgrade') and
|
953 |
|
self.headers.get('upgrade').lower() == 'websocket'):
|
954 |
|
|
955 |
|
if (self.headers.get('sec-websocket-key1') or
|
956 |
|
self.headers.get('websocket-key1')):
|
957 |
|
# For Hixie-76 read out the key hash
|
958 |
|
self.headers.__setitem__('key3', self.rfile.read(8))
|
959 |
|
|
960 |
|
# Just indicate that an WebSocket upgrade is needed
|
961 |
|
self.last_code = 101
|
962 |
|
self.last_message = "101 Switching Protocols"
|
963 |
|
elif self.only_upgrade:
|
964 |
|
# Normal web request responses are disabled
|
965 |
|
self.last_code = 405
|
966 |
|
self.last_message = "405 Method Not Allowed"
|
967 |
|
else:
|
968 |
|
SimpleHTTPRequestHandler.do_GET(self)
|
|
1026 |
# Restore signals
|
|
1027 |
for sig, func in original_signals.items():
|
|
1028 |
signal.signal(sig, func)
|
969 |
1029 |
|
970 |
|
def send_response(self, code, message=None):
|
971 |
|
# Save the status code
|
972 |
|
self.last_code = code
|
973 |
|
SimpleHTTPRequestHandler.send_response(self, code, message)
|
974 |
1030 |
|
975 |
|
def log_message(self, f, *args):
|
976 |
|
# Save instead of printing
|
977 |
|
self.last_message = f % args
|