26 Commits

Author SHA1 Message Date
Pierre Ossman 06e61fa4cc websockify 0.11.0 2022-12-16 13:11:04 +01:00
Pierre Ossman 4f9d496dfe Use local websockify when building container
It's very surprising to get some external copy of websockify when you
are building an image in your local websockify source tree. Make sure we
are using the local copy of everything.
2022-12-16 13:10:58 +01:00
Pierre Ossman 1979e92f0e Also require all token plugin requirements
Token plugins are technically optional, but if you are installing
websockify via pip then all of these are available anyway. So let's make
things simple for users.
2022-12-16 13:08:23 +01:00
Adam Tilghman d2affc73b5 Insert rebinder at the head of the (possibly empty) LD_PRELOAD pathlist,
required for use cases relying on other preloaded libraries e.g. nss_wrapper.
2022-11-16 16:35:39 -08:00
Pierre Ossman 27ee353401 Don't include default message to send_error()
Python can provide this for us, so avoid duplication.
2022-11-16 15:28:18 +01:00
Pierre Ossman 39c7cb0115 Merge branch 'http_api' of https://github.com/CendioOssman/websockify 2022-11-08 14:29:12 +01:00
Linn Mattsson be7b868518 Remove logging from handle_upgrade()
The logging should be handled directly in send_response() instead, which
is the default of Python's built-in send_response(). Remove this manual
logging to avoid logging the same call twice.
2022-11-08 14:24:46 +01:00
Linn Mattsson 4695f96728 Add new websocket class HttpWebSocket
This class acts as a glue between websocket and http functionality by
taking a 'request_handler' and using its functions for send_response(),
send_header() and end_headers().
2022-11-08 14:24:46 +01:00
Linn Mattsson 832ae23f00 Make websocket's API more intuitive
Functions connect() and accept() are using http functionality, like
sending requests and headers. Let's create separate functions with more
intuitive names for these calls. This allows subclasses to override
these functions, as well as makes the code easier to understand at a
glance.
2022-11-08 14:05:46 +01:00
Jokin c123bfbbff Add requests module 2022-10-20 11:28:34 +00:00
Manoj Ghosh 70579756f3 expose --file-only option to disable dir listing 2022-10-18 04:10:44 -07:00
Pierre Ossman e9c80aa32a Merge branch 'master' of https://github.com/msnatepg/websockify 2022-10-10 10:13:01 +02:00
Maximilian Sesterhenn caef680fff ensure that queues are empty when closing connections 2022-10-07 14:24:53 +02:00
Pierre Ossman 7133f85df6 Merge branch 'verbose_logging' of https://github.com/javicacheiro/websockify 2022-08-18 11:11:22 +02:00
Samuel Mannehed 33910d758d Merge pull request #521 from javicacheiro/fix_jwcrypto
Tests break with jwcrypto>=1.3
2022-05-26 15:59:39 +02:00
Javier Cacheiro 0f17500348 Support for jwcrypto>=1.3 2022-05-25 12:40:29 +02:00
Pierre Ossman 4b194636e2 Merge branch 'dockerfile' of https://github.com/javicacheiro/websockify 2022-05-11 14:23:40 +02:00
Javier Cacheiro ffa31d6c2c When using verbose set root log level to DEBUG 2022-05-03 09:58:50 +02:00
Javier Cacheiro dec26a6a2d Docker support 2022-04-22 13:21:57 +02:00
Pierre Ossman d96ecf0859 Add more alternatives to usage string
If you use a token plugin, or a Unix socket target, then you should no
longer specify a target on the command line. Add these to the usage
string to make this clear.
2022-04-22 10:19:54 +02:00
Pierre Ossman d0ad4a1b2a Merge branches 'use_logging' and 'fix_logging_configuration' of https://github.com/javicacheiro/websockify 2022-04-14 09:15:21 +02:00
Javier Cacheiro e1f903b9e8 Apply configuration to the root logger 2022-04-13 17:02:05 +02:00
Javier Cacheiro 62ac4aeb03 Use logging instead of directly printing messages to sys.stderr 2022-04-13 16:12:31 +02:00
Pierre Ossman e4cff3746d Explicitly install old wrapt on Python 3.4
Something is broken in pip so it installs a wrapt that doesn't support
Python 3.4. Work around this by manually request a version that is known
to work.
2022-04-13 12:50:25 +02:00
Javier Cacheiro d5e8d709d7 Add tests for TokenRedis 2022-04-12 10:58:42 +02:00
Pierre Ossman dc345815c0 Use RSA-OAEP instead of RSA1_5 for jwt tests
The latest version of jwcrypto has disabled RSA1_5 by default, making
the tests fail.
2021-07-23 09:38:58 +02:00
14 changed files with 301 additions and 78 deletions
+6
View File
@@ -1,6 +1,12 @@
Changes
=======
0.11.0
------
* Command line now supports disabling directory listings
* Basic Dockerfile included
0.10.0
------
+32
View File
@@ -168,3 +168,35 @@ before running `python3 setup.py install`.
Afterwards, websockify should be available in your path. Run
`websockify --help` to confirm it's installed correctly.
### Running with Docker/Podman
You can also run websockify using Docker, Podman, Singularity, udocker or
your favourite container runtime that support OCI container images.
The entrypoint of the image is the `run` command.
To build the image:
```
./docker/build.sh
```
Once built you can just launch it with the same
arguments you would give to the `run` command and taking care of
assigning the port mappings:
```
docker run -it --rm -p <port>:<container_port> novnc/websockify <container_port> <run_arguments>
```
For example to forward traffic from local port 7000 to 10.1.1.1:5902
you can use:
```
docker run -it --rm -p 7000:80 novnc/websockify 80 10.1.1.1:5902
```
If you need to include files, like for example for the `--web` or `--cert`
options you can just mount the required files in the `/data` volume and then
you can reference them in the usual way:
```
docker run -it --rm -p 443:443 -v websockify-data:/data novnc/websockify --cert /data/self.pem --web /data/noVNC :443 --token-plugin TokenRedis --token-source myredis.local:6379 --ssl-only --ssl-version tlsv1_2
```
+16
View File
@@ -0,0 +1,16 @@
FROM python
COPY websockify-*.tar.gz /
RUN python3 -m pip install websockify-*.tar.gz
RUN rm -rf /websockify-* /root/.cache
VOLUME /data
EXPOSE 80
EXPOSE 443
WORKDIR /opt/websockify
ENTRYPOINT ["/usr/local/bin/websockify"]
CMD ["--help"]
+5
View File
@@ -0,0 +1,5 @@
#!/usr/bin/env sh
set -e -x
cd "$(dirname "$0")"
(cd .. && python3 setup.py sdist --dist-dir docker/)
docker build -t novnc/websockify .
+6 -2
View File
@@ -1,6 +1,6 @@
from setuptools import setup, find_packages
version = '0.10.0'
version = '0.11.0'
name = 'websockify'
long_description = open("README.md").read() + "\n" + \
open("CHANGES.txt").read() + "\n"
@@ -29,7 +29,11 @@ setup(name=name,
packages=['websockify'],
include_package_data=True,
install_requires=['numpy'],
install_requires=[
'numpy', 'requests',
'jwcrypto',
'redis', 'simplejson',
],
zip_safe=False,
entry_points={
'console_scripts': [
+1
View File
@@ -4,3 +4,4 @@ jwcrypto
six
redis
simplejson
wrapt<=1.12.1;python_version<="3.4"
+38 -12
View File
@@ -4,9 +4,9 @@
import unittest
from unittest.mock import patch, mock_open, MagicMock
from jwcrypto import jwt
from jwcrypto import jwt, jwk
from websockify.token_plugins import ReadOnlyTokenFile, JWTTokenApi
from websockify.token_plugins import ReadOnlyTokenFile, JWTTokenApi, TokenRedis
class ReadOnlyTokenFileTestCase(unittest.TestCase):
patch('os.path.isdir', MagicMock(return_value=False))
@@ -56,7 +56,7 @@ class JWSTokenTestCase(unittest.TestCase):
def test_asymmetric_jws_token_plugin(self):
plugin = JWTTokenApi("./tests/fixtures/public.pem")
key = jwt.JWK()
key = jwk.JWK()
private_key = open("./tests/fixtures/private.pem", "rb").read()
key.import_from_pem(private_key)
jwt_token = jwt.JWT({"alg": "RS256"}, {'host': "remote_host", 'port': "remote_port"})
@@ -71,7 +71,7 @@ class JWSTokenTestCase(unittest.TestCase):
def test_asymmetric_jws_token_plugin_with_illigal_key_exception(self):
plugin = JWTTokenApi("wrong.pub")
key = jwt.JWK()
key = jwk.JWK()
private_key = open("./tests/fixtures/private.pem", "rb").read()
key.import_from_pem(private_key)
jwt_token = jwt.JWT({"alg": "RS256"}, {'host': "remote_host", 'port': "remote_port"})
@@ -85,7 +85,7 @@ class JWSTokenTestCase(unittest.TestCase):
def test_jwt_valid_time(self, mock_time):
plugin = JWTTokenApi("./tests/fixtures/public.pem")
key = jwt.JWK()
key = jwk.JWK()
private_key = open("./tests/fixtures/private.pem", "rb").read()
key.import_from_pem(private_key)
jwt_token = jwt.JWT({"alg": "RS256"}, {'host': "remote_host", 'port': "remote_port", 'nbf': 100, 'exp': 200 })
@@ -102,7 +102,7 @@ class JWSTokenTestCase(unittest.TestCase):
def test_jwt_early_time(self, mock_time):
plugin = JWTTokenApi("./tests/fixtures/public.pem")
key = jwt.JWK()
key = jwk.JWK()
private_key = open("./tests/fixtures/private.pem", "rb").read()
key.import_from_pem(private_key)
jwt_token = jwt.JWT({"alg": "RS256"}, {'host': "remote_host", 'port': "remote_port", 'nbf': 100, 'exp': 200 })
@@ -117,7 +117,7 @@ class JWSTokenTestCase(unittest.TestCase):
def test_jwt_late_time(self, mock_time):
plugin = JWTTokenApi("./tests/fixtures/public.pem")
key = jwt.JWK()
key = jwk.JWK()
private_key = open("./tests/fixtures/private.pem", "rb").read()
key.import_from_pem(private_key)
jwt_token = jwt.JWT({"alg": "RS256"}, {'host': "remote_host", 'port': "remote_port", 'nbf': 100, 'exp': 200 })
@@ -132,7 +132,7 @@ class JWSTokenTestCase(unittest.TestCase):
plugin = JWTTokenApi("./tests/fixtures/symmetric.key")
secret = open("./tests/fixtures/symmetric.key").read()
key = jwt.JWK()
key = jwk.JWK()
key.import_key(kty="oct",k=secret)
jwt_token = jwt.JWT({"alg": "HS256"}, {'host': "remote_host", 'port': "remote_port"})
jwt_token.make_signed_token(key)
@@ -147,7 +147,7 @@ class JWSTokenTestCase(unittest.TestCase):
plugin = JWTTokenApi("wrong_sauce")
secret = open("./tests/fixtures/symmetric.key").read()
key = jwt.JWK()
key = jwk.JWK()
key.import_key(kty="oct",k=secret)
jwt_token = jwt.JWT({"alg": "HS256"}, {'host': "remote_host", 'port': "remote_port"})
jwt_token.make_signed_token(key)
@@ -159,15 +159,15 @@ class JWSTokenTestCase(unittest.TestCase):
def test_asymmetric_jwe_token_plugin(self):
plugin = JWTTokenApi("./tests/fixtures/private.pem")
private_key = jwt.JWK()
public_key = jwt.JWK()
private_key = jwk.JWK()
public_key = jwk.JWK()
private_key_data = open("./tests/fixtures/private.pem", "rb").read()
public_key_data = open("./tests/fixtures/public.pem", "rb").read()
private_key.import_from_pem(private_key_data)
public_key.import_from_pem(public_key_data)
jwt_token = jwt.JWT({"alg": "RS256"}, {'host': "remote_host", 'port': "remote_port"})
jwt_token.make_signed_token(private_key)
jwe_token = jwt.JWT(header={"alg": "RSA1_5", "enc": "A256CBC-HS512"},
jwe_token = jwt.JWT(header={"alg": "RSA-OAEP", "enc": "A256CBC-HS512"},
claims=jwt_token.serialize())
jwe_token.make_encrypted_token(public_key)
@@ -177,3 +177,29 @@ class JWSTokenTestCase(unittest.TestCase):
self.assertEqual(result[0], "remote_host")
self.assertEqual(result[1], "remote_port")
class TokenRedisTestCase(unittest.TestCase):
@patch('redis.Redis')
def test_empty(self, mock_redis):
plugin = TokenRedis('127.0.0.1:1234')
instance = mock_redis.return_value
instance.get.return_value = None
result = plugin.lookup('testhost')
instance.get.assert_called_once_with('testhost')
self.assertIsNone(result)
@patch('redis.Redis')
def test_simple(self, mock_redis):
plugin = TokenRedis('127.0.0.1:1234')
instance = mock_redis.return_value
instance.get.return_value = b'{"host": "remote_host:remote_port"}'
result = plugin.lookup('testhost')
instance.get.assert_called_once_with('testhost')
self.assertIsNotNone(result)
self.assertEqual(result[0], 'remote_host')
self.assertEqual(result[1], 'remote_port')
+69
View File
@@ -0,0 +1,69 @@
""" Unit tests for websocketserver """
import unittest
from unittest.mock import patch, MagicMock
from websockify.websocketserver import HttpWebSocket
class HttpWebSocketTest(unittest.TestCase):
@patch("websockify.websocketserver.WebSocket.__init__", autospec=True)
def test_constructor(self, websock):
# Given
req_obj = MagicMock()
# When
sock = HttpWebSocket(req_obj)
# Then
websock.assert_called_once_with(sock)
self.assertEqual(sock.request_handler, req_obj)
@patch("websockify.websocketserver.WebSocket.__init__", MagicMock(autospec=True))
def test_send_response(self):
# Given
req_obj = MagicMock()
sock = HttpWebSocket(req_obj)
# When
sock.send_response(200, "message")
# Then
req_obj.send_response.assert_called_once_with(200, "message")
@patch("websockify.websocketserver.WebSocket.__init__", MagicMock(autospec=True))
def test_send_response_default_message(self):
# Given
req_obj = MagicMock()
sock = HttpWebSocket(req_obj)
# When
sock.send_response(200)
# Then
req_obj.send_response.assert_called_once_with(200, None)
@patch("websockify.websocketserver.WebSocket.__init__", MagicMock(autospec=True))
def test_send_header(self):
# Given
req_obj = MagicMock()
sock = HttpWebSocket(req_obj)
# When
sock.send_header("keyword", "value")
# Then
req_obj.send_header.assert_called_once_with("keyword", "value")
@patch("websockify.websocketserver.WebSocket.__init__", MagicMock(autospec=True))
def test_end_headers(self):
# Given
req_obj = MagicMock()
sock = HttpWebSocket(req_obj)
# When
sock.end_headers()
# Then
req_obj.end_headers.assert_called_once_with()
+2 -2
View File
@@ -86,7 +86,7 @@ class WebSockifyRequestHandlerTestCase(unittest.TestCase):
FakeSocket(b'GET /tmp.txt HTTP/1.1'), '127.0.0.1', server)
handler.do_GET()
send_error.assert_called_with(405, ANY)
send_error.assert_called_with(405)
@patch('websockify.websockifyserver.WebSockifyRequestHandler.send_error')
def test_list_dir_with_file_only_returns_error(self, send_error):
@@ -96,7 +96,7 @@ class WebSockifyRequestHandlerTestCase(unittest.TestCase):
handler.path = '/'
handler.do_GET()
send_error.assert_called_with(404, ANY)
send_error.assert_called_with(404)
class WebSockifyServerTestCase(unittest.TestCase):
+29 -25
View File
@@ -1,8 +1,12 @@
import logging
import os
import sys
import time
import re
logger = logging.getLogger(__name__)
class BasePlugin():
def __init__(self, src):
self.source = src
@@ -35,7 +39,7 @@ class ReadOnlyTokenFile(BasePlugin):
tok, target = re.split(':\s', line)
self._targets[tok] = target.strip().rsplit(':', 1)
except ValueError:
print("Syntax error in %s on line %d" % (self.source, index), file=sys.stderr)
logger.error("Syntax error in %s on line %d" % (self.source, index))
index += 1
def lookup(self, token):
@@ -99,16 +103,16 @@ class JWTTokenApi(BasePlugin):
def lookup(self, token):
try:
from jwcrypto import jwt
from jwcrypto import jwt, jwk
import json
key = jwt.JWK()
key = jwk.JWK()
try:
with open(self.source, 'rb') as key_file:
key_data = key_file.read()
except Exception as e:
print("Error loading key file: %s" % str(e), file=sys.stderr)
logger.error("Error loading key file: %s" % str(e))
return None
try:
@@ -117,7 +121,7 @@ class JWTTokenApi(BasePlugin):
try:
key.import_key(k=key_data.decode('utf-8'),kty='oct')
except:
print('Failed to correctly parse key data!', file=sys.stderr)
logger.error('Failed to correctly parse key data!')
return None
try:
@@ -129,40 +133,40 @@ class JWTTokenApi(BasePlugin):
token = jwt.JWT(key=key, jwt=token.claims)
parsed = json.loads(token.claims)
if 'nbf' in parsed:
# Not Before is present, so we need to check it
if time.time() < parsed['nbf']:
print('Token can not be used yet!', file=sys.stderr)
logger.warning('Token can not be used yet!')
return None
if 'exp' in parsed:
# Expiration time is present, so we need to check it
if time.time() > parsed['exp']:
print('Token has expired!', file=sys.stderr)
logger.warning('Token has expired!')
return None
return (parsed['host'], parsed['port'])
except Exception as e:
print("Failed to parse token: %s" % str(e), file=sys.stderr)
logger.error("Failed to parse token: %s" % str(e))
return None
except ImportError as e:
print("package jwcrypto not found, are you sure you've installed it correctly?", file=sys.stderr)
except ImportError:
logger.error("package jwcrypto not found, are you sure you've installed it correctly?")
return None
class TokenRedis():
"""
The TokenRedis plugin expects the format of the data in a form of json.
Prepare data with:
redis-cli set hello '{"host":"127.0.0.1:5000"}'
Verify with:
redis-cli --raw get hello
Spawn a test "server" using netcat
nc -l 5000 -v
Note: you have to install also the 'redis' and 'simplejson' modules
pip install redis simplejson
"""
@@ -172,14 +176,14 @@ class TokenRedis():
import redis
import simplejson
self._server, self._port = src.split(":")
print("TokenRedis backend initilized (%s:%s)" %
(self._server, self._port), file=sys.stderr)
logger.info("TokenRedis backend initilized (%s:%s)" %
(self._server, self._port))
except ValueError:
print("The provided --token-source='%s' is not in an expected format <host>:<port>" %
src, file=sys.stderr)
logger.error("The provided --token-source='%s' is not in an expected format <host>:<port>" %
src)
sys.exit()
except ImportError:
print("package redis or simplejson not found, are you sure you've installed them correctly?", file=sys.stderr)
logger.error("package redis or simplejson not found, are you sure you've installed them correctly?")
sys.exit()
def lookup(self, token):
@@ -187,20 +191,20 @@ class TokenRedis():
import redis
import simplejson
except ImportError:
print("package redis or simplejson not found, are you sure you've installed them correctly?", file=sys.stderr)
logger.error("package redis or simplejson not found, are you sure you've installed them correctly?")
sys.exit()
print("resolving token '%s'" % token, file=sys.stderr)
logger.info("resolving token '%s'" % token)
client = redis.Redis(host=self._server, port=self._port)
stuff = client.get(token)
if stuff is None:
return None
else:
responseStr = stuff.decode("utf-8")
print("response from redis : %s" % responseStr, file=sys.stderr)
logger.debug("response from redis : %s" % responseStr)
combo = simplejson.loads(responseStr)
(host, port) = combo["host"].split(':')
print("host: %s, port: %s" % (host,port), file=sys.stderr)
logger.debug("host: %s, port: %s" % (host,port))
return [host, port]
@@ -228,5 +232,5 @@ class UnixDomainSocketDirectory(BasePlugin):
return [ 'unix_socket', uds_path ]
except Exception as e:
print("Error finding unix domain socket: %s" % str(e), file=sys.stderr)
logger.error("Error finding unix domain socket: %s" % str(e))
return None
+27 -15
View File
@@ -158,19 +158,19 @@ class WebSocket(object):
if not path:
path = "/"
self._queue_str("GET %s HTTP/1.1\r\n" % path)
self._queue_str("Host: %s\r\n" % uri.hostname)
self._queue_str("Upgrade: websocket\r\n")
self._queue_str("Connection: upgrade\r\n")
self._queue_str("Sec-WebSocket-Key: %s\r\n" % self._key)
self._queue_str("Sec-WebSocket-Version: 13\r\n")
self.send_request("GET", path)
self.send_header("Host", uri.hostname)
self.send_header("Upgrade", "websocket")
self.send_header("Connection", "upgrade")
self.send_header("Sec-WebSocket-Key", self._key)
self.send_header("Sec-WebSocket-Version", 13)
if origin is not None:
self._queue_str("Origin: %s\r\n" % origin)
self.send_header("Origin", origin)
if len(protocols) > 0:
self._queue_str("Sec-WebSocket-Protocol: %s\r\n" % ", ".join(protocols))
self.send_header("Sec-WebSocket-Protocol", ", ".join(protocols))
self._queue_str("\r\n")
self.end_headers()
self._state = "send_headers"
@@ -283,15 +283,15 @@ class WebSocket(object):
if self.protocol not in protocols:
raise Exception('Invalid protocol selected')
self._queue_str("HTTP/1.1 101 Switching Protocols\r\n")
self._queue_str("Upgrade: websocket\r\n")
self._queue_str("Connection: Upgrade\r\n")
self._queue_str("Sec-WebSocket-Accept: %s\r\n" % accept)
self.send_response(101, "Switching Protocols")
self.send_header("Upgrade", "websocket")
self.send_header("Connection", "Upgrade")
self.send_header("Sec-WebSocket-Accept", accept)
if self.protocol:
self._queue_str("Sec-WebSocket-Protocol: %s\r\n" % self.protocol)
self.send_header("Sec-WebSocket-Protocol", self.protocol)
self._queue_str("\r\n")
self.end_headers()
self._state = "flush"
@@ -447,6 +447,18 @@ class WebSocket(object):
return len(msg)
def send_response(self, code, message):
self._queue_str("HTTP/1.1 %d %s\r\n" % (code, message))
def send_header(self, keyword, value):
self._queue_str("%s: %s\r\n" % (keyword, value))
def end_headers(self):
self._queue_str("\r\n")
def send_request(self, type, path):
self._queue_str("%s %s HTTP/1.1\r\n" % (type.upper(), path))
def ping(self, data=b''):
"""Write a ping message to the WebSocket
+46 -13
View File
@@ -35,15 +35,15 @@ Traffic Legend:
< - Client send
<. - 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 not self.server.token_plugin:
return
@@ -72,7 +72,7 @@ Traffic Legend:
except (TypeError, AttributeError, KeyError):
# not a SSL connection or client presented no certificate with valid data
pass
try:
self.server.auth_plugin.authenticate(
headers=self.headers, target_host=self.server.target_host,
@@ -222,6 +222,18 @@ Traffic Legend:
tqueue.extend(bufs)
if closed:
while (len(tqueue) != 0):
# Send queued client data to the target
dat = tqueue.pop(0)
sent = target.send(dat)
if sent == len(dat):
self.print_traffic(">")
else:
# requeue the remaining data
tqueue.insert(0, dat[sent:])
self.print_traffic(".>")
# TODO: What about blocking on client socket?
if self.verbose:
self.log_message("%s:%s: Client closed connection",
@@ -245,6 +257,16 @@ Traffic Legend:
# Receive target data, encode it and queue for client
buf = target.recv(self.buffer_size)
if len(buf) == 0:
# Target socket closed, flushing queues and closing client-side websocket
# Send queued target data to the client
if len(cqueue) != 0:
c_pend = True
while(c_pend):
c_pend = self.send_frames(cqueue)
cqueue = []
if self.verbose:
self.log_message("%s:%s: Target closed connection",
self.server.target_host, self.server.target_port)
@@ -303,8 +325,11 @@ class WebSocketProxy(websockifyserver.WebSockifyServer):
self.target_port = sock.getsockname()[1]
sock.close()
# Insert rebinder at the head of the (possibly empty) LD_PRELOAD pathlist
ld_preloads = filter(None, [ self.rebinder, os.environ.get("LD_PRELOAD", None) ])
os.environ.update({
"LD_PRELOAD": self.rebinder,
"LD_PRELOAD": os.pathsep.join(ld_preloads),
"REBIND_OLD_PORT": str(kwargs['listen_port']),
"REBIND_NEW_PORT": str(self.target_port)})
@@ -403,7 +428,7 @@ def select_ssl_version(version):
# It so happens that version names sorted lexicographically form a list
# from the least to the most secure
keys = list(SSL_OPTIONS.keys())
keys.sort()
keys.sort()
fallback = keys[-1]
logger = logging.getLogger(WebSocketProxy.log_prefix)
logger.warn("TLS version %s unsupported. Falling back to %s",
@@ -413,19 +438,22 @@ def select_ssl_version(version):
def websockify_init():
# Setup basic logging to stderr.
logger = logging.getLogger(WebSocketProxy.log_prefix)
logger.propagate = False
logger.setLevel(logging.INFO)
stderr_handler = logging.StreamHandler()
stderr_handler.setLevel(logging.DEBUG)
log_formatter = logging.Formatter("%(message)s")
stderr_handler.setFormatter(log_formatter)
logger.addHandler(stderr_handler)
root = logging.getLogger()
root.addHandler(stderr_handler)
root.setLevel(logging.INFO)
# Setup optparse.
usage = "\n %prog [options]"
usage += " [source_addr:]source_port [target_addr:target_port]"
usage += "\n %prog [options]"
usage += " --token-plugin=CLASS [source_addr:]source_port"
usage += "\n %prog [options]"
usage += " --unix-target=FILE [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",
@@ -517,6 +545,8 @@ def websockify_init():
parser.add_option("--legacy-syslog", action="store_true",
help="Use the old syslog protocol instead of RFC 5424. "
"Use this if the messages produced by websockify seem abnormal.")
parser.add_option("--file-only", action="store_true",
help="use this to disable directory listings in web server.")
(opts, args) = parser.parse_args()
@@ -552,7 +582,8 @@ def websockify_init():
log_file_handler = logging.FileHandler(opts.log_file)
log_file_handler.setLevel(logging.DEBUG)
log_file_handler.setFormatter(log_formatter)
logger.addHandler(log_file_handler)
root = logging.getLogger()
root.addHandler(log_file_handler)
del opts.log_file
@@ -585,13 +616,15 @@ def websockify_init():
legacy=opts.legacy_syslog)
syslog_handler.setLevel(logging.DEBUG)
syslog_handler.setFormatter(log_formatter)
logger.addHandler(syslog_handler)
root = logging.getLogger()
root.addHandler(syslog_handler)
del opts.syslog
del opts.legacy_syslog
if opts.verbose:
logger.setLevel(logging.DEBUG)
root = logging.getLogger()
root.setLevel(logging.DEBUG)
# Transform to absolute path as daemon may chdir
+19 -4
View File
@@ -12,6 +12,23 @@ from http.server import BaseHTTPRequestHandler, HTTPServer
from websockify.websocket import WebSocket, WebSocketWantReadError, WebSocketWantWriteError
class HttpWebSocket(WebSocket):
"""Class to glue websocket and http request functionality together"""
def __init__(self, request_handler):
super().__init__()
self.request_handler = request_handler
def send_response(self, code, message=None):
self.request_handler.send_response(code, message)
def send_header(self, keyword, value):
self.request_handler.send_header(keyword, value)
def end_headers(self):
self.request_handler.end_headers()
class WebSocketRequestHandlerMixIn:
"""WebSocket request handler mix-in class
@@ -25,7 +42,7 @@ class WebSocketRequestHandlerMixIn:
use for the WebSocket connection.
"""
SocketClass = WebSocket
SocketClass = HttpWebSocket
def handle_one_request(self):
"""Extended request handler
@@ -59,7 +76,7 @@ class WebSocketRequestHandlerMixIn:
The WebSocket object will then replace the request object and
handle_websocket() will be called.
"""
websocket = self.SocketClass()
websocket = self.SocketClass(self)
try:
websocket.accept(self.request, self.headers)
except Exception:
@@ -67,8 +84,6 @@ class WebSocketRequestHandlerMixIn:
self.send_error(400, str(exc))
return
self.log_request(101)
self.request = websocket
# Other requests cannot follow Websocket data
+5 -5
View File
@@ -29,10 +29,10 @@ if sys.platform == 'win32':
# make sockets pickle-able/inheritable
import multiprocessing.reduction
from websockify.websocket import WebSocket, WebSocketWantReadError, WebSocketWantWriteError
from websockify.websocket import WebSocketWantReadError, WebSocketWantWriteError
from websockify.websocketserver import WebSocketRequestHandlerMixIn
class CompatibleWebSocket(WebSocket):
class CompatibleWebSocket(WebSocketRequestHandlerMixIn.SocketClass):
def select_subprotocol(self, protocols):
# Handle old websockify clients that still specify a sub-protocol
if 'binary' in protocols:
@@ -250,13 +250,13 @@ class WebSockifyRequestHandler(WebSocketRequestHandlerMixIn, SimpleHTTPRequestHa
self.auth_connection()
if self.only_upgrade:
self.send_error(405, "Method Not Allowed")
self.send_error(405)
else:
super().do_GET()
def list_directory(self, path):
if self.file_only:
self.send_error(404, "No such file")
self.send_error(404)
else:
return super().list_directory(path)
@@ -277,7 +277,7 @@ class WebSockifyRequestHandler(WebSocketRequestHandlerMixIn, SimpleHTTPRequestHa
self.auth_connection()
if self.only_upgrade:
self.send_error(405, "Method Not Allowed")
self.send_error(405)
else:
super().do_HEAD()