Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1f132f9d84 | |||
| 4e6938e9c2 | |||
| 68b1587ada | |||
| 0b906a1c3e | |||
| 6f0e06772a | |||
| 8a13c48428 | |||
| b2a93bf709 | |||
| 9bc002dcf9 | |||
| dc979f042c | |||
| 72b629838a | |||
| d947fb6e30 | |||
| 8dacbc974c | |||
| c415521214 | |||
| 5bd6554651 | |||
| 50cc65f1e4 | |||
| 3c04cf8c45 | |||
| 62c3a59192 | |||
| e401701311 | |||
| 714aa34e4e | |||
| 05cac26257 | |||
| 487db5f7c9 | |||
| f693871c58 | |||
| b445296816 | |||
| 1e2b5c2256 | |||
| 6c1543c05b | |||
| b7684e0914 | |||
| 12432c9b06 | |||
| 2051c2419f | |||
| cb6b309dad | |||
| addc2e6b20 |
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
other/.lein-deps-sum
|
other/.lein-deps-sum
|
||||||
other/classes
|
other/classes
|
||||||
other/lib
|
other/lib
|
||||||
other/node_modules
|
other/js/node_modules
|
||||||
.project
|
.project
|
||||||
.pydevproject
|
.pydevproject
|
||||||
target.cfg
|
target.cfg
|
||||||
|
|||||||
+11
@@ -1,6 +1,17 @@
|
|||||||
Changes
|
Changes
|
||||||
=======
|
=======
|
||||||
|
|
||||||
|
0.8.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
* Make websockify properly terminate children on SIGTERM (#226)
|
||||||
|
* Remove logging in signal handlers (this can cause Python to hang under certain conditions) (#219)
|
||||||
|
* Make it easier to log to a file (#205)
|
||||||
|
* Add support for IPv6 addresses in tokens in the TokenFile token plugins (#197)
|
||||||
|
* Improve auth plugin framework to enable better support for HTTP auth (#194, #201)
|
||||||
|
* Fix bug in JSONTokenAPI token plugin (#192)
|
||||||
|
* Fix a missing variable in the exception handler (#178)
|
||||||
|
|
||||||
0.7.0
|
0.7.0
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
|||||||
@@ -47,17 +47,29 @@ which is why the negotiation is necessary.
|
|||||||
|
|
||||||
### Encrypted WebSocket connections (wss://)
|
### Encrypted WebSocket connections (wss://)
|
||||||
|
|
||||||
To encrypt the traffic using the WebSocket 'wss://' URI scheme you
|
To encrypt the traffic using the WebSocket 'wss://' URI scheme you need to
|
||||||
need to generate a certificate for websockify to load. By default websockify
|
generate a certificate and key for Websockify to load. By default, Websockify
|
||||||
loads a certificate file name `self.pem` but the `--cert=CERT` option can
|
loads a certificate file name `self.pem` but the `--cert=CERT` and `--key=KEY`
|
||||||
override the file name. You can generate a self-signed certificate using
|
options can override the file name. You can generate a self-signed certificate
|
||||||
openssl. When asked for the common name, use the hostname of the server where
|
using openssl. When asked for the common name, use the hostname of the server
|
||||||
the proxy will be running:
|
where the proxy will be running:
|
||||||
|
|
||||||
```
|
```
|
||||||
openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem
|
openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem
|
||||||
```
|
```
|
||||||
|
|
||||||
|
For a self-signed certificate to work, you need to make your client/browser
|
||||||
|
understand it. You can do this by installing it as accepted certificate, or by
|
||||||
|
using that same certificate for a HTTPS connection to which you navigate first
|
||||||
|
and approve. Browsers generally don't give you the "trust certificate?" prompt
|
||||||
|
by opening a WSS socket with invalid certificate, hence you need to have it
|
||||||
|
acccept it by either of those two methods.
|
||||||
|
|
||||||
|
If you have a commercial/valid SSL certificate with one ore more intermediate
|
||||||
|
certificates, concat them into one file, server certificate first, then the
|
||||||
|
intermediate(s) from the CA, etc. Point to this file with the `--cert` option
|
||||||
|
and then also to the key with `--key`. Finally, use `--ssl-only` as needed.
|
||||||
|
|
||||||
|
|
||||||
### Websock Javascript library
|
### Websock Javascript library
|
||||||
|
|
||||||
@@ -103,11 +115,14 @@ These are not necessary for the basic operation.
|
|||||||
|
|
||||||
* Mini-webserver: websockify can detect and respond to normal web
|
* Mini-webserver: websockify can detect and respond to normal web
|
||||||
requests on the same port as the WebSockets proxy and Flash security
|
requests on the same port as the WebSockets proxy and Flash security
|
||||||
policy. This functionality is activate with the `--web DIR` option
|
policy. This functionality is activated with the `--web DIR` option
|
||||||
where DIR is the root of the web directory to serve.
|
where DIR is the root of the web directory to serve.
|
||||||
|
|
||||||
* Wrap a program: see the "Wrap a Program" section below.
|
* Wrap a program: see the "Wrap a Program" section below.
|
||||||
|
|
||||||
|
* Log files: websockify can save all logging information in a file.
|
||||||
|
This functionality is activated with the `--log-file FILE` option
|
||||||
|
where FILE is the file where the logs should be saved.
|
||||||
|
|
||||||
### Implementations of websockify
|
### Implementations of websockify
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
"author": "Joel Martin <github@martintribe.org> (http://github.com/kanaka)",
|
"author": "Joel Martin <github@martintribe.org> (http://github.com/kanaka)",
|
||||||
"name": "websockify",
|
"name": "websockify",
|
||||||
"description": "websockify is a WebSocket-to-TCP proxy/bridge",
|
"description": "websockify is a WebSocket-to-TCP proxy/bridge",
|
||||||
"license": "LGPL-3",
|
"license": "LGPL-3.0",
|
||||||
"version": "0.7.0",
|
"version": "0.8.0",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git://github.com/kanaka/websockify.git"
|
"url": "git://github.com/kanaka/websockify.git"
|
||||||
@@ -17,7 +17,6 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ws": ">=0.4.27",
|
"ws": ">=0.4.27",
|
||||||
"base64": "latest",
|
|
||||||
"optimist": "latest",
|
"optimist": "latest",
|
||||||
"policyfile": "latest"
|
"policyfile": "latest"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from setuptools import setup, find_packages
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
version = '0.7.0'
|
version = '0.8.0'
|
||||||
name = 'websockify'
|
name = 'websockify'
|
||||||
long_description = open("README.md").read() + "\n" + \
|
long_description = open("README.md").read() + "\n" + \
|
||||||
open("CHANGES.txt").read() + "\n"
|
open("CHANGES.txt").read() + "\n"
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
""" Unit tests for Authentication plugins"""
|
||||||
|
|
||||||
|
from websockify.auth_plugins import BasicHTTPAuth, AuthenticationError
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
|
||||||
|
class BasicHTTPAuthTestCase(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.plugin = BasicHTTPAuth('Aladdin:open sesame')
|
||||||
|
|
||||||
|
def test_no_auth(self):
|
||||||
|
headers = {}
|
||||||
|
self.assertRaises(AuthenticationError, self.plugin.authenticate, headers, 'localhost', '1234')
|
||||||
|
|
||||||
|
def test_invalid_password(self):
|
||||||
|
headers = {'Authorization': 'Basic QWxhZGRpbjpzZXNhbWUgc3RyZWV0'}
|
||||||
|
self.assertRaises(AuthenticationError, self.plugin.authenticate, headers, 'localhost', '1234')
|
||||||
|
|
||||||
|
def test_valid_password(self):
|
||||||
|
headers = {'Authorization': 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=='}
|
||||||
|
self.plugin.authenticate(headers, 'localhost', '1234')
|
||||||
|
|
||||||
|
def test_garbage_auth(self):
|
||||||
|
headers = {'Authorization': 'Basic xxxxxxxxxxxxxxxxxxxxxxxxxxxx'}
|
||||||
|
self.assertRaises(AuthenticationError, self.plugin.authenticate, headers, 'localhost', '1234')
|
||||||
@@ -106,11 +106,11 @@ class ProxyRequestHandlerTestCase(unittest.TestCase):
|
|||||||
def lookup(self, token):
|
def lookup(self, token):
|
||||||
return (self.source + token).split(',')
|
return (self.source + token).split(',')
|
||||||
|
|
||||||
self.stubs.Set(websocketproxy.ProxyRequestHandler, 'do_proxy',
|
self.stubs.Set(websocketproxy.ProxyRequestHandler, 'send_auth_error',
|
||||||
lambda *args, **kwargs: None)
|
staticmethod(lambda *args, **kwargs: None))
|
||||||
|
|
||||||
self.handler.server.token_plugin = TestPlugin("somehost,")
|
self.handler.server.token_plugin = TestPlugin("somehost,")
|
||||||
self.handler.new_websocket_client()
|
self.handler.validate_connection()
|
||||||
|
|
||||||
self.assertEqual(self.handler.server.target_host, "somehost")
|
self.assertEqual(self.handler.server.target_host, "somehost")
|
||||||
self.assertEqual(self.handler.server.target_port, "blah")
|
self.assertEqual(self.handler.server.target_port, "blah")
|
||||||
@@ -119,9 +119,9 @@ class ProxyRequestHandlerTestCase(unittest.TestCase):
|
|||||||
class TestPlugin(auth_plugins.BasePlugin):
|
class TestPlugin(auth_plugins.BasePlugin):
|
||||||
def authenticate(self, headers, target_host, target_port):
|
def authenticate(self, headers, target_host, target_port):
|
||||||
if target_host == self.source:
|
if target_host == self.source:
|
||||||
raise auth_plugins.AuthenticationError("some error")
|
raise auth_plugins.AuthenticationError(response_msg="some_error")
|
||||||
|
|
||||||
self.stubs.Set(websocketproxy.ProxyRequestHandler, 'do_proxy',
|
self.stubs.Set(websocketproxy.ProxyRequestHandler, 'send_auth_error',
|
||||||
staticmethod(lambda *args, **kwargs: None))
|
staticmethod(lambda *args, **kwargs: None))
|
||||||
|
|
||||||
self.handler.server.auth_plugin = TestPlugin("somehost")
|
self.handler.server.auth_plugin = TestPlugin("somehost")
|
||||||
@@ -129,8 +129,8 @@ class ProxyRequestHandlerTestCase(unittest.TestCase):
|
|||||||
self.handler.server.target_port = "someport"
|
self.handler.server.target_port = "someport"
|
||||||
|
|
||||||
self.assertRaises(auth_plugins.AuthenticationError,
|
self.assertRaises(auth_plugins.AuthenticationError,
|
||||||
self.handler.new_websocket_client)
|
self.handler.validate_connection)
|
||||||
|
|
||||||
self.handler.server.target_host = "someotherhost"
|
self.handler.server.target_host = "someotherhost"
|
||||||
self.handler.new_websocket_client()
|
self.handler.validate_connection()
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,15 @@ class BasePlugin(object):
|
|||||||
|
|
||||||
|
|
||||||
class AuthenticationError(Exception):
|
class AuthenticationError(Exception):
|
||||||
pass
|
def __init__(self, log_msg=None, response_code=403, response_headers={}, response_msg=None):
|
||||||
|
self.code = response_code
|
||||||
|
self.headers = response_headers
|
||||||
|
self.msg = response_msg
|
||||||
|
|
||||||
|
if log_msg is None:
|
||||||
|
log_msg = response_msg
|
||||||
|
|
||||||
|
super(AuthenticationError, self).__init__('%s %s' % (self.code, log_msg))
|
||||||
|
|
||||||
|
|
||||||
class InvalidOriginError(AuthenticationError):
|
class InvalidOriginError(AuthenticationError):
|
||||||
@@ -16,10 +24,52 @@ class InvalidOriginError(AuthenticationError):
|
|||||||
self.actual_origin = actual
|
self.actual_origin = actual
|
||||||
|
|
||||||
super(InvalidOriginError, self).__init__(
|
super(InvalidOriginError, self).__init__(
|
||||||
"Invalid Origin Header: Expected one of "
|
response_msg='Invalid Origin',
|
||||||
"%s, got '%s'" % (expected, actual))
|
log_msg="Invalid Origin Header: Expected one of "
|
||||||
|
"%s, got '%s'" % (expected, actual))
|
||||||
|
|
||||||
|
|
||||||
|
class BasicHTTPAuth(object):
|
||||||
|
"""Verifies Basic Auth headers. Specify src as username:password"""
|
||||||
|
|
||||||
|
def __init__(self, src=None):
|
||||||
|
self.src = src
|
||||||
|
|
||||||
|
def authenticate(self, headers, target_host, target_port):
|
||||||
|
import base64
|
||||||
|
auth_header = headers.get('Authorization')
|
||||||
|
if auth_header:
|
||||||
|
if not auth_header.startswith('Basic '):
|
||||||
|
raise AuthenticationError(response_code=403)
|
||||||
|
|
||||||
|
try:
|
||||||
|
user_pass_raw = base64.b64decode(auth_header[6:])
|
||||||
|
except TypeError:
|
||||||
|
raise AuthenticationError(response_code=403)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# http://stackoverflow.com/questions/7242316/what-encoding-should-i-use-for-http-basic-authentication
|
||||||
|
user_pass_as_text = user_pass_raw.decode('ISO-8859-1')
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
raise AuthenticationError(response_code=403)
|
||||||
|
|
||||||
|
user_pass = user_pass_as_text.split(':', 1)
|
||||||
|
if len(user_pass) != 2:
|
||||||
|
raise AuthenticationError(response_code=403)
|
||||||
|
|
||||||
|
if not self.validate_creds(*user_pass):
|
||||||
|
raise AuthenticationError(response_code=403)
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise AuthenticationError(response_code=401,
|
||||||
|
response_headers={'WWW-Authenticate': 'Basic realm="Websockify"'})
|
||||||
|
|
||||||
|
def validate_creds(self, username, password):
|
||||||
|
if '%s:%s' % (username, password) == self.src:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
class ExpectOrigin(object):
|
class ExpectOrigin(object):
|
||||||
def __init__(self, src=None):
|
def __init__(self, src=None):
|
||||||
if src is None:
|
if src is None:
|
||||||
@@ -28,6 +78,6 @@ class ExpectOrigin(object):
|
|||||||
self.source = src.split()
|
self.source = src.split()
|
||||||
|
|
||||||
def authenticate(self, headers, target_host, target_port):
|
def authenticate(self, headers, target_host, target_port):
|
||||||
origin = headers.getheader('Origin', None)
|
origin = headers.get('Origin', None)
|
||||||
if origin is None or origin not in self.source:
|
if origin is None or origin not in self.source:
|
||||||
raise InvalidOriginError(expected=self.source, actual=origin)
|
raise InvalidOriginError(expected=self.source, actual=origin)
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class ReadOnlyTokenFile(BasePlugin):
|
|||||||
for line in [l.strip() for l in open(f).readlines()]:
|
for line in [l.strip() for l in open(f).readlines()]:
|
||||||
if line and not line.startswith('#'):
|
if line and not line.startswith('#'):
|
||||||
tok, target = line.split(': ')
|
tok, target = line.split(': ')
|
||||||
self._targets[tok] = target.strip().split(':')
|
self._targets[tok] = target.strip().rsplit(':', 1)
|
||||||
|
|
||||||
def lookup(self, token):
|
def lookup(self, token):
|
||||||
if self._targets is None:
|
if self._targets is None:
|
||||||
@@ -79,4 +79,5 @@ class JSONTokenApi(BaseTokenAPI):
|
|||||||
# should go
|
# should go
|
||||||
|
|
||||||
def process_result(self, resp):
|
def process_result(self, resp):
|
||||||
return (resp.json['host'], resp.json['port'])
|
resp_json = resp.json()
|
||||||
|
return (resp_json['host'], resp_json['port'])
|
||||||
|
|||||||
+51
-8
@@ -113,6 +113,9 @@ class WebSocketRequestHandler(SimpleHTTPRequestHandler):
|
|||||||
|
|
||||||
SimpleHTTPRequestHandler.__init__(self, req, addr, server)
|
SimpleHTTPRequestHandler.__init__(self, req, addr, server)
|
||||||
|
|
||||||
|
def log_message(self, format, *args):
|
||||||
|
self.logger.info("%s - - [%s] %s" % (self.address_string(), self.log_date_time_string(), format % args))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def unmask(buf, hlen, plen):
|
def unmask(buf, hlen, plen):
|
||||||
pstart = hlen + 4
|
pstart = hlen + 4
|
||||||
@@ -474,9 +477,13 @@ class WebSocketRequestHandler(SimpleHTTPRequestHandler):
|
|||||||
"""Upgrade a connection to Websocket, if requested. If this succeeds,
|
"""Upgrade a connection to Websocket, if requested. If this succeeds,
|
||||||
new_websocket_client() will be called. Otherwise, False is returned.
|
new_websocket_client() will be called. Otherwise, False is returned.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if (self.headers.get('upgrade') and
|
if (self.headers.get('upgrade') and
|
||||||
self.headers.get('upgrade').lower() == 'websocket'):
|
self.headers.get('upgrade').lower() == 'websocket'):
|
||||||
|
|
||||||
|
# ensure connection is authorized, and determine the target
|
||||||
|
self.validate_connection()
|
||||||
|
|
||||||
if not self.do_websocket_handshake():
|
if not self.do_websocket_handshake():
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -549,6 +556,10 @@ class WebSocketRequestHandler(SimpleHTTPRequestHandler):
|
|||||||
""" Do something with a WebSockets client connection. """
|
""" Do something with a WebSockets client connection. """
|
||||||
raise Exception("WebSocketRequestHandler.new_websocket_client() must be overloaded")
|
raise Exception("WebSocketRequestHandler.new_websocket_client() must be overloaded")
|
||||||
|
|
||||||
|
def validate_connection(self):
|
||||||
|
""" Ensure that the connection is a valid connection, and set the target. """
|
||||||
|
pass
|
||||||
|
|
||||||
def do_HEAD(self):
|
def do_HEAD(self):
|
||||||
if self.only_upgrade:
|
if self.only_upgrade:
|
||||||
self.send_error(405, "Method Not Allowed")
|
self.send_error(405, "Method Not Allowed")
|
||||||
@@ -737,6 +748,10 @@ class WebSocketServer(object):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def daemonize(keepfd=None, chdir='/'):
|
def daemonize(keepfd=None, chdir='/'):
|
||||||
|
|
||||||
|
if keepfd is None:
|
||||||
|
keepfd = []
|
||||||
|
|
||||||
os.umask(0)
|
os.umask(0)
|
||||||
if chdir:
|
if chdir:
|
||||||
os.chdir(chdir)
|
os.chdir(chdir)
|
||||||
@@ -759,7 +774,7 @@ class WebSocketServer(object):
|
|||||||
if maxfd == resource.RLIM_INFINITY: maxfd = 256
|
if maxfd == resource.RLIM_INFINITY: maxfd = 256
|
||||||
for fd in reversed(range(maxfd)):
|
for fd in reversed(range(maxfd)):
|
||||||
try:
|
try:
|
||||||
if fd != keepfd:
|
if fd not in keepfd:
|
||||||
os.close(fd)
|
os.close(fd)
|
||||||
except OSError:
|
except OSError:
|
||||||
_, exc, _ = sys.exc_info()
|
_, exc, _ = sys.exc_info()
|
||||||
@@ -789,7 +804,7 @@ class WebSocketServer(object):
|
|||||||
"""
|
"""
|
||||||
ready = select.select([sock], [], [], 3)[0]
|
ready = select.select([sock], [], [], 3)[0]
|
||||||
|
|
||||||
|
|
||||||
if not ready:
|
if not ready:
|
||||||
raise self.EClose("ignoring socket not ready")
|
raise self.EClose("ignoring socket not ready")
|
||||||
# Peek, but do not read the data so that we have a opportunity
|
# Peek, but do not read the data so that we have a opportunity
|
||||||
@@ -880,11 +895,14 @@ class WebSocketServer(object):
|
|||||||
raise self.Terminate()
|
raise self.Terminate()
|
||||||
|
|
||||||
def multiprocessing_SIGCHLD(self, sig, stack):
|
def multiprocessing_SIGCHLD(self, sig, stack):
|
||||||
self.vmsg('Reaping zombies, active child count is %s', len(multiprocessing.active_children()))
|
# TODO: figure out a way to actually log this information without
|
||||||
|
# calling `log` in the signal handlers
|
||||||
|
multiprocessing.active_children()
|
||||||
|
|
||||||
def fallback_SIGCHLD(self, sig, stack):
|
def fallback_SIGCHLD(self, sig, stack):
|
||||||
# Reap zombies when using os.fork() (python 2.4)
|
# Reap zombies when using os.fork() (python 2.4)
|
||||||
self.vmsg("Got SIGCHLD, reaping zombies")
|
# TODO: figure out a way to actually log this information without
|
||||||
|
# calling `log` in the signal handlers
|
||||||
try:
|
try:
|
||||||
result = os.waitpid(-1, os.WNOHANG)
|
result = os.waitpid(-1, os.WNOHANG)
|
||||||
while result[0]:
|
while result[0]:
|
||||||
@@ -894,16 +912,18 @@ class WebSocketServer(object):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def do_SIGINT(self, sig, stack):
|
def do_SIGINT(self, sig, stack):
|
||||||
self.msg("Got SIGINT, exiting")
|
# TODO: figure out a way to actually log this information without
|
||||||
|
# calling `log` in the signal handlers
|
||||||
self.terminate()
|
self.terminate()
|
||||||
|
|
||||||
def do_SIGTERM(self, sig, stack):
|
def do_SIGTERM(self, sig, stack):
|
||||||
self.msg("Got SIGTERM, exiting")
|
# TODO: figure out a way to actually log this information without
|
||||||
|
# calling `log` in the signal handlers
|
||||||
self.terminate()
|
self.terminate()
|
||||||
|
|
||||||
def top_new_client(self, startsock, address):
|
def top_new_client(self, startsock, address):
|
||||||
""" Do something with a WebSockets client connection. """
|
""" Do something with a WebSockets client connection. """
|
||||||
# handler process
|
# handler process
|
||||||
client = None
|
client = None
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
@@ -926,6 +946,18 @@ class WebSocketServer(object):
|
|||||||
# Original socket closed by caller
|
# Original socket closed by caller
|
||||||
client.close()
|
client.close()
|
||||||
|
|
||||||
|
def get_log_fd(self):
|
||||||
|
"""
|
||||||
|
Get file descriptors for the loggers.
|
||||||
|
They should not be closed when the process is forked.
|
||||||
|
"""
|
||||||
|
descriptors = []
|
||||||
|
for handler in self.logger.parent.handlers:
|
||||||
|
if isinstance(handler, logging.FileHandler):
|
||||||
|
descriptors.append(handler.stream.fileno())
|
||||||
|
|
||||||
|
return descriptors
|
||||||
|
|
||||||
def start_server(self):
|
def start_server(self):
|
||||||
"""
|
"""
|
||||||
Daemonize if requested. Listen for for connections. Run
|
Daemonize if requested. Listen for for connections. Run
|
||||||
@@ -941,7 +973,9 @@ class WebSocketServer(object):
|
|||||||
tcp_keepintvl=self.tcp_keepintvl)
|
tcp_keepintvl=self.tcp_keepintvl)
|
||||||
|
|
||||||
if self.daemon:
|
if self.daemon:
|
||||||
self.daemonize(keepfd=lsock.fileno(), chdir=self.web)
|
keepfd = self.get_log_fd()
|
||||||
|
keepfd.append(lsock.fileno())
|
||||||
|
self.daemonize(keepfd=keepfd, chdir=self.web)
|
||||||
|
|
||||||
self.started() # Some things need to happen after daemonizing
|
self.started() # Some things need to happen after daemonizing
|
||||||
|
|
||||||
@@ -1045,8 +1079,17 @@ class WebSocketServer(object):
|
|||||||
|
|
||||||
except (self.Terminate, SystemExit, KeyboardInterrupt):
|
except (self.Terminate, SystemExit, KeyboardInterrupt):
|
||||||
self.msg("In exit")
|
self.msg("In exit")
|
||||||
|
# terminate all child processes
|
||||||
|
if multiprocessing and not self.run_once:
|
||||||
|
children = multiprocessing.active_children()
|
||||||
|
|
||||||
|
for child in children:
|
||||||
|
self.msg("Terminating child %s" % child.pid)
|
||||||
|
child.terminate()
|
||||||
|
|
||||||
break
|
break
|
||||||
except Exception:
|
except Exception:
|
||||||
|
exc = sys.exc_info()[1]
|
||||||
self.msg("handler exception: %s", str(exc))
|
self.msg("handler exception: %s", str(exc))
|
||||||
self.vmsg("exception", exc_info=True)
|
self.vmsg("exception", exc_info=True)
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ try: from http.server import HTTPServer
|
|||||||
except: from BaseHTTPServer import HTTPServer
|
except: from BaseHTTPServer import HTTPServer
|
||||||
import select
|
import select
|
||||||
from websockify import websocket
|
from websockify import websocket
|
||||||
|
from websockify import auth_plugins as auth
|
||||||
try:
|
try:
|
||||||
from urllib.parse import parse_qs, urlparse
|
from urllib.parse import parse_qs, urlparse
|
||||||
except:
|
except:
|
||||||
@@ -37,20 +38,34 @@ Traffic Legend:
|
|||||||
< - Client send
|
< - Client send
|
||||||
<. - Client send partial
|
<. - Client send partial
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def send_auth_error(self, ex):
|
||||||
|
self.send_response(ex.code, ex.msg)
|
||||||
|
self.send_header('Content-Type', 'text/html')
|
||||||
|
for name, val in ex.headers.items():
|
||||||
|
self.send_header(name, val)
|
||||||
|
|
||||||
|
self.end_headers()
|
||||||
|
|
||||||
|
def validate_connection(self):
|
||||||
|
if self.server.token_plugin:
|
||||||
|
(self.server.target_host, self.server.target_port) = self.get_target(self.server.token_plugin, self.path)
|
||||||
|
|
||||||
|
if self.server.auth_plugin:
|
||||||
|
try:
|
||||||
|
self.server.auth_plugin.authenticate(
|
||||||
|
headers=self.headers, target_host=self.server.target_host,
|
||||||
|
target_port=self.server.target_port)
|
||||||
|
except auth.AuthenticationError:
|
||||||
|
ex = sys.exc_info()[1]
|
||||||
|
self.send_auth_error(ex)
|
||||||
|
raise
|
||||||
|
|
||||||
def new_websocket_client(self):
|
def new_websocket_client(self):
|
||||||
"""
|
"""
|
||||||
Called after a new WebSocket connection has been established.
|
Called after a new WebSocket connection has been established.
|
||||||
"""
|
"""
|
||||||
# Checks if we receive a token, and look
|
# Checking for a token is done in validate_connection()
|
||||||
# for a valid target for it then
|
|
||||||
if self.server.token_plugin:
|
|
||||||
(self.server.target_host, self.server.target_port) = self.get_target(self.server.token_plugin, self.path)
|
|
||||||
|
|
||||||
if self.server.auth_plugin:
|
|
||||||
self.server.auth_plugin.authenticate(
|
|
||||||
headers=self.headers, target_host=self.server.target_host,
|
|
||||||
target_port=self.server.target_port)
|
|
||||||
|
|
||||||
# Connect to the target
|
# Connect to the target
|
||||||
if self.server.wrap_cmd:
|
if self.server.wrap_cmd:
|
||||||
@@ -396,9 +411,22 @@ def websockify_init():
|
|||||||
help="Automatically respond to ping frames with a pong")
|
help="Automatically respond to ping frames with a pong")
|
||||||
parser.add_option("--heartbeat", type=int, default=0,
|
parser.add_option("--heartbeat", type=int, default=0,
|
||||||
help="send a ping to the client every HEARTBEAT seconds")
|
help="send a ping to the client every HEARTBEAT seconds")
|
||||||
|
parser.add_option("--log-file", metavar="FILE",
|
||||||
|
dest="log_file",
|
||||||
|
help="File where logs will be saved")
|
||||||
|
|
||||||
|
|
||||||
(opts, args) = parser.parse_args()
|
(opts, args) = parser.parse_args()
|
||||||
|
|
||||||
|
if opts.log_file:
|
||||||
|
opts.log_file = os.path.abspath(opts.log_file)
|
||||||
|
handler = logging.FileHandler(opts.log_file)
|
||||||
|
handler.setLevel(logging.DEBUG)
|
||||||
|
handler.setFormatter(logging.Formatter("%(message)s"))
|
||||||
|
logging.getLogger(WebSocketProxy.log_prefix).addHandler(handler)
|
||||||
|
|
||||||
|
del opts.log_file
|
||||||
|
|
||||||
if opts.verbose:
|
if opts.verbose:
|
||||||
logging.getLogger(WebSocketProxy.log_prefix).setLevel(logging.DEBUG)
|
logging.getLogger(WebSocketProxy.log_prefix).setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user