Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 43c371fc7a | |||
| 354668143f | |||
| e28b7c8c75 | |||
| a1346552fb | |||
| 3d2e93aeb0 | |||
| e23d4e337c | |||
| 8121a5265a | |||
| 5dd81a0363 | |||
| 7f53e9c22c | |||
| d54020538d | |||
| ac74ade2ca | |||
| 9ac3272d2b | |||
| 5d17281187 | |||
| 789b80f719 | |||
| 06e61fa4cc | |||
| 4f9d496dfe | |||
| 1979e92f0e | |||
| 63eb24dd8e | |||
| d2affc73b5 | |||
| 27ee353401 | |||
| 39c7cb0115 | |||
| be7b868518 | |||
| 4695f96728 | |||
| 832ae23f00 | |||
| c123bfbbff | |||
| 70579756f3 | |||
| e9c80aa32a | |||
| caef680fff | |||
| 7133f85df6 | |||
| 33910d758d | |||
| 0f17500348 | |||
| 4b194636e2 | |||
| ffa31d6c2c | |||
| dec26a6a2d | |||
| d96ecf0859 | |||
| d0ad4a1b2a | |||
| e1f903b9e8 | |||
| 62ac4aeb03 | |||
| e4cff3746d | |||
| d5e8d709d7 | |||
| dc345815c0 |
@@ -4,16 +4,17 @@ on: [push, pull_request]
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-18.04
|
runs-on: ubuntu-20.04
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version:
|
python-version:
|
||||||
- 3.4
|
|
||||||
- 3.5
|
- 3.5
|
||||||
- 3.6
|
- 3.6
|
||||||
- 3.7
|
- 3.7
|
||||||
- 3.8
|
- 3.8
|
||||||
- 3.9
|
- 3.9
|
||||||
|
- "3.10"
|
||||||
|
- 3.11
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
@@ -21,10 +22,13 @@ jobs:
|
|||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
- name: Install dependencies
|
- name: Update pip and setuptools
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
python -m pip install setuptools
|
python -m pip install setuptools
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install -e .
|
||||||
python -m pip install -r test-requirements.txt
|
python -m pip install -r test-requirements.txt
|
||||||
- name: Install old numpy
|
- name: Install old numpy
|
||||||
run: |
|
run: |
|
||||||
@@ -32,4 +36,4 @@ jobs:
|
|||||||
if: ${{ matrix.python-version >= '3.4' && matrix.python-version < '3.7' }}
|
if: ${{ matrix.python-version >= '3.4' && matrix.python-version < '3.7' }}
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: |
|
run: |
|
||||||
python setup.py nosetests --verbosity=3
|
python -m nose2 --verbosity=3
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
Changes
|
Changes
|
||||||
=======
|
=======
|
||||||
|
|
||||||
|
0.11.0
|
||||||
|
------
|
||||||
|
|
||||||
|
* Command line now supports disabling directory listings
|
||||||
|
* Basic Dockerfile included
|
||||||
|
|
||||||
0.10.0
|
0.10.0
|
||||||
------
|
------
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,12 @@ 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
|
by opening a WSS socket with invalid certificate, hence you need to have it
|
||||||
accept it by either of those two methods.
|
accept it by either of those two methods.
|
||||||
|
|
||||||
|
The ports may be considered as distinguishing connections by the browser,
|
||||||
|
for example, if your website url is https://my.local:8443 and your WebSocket
|
||||||
|
url is wss://my.local:8001, first browse to https://my.local:8001, add the
|
||||||
|
exception, then browse to https://my.local:8443 and add another exception.
|
||||||
|
Then an html page served over :8443 will be able to open WSS to :8001
|
||||||
|
|
||||||
If you have a commercial/valid SSL certificate with one or more intermediate
|
If you have a commercial/valid SSL certificate with one or more intermediate
|
||||||
certificates, concat them into one file, server certificate first, then the
|
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
|
intermediate(s) from the CA, etc. Point to this file with the `--cert` option
|
||||||
@@ -168,3 +174,35 @@ before running `python3 setup.py install`.
|
|||||||
|
|
||||||
Afterwards, websockify should be available in your path. Run
|
Afterwards, websockify should be available in your path. Run
|
||||||
`websockify --help` to confirm it's installed correctly.
|
`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
|
||||||
|
```
|
||||||
|
|||||||
@@ -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"]
|
||||||
Executable
+5
@@ -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 .
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
from setuptools import setup, find_packages
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
version = '0.10.0'
|
version = '0.11.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"
|
||||||
@@ -29,7 +29,11 @@ setup(name=name,
|
|||||||
|
|
||||||
packages=['websockify'],
|
packages=['websockify'],
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
install_requires=['numpy'],
|
install_requires=[
|
||||||
|
'numpy', 'requests',
|
||||||
|
'jwcrypto',
|
||||||
|
'redis',
|
||||||
|
],
|
||||||
zip_safe=False,
|
zip_safe=False,
|
||||||
entry_points={
|
entry_points={
|
||||||
'console_scripts': [
|
'console_scripts': [
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
mock
|
mock
|
||||||
nose
|
nose2
|
||||||
jwcrypto
|
|
||||||
six
|
six
|
||||||
redis
|
redis
|
||||||
simplejson
|
wrapt<=1.12.1;python_version<="3.4"
|
||||||
|
|||||||
+188
-12
@@ -4,9 +4,9 @@
|
|||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
from unittest.mock import patch, mock_open, MagicMock
|
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):
|
class ReadOnlyTokenFileTestCase(unittest.TestCase):
|
||||||
patch('os.path.isdir', MagicMock(return_value=False))
|
patch('os.path.isdir', MagicMock(return_value=False))
|
||||||
@@ -56,7 +56,7 @@ class JWSTokenTestCase(unittest.TestCase):
|
|||||||
def test_asymmetric_jws_token_plugin(self):
|
def test_asymmetric_jws_token_plugin(self):
|
||||||
plugin = JWTTokenApi("./tests/fixtures/public.pem")
|
plugin = JWTTokenApi("./tests/fixtures/public.pem")
|
||||||
|
|
||||||
key = jwt.JWK()
|
key = jwk.JWK()
|
||||||
private_key = open("./tests/fixtures/private.pem", "rb").read()
|
private_key = open("./tests/fixtures/private.pem", "rb").read()
|
||||||
key.import_from_pem(private_key)
|
key.import_from_pem(private_key)
|
||||||
jwt_token = jwt.JWT({"alg": "RS256"}, {'host': "remote_host", 'port': "remote_port"})
|
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):
|
def test_asymmetric_jws_token_plugin_with_illigal_key_exception(self):
|
||||||
plugin = JWTTokenApi("wrong.pub")
|
plugin = JWTTokenApi("wrong.pub")
|
||||||
|
|
||||||
key = jwt.JWK()
|
key = jwk.JWK()
|
||||||
private_key = open("./tests/fixtures/private.pem", "rb").read()
|
private_key = open("./tests/fixtures/private.pem", "rb").read()
|
||||||
key.import_from_pem(private_key)
|
key.import_from_pem(private_key)
|
||||||
jwt_token = jwt.JWT({"alg": "RS256"}, {'host': "remote_host", 'port': "remote_port"})
|
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):
|
def test_jwt_valid_time(self, mock_time):
|
||||||
plugin = JWTTokenApi("./tests/fixtures/public.pem")
|
plugin = JWTTokenApi("./tests/fixtures/public.pem")
|
||||||
|
|
||||||
key = jwt.JWK()
|
key = jwk.JWK()
|
||||||
private_key = open("./tests/fixtures/private.pem", "rb").read()
|
private_key = open("./tests/fixtures/private.pem", "rb").read()
|
||||||
key.import_from_pem(private_key)
|
key.import_from_pem(private_key)
|
||||||
jwt_token = jwt.JWT({"alg": "RS256"}, {'host': "remote_host", 'port': "remote_port", 'nbf': 100, 'exp': 200 })
|
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):
|
def test_jwt_early_time(self, mock_time):
|
||||||
plugin = JWTTokenApi("./tests/fixtures/public.pem")
|
plugin = JWTTokenApi("./tests/fixtures/public.pem")
|
||||||
|
|
||||||
key = jwt.JWK()
|
key = jwk.JWK()
|
||||||
private_key = open("./tests/fixtures/private.pem", "rb").read()
|
private_key = open("./tests/fixtures/private.pem", "rb").read()
|
||||||
key.import_from_pem(private_key)
|
key.import_from_pem(private_key)
|
||||||
jwt_token = jwt.JWT({"alg": "RS256"}, {'host': "remote_host", 'port': "remote_port", 'nbf': 100, 'exp': 200 })
|
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):
|
def test_jwt_late_time(self, mock_time):
|
||||||
plugin = JWTTokenApi("./tests/fixtures/public.pem")
|
plugin = JWTTokenApi("./tests/fixtures/public.pem")
|
||||||
|
|
||||||
key = jwt.JWK()
|
key = jwk.JWK()
|
||||||
private_key = open("./tests/fixtures/private.pem", "rb").read()
|
private_key = open("./tests/fixtures/private.pem", "rb").read()
|
||||||
key.import_from_pem(private_key)
|
key.import_from_pem(private_key)
|
||||||
jwt_token = jwt.JWT({"alg": "RS256"}, {'host': "remote_host", 'port': "remote_port", 'nbf': 100, 'exp': 200 })
|
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")
|
plugin = JWTTokenApi("./tests/fixtures/symmetric.key")
|
||||||
|
|
||||||
secret = open("./tests/fixtures/symmetric.key").read()
|
secret = open("./tests/fixtures/symmetric.key").read()
|
||||||
key = jwt.JWK()
|
key = jwk.JWK()
|
||||||
key.import_key(kty="oct",k=secret)
|
key.import_key(kty="oct",k=secret)
|
||||||
jwt_token = jwt.JWT({"alg": "HS256"}, {'host': "remote_host", 'port': "remote_port"})
|
jwt_token = jwt.JWT({"alg": "HS256"}, {'host': "remote_host", 'port': "remote_port"})
|
||||||
jwt_token.make_signed_token(key)
|
jwt_token.make_signed_token(key)
|
||||||
@@ -147,7 +147,7 @@ class JWSTokenTestCase(unittest.TestCase):
|
|||||||
plugin = JWTTokenApi("wrong_sauce")
|
plugin = JWTTokenApi("wrong_sauce")
|
||||||
|
|
||||||
secret = open("./tests/fixtures/symmetric.key").read()
|
secret = open("./tests/fixtures/symmetric.key").read()
|
||||||
key = jwt.JWK()
|
key = jwk.JWK()
|
||||||
key.import_key(kty="oct",k=secret)
|
key.import_key(kty="oct",k=secret)
|
||||||
jwt_token = jwt.JWT({"alg": "HS256"}, {'host': "remote_host", 'port': "remote_port"})
|
jwt_token = jwt.JWT({"alg": "HS256"}, {'host': "remote_host", 'port': "remote_port"})
|
||||||
jwt_token.make_signed_token(key)
|
jwt_token.make_signed_token(key)
|
||||||
@@ -159,15 +159,15 @@ class JWSTokenTestCase(unittest.TestCase):
|
|||||||
def test_asymmetric_jwe_token_plugin(self):
|
def test_asymmetric_jwe_token_plugin(self):
|
||||||
plugin = JWTTokenApi("./tests/fixtures/private.pem")
|
plugin = JWTTokenApi("./tests/fixtures/private.pem")
|
||||||
|
|
||||||
private_key = jwt.JWK()
|
private_key = jwk.JWK()
|
||||||
public_key = jwt.JWK()
|
public_key = jwk.JWK()
|
||||||
private_key_data = open("./tests/fixtures/private.pem", "rb").read()
|
private_key_data = open("./tests/fixtures/private.pem", "rb").read()
|
||||||
public_key_data = open("./tests/fixtures/public.pem", "rb").read()
|
public_key_data = open("./tests/fixtures/public.pem", "rb").read()
|
||||||
private_key.import_from_pem(private_key_data)
|
private_key.import_from_pem(private_key_data)
|
||||||
public_key.import_from_pem(public_key_data)
|
public_key.import_from_pem(public_key_data)
|
||||||
jwt_token = jwt.JWT({"alg": "RS256"}, {'host': "remote_host", 'port': "remote_port"})
|
jwt_token = jwt.JWT({"alg": "RS256"}, {'host': "remote_host", 'port': "remote_port"})
|
||||||
jwt_token.make_signed_token(private_key)
|
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())
|
claims=jwt_token.serialize())
|
||||||
jwe_token.make_encrypted_token(public_key)
|
jwe_token.make_encrypted_token(public_key)
|
||||||
|
|
||||||
@@ -177,3 +177,179 @@ class JWSTokenTestCase(unittest.TestCase):
|
|||||||
self.assertEqual(result[0], "remote_host")
|
self.assertEqual(result[0], "remote_host")
|
||||||
self.assertEqual(result[1], "remote_port")
|
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')
|
||||||
|
|
||||||
|
@patch('redis.Redis')
|
||||||
|
def test_json_token_with_spaces(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')
|
||||||
|
|
||||||
|
@patch('redis.Redis')
|
||||||
|
def test_text_token(self, mock_redis):
|
||||||
|
plugin = TokenRedis('127.0.0.1:1234')
|
||||||
|
|
||||||
|
instance = mock_redis.return_value
|
||||||
|
instance.get.return_value = b'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')
|
||||||
|
|
||||||
|
@patch('redis.Redis')
|
||||||
|
def test_text_token_with_spaces(self, mock_redis):
|
||||||
|
plugin = TokenRedis('127.0.0.1:1234')
|
||||||
|
|
||||||
|
instance = mock_redis.return_value
|
||||||
|
instance.get.return_value = b' 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')
|
||||||
|
|
||||||
|
@patch('redis.Redis')
|
||||||
|
def test_invalid_token(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.assertIsNone(result)
|
||||||
|
|
||||||
|
def test_src_only_host(self):
|
||||||
|
plugin = TokenRedis('127.0.0.1')
|
||||||
|
|
||||||
|
self.assertEqual(plugin._server, '127.0.0.1')
|
||||||
|
self.assertEqual(plugin._port, 6379)
|
||||||
|
self.assertEqual(plugin._db, 0)
|
||||||
|
self.assertEqual(plugin._password, None)
|
||||||
|
|
||||||
|
def test_src_with_host_port(self):
|
||||||
|
plugin = TokenRedis('127.0.0.1:1234')
|
||||||
|
|
||||||
|
self.assertEqual(plugin._server, '127.0.0.1')
|
||||||
|
self.assertEqual(plugin._port, 1234)
|
||||||
|
self.assertEqual(plugin._db, 0)
|
||||||
|
self.assertEqual(plugin._password, None)
|
||||||
|
|
||||||
|
def test_src_with_host_port_db(self):
|
||||||
|
plugin = TokenRedis('127.0.0.1:1234:2')
|
||||||
|
|
||||||
|
self.assertEqual(plugin._server, '127.0.0.1')
|
||||||
|
self.assertEqual(plugin._port, 1234)
|
||||||
|
self.assertEqual(plugin._db, 2)
|
||||||
|
self.assertEqual(plugin._password, None)
|
||||||
|
|
||||||
|
def test_src_with_host_port_db_pass(self):
|
||||||
|
plugin = TokenRedis('127.0.0.1:1234:2:verysecret')
|
||||||
|
|
||||||
|
self.assertEqual(plugin._server, '127.0.0.1')
|
||||||
|
self.assertEqual(plugin._port, 1234)
|
||||||
|
self.assertEqual(plugin._db, 2)
|
||||||
|
self.assertEqual(plugin._password, 'verysecret')
|
||||||
|
|
||||||
|
def test_src_with_host_empty_port_empty_db_pass(self):
|
||||||
|
plugin = TokenRedis('127.0.0.1:::verysecret')
|
||||||
|
|
||||||
|
self.assertEqual(plugin._server, '127.0.0.1')
|
||||||
|
self.assertEqual(plugin._port, 6379)
|
||||||
|
self.assertEqual(plugin._db, 0)
|
||||||
|
self.assertEqual(plugin._password, 'verysecret')
|
||||||
|
|
||||||
|
def test_src_with_host_empty_port_empty_db_empty_pass(self):
|
||||||
|
plugin = TokenRedis('127.0.0.1:::')
|
||||||
|
|
||||||
|
self.assertEqual(plugin._server, '127.0.0.1')
|
||||||
|
self.assertEqual(plugin._port, 6379)
|
||||||
|
self.assertEqual(plugin._db, 0)
|
||||||
|
self.assertEqual(plugin._password, None)
|
||||||
|
|
||||||
|
def test_src_with_host_empty_port_empty_db_no_pass(self):
|
||||||
|
plugin = TokenRedis('127.0.0.1::')
|
||||||
|
|
||||||
|
self.assertEqual(plugin._server, '127.0.0.1')
|
||||||
|
self.assertEqual(plugin._port, 6379)
|
||||||
|
self.assertEqual(plugin._db, 0)
|
||||||
|
self.assertEqual(plugin._password, None)
|
||||||
|
|
||||||
|
def test_src_with_host_empty_port_no_db_no_pass(self):
|
||||||
|
plugin = TokenRedis('127.0.0.1:')
|
||||||
|
|
||||||
|
self.assertEqual(plugin._server, '127.0.0.1')
|
||||||
|
self.assertEqual(plugin._port, 6379)
|
||||||
|
self.assertEqual(plugin._db, 0)
|
||||||
|
self.assertEqual(plugin._password, None)
|
||||||
|
|
||||||
|
def test_src_with_host_empty_port_db_no_pass(self):
|
||||||
|
plugin = TokenRedis('127.0.0.1::2')
|
||||||
|
|
||||||
|
self.assertEqual(plugin._server, '127.0.0.1')
|
||||||
|
self.assertEqual(plugin._port, 6379)
|
||||||
|
self.assertEqual(plugin._db, 2)
|
||||||
|
self.assertEqual(plugin._password, None)
|
||||||
|
|
||||||
|
def test_src_with_host_port_empty_db_pass(self):
|
||||||
|
plugin = TokenRedis('127.0.0.1:1234::verysecret')
|
||||||
|
|
||||||
|
self.assertEqual(plugin._server, '127.0.0.1')
|
||||||
|
self.assertEqual(plugin._port, 1234)
|
||||||
|
self.assertEqual(plugin._db, 0)
|
||||||
|
self.assertEqual(plugin._password, 'verysecret')
|
||||||
|
|
||||||
|
def test_src_with_host_empty_port_db_pass(self):
|
||||||
|
plugin = TokenRedis('127.0.0.1::2:verysecret')
|
||||||
|
|
||||||
|
self.assertEqual(plugin._server, '127.0.0.1')
|
||||||
|
self.assertEqual(plugin._port, 6379)
|
||||||
|
self.assertEqual(plugin._db, 2)
|
||||||
|
self.assertEqual(plugin._password, 'verysecret')
|
||||||
|
|
||||||
|
def test_src_with_host_empty_port_db_empty_pass(self):
|
||||||
|
plugin = TokenRedis('127.0.0.1::2:')
|
||||||
|
|
||||||
|
self.assertEqual(plugin._server, '127.0.0.1')
|
||||||
|
self.assertEqual(plugin._port, 6379)
|
||||||
|
self.assertEqual(plugin._db, 2)
|
||||||
|
self.assertEqual(plugin._password, None)
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|
||||||
@@ -86,7 +86,7 @@ class WebSockifyRequestHandlerTestCase(unittest.TestCase):
|
|||||||
FakeSocket(b'GET /tmp.txt HTTP/1.1'), '127.0.0.1', server)
|
FakeSocket(b'GET /tmp.txt HTTP/1.1'), '127.0.0.1', server)
|
||||||
|
|
||||||
handler.do_GET()
|
handler.do_GET()
|
||||||
send_error.assert_called_with(405, ANY)
|
send_error.assert_called_with(405)
|
||||||
|
|
||||||
@patch('websockify.websockifyserver.WebSockifyRequestHandler.send_error')
|
@patch('websockify.websockifyserver.WebSockifyRequestHandler.send_error')
|
||||||
def test_list_dir_with_file_only_returns_error(self, 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.path = '/'
|
||||||
handler.do_GET()
|
handler.do_GET()
|
||||||
send_error.assert_called_with(404, ANY)
|
send_error.assert_called_with(404)
|
||||||
|
|
||||||
|
|
||||||
class WebSockifyServerTestCase(unittest.TestCase):
|
class WebSockifyServerTestCase(unittest.TestCase):
|
||||||
|
|||||||
+121
-37
@@ -1,7 +1,12 @@
|
|||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import re
|
import re
|
||||||
|
import json
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class BasePlugin():
|
class BasePlugin():
|
||||||
def __init__(self, src):
|
def __init__(self, src):
|
||||||
@@ -35,7 +40,7 @@ class ReadOnlyTokenFile(BasePlugin):
|
|||||||
tok, target = re.split(':\s', line)
|
tok, target = re.split(':\s', line)
|
||||||
self._targets[tok] = target.strip().rsplit(':', 1)
|
self._targets[tok] = target.strip().rsplit(':', 1)
|
||||||
except ValueError:
|
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
|
index += 1
|
||||||
|
|
||||||
def lookup(self, token):
|
def lookup(self, token):
|
||||||
@@ -99,16 +104,16 @@ class JWTTokenApi(BasePlugin):
|
|||||||
|
|
||||||
def lookup(self, token):
|
def lookup(self, token):
|
||||||
try:
|
try:
|
||||||
from jwcrypto import jwt
|
from jwcrypto import jwt, jwk
|
||||||
import json
|
import json
|
||||||
|
|
||||||
key = jwt.JWK()
|
key = jwk.JWK()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(self.source, 'rb') as key_file:
|
with open(self.source, 'rb') as key_file:
|
||||||
key_data = key_file.read()
|
key_data = key_file.read()
|
||||||
except Exception as e:
|
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
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -117,7 +122,7 @@ class JWTTokenApi(BasePlugin):
|
|||||||
try:
|
try:
|
||||||
key.import_key(k=key_data.decode('utf-8'),kty='oct')
|
key.import_key(k=key_data.decode('utf-8'),kty='oct')
|
||||||
except:
|
except:
|
||||||
print('Failed to correctly parse key data!', file=sys.stderr)
|
logger.error('Failed to correctly parse key data!')
|
||||||
return None
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -133,74 +138,153 @@ class JWTTokenApi(BasePlugin):
|
|||||||
if 'nbf' in parsed:
|
if 'nbf' in parsed:
|
||||||
# Not Before is present, so we need to check it
|
# Not Before is present, so we need to check it
|
||||||
if time.time() < parsed['nbf']:
|
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
|
return None
|
||||||
|
|
||||||
if 'exp' in parsed:
|
if 'exp' in parsed:
|
||||||
# Expiration time is present, so we need to check it
|
# Expiration time is present, so we need to check it
|
||||||
if time.time() > parsed['exp']:
|
if time.time() > parsed['exp']:
|
||||||
print('Token has expired!', file=sys.stderr)
|
logger.warning('Token has expired!')
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return (parsed['host'], parsed['port'])
|
return (parsed['host'], parsed['port'])
|
||||||
except Exception as e:
|
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
|
return None
|
||||||
except ImportError as e:
|
except ImportError:
|
||||||
print("package jwcrypto not found, are you sure you've installed it correctly?", file=sys.stderr)
|
logger.error("package jwcrypto not found, are you sure you've installed it correctly?")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
class TokenRedis():
|
|
||||||
"""
|
class TokenRedis(BasePlugin):
|
||||||
The TokenRedis plugin expects the format of the data in a form of json.
|
"""Token plugin based on the Redis in-memory data store.
|
||||||
|
|
||||||
|
The token source is in the format:
|
||||||
|
|
||||||
|
host[:port[:db[:password]]]
|
||||||
|
|
||||||
|
where port, db and password are optional. If port or db are left empty
|
||||||
|
they will take its default value, ie. 6379 and 0 respectively.
|
||||||
|
|
||||||
|
If your redis server is using the default port (6379) then you can use:
|
||||||
|
|
||||||
|
my-redis-host
|
||||||
|
|
||||||
|
In case you need to authenticate with the redis server and you are using
|
||||||
|
the default database and port you can use:
|
||||||
|
|
||||||
|
my-redis-host:::verysecretpass
|
||||||
|
|
||||||
|
In the more general case you will use:
|
||||||
|
|
||||||
|
my-redis-host:6380:1:verysecretpass
|
||||||
|
|
||||||
|
The TokenRedis plugin expects the format of the target in one of these two
|
||||||
|
formats:
|
||||||
|
|
||||||
|
- JSON
|
||||||
|
|
||||||
|
{"host": "target-host:target-port"}
|
||||||
|
|
||||||
|
- Plain text
|
||||||
|
|
||||||
|
target-host:target-port
|
||||||
|
|
||||||
Prepare data with:
|
Prepare data with:
|
||||||
redis-cli set hello '{"host":"127.0.0.1:5000"}'
|
|
||||||
|
redis-cli set my-token '{"host": "127.0.0.1:5000"}'
|
||||||
|
|
||||||
Verify with:
|
Verify with:
|
||||||
redis-cli --raw get hello
|
|
||||||
|
redis-cli --raw get my-token
|
||||||
|
|
||||||
Spawn a test "server" using netcat
|
Spawn a test "server" using netcat
|
||||||
|
|
||||||
nc -l 5000 -v
|
nc -l 5000 -v
|
||||||
|
|
||||||
Note: you have to install also the 'redis' and 'simplejson' modules
|
Note: This Token Plugin depends on the 'redis' module, so you have
|
||||||
pip install redis simplejson
|
to install it before using this plugin:
|
||||||
|
|
||||||
|
pip install redis
|
||||||
"""
|
"""
|
||||||
def __init__(self, src):
|
def __init__(self, src):
|
||||||
try:
|
try:
|
||||||
# import those ahead of time so we provide error earlier
|
|
||||||
import redis
|
import redis
|
||||||
import simplejson
|
|
||||||
self._server, self._port = src.split(":")
|
|
||||||
print("TokenRedis backend initilized (%s:%s)" %
|
|
||||||
(self._server, self._port), file=sys.stderr)
|
|
||||||
except ValueError:
|
|
||||||
print("The provided --token-source='%s' is not in an expected format <host>:<port>" %
|
|
||||||
src, file=sys.stderr)
|
|
||||||
sys.exit()
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
print("package redis or simplejson not found, are you sure you've installed them correctly?", file=sys.stderr)
|
logger.error("Unable to load redis module")
|
||||||
|
sys.exit()
|
||||||
|
# Default values
|
||||||
|
self._port = 6379
|
||||||
|
self._db = 0
|
||||||
|
self._password = None
|
||||||
|
try:
|
||||||
|
fields = src.split(":")
|
||||||
|
if len(fields) == 1:
|
||||||
|
self._server = fields[0]
|
||||||
|
elif len(fields) == 2:
|
||||||
|
self._server, self._port = fields
|
||||||
|
if not self._port:
|
||||||
|
self._port = 6379
|
||||||
|
elif len(fields) == 3:
|
||||||
|
self._server, self._port, self._db = fields
|
||||||
|
if not self._port:
|
||||||
|
self._port = 6379
|
||||||
|
if not self._db:
|
||||||
|
self._db = 0
|
||||||
|
elif len(fields) == 4:
|
||||||
|
self._server, self._port, self._db, self._password = fields
|
||||||
|
if not self._port:
|
||||||
|
self._port = 6379
|
||||||
|
if not self._db:
|
||||||
|
self._db = 0
|
||||||
|
if not self._password:
|
||||||
|
self._password = None
|
||||||
|
else:
|
||||||
|
raise ValueError
|
||||||
|
self._port = int(self._port)
|
||||||
|
self._db = int(self._db)
|
||||||
|
logger.info("TokenRedis backend initilized (%s:%s)" %
|
||||||
|
(self._server, self._port))
|
||||||
|
except ValueError:
|
||||||
|
logger.error("The provided --token-source='%s' is not in the "
|
||||||
|
"expected format <host>[:<port>[:<db>[:<password>]]]" %
|
||||||
|
src)
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
def lookup(self, token):
|
def lookup(self, token):
|
||||||
try:
|
try:
|
||||||
import redis
|
import redis
|
||||||
import simplejson
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
print("package redis or simplejson not found, are you sure you've installed them correctly?", file=sys.stderr)
|
logger.error("package redis not found, are you sure you've installed them correctly?")
|
||||||
sys.exit()
|
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)
|
client = redis.Redis(host=self._server, port=self._port,
|
||||||
|
db=self._db, password=self._password)
|
||||||
stuff = client.get(token)
|
stuff = client.get(token)
|
||||||
if stuff is None:
|
if stuff is None:
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
responseStr = stuff.decode("utf-8")
|
responseStr = stuff.decode("utf-8").strip()
|
||||||
print("response from redis : %s" % responseStr, file=sys.stderr)
|
logger.debug("response from redis : %s" % responseStr)
|
||||||
combo = simplejson.loads(responseStr)
|
if responseStr.startswith("{"):
|
||||||
(host, port) = combo["host"].split(':')
|
try:
|
||||||
print("host: %s, port: %s" % (host,port), file=sys.stderr)
|
combo = json.loads(responseStr)
|
||||||
|
host, port = combo["host"].split(":")
|
||||||
|
except ValueError:
|
||||||
|
logger.error("Unable to decode JSON token: %s" %
|
||||||
|
responseStr)
|
||||||
|
return None
|
||||||
|
except KeyError:
|
||||||
|
logger.error("Unable to find 'host' key in JSON token: %s" %
|
||||||
|
responseStr)
|
||||||
|
return None
|
||||||
|
elif re.match(r'\S+:\S+', responseStr):
|
||||||
|
host, port = responseStr.split(":")
|
||||||
|
else:
|
||||||
|
logger.error("Unable to parse token: %s" % responseStr)
|
||||||
|
return None
|
||||||
|
logger.debug("host: %s, port: %s" % (host, port))
|
||||||
return [host, port]
|
return [host, port]
|
||||||
|
|
||||||
|
|
||||||
@@ -228,5 +312,5 @@ class UnixDomainSocketDirectory(BasePlugin):
|
|||||||
|
|
||||||
return [ 'unix_socket', uds_path ]
|
return [ 'unix_socket', uds_path ]
|
||||||
except Exception as e:
|
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
|
return None
|
||||||
|
|||||||
+28
-16
@@ -158,19 +158,19 @@ class WebSocket(object):
|
|||||||
if not path:
|
if not path:
|
||||||
path = "/"
|
path = "/"
|
||||||
|
|
||||||
self._queue_str("GET %s HTTP/1.1\r\n" % path)
|
self.send_request("GET", path)
|
||||||
self._queue_str("Host: %s\r\n" % uri.hostname)
|
self.send_header("Host", uri.hostname)
|
||||||
self._queue_str("Upgrade: websocket\r\n")
|
self.send_header("Upgrade", "websocket")
|
||||||
self._queue_str("Connection: upgrade\r\n")
|
self.send_header("Connection", "upgrade")
|
||||||
self._queue_str("Sec-WebSocket-Key: %s\r\n" % self._key)
|
self.send_header("Sec-WebSocket-Key", self._key)
|
||||||
self._queue_str("Sec-WebSocket-Version: 13\r\n")
|
self.send_header("Sec-WebSocket-Version", 13)
|
||||||
|
|
||||||
if origin is not None:
|
if origin is not None:
|
||||||
self._queue_str("Origin: %s\r\n" % origin)
|
self.send_header("Origin", origin)
|
||||||
if len(protocols) > 0:
|
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"
|
self._state = "send_headers"
|
||||||
|
|
||||||
@@ -283,15 +283,15 @@ class WebSocket(object):
|
|||||||
if self.protocol not in protocols:
|
if self.protocol not in protocols:
|
||||||
raise Exception('Invalid protocol selected')
|
raise Exception('Invalid protocol selected')
|
||||||
|
|
||||||
self._queue_str("HTTP/1.1 101 Switching Protocols\r\n")
|
self.send_response(101, "Switching Protocols")
|
||||||
self._queue_str("Upgrade: websocket\r\n")
|
self.send_header("Upgrade", "websocket")
|
||||||
self._queue_str("Connection: Upgrade\r\n")
|
self.send_header("Connection", "Upgrade")
|
||||||
self._queue_str("Sec-WebSocket-Accept: %s\r\n" % accept)
|
self.send_header("Sec-WebSocket-Accept", accept)
|
||||||
|
|
||||||
if self.protocol:
|
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"
|
self._state = "flush"
|
||||||
|
|
||||||
@@ -447,6 +447,18 @@ class WebSocket(object):
|
|||||||
|
|
||||||
return len(msg)
|
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''):
|
def ping(self, data=b''):
|
||||||
"""Write a ping message to the WebSocket
|
"""Write a ping message to the WebSocket
|
||||||
|
|
||||||
@@ -654,7 +666,7 @@ class WebSocket(object):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
if code is None:
|
if code is None:
|
||||||
self.close_code = code = 1005
|
self.close_code = 1005
|
||||||
self.close_reason = "No close status code specified by peer"
|
self.close_reason = "No close status code specified by peer"
|
||||||
else:
|
else:
|
||||||
self.close_code = code
|
self.close_code = code
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ as taken from http://docs.python.org/dev/library/ssl.html#certificates
|
|||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import signal, socket, optparse, time, os, sys, subprocess, logging, errno, ssl
|
import signal, socket, optparse, time, os, sys, subprocess, logging, errno, ssl, stat
|
||||||
from socketserver import ThreadingMixIn
|
from socketserver import ThreadingMixIn
|
||||||
from http.server import HTTPServer
|
from http.server import HTTPServer
|
||||||
|
|
||||||
@@ -112,7 +112,9 @@ Traffic Legend:
|
|||||||
self.server.target_host, self.server.target_port, e)
|
self.server.target_host, self.server.target_port, e)
|
||||||
raise self.CClose(1011, "Failed to connect to downstream server")
|
raise self.CClose(1011, "Failed to connect to downstream server")
|
||||||
|
|
||||||
self.request.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)
|
# Option unavailable when listening to unix socket
|
||||||
|
if not self.server.unix_listen:
|
||||||
|
self.request.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)
|
||||||
if not self.server.wrap_cmd and not self.server.unix_target:
|
if not self.server.wrap_cmd and not self.server.unix_target:
|
||||||
tsock.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)
|
tsock.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)
|
||||||
|
|
||||||
@@ -222,6 +224,18 @@ Traffic Legend:
|
|||||||
tqueue.extend(bufs)
|
tqueue.extend(bufs)
|
||||||
|
|
||||||
if closed:
|
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?
|
# TODO: What about blocking on client socket?
|
||||||
if self.verbose:
|
if self.verbose:
|
||||||
self.log_message("%s:%s: Client closed connection",
|
self.log_message("%s:%s: Client closed connection",
|
||||||
@@ -245,6 +259,16 @@ Traffic Legend:
|
|||||||
# Receive target data, encode it and queue for client
|
# Receive target data, encode it and queue for client
|
||||||
buf = target.recv(self.buffer_size)
|
buf = target.recv(self.buffer_size)
|
||||||
if len(buf) == 0:
|
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:
|
if self.verbose:
|
||||||
self.log_message("%s:%s: Target closed connection",
|
self.log_message("%s:%s: Target closed connection",
|
||||||
self.server.target_host, self.server.target_port)
|
self.server.target_host, self.server.target_port)
|
||||||
@@ -303,8 +327,11 @@ class WebSocketProxy(websockifyserver.WebSockifyServer):
|
|||||||
self.target_port = sock.getsockname()[1]
|
self.target_port = sock.getsockname()[1]
|
||||||
sock.close()
|
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({
|
os.environ.update({
|
||||||
"LD_PRELOAD": self.rebinder,
|
"LD_PRELOAD": os.pathsep.join(ld_preloads),
|
||||||
"REBIND_OLD_PORT": str(kwargs['listen_port']),
|
"REBIND_OLD_PORT": str(kwargs['listen_port']),
|
||||||
"REBIND_NEW_PORT": str(self.target_port)})
|
"REBIND_NEW_PORT": str(self.target_port)})
|
||||||
|
|
||||||
@@ -413,18 +440,21 @@ def select_ssl_version(version):
|
|||||||
|
|
||||||
def websockify_init():
|
def websockify_init():
|
||||||
# Setup basic logging to stderr.
|
# Setup basic logging to stderr.
|
||||||
logger = logging.getLogger(WebSocketProxy.log_prefix)
|
|
||||||
logger.propagate = False
|
|
||||||
logger.setLevel(logging.INFO)
|
|
||||||
stderr_handler = logging.StreamHandler()
|
stderr_handler = logging.StreamHandler()
|
||||||
stderr_handler.setLevel(logging.DEBUG)
|
stderr_handler.setLevel(logging.DEBUG)
|
||||||
log_formatter = logging.Formatter("%(message)s")
|
log_formatter = logging.Formatter("%(message)s")
|
||||||
stderr_handler.setFormatter(log_formatter)
|
stderr_handler.setFormatter(log_formatter)
|
||||||
logger.addHandler(stderr_handler)
|
root = logging.getLogger()
|
||||||
|
root.addHandler(stderr_handler)
|
||||||
|
root.setLevel(logging.INFO)
|
||||||
|
|
||||||
# Setup optparse.
|
# Setup optparse.
|
||||||
usage = "\n %prog [options]"
|
usage = "\n %prog [options]"
|
||||||
usage += " [source_addr:]source_port [target_addr:target_port]"
|
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 += "\n %prog [options]"
|
||||||
usage += " [source_addr:]source_port -- WRAP_COMMAND_LINE"
|
usage += " [source_addr:]source_port -- WRAP_COMMAND_LINE"
|
||||||
parser = optparse.OptionParser(usage=usage)
|
parser = optparse.OptionParser(usage=usage)
|
||||||
@@ -467,6 +497,10 @@ def websockify_init():
|
|||||||
parser.add_option("--ssl-ciphers", action="store",
|
parser.add_option("--ssl-ciphers", action="store",
|
||||||
help="list of ciphers allowed for connection. For a list of "
|
help="list of ciphers allowed for connection. For a list of "
|
||||||
"supported ciphers run `openssl ciphers`")
|
"supported ciphers run `openssl ciphers`")
|
||||||
|
parser.add_option("--unix-listen",
|
||||||
|
help="listen to unix socket", metavar="FILE", default=None)
|
||||||
|
parser.add_option("--unix-listen-mode", default=None,
|
||||||
|
help="specify mode for unix socket (defaults to 0600)")
|
||||||
parser.add_option("--unix-target",
|
parser.add_option("--unix-target",
|
||||||
help="connect to unix socket target", metavar="FILE")
|
help="connect to unix socket target", metavar="FILE")
|
||||||
parser.add_option("--inetd",
|
parser.add_option("--inetd",
|
||||||
@@ -517,6 +551,8 @@ def websockify_init():
|
|||||||
parser.add_option("--legacy-syslog", action="store_true",
|
parser.add_option("--legacy-syslog", action="store_true",
|
||||||
help="Use the old syslog protocol instead of RFC 5424. "
|
help="Use the old syslog protocol instead of RFC 5424. "
|
||||||
"Use this if the messages produced by websockify seem abnormal.")
|
"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()
|
(opts, args) = parser.parse_args()
|
||||||
|
|
||||||
@@ -552,7 +588,8 @@ def websockify_init():
|
|||||||
log_file_handler = logging.FileHandler(opts.log_file)
|
log_file_handler = logging.FileHandler(opts.log_file)
|
||||||
log_file_handler.setLevel(logging.DEBUG)
|
log_file_handler.setLevel(logging.DEBUG)
|
||||||
log_file_handler.setFormatter(log_formatter)
|
log_file_handler.setFormatter(log_formatter)
|
||||||
logger.addHandler(log_file_handler)
|
root = logging.getLogger()
|
||||||
|
root.addHandler(log_file_handler)
|
||||||
|
|
||||||
del opts.log_file
|
del opts.log_file
|
||||||
|
|
||||||
@@ -585,13 +622,15 @@ def websockify_init():
|
|||||||
legacy=opts.legacy_syslog)
|
legacy=opts.legacy_syslog)
|
||||||
syslog_handler.setLevel(logging.DEBUG)
|
syslog_handler.setLevel(logging.DEBUG)
|
||||||
syslog_handler.setFormatter(log_formatter)
|
syslog_handler.setFormatter(log_formatter)
|
||||||
logger.addHandler(syslog_handler)
|
root = logging.getLogger()
|
||||||
|
root.addHandler(syslog_handler)
|
||||||
|
|
||||||
del opts.syslog
|
del opts.syslog
|
||||||
del opts.legacy_syslog
|
del opts.legacy_syslog
|
||||||
|
|
||||||
if opts.verbose:
|
if opts.verbose:
|
||||||
logger.setLevel(logging.DEBUG)
|
root = logging.getLogger()
|
||||||
|
root.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
|
||||||
# Transform to absolute path as daemon may chdir
|
# Transform to absolute path as daemon may chdir
|
||||||
@@ -617,6 +656,16 @@ def websockify_init():
|
|||||||
|
|
||||||
if opts.inetd:
|
if opts.inetd:
|
||||||
opts.listen_fd = sys.stdin.fileno()
|
opts.listen_fd = sys.stdin.fileno()
|
||||||
|
elif opts.unix_listen:
|
||||||
|
if opts.unix_listen_mode:
|
||||||
|
try:
|
||||||
|
# Parse octal notation (like 750)
|
||||||
|
opts.unix_listen_mode = int(opts.unix_listen_mode, 8)
|
||||||
|
except ValueError:
|
||||||
|
parser.error("Error parsing listen unix socket mode")
|
||||||
|
else:
|
||||||
|
# Default to 0600 (Owner Read/Write)
|
||||||
|
opts.unix_listen_mode = stat.S_IREAD | stat.S_IWRITE
|
||||||
else:
|
else:
|
||||||
if len(args) < 1:
|
if len(args) < 1:
|
||||||
parser.error("Too few arguments")
|
parser.error("Too few arguments")
|
||||||
|
|||||||
@@ -12,6 +12,23 @@ from http.server import BaseHTTPRequestHandler, HTTPServer
|
|||||||
|
|
||||||
from websockify.websocket import WebSocket, WebSocketWantReadError, WebSocketWantWriteError
|
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:
|
class WebSocketRequestHandlerMixIn:
|
||||||
"""WebSocket request handler mix-in class
|
"""WebSocket request handler mix-in class
|
||||||
|
|
||||||
@@ -25,7 +42,7 @@ class WebSocketRequestHandlerMixIn:
|
|||||||
use for the WebSocket connection.
|
use for the WebSocket connection.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
SocketClass = WebSocket
|
SocketClass = HttpWebSocket
|
||||||
|
|
||||||
def handle_one_request(self):
|
def handle_one_request(self):
|
||||||
"""Extended request handler
|
"""Extended request handler
|
||||||
@@ -59,7 +76,7 @@ class WebSocketRequestHandlerMixIn:
|
|||||||
The WebSocket object will then replace the request object and
|
The WebSocket object will then replace the request object and
|
||||||
handle_websocket() will be called.
|
handle_websocket() will be called.
|
||||||
"""
|
"""
|
||||||
websocket = self.SocketClass()
|
websocket = self.SocketClass(self)
|
||||||
try:
|
try:
|
||||||
websocket.accept(self.request, self.headers)
|
websocket.accept(self.request, self.headers)
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -67,8 +84,6 @@ class WebSocketRequestHandlerMixIn:
|
|||||||
self.send_error(400, str(exc))
|
self.send_error(400, str(exc))
|
||||||
return
|
return
|
||||||
|
|
||||||
self.log_request(101)
|
|
||||||
|
|
||||||
self.request = websocket
|
self.request = websocket
|
||||||
|
|
||||||
# Other requests cannot follow Websocket data
|
# Other requests cannot follow Websocket data
|
||||||
|
|||||||
@@ -29,10 +29,10 @@ if sys.platform == 'win32':
|
|||||||
# make sockets pickle-able/inheritable
|
# make sockets pickle-able/inheritable
|
||||||
import multiprocessing.reduction
|
import multiprocessing.reduction
|
||||||
|
|
||||||
from websockify.websocket import WebSocket, WebSocketWantReadError, WebSocketWantWriteError
|
from websockify.websocket import WebSocketWantReadError, WebSocketWantWriteError
|
||||||
from websockify.websocketserver import WebSocketRequestHandlerMixIn
|
from websockify.websocketserver import WebSocketRequestHandlerMixIn
|
||||||
|
|
||||||
class CompatibleWebSocket(WebSocket):
|
class CompatibleWebSocket(WebSocketRequestHandlerMixIn.SocketClass):
|
||||||
def select_subprotocol(self, protocols):
|
def select_subprotocol(self, protocols):
|
||||||
# Handle old websockify clients that still specify a sub-protocol
|
# Handle old websockify clients that still specify a sub-protocol
|
||||||
if 'binary' in protocols:
|
if 'binary' in protocols:
|
||||||
@@ -250,13 +250,13 @@ class WebSockifyRequestHandler(WebSocketRequestHandlerMixIn, SimpleHTTPRequestHa
|
|||||||
self.auth_connection()
|
self.auth_connection()
|
||||||
|
|
||||||
if self.only_upgrade:
|
if self.only_upgrade:
|
||||||
self.send_error(405, "Method Not Allowed")
|
self.send_error(405)
|
||||||
else:
|
else:
|
||||||
super().do_GET()
|
super().do_GET()
|
||||||
|
|
||||||
def list_directory(self, path):
|
def list_directory(self, path):
|
||||||
if self.file_only:
|
if self.file_only:
|
||||||
self.send_error(404, "No such file")
|
self.send_error(404)
|
||||||
else:
|
else:
|
||||||
return super().list_directory(path)
|
return super().list_directory(path)
|
||||||
|
|
||||||
@@ -277,7 +277,7 @@ class WebSockifyRequestHandler(WebSocketRequestHandlerMixIn, SimpleHTTPRequestHa
|
|||||||
self.auth_connection()
|
self.auth_connection()
|
||||||
|
|
||||||
if self.only_upgrade:
|
if self.only_upgrade:
|
||||||
self.send_error(405, "Method Not Allowed")
|
self.send_error(405)
|
||||||
else:
|
else:
|
||||||
super().do_HEAD()
|
super().do_HEAD()
|
||||||
|
|
||||||
@@ -325,37 +325,40 @@ class WebSockifyServer():
|
|||||||
file_only=False,
|
file_only=False,
|
||||||
run_once=False, timeout=0, idle_timeout=0, traffic=False,
|
run_once=False, timeout=0, idle_timeout=0, traffic=False,
|
||||||
tcp_keepalive=True, tcp_keepcnt=None, tcp_keepidle=None,
|
tcp_keepalive=True, tcp_keepcnt=None, tcp_keepidle=None,
|
||||||
tcp_keepintvl=None, ssl_ciphers=None, ssl_options=0):
|
tcp_keepintvl=None, ssl_ciphers=None, ssl_options=0,
|
||||||
|
unix_listen=None, unix_listen_mode=None):
|
||||||
|
|
||||||
# settings
|
# settings
|
||||||
self.RequestHandlerClass = RequestHandlerClass
|
self.RequestHandlerClass = RequestHandlerClass
|
||||||
self.verbose = verbose
|
self.verbose = verbose
|
||||||
self.listen_fd = listen_fd
|
self.listen_fd = listen_fd
|
||||||
self.listen_host = listen_host
|
self.unix_listen = unix_listen
|
||||||
self.listen_port = listen_port
|
self.unix_listen_mode = unix_listen_mode
|
||||||
self.prefer_ipv6 = source_is_ipv6
|
self.listen_host = listen_host
|
||||||
self.ssl_only = ssl_only
|
self.listen_port = listen_port
|
||||||
self.ssl_ciphers = ssl_ciphers
|
self.prefer_ipv6 = source_is_ipv6
|
||||||
self.ssl_options = ssl_options
|
self.ssl_only = ssl_only
|
||||||
self.verify_client = verify_client
|
self.ssl_ciphers = ssl_ciphers
|
||||||
self.daemon = daemon
|
self.ssl_options = ssl_options
|
||||||
self.run_once = run_once
|
self.verify_client = verify_client
|
||||||
self.timeout = timeout
|
self.daemon = daemon
|
||||||
self.idle_timeout = idle_timeout
|
self.run_once = run_once
|
||||||
self.traffic = traffic
|
self.timeout = timeout
|
||||||
self.file_only = file_only
|
self.idle_timeout = idle_timeout
|
||||||
self.web_auth = web_auth
|
self.traffic = traffic
|
||||||
|
self.file_only = file_only
|
||||||
|
self.web_auth = web_auth
|
||||||
|
|
||||||
self.launch_time = time.time()
|
self.launch_time = time.time()
|
||||||
self.ws_connection = False
|
self.ws_connection = False
|
||||||
self.handler_id = 1
|
self.handler_id = 1
|
||||||
self.terminating = False
|
self.terminating = False
|
||||||
|
|
||||||
self.logger = self.get_logger()
|
self.logger = self.get_logger()
|
||||||
self.tcp_keepalive = tcp_keepalive
|
self.tcp_keepalive = tcp_keepalive
|
||||||
self.tcp_keepcnt = tcp_keepcnt
|
self.tcp_keepcnt = tcp_keepcnt
|
||||||
self.tcp_keepidle = tcp_keepidle
|
self.tcp_keepidle = tcp_keepidle
|
||||||
self.tcp_keepintvl = tcp_keepintvl
|
self.tcp_keepintvl = tcp_keepintvl
|
||||||
|
|
||||||
# keyfile path must be None if not specified
|
# keyfile path must be None if not specified
|
||||||
self.key = None
|
self.key = None
|
||||||
@@ -387,6 +390,8 @@ class WebSockifyServer():
|
|||||||
self.msg("WebSocket server settings:")
|
self.msg("WebSocket server settings:")
|
||||||
if self.listen_fd != None:
|
if self.listen_fd != None:
|
||||||
self.msg(" - Listen for inetd connections")
|
self.msg(" - Listen for inetd connections")
|
||||||
|
elif self.unix_listen != None:
|
||||||
|
self.msg(" - Listen on unix socket %s", self.unix_listen)
|
||||||
else:
|
else:
|
||||||
self.msg(" - Listen on %s:%s",
|
self.msg(" - Listen on %s:%s",
|
||||||
self.listen_host, self.listen_port)
|
self.listen_host, self.listen_port)
|
||||||
@@ -421,8 +426,9 @@ class WebSockifyServer():
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def socket(host, port=None, connect=False, prefer_ipv6=False,
|
def socket(host, port=None, connect=False, prefer_ipv6=False,
|
||||||
unix_socket=None, use_ssl=False, tcp_keepalive=True,
|
unix_socket=None, unix_socket_mode=None, unix_socket_listen=False,
|
||||||
tcp_keepcnt=None, tcp_keepidle=None, tcp_keepintvl=None):
|
use_ssl=False, tcp_keepalive=True, tcp_keepcnt=None,
|
||||||
|
tcp_keepidle=None, tcp_keepintvl=None):
|
||||||
""" Resolve a host (and optional port) to an IPv4 or IPv6
|
""" Resolve a host (and optional port) to an IPv4 or IPv6
|
||||||
address. Create a socket. Bind to it if listen is set,
|
address. Create a socket. Bind to it if listen is set,
|
||||||
otherwise connect to it. Return the socket.
|
otherwise connect to it. Return the socket.
|
||||||
@@ -470,8 +476,22 @@ class WebSockifyServer():
|
|||||||
sock.bind(addrs[0][4])
|
sock.bind(addrs[0][4])
|
||||||
sock.listen(100)
|
sock.listen(100)
|
||||||
else:
|
else:
|
||||||
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
if unix_socket_listen:
|
||||||
sock.connect(unix_socket)
|
# Make sure the socket does not already exist
|
||||||
|
try:
|
||||||
|
os.unlink(unix_socket)
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||||
|
oldmask = os.umask(0o777 ^ unix_socket_mode)
|
||||||
|
try:
|
||||||
|
sock.bind(unix_socket)
|
||||||
|
finally:
|
||||||
|
os.umask(oldmask)
|
||||||
|
sock.listen(100)
|
||||||
|
else:
|
||||||
|
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||||
|
sock.connect(unix_socket)
|
||||||
|
|
||||||
return sock
|
return sock
|
||||||
|
|
||||||
@@ -700,6 +720,11 @@ class WebSockifyServer():
|
|||||||
|
|
||||||
if self.listen_fd != None:
|
if self.listen_fd != None:
|
||||||
lsock = socket.fromfd(self.listen_fd, socket.AF_INET, socket.SOCK_STREAM)
|
lsock = socket.fromfd(self.listen_fd, socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
elif self.unix_listen != None:
|
||||||
|
lsock = self.socket(host=None,
|
||||||
|
unix_socket=self.unix_listen,
|
||||||
|
unix_socket_mode=self.unix_listen_mode,
|
||||||
|
unix_socket_listen=True)
|
||||||
else:
|
else:
|
||||||
lsock = self.socket(self.listen_host, self.listen_port, False,
|
lsock = self.socket(self.listen_host, self.listen_port, False,
|
||||||
self.prefer_ipv6,
|
self.prefer_ipv6,
|
||||||
@@ -766,6 +791,9 @@ class WebSockifyServer():
|
|||||||
ready = select.select([lsock], [], [], 1)[0]
|
ready = select.select([lsock], [], [], 1)[0]
|
||||||
if lsock in ready:
|
if lsock in ready:
|
||||||
startsock, address = lsock.accept()
|
startsock, address = lsock.accept()
|
||||||
|
# Unix Socket will not report address (empty string), but address[0] is logged a bunch
|
||||||
|
if self.unix_listen != None:
|
||||||
|
address = [ self.unix_listen ]
|
||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
except self.Terminate:
|
except self.Terminate:
|
||||||
|
|||||||
Reference in New Issue
Block a user