Compare commits
95 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 | |||
| 71d55fcaa8 | |||
| 1c35fdd72b | |||
| eca301c05b | |||
| b9b269c73f | |||
| da130946eb | |||
| 100a776409 | |||
| 984dcc62d3 | |||
| c5d365dd1d | |||
| 27e0ce71ed | |||
| 24c3f75ede | |||
| 8b724074e0 | |||
| 55f1fe2603 | |||
| f1e33fa4c7 | |||
| 72704b3fe8 | |||
| 4afdcb00ab | |||
| 982e475936 | |||
| 6012052686 | |||
| e4e85f8082 | |||
| f7b0867471 | |||
| 9dab8b360b | |||
| 4afafcd152 | |||
| b22a6b0ce0 | |||
| 980237c9cd | |||
| fadb25e026 | |||
| 3f17696dc6 | |||
| a82eb10b48 | |||
| 1f618c8f41 | |||
| e9367b03be | |||
| adc278657a | |||
| 108f993eb3 | |||
| 33710b3972 | |||
| 96eda1a5c7 | |||
| e80739b296 | |||
| 4c2edaabc2 | |||
| d2155697fc | |||
| 2b44803f83 | |||
| 01ef6a6a55 | |||
| 6caf23c067 | |||
| d72ace2ae6 | |||
| 536a548db5 | |||
| 997b3897e7 | |||
| 7d4841c49a | |||
| 9851dd9012 | |||
| 9b1780f068 | |||
| e5b6dfe17f | |||
| 992e09eac4 | |||
| 663b39cd38 | |||
| 57be17d7f5 | |||
| 86a20b23f5 | |||
| 7bd955a379 | |||
| 6d48b1507e | |||
| 76897b5093 | |||
| 4ecff119c4 | |||
| 5052f823d7 |
@@ -0,0 +1,39 @@
|
||||
name: Test
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
python-version:
|
||||
- 3.5
|
||||
- 3.6
|
||||
- 3.7
|
||||
- 3.8
|
||||
- 3.9
|
||||
- "3.10"
|
||||
- 3.11
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Update pip and setuptools
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install setuptools
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install -e .
|
||||
python -m pip install -r test-requirements.txt
|
||||
- name: Install old numpy
|
||||
run: |
|
||||
python -m pip install 'numpy<1.17'
|
||||
if: ${{ matrix.python-version >= '3.4' && matrix.python-version < '3.7' }}
|
||||
- name: Run tests
|
||||
run: |
|
||||
python -m nose2 --verbosity=3
|
||||
@@ -6,4 +6,7 @@
|
||||
target.cfg
|
||||
target.cfg.d
|
||||
.tox
|
||||
|
||||
build/
|
||||
dist/
|
||||
*.egg-info
|
||||
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
language: python
|
||||
python:
|
||||
- 2.6
|
||||
- 2.7
|
||||
- 3.3
|
||||
- 3.4
|
||||
# We need trusty for Python 2.6. We can raise this when we no longer
|
||||
# care about 2.6.
|
||||
dist: trusty
|
||||
|
||||
install:
|
||||
- pip install 'setuptools>=18.5,<=39.0.0'
|
||||
- pip install -r test-requirements.txt
|
||||
|
||||
script: python setup.py nosetests --verbosity=3
|
||||
+16
@@ -1,6 +1,22 @@
|
||||
Changes
|
||||
=======
|
||||
|
||||
0.11.0
|
||||
------
|
||||
|
||||
* Command line now supports disabling directory listings
|
||||
* Basic Dockerfile included
|
||||
|
||||
0.10.0
|
||||
------
|
||||
|
||||
* Python 3.4 or newer is now required
|
||||
* Empty message frames are now supported
|
||||
* Tokens can now specify a Unix domain socket file to connect to
|
||||
* Time limits on JWT tokens are now respected
|
||||
* Whitespace is better tolerated in token files
|
||||
* Lots of minor fixes...
|
||||
|
||||
0.9.0
|
||||
-----
|
||||
|
||||
|
||||
+1
-2
@@ -1,2 +1 @@
|
||||
include CHANGES.txt README.md LICENSE.txt
|
||||
graft include
|
||||
include CHANGES.txt README.md COPYING
|
||||
|
||||
@@ -55,9 +55,15 @@ 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.
|
||||
accept it by either of those two methods.
|
||||
|
||||
If you have a commercial/valid SSL certificate with one ore more intermediate
|
||||
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
|
||||
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.
|
||||
@@ -125,7 +131,7 @@ In addition to proxying from a source address to a target address
|
||||
launch a program on the local system and proxy WebSockets traffic to
|
||||
a normal TCP port owned/bound by the program.
|
||||
|
||||
The is accomplished with a small LD_PRELOAD library (`rebind.so`)
|
||||
This is accomplished by the LD_PRELOAD library (`rebind.so`)
|
||||
which intercepts bind() system calls by the program. The specified
|
||||
port is moved to a new localhost/loopback free high port. websockify
|
||||
then proxies WebSockets traffic directed to the original port to the
|
||||
@@ -159,12 +165,44 @@ project demonstrates a simple WebSockets based telnet client (use
|
||||
### Installing websockify
|
||||
|
||||
Download one of the releases or the latest development version, extract
|
||||
it and run `python setup.py install` as root in the directory where you
|
||||
it and run `python3 setup.py install` as root in the directory where you
|
||||
extracted the files. Normally, this will also install numpy for better
|
||||
performance, if you don't have it installed already. However, numpy is
|
||||
optional. If you don't want to install numpy or if you can't compile it,
|
||||
you can edit setup.py and remove the `install_requires=['numpy'],` line
|
||||
before running `python setup.py install`.
|
||||
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
|
||||
```
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
Running Websockify as a Windows service
|
||||
=======================================
|
||||
|
||||
Installation and configuration
|
||||
------------------------------
|
||||
|
||||
Download the following software:
|
||||
|
||||
* Python, from https://www.python.org/downloads/windows/
|
||||
* SrvAny, from http://simpleauto.byethost8.com/Zip/SrvAny.zip
|
||||
|
||||
Note that there is [a modern alternative for SrvAny](https://github.com/rwmjones/rhsrvany),
|
||||
but that project does not provide binaries.
|
||||
|
||||
Install Python for all users, not just the current one. Extract Websockify
|
||||
into a directory, e.g. `C:\Program Files\websockify`, so that e.g.
|
||||
`README.md` ends up there. Extract the `SrvAny.zip` archive, copy the
|
||||
`WIN7\SrvAny.exe` file into `C:\Program Files\websockify`.
|
||||
|
||||
Then create a batch file, `C:\Program Files\websockify\run.bat`, that runs
|
||||
Websockify from its directory with the correct options under the correct
|
||||
Python interpreter:
|
||||
|
||||
```
|
||||
C:
|
||||
cd "\Program Files\websockify"
|
||||
"C:\Program Files\Python39\python.exe" -m websockify 5901 127.0.0.1:5900
|
||||
```
|
||||
|
||||
Run it by hand once so that Windows asks you about a firewall exception.
|
||||
After confirming the exception, press `Ctrl+C` to terminate the script.
|
||||
|
||||
Then create a Windows service for Websockify (use an Administrator command
|
||||
prompt for that). For paths with spaces, like in this example, double-escaping
|
||||
is needed: once for `cmd.exe` and once for `SrvAny.exe`.
|
||||
|
||||
```
|
||||
C:
|
||||
cd "\Program Files\websockify"
|
||||
SrvAny.exe -install Websockify 10s \\\"C:\Program Files\websockify\run.bat\\\"
|
||||
```
|
||||
|
||||
In the Windows Control Panel, under Services, a new "Websockify" service will
|
||||
appear. In its properties dialog, you can change the startup type, e.g. make
|
||||
it start automatically at boot. Or, you can start the service manually.
|
||||
|
||||
Uninstallation
|
||||
--------------
|
||||
|
||||
If you want to remove the service, first set its startup type to Manual, then
|
||||
reboot the PC. Then run this command using the Administrator command prompt:
|
||||
|
||||
```
|
||||
C:
|
||||
cd "\Program Files\websockify"
|
||||
SrvAny.exe -remove Websockify
|
||||
```
|
||||
|
||||
After that, you will be able to remove the `C:\Program Files\websockify`
|
||||
directory completely.
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
-----------------------------------
|
||||
Windows noVNC Websockify Service
|
||||
-----------------------------------
|
||||
|
||||
The "noVNC Websocket Service.exe" file is a windows service wrapper created with Visual Studio 2010 to create a windows service to start stop the noVNC Websocket Server. All files used to create the wrapper can be found in 'noVNC Websocket Service Project' folder.
|
||||
|
||||
To download the precompiled executables please grab the zip in the downloads section of websockify project:
|
||||
https://github.com/novnc/websockify
|
||||
|
||||
---------------------------
|
||||
Installation
|
||||
---------------------------
|
||||
|
||||
1. This service requires websockify.exe be in the same directory. Instructions on how to compile websockify python script as a windows executable can be found here:
|
||||
https://github.com/novnc/websockify/wiki/Compiling-Websockify-as-Windows-Executable
|
||||
|
||||
2.To add this service to a Windows PC you need to run the commandline as administrator and then run this line:
|
||||
|
||||
sc create "noVNC Websocket Server" binPath= "PATH TO noVNC eg C:\noVNC\utils\Windows\Websocket Service.exe" DisplayName= "noVNC Websocket Server"
|
||||
|
||||
3 .Once this is run you will be able to access the service via Control Panel > Admin Tools > Services. In here you can specify whether you want the service to run automatically and start at stop the service.
|
||||
|
||||
---------------------------
|
||||
Configuration
|
||||
---------------------------
|
||||
The file noVNCConfig.ini must be in the same directory as "noVNC Websocket Service.exe".
|
||||
|
||||
This file contains a single line which is the websockify.exe statup arguements. An example is:
|
||||
192.168.0.1:5901 192.168.0.1:5900
|
||||
|
||||
All websockify supported arguements will work if added here.
|
||||
|
||||
---------------------------
|
||||
Deletion
|
||||
---------------------------
|
||||
|
||||
You can delete the service at any time by running the commandline as admin and using this command:
|
||||
sc delete "noVNC Websocket Server".
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.ServiceProcess;
|
||||
using System.Text;
|
||||
|
||||
namespace MELT_Command_Websocket
|
||||
{
|
||||
static class Program
|
||||
{
|
||||
/// <summary>
|
||||
/// The main entry point for the application.
|
||||
/// </summary>
|
||||
static void Main()
|
||||
{
|
||||
ServiceBase[] ServicesToRun;
|
||||
ServicesToRun = new ServiceBase[]
|
||||
{
|
||||
new Service1()
|
||||
};
|
||||
ServiceBase.Run(ServicesToRun);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
namespace MELT_Command_Websocket
|
||||
{
|
||||
partial class ProjectInstaller
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Component Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.serviceProcessInstaller1 = new System.ServiceProcess.ServiceProcessInstaller();
|
||||
this.serviceInstaller1 = new System.ServiceProcess.ServiceInstaller();
|
||||
//
|
||||
// serviceProcessInstaller1
|
||||
//
|
||||
this.serviceProcessInstaller1.Account = System.ServiceProcess.ServiceAccount.NetworkService;
|
||||
this.serviceProcessInstaller1.Installers.AddRange(new System.Configuration.Install.Installer[] {
|
||||
this.serviceInstaller1});
|
||||
this.serviceProcessInstaller1.Password = null;
|
||||
this.serviceProcessInstaller1.Username = null;
|
||||
//
|
||||
// serviceInstaller1
|
||||
//
|
||||
this.serviceInstaller1.Description = "noVNC Websocket Service";
|
||||
this.serviceInstaller1.DisplayName = "noVNC Websocket Service";
|
||||
this.serviceInstaller1.ServiceName = "noVNC Websocket Service";
|
||||
this.serviceInstaller1.StartType = System.ServiceProcess.ServiceStartMode.Automatic;
|
||||
//
|
||||
// ProjectInstaller
|
||||
//
|
||||
this.Installers.AddRange(new System.Configuration.Install.Installer[] {
|
||||
this.serviceProcessInstaller1});
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.ServiceProcess.ServiceProcessInstaller serviceProcessInstaller1;
|
||||
private System.ServiceProcess.ServiceInstaller serviceInstaller1;
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Configuration.Install;
|
||||
using System.Linq;
|
||||
|
||||
|
||||
namespace MELT_Command_Websocket
|
||||
{
|
||||
[RunInstaller(true)]
|
||||
public partial class ProjectInstaller : System.Configuration.Install.Installer
|
||||
{
|
||||
public ProjectInstaller()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<metadata name="serviceProcessInstaller1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>17, 56</value>
|
||||
</metadata>
|
||||
<metadata name="serviceInstaller1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>196, 17</value>
|
||||
</metadata>
|
||||
<metadata name="$this.TrayLargeIcon" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>False</value>
|
||||
</metadata>
|
||||
</root>
|
||||
@@ -1,36 +0,0 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("MELT Command Websocket")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Microsoft")]
|
||||
[assembly: AssemblyProduct("MELT Command Websocket")]
|
||||
[assembly: AssemblyCopyright("Copyright © Microsoft 2011")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("5ab831cb-6852-4ce1-849c-b26725b0e10b")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
@@ -1,37 +0,0 @@
|
||||
namespace MELT_Command_Websocket
|
||||
{
|
||||
partial class Service1
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Component Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
components = new System.ComponentModel.Container();
|
||||
this.ServiceName = "Service1";
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.ServiceProcess;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
|
||||
namespace MELT_Command_Websocket
|
||||
{
|
||||
public partial class Service1 : ServiceBase
|
||||
{
|
||||
Process websockify;
|
||||
public Service1()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override void OnStart(string[] args)
|
||||
{
|
||||
|
||||
string configpath = AppDomain.CurrentDomain.BaseDirectory + "\\noVNCConfig.ini";
|
||||
string sockifypath = AppDomain.CurrentDomain.BaseDirectory + "\\websockify.exe";
|
||||
//Load commandline arguements from config file.
|
||||
StreamReader streamReader = new StreamReader(configpath);
|
||||
string arguements = streamReader.ReadLine();
|
||||
streamReader.Close();
|
||||
|
||||
//Start websockify.
|
||||
websockify = System.Diagnostics.Process.Start(sockifypath, arguements);
|
||||
}
|
||||
|
||||
protected override void OnStop()
|
||||
{
|
||||
//Service stopped. Close websockify.
|
||||
websockify.Kill();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
|
||||
<ProductVersion>8.0.30703</ProductVersion>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>{6B86AE7B-6BBD-4E74-8802-5995E8B6D27D}</ProjectGuid>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>noVNC_Websocket_Service</RootNamespace>
|
||||
<AssemblyName>noVNC Websocket Service</AssemblyName>
|
||||
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Configuration.Install" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Management" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.ServiceProcess" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="ProjectInstaller.cs">
|
||||
<SubType>Component</SubType>
|
||||
</Compile>
|
||||
<Compile Include="ProjectInstaller.Designer.cs">
|
||||
<DependentUpon>ProjectInstaller.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Service1.cs">
|
||||
<SubType>Component</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Service1.Designer.cs">
|
||||
<DependentUpon>Service1.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="ProjectInstaller.resx">
|
||||
<DependentUpon>ProjectInstaller.cs</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
@@ -1,20 +0,0 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 11.00
|
||||
# Visual Studio 2010
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "noVNC Websocket", "noVNC Websocket.csproj", "{6B86AE7B-6BBD-4E74-8802-5995E8B6D27D}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x86 = Debug|x86
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{6B86AE7B-6BBD-4E74-8802-5995E8B6D27D}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{6B86AE7B-6BBD-4E74-8802-5995E8B6D27D}.Debug|x86.Build.0 = Debug|x86
|
||||
{6B86AE7B-6BBD-4E74-8802-5995E8B6D27D}.Release|x86.ActiveCfg = Release|x86
|
||||
{6B86AE7B-6BBD-4E74-8802-5995E8B6D27D}.Release|x86.Build.0 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -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,7 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
BASE_DIR="$(cd $(dirname "$0"); pwd)"
|
||||
|
||||
cd "$BASE_DIR"
|
||||
|
||||
python -m websockify $@
|
||||
set -e
|
||||
cd "$(dirname "$0")"
|
||||
exec python3 -m websockify "$@"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
version = '0.9.0'
|
||||
version = '0.11.0'
|
||||
name = 'websockify'
|
||||
long_description = open("README.md").read() + "\n" + \
|
||||
open("CHANGES.txt").read() + "\n"
|
||||
@@ -12,12 +12,14 @@ setup(name=name,
|
||||
long_description_content_type="text/markdown",
|
||||
classifiers=[
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 2",
|
||||
"Programming Language :: Python :: 2.6",
|
||||
"Programming Language :: Python :: 2.7",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.3",
|
||||
"Programming Language :: Python :: 3.4"
|
||||
"Programming Language :: Python :: 3 :: Only",
|
||||
"Programming Language :: Python :: 3.4",
|
||||
"Programming Language :: Python :: 3.5",
|
||||
"Programming Language :: Python :: 3.6",
|
||||
"Programming Language :: Python :: 3.7",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
],
|
||||
keywords='noVNC websockify',
|
||||
license='LGPLv3',
|
||||
@@ -27,7 +29,11 @@ setup(name=name,
|
||||
|
||||
packages=['websockify'],
|
||||
include_package_data=True,
|
||||
install_requires=['numpy'],
|
||||
install_requires=[
|
||||
'numpy', 'requests',
|
||||
'jwcrypto',
|
||||
'redis',
|
||||
],
|
||||
zip_safe=False,
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
mox3
|
||||
nose
|
||||
jwcrypto;python_version>="2.7"
|
||||
enum34;python_version=="3.3"
|
||||
redis;python_version>="2.7"
|
||||
simplejson;python_version>="2.7"
|
||||
mock
|
||||
nose2
|
||||
six
|
||||
redis
|
||||
wrapt<=1.12.1;python_version<="3.4"
|
||||
|
||||
@@ -0,0 +1,355 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
""" Unit tests for Token plugins"""
|
||||
|
||||
import unittest
|
||||
from unittest.mock import patch, mock_open, MagicMock
|
||||
from jwcrypto import jwt, jwk
|
||||
|
||||
from websockify.token_plugins import ReadOnlyTokenFile, JWTTokenApi, TokenRedis
|
||||
|
||||
class ReadOnlyTokenFileTestCase(unittest.TestCase):
|
||||
patch('os.path.isdir', MagicMock(return_value=False))
|
||||
def test_empty(self):
|
||||
plugin = ReadOnlyTokenFile('configfile')
|
||||
|
||||
config = ""
|
||||
pyopen = mock_open(read_data=config)
|
||||
|
||||
with patch("websockify.token_plugins.open", pyopen, create=True):
|
||||
result = plugin.lookup('testhost')
|
||||
|
||||
pyopen.assert_called_once_with('configfile')
|
||||
self.assertIsNone(result)
|
||||
|
||||
patch('os.path.isdir', MagicMock(return_value=False))
|
||||
def test_simple(self):
|
||||
plugin = ReadOnlyTokenFile('configfile')
|
||||
|
||||
config = "testhost: remote_host:remote_port"
|
||||
pyopen = mock_open(read_data=config)
|
||||
|
||||
with patch("websockify.token_plugins.open", pyopen, create=True):
|
||||
result = plugin.lookup('testhost')
|
||||
|
||||
pyopen.assert_called_once_with('configfile')
|
||||
self.assertIsNotNone(result)
|
||||
self.assertEqual(result[0], "remote_host")
|
||||
self.assertEqual(result[1], "remote_port")
|
||||
|
||||
patch('os.path.isdir', MagicMock(return_value=False))
|
||||
def test_tabs(self):
|
||||
plugin = ReadOnlyTokenFile('configfile')
|
||||
|
||||
config = "testhost:\tremote_host:remote_port"
|
||||
pyopen = mock_open(read_data=config)
|
||||
|
||||
with patch("websockify.token_plugins.open", pyopen, create=True):
|
||||
result = plugin.lookup('testhost')
|
||||
|
||||
pyopen.assert_called_once_with('configfile')
|
||||
self.assertIsNotNone(result)
|
||||
self.assertEqual(result[0], "remote_host")
|
||||
self.assertEqual(result[1], "remote_port")
|
||||
|
||||
class JWSTokenTestCase(unittest.TestCase):
|
||||
def test_asymmetric_jws_token_plugin(self):
|
||||
plugin = JWTTokenApi("./tests/fixtures/public.pem")
|
||||
|
||||
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"})
|
||||
jwt_token.make_signed_token(key)
|
||||
|
||||
result = plugin.lookup(jwt_token.serialize())
|
||||
|
||||
self.assertIsNotNone(result)
|
||||
self.assertEqual(result[0], "remote_host")
|
||||
self.assertEqual(result[1], "remote_port")
|
||||
|
||||
def test_asymmetric_jws_token_plugin_with_illigal_key_exception(self):
|
||||
plugin = JWTTokenApi("wrong.pub")
|
||||
|
||||
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"})
|
||||
jwt_token.make_signed_token(key)
|
||||
|
||||
result = plugin.lookup(jwt_token.serialize())
|
||||
|
||||
self.assertIsNone(result)
|
||||
|
||||
@patch('time.time')
|
||||
def test_jwt_valid_time(self, mock_time):
|
||||
plugin = JWTTokenApi("./tests/fixtures/public.pem")
|
||||
|
||||
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 })
|
||||
jwt_token.make_signed_token(key)
|
||||
mock_time.return_value = 150
|
||||
|
||||
result = plugin.lookup(jwt_token.serialize())
|
||||
|
||||
self.assertIsNotNone(result)
|
||||
self.assertEqual(result[0], "remote_host")
|
||||
self.assertEqual(result[1], "remote_port")
|
||||
|
||||
@patch('time.time')
|
||||
def test_jwt_early_time(self, mock_time):
|
||||
plugin = JWTTokenApi("./tests/fixtures/public.pem")
|
||||
|
||||
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 })
|
||||
jwt_token.make_signed_token(key)
|
||||
mock_time.return_value = 50
|
||||
|
||||
result = plugin.lookup(jwt_token.serialize())
|
||||
|
||||
self.assertIsNone(result)
|
||||
|
||||
@patch('time.time')
|
||||
def test_jwt_late_time(self, mock_time):
|
||||
plugin = JWTTokenApi("./tests/fixtures/public.pem")
|
||||
|
||||
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 })
|
||||
jwt_token.make_signed_token(key)
|
||||
mock_time.return_value = 250
|
||||
|
||||
result = plugin.lookup(jwt_token.serialize())
|
||||
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_symmetric_jws_token_plugin(self):
|
||||
plugin = JWTTokenApi("./tests/fixtures/symmetric.key")
|
||||
|
||||
secret = open("./tests/fixtures/symmetric.key").read()
|
||||
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)
|
||||
|
||||
result = plugin.lookup(jwt_token.serialize())
|
||||
|
||||
self.assertIsNotNone(result)
|
||||
self.assertEqual(result[0], "remote_host")
|
||||
self.assertEqual(result[1], "remote_port")
|
||||
|
||||
def test_symmetric_jws_token_plugin_with_illigal_key_exception(self):
|
||||
plugin = JWTTokenApi("wrong_sauce")
|
||||
|
||||
secret = open("./tests/fixtures/symmetric.key").read()
|
||||
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)
|
||||
|
||||
result = plugin.lookup(jwt_token.serialize())
|
||||
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_asymmetric_jwe_token_plugin(self):
|
||||
plugin = JWTTokenApi("./tests/fixtures/private.pem")
|
||||
|
||||
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": "RSA-OAEP", "enc": "A256CBC-HS512"},
|
||||
claims=jwt_token.serialize())
|
||||
jwe_token.make_encrypted_token(public_key)
|
||||
|
||||
result = plugin.lookup(jwt_token.serialize())
|
||||
|
||||
self.assertIsNotNone(result)
|
||||
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')
|
||||
|
||||
@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)
|
||||
+14
-123
@@ -20,31 +20,18 @@ import sys
|
||||
import unittest
|
||||
import unittest
|
||||
import socket
|
||||
from io import StringIO
|
||||
from io import BytesIO
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
from mox3 import stubout
|
||||
|
||||
from websockify import websockifyserver
|
||||
from websockify import websocketproxy
|
||||
from websockify import token_plugins
|
||||
from websockify import auth_plugins
|
||||
|
||||
if sys.version_info >= (2,7):
|
||||
from jwcrypto import jwt
|
||||
|
||||
try:
|
||||
from StringIO import StringIO
|
||||
BytesIO = StringIO
|
||||
except ImportError:
|
||||
from io import StringIO
|
||||
from io import BytesIO
|
||||
|
||||
|
||||
class FakeSocket(object):
|
||||
def __init__(self, data=''):
|
||||
if isinstance(data, bytes):
|
||||
self._data = data
|
||||
else:
|
||||
self._data = data.encode('latin_1')
|
||||
def __init__(self, data=b''):
|
||||
self._data = data
|
||||
|
||||
def recv(self, amt, flags=None):
|
||||
res = self._data[0:amt]
|
||||
@@ -74,16 +61,14 @@ class FakeServer(object):
|
||||
class ProxyRequestHandlerTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
super(ProxyRequestHandlerTestCase, self).setUp()
|
||||
self.stubs = stubout.StubOutForTesting()
|
||||
self.handler = websocketproxy.ProxyRequestHandler(
|
||||
FakeSocket(''), "127.0.0.1", FakeServer())
|
||||
FakeSocket(), "127.0.0.1", FakeServer())
|
||||
self.handler.path = "https://localhost:6080/websockify?token=blah"
|
||||
self.handler.headers = None
|
||||
self.stubs.Set(websockifyserver.WebSockifyServer, 'socket',
|
||||
staticmethod(lambda *args, **kwargs: None))
|
||||
patch('websockify.websockifyserver.WebSockifyServer.socket').start()
|
||||
|
||||
def tearDown(self):
|
||||
self.stubs.UnsetAll()
|
||||
patch.stopall()
|
||||
super(ProxyRequestHandlerTestCase, self).tearDown()
|
||||
|
||||
def test_get_target(self):
|
||||
@@ -112,128 +97,34 @@ class ProxyRequestHandlerTestCase(unittest.TestCase):
|
||||
def lookup(self, token):
|
||||
return None
|
||||
|
||||
self.assertRaises(FakeServer.EClose, self.handler.get_target,
|
||||
TestPlugin(None))
|
||||
with self.assertRaises(FakeServer.EClose):
|
||||
self.handler.get_target(TestPlugin(None))
|
||||
|
||||
@patch('websockify.websocketproxy.ProxyRequestHandler.send_auth_error', MagicMock())
|
||||
def test_token_plugin(self):
|
||||
class TestPlugin(token_plugins.BasePlugin):
|
||||
def lookup(self, token):
|
||||
return (self.source + token).split(',')
|
||||
|
||||
self.stubs.Set(websocketproxy.ProxyRequestHandler, 'send_auth_error',
|
||||
staticmethod(lambda *args, **kwargs: None))
|
||||
|
||||
self.handler.server.token_plugin = TestPlugin("somehost,")
|
||||
self.handler.validate_connection()
|
||||
|
||||
self.assertEqual(self.handler.server.target_host, "somehost")
|
||||
self.assertEqual(self.handler.server.target_port, "blah")
|
||||
|
||||
if sys.version_info >= (2,7):
|
||||
def test_asymmetric_jws_token_plugin(self):
|
||||
key = jwt.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"})
|
||||
jwt_token.make_signed_token(key)
|
||||
self.handler.path = "https://localhost:6080/websockify?token={jwt_token}".format(jwt_token=jwt_token.serialize())
|
||||
|
||||
self.stubs.Set(websocketproxy.ProxyRequestHandler, 'send_auth_error',
|
||||
staticmethod(lambda *args, **kwargs: None))
|
||||
|
||||
self.handler.server.token_plugin = token_plugins.JWTTokenApi("./tests/fixtures/public.pem")
|
||||
self.handler.validate_connection()
|
||||
|
||||
self.assertEqual(self.handler.server.target_host, "remote_host")
|
||||
self.assertEqual(self.handler.server.target_port, "remote_port")
|
||||
|
||||
def test_asymmetric_jws_token_plugin_with_illigal_key_exception(self):
|
||||
key = jwt.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"})
|
||||
jwt_token.make_signed_token(key)
|
||||
self.handler.path = "https://localhost:6080/websockify?token={jwt_token}".format(jwt_token=jwt_token.serialize())
|
||||
|
||||
self.stubs.Set(websocketproxy.ProxyRequestHandler, 'send_auth_error',
|
||||
staticmethod(lambda *args, **kwargs: None))
|
||||
|
||||
self.handler.server.token_plugin = token_plugins.JWTTokenApi("wrong.pub")
|
||||
self.assertRaises(self.handler.server.EClose,
|
||||
self.handler.validate_connection)
|
||||
|
||||
|
||||
def test_symmetric_jws_token_plugin(self):
|
||||
secret = open("./tests/fixtures/symmetric.key").read()
|
||||
key = jwt.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)
|
||||
self.handler.path = "https://localhost:6080/websockify?token={jwt_token}".format(jwt_token=jwt_token.serialize())
|
||||
|
||||
self.stubs.Set(websocketproxy.ProxyRequestHandler, 'send_auth_error',
|
||||
staticmethod(lambda *args, **kwargs: None))
|
||||
|
||||
self.handler.server.token_plugin = token_plugins.JWTTokenApi("./tests/fixtures/symmetric.key")
|
||||
self.handler.validate_connection()
|
||||
|
||||
self.assertEqual(self.handler.server.target_host, "remote_host")
|
||||
self.assertEqual(self.handler.server.target_port, "remote_port")
|
||||
|
||||
def test_symmetric_jws_token_plugin_with_illigal_key_exception(self):
|
||||
secret = open("./tests/fixtures/symmetric.key").read()
|
||||
key = jwt.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)
|
||||
self.handler.path = "https://localhost:6080/websockify?token={jwt_token}".format(jwt_token=jwt_token.serialize())
|
||||
|
||||
self.stubs.Set(websocketproxy.ProxyRequestHandler, 'send_auth_error',
|
||||
staticmethod(lambda *args, **kwargs: None))
|
||||
|
||||
self.handler.server.token_plugin = token_plugins.JWTTokenApi("wrong_sauce")
|
||||
self.assertRaises(self.handler.server.EClose,
|
||||
self.handler.validate_connection)
|
||||
|
||||
def test_asymmetric_jwe_token_plugin(self):
|
||||
private_key = jwt.JWK()
|
||||
public_key = jwt.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"},
|
||||
claims=jwt_token.serialize())
|
||||
jwe_token.make_encrypted_token(public_key)
|
||||
|
||||
self.handler.path = "https://localhost:6080/websockify?token={jwt_token}".format(jwt_token=jwe_token.serialize())
|
||||
|
||||
self.stubs.Set(websocketproxy.ProxyRequestHandler, 'send_auth_error',
|
||||
staticmethod(lambda *args, **kwargs: None))
|
||||
|
||||
self.handler.server.token_plugin = token_plugins.JWTTokenApi("./tests/fixtures/private.pem")
|
||||
self.handler.validate_connection()
|
||||
|
||||
self.assertEqual(self.handler.server.target_host, "remote_host")
|
||||
self.assertEqual(self.handler.server.target_port, "remote_port")
|
||||
|
||||
@patch('websockify.websocketproxy.ProxyRequestHandler.send_auth_error', MagicMock())
|
||||
def test_auth_plugin(self):
|
||||
class TestPlugin(auth_plugins.BasePlugin):
|
||||
def authenticate(self, headers, target_host, target_port):
|
||||
if target_host == self.source:
|
||||
raise auth_plugins.AuthenticationError(response_msg="some_error")
|
||||
|
||||
self.stubs.Set(websocketproxy.ProxyRequestHandler, 'send_auth_error',
|
||||
staticmethod(lambda *args, **kwargs: None))
|
||||
|
||||
self.handler.server.auth_plugin = TestPlugin("somehost")
|
||||
self.handler.server.target_host = "somehost"
|
||||
self.handler.server.target_port = "someport"
|
||||
|
||||
self.assertRaises(auth_plugins.AuthenticationError,
|
||||
self.handler.auth_connection)
|
||||
with self.assertRaises(auth_plugins.AuthenticationError):
|
||||
self.handler.auth_connection()
|
||||
|
||||
self.handler.server.target_host = "someotherhost"
|
||||
self.handler.auth_connection()
|
||||
|
||||
@@ -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()
|
||||
|
||||
+58
-114
@@ -22,39 +22,26 @@ import select
|
||||
import shutil
|
||||
import socket
|
||||
import ssl
|
||||
from mox3 import stubout
|
||||
from unittest.mock import patch, MagicMock, ANY
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
import socket
|
||||
import signal
|
||||
from http.server import BaseHTTPRequestHandler
|
||||
from io import StringIO
|
||||
from io import BytesIO
|
||||
|
||||
from websockify import websockifyserver
|
||||
|
||||
try:
|
||||
from BaseHTTPServer import BaseHTTPRequestHandler
|
||||
except ImportError:
|
||||
from http.server import BaseHTTPRequestHandler
|
||||
|
||||
try:
|
||||
from StringIO import StringIO
|
||||
BytesIO = StringIO
|
||||
except ImportError:
|
||||
from io import StringIO
|
||||
from io import BytesIO
|
||||
|
||||
|
||||
|
||||
|
||||
def raise_oserror(*args, **kwargs):
|
||||
raise OSError('fake error')
|
||||
|
||||
|
||||
class FakeSocket(object):
|
||||
def __init__(self, data=''):
|
||||
if isinstance(data, bytes):
|
||||
self._data = data
|
||||
else:
|
||||
self._data = data.encode('latin_1')
|
||||
def __init__(self, data=b''):
|
||||
self._data = data
|
||||
|
||||
def recv(self, amt, flags=None):
|
||||
res = self._data[0:amt]
|
||||
@@ -73,22 +60,13 @@ class FakeSocket(object):
|
||||
class WebSockifyRequestHandlerTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
super(WebSockifyRequestHandlerTestCase, self).setUp()
|
||||
self.stubs = stubout.StubOutForTesting()
|
||||
self.tmpdir = tempfile.mkdtemp('-websockify-tests')
|
||||
# Mock this out cause it screws tests up
|
||||
self.stubs.Set(os, 'chdir', lambda *args, **kwargs: None)
|
||||
self.stubs.Set(BaseHTTPRequestHandler, 'send_response',
|
||||
lambda *args, **kwargs: None)
|
||||
|
||||
def fake_send_error(self, code, message=None, explain=None):
|
||||
self.last_code = code
|
||||
|
||||
self.stubs.Set(BaseHTTPRequestHandler, 'send_error',
|
||||
fake_send_error)
|
||||
patch('os.chdir').start()
|
||||
|
||||
def tearDown(self):
|
||||
"""Called automatically after each test."""
|
||||
self.stubs.UnsetAll()
|
||||
patch.stopall()
|
||||
os.rmdir(self.tmpdir)
|
||||
super(WebSockifyRequestHandlerTestCase, self).tearDown()
|
||||
|
||||
@@ -101,47 +79,36 @@ class WebSockifyRequestHandlerTestCase(unittest.TestCase):
|
||||
record=self.tmpdir, daemon=False, ssl_only=0, idle_timeout=1,
|
||||
**kwargs)
|
||||
|
||||
def test_normal_get_with_only_upgrade_returns_error(self):
|
||||
@patch('websockify.websockifyserver.WebSockifyRequestHandler.send_error')
|
||||
def test_normal_get_with_only_upgrade_returns_error(self, send_error):
|
||||
server = self._get_server(web=None)
|
||||
handler = websockifyserver.WebSockifyRequestHandler(
|
||||
FakeSocket('GET /tmp.txt HTTP/1.1'), '127.0.0.1', server)
|
||||
|
||||
def fake_send_response(self, code, message=None):
|
||||
self.last_code = code
|
||||
|
||||
self.stubs.Set(BaseHTTPRequestHandler, 'send_response',
|
||||
fake_send_response)
|
||||
FakeSocket(b'GET /tmp.txt HTTP/1.1'), '127.0.0.1', server)
|
||||
|
||||
handler.do_GET()
|
||||
self.assertEqual(handler.last_code, 405)
|
||||
send_error.assert_called_with(405)
|
||||
|
||||
def test_list_dir_with_file_only_returns_error(self):
|
||||
@patch('websockify.websockifyserver.WebSockifyRequestHandler.send_error')
|
||||
def test_list_dir_with_file_only_returns_error(self, send_error):
|
||||
server = self._get_server(file_only=True)
|
||||
handler = websockifyserver.WebSockifyRequestHandler(
|
||||
FakeSocket('GET / HTTP/1.1'), '127.0.0.1', server)
|
||||
|
||||
def fake_send_response(self, code, message=None):
|
||||
self.last_code = code
|
||||
|
||||
self.stubs.Set(BaseHTTPRequestHandler, 'send_response',
|
||||
fake_send_response)
|
||||
FakeSocket(b'GET / HTTP/1.1'), '127.0.0.1', server)
|
||||
|
||||
handler.path = '/'
|
||||
handler.do_GET()
|
||||
self.assertEqual(handler.last_code, 404)
|
||||
send_error.assert_called_with(404)
|
||||
|
||||
|
||||
class WebSockifyServerTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
super(WebSockifyServerTestCase, self).setUp()
|
||||
self.stubs = stubout.StubOutForTesting()
|
||||
self.tmpdir = tempfile.mkdtemp('-websockify-tests')
|
||||
# Mock this out cause it screws tests up
|
||||
self.stubs.Set(os, 'chdir', lambda *args, **kwargs: None)
|
||||
patch('os.chdir').start()
|
||||
|
||||
def tearDown(self):
|
||||
"""Called automatically after each test."""
|
||||
self.stubs.UnsetAll()
|
||||
patch.stopall()
|
||||
os.rmdir(self.tmpdir)
|
||||
super(WebSockifyServerTestCase, self).tearDown()
|
||||
|
||||
@@ -154,10 +121,10 @@ class WebSockifyServerTestCase(unittest.TestCase):
|
||||
|
||||
def test_daemonize_raises_error_while_closing_fds(self):
|
||||
server = self._get_server(daemon=True, ssl_only=1, idle_timeout=1)
|
||||
self.stubs.Set(os, 'fork', lambda *args: 0)
|
||||
self.stubs.Set(signal, 'signal', lambda *args: None)
|
||||
self.stubs.Set(os, 'setsid', lambda *args: None)
|
||||
self.stubs.Set(os, 'close', raise_oserror)
|
||||
patch('os.fork').start().return_value = 0
|
||||
patch('signal.signal').start()
|
||||
patch('os.setsid').start()
|
||||
patch('os.close').start().side_effect = raise_oserror
|
||||
self.assertRaises(OSError, server.daemonize, keepfd=None, chdir='./')
|
||||
|
||||
def test_daemonize_ignores_ebadf_error_while_closing_fds(self):
|
||||
@@ -165,11 +132,11 @@ class WebSockifyServerTestCase(unittest.TestCase):
|
||||
raise OSError(errno.EBADF, 'fake error')
|
||||
|
||||
server = self._get_server(daemon=True, ssl_only=1, idle_timeout=1)
|
||||
self.stubs.Set(os, 'fork', lambda *args: 0)
|
||||
self.stubs.Set(os, 'setsid', lambda *args: None)
|
||||
self.stubs.Set(signal, 'signal', lambda *args: None)
|
||||
self.stubs.Set(os, 'close', raise_oserror_ebadf)
|
||||
self.stubs.Set(os, 'open', raise_oserror)
|
||||
patch('os.fork').start().return_value = 0
|
||||
patch('signal.signal').start()
|
||||
patch('os.setsid').start()
|
||||
patch('os.close').start().side_effect = raise_oserror_ebadf
|
||||
patch('os.open').start().side_effect = raise_oserror
|
||||
self.assertRaises(OSError, server.daemonize, keepfd=None, chdir='./')
|
||||
|
||||
def test_handshake_fails_on_not_ready(self):
|
||||
@@ -178,7 +145,7 @@ class WebSockifyServerTestCase(unittest.TestCase):
|
||||
def fake_select(rlist, wlist, xlist, timeout=None):
|
||||
return ([], [], [])
|
||||
|
||||
self.stubs.Set(select, 'select', fake_select)
|
||||
patch('select.select').start().side_effect = fake_select
|
||||
self.assertRaises(
|
||||
websockifyserver.WebSockifyServer.EClose, server.do_handshake,
|
||||
FakeSocket(), '127.0.0.1')
|
||||
@@ -191,7 +158,7 @@ class WebSockifyServerTestCase(unittest.TestCase):
|
||||
def fake_select(rlist, wlist, xlist, timeout=None):
|
||||
return ([sock], [], [])
|
||||
|
||||
self.stubs.Set(select, 'select', fake_select)
|
||||
patch('select.select').start().side_effect = fake_select
|
||||
self.assertRaises(
|
||||
websockifyserver.WebSockifyServer.EClose, server.do_handshake,
|
||||
sock, '127.0.0.1')
|
||||
@@ -203,12 +170,12 @@ class WebSockifyServerTestCase(unittest.TestCase):
|
||||
def test_handshake_ssl_only_without_ssl_raises_error(self):
|
||||
server = self._get_server(daemon=True, ssl_only=1, idle_timeout=1)
|
||||
|
||||
sock = FakeSocket('some initial data')
|
||||
sock = FakeSocket(b'some initial data')
|
||||
|
||||
def fake_select(rlist, wlist, xlist, timeout=None):
|
||||
return ([sock], [], [])
|
||||
|
||||
self.stubs.Set(select, 'select', fake_select)
|
||||
patch('select.select').start().side_effect = fake_select
|
||||
self.assertRaises(
|
||||
websockifyserver.WebSockifyServer.EClose, server.do_handshake,
|
||||
sock, '127.0.0.1')
|
||||
@@ -225,12 +192,12 @@ class WebSockifyServerTestCase(unittest.TestCase):
|
||||
handler_class=FakeHandler, daemon=True,
|
||||
ssl_only=0, idle_timeout=1)
|
||||
|
||||
sock = FakeSocket('some initial data')
|
||||
sock = FakeSocket(b'some initial data')
|
||||
|
||||
def fake_select(rlist, wlist, xlist, timeout=None):
|
||||
return ([sock], [], [])
|
||||
|
||||
self.stubs.Set(select, 'select', fake_select)
|
||||
patch('select.select').start().side_effect = fake_select
|
||||
self.assertEqual(server.do_handshake(sock, '127.0.0.1'), sock)
|
||||
self.assertTrue(FakeHandler.CALLED, True)
|
||||
|
||||
@@ -246,12 +213,12 @@ class WebSockifyServerTestCase(unittest.TestCase):
|
||||
server = self._get_server(daemon=True, ssl_only=0, idle_timeout=1,
|
||||
cert='afdsfasdafdsafdsafdsafdas')
|
||||
|
||||
sock = FakeSocket("\x16some ssl data")
|
||||
sock = FakeSocket(b"\x16some ssl data")
|
||||
|
||||
def fake_select(rlist, wlist, xlist, timeout=None):
|
||||
return ([sock], [], [])
|
||||
|
||||
self.stubs.Set(select, 'select', fake_select)
|
||||
patch('select.select').start().side_effect = fake_select
|
||||
self.assertRaises(
|
||||
websockifyserver.WebSockifyServer.EClose, server.do_handshake,
|
||||
sock, '127.0.0.1')
|
||||
@@ -259,7 +226,7 @@ class WebSockifyServerTestCase(unittest.TestCase):
|
||||
def test_do_handshake_ssl_error_eof_raises_close_error(self):
|
||||
server = self._get_server(daemon=True, ssl_only=0, idle_timeout=1)
|
||||
|
||||
sock = FakeSocket("\x16some ssl data")
|
||||
sock = FakeSocket(b"\x16some ssl data")
|
||||
|
||||
def fake_select(rlist, wlist, xlist, timeout=None):
|
||||
return ([sock], [], [])
|
||||
@@ -280,13 +247,8 @@ class WebSockifyServerTestCase(unittest.TestCase):
|
||||
def wrap_socket(self, *args, **kwargs):
|
||||
raise ssl.SSLError(ssl.SSL_ERROR_EOF)
|
||||
|
||||
self.stubs.Set(select, 'select', fake_select)
|
||||
if (hasattr(ssl, 'create_default_context')):
|
||||
# for recent versions of python
|
||||
self.stubs.Set(ssl, 'create_default_context', fake_create_default_context)
|
||||
else:
|
||||
# for fallback for old versions of python
|
||||
self.stubs.Set(ssl, 'wrap_socket', fake_wrap_socket)
|
||||
patch('select.select').start().side_effect = fake_select
|
||||
patch('ssl.create_default_context').start().side_effect = fake_create_default_context
|
||||
self.assertRaises(
|
||||
websockifyserver.WebSockifyServer.EClose, server.do_handshake,
|
||||
sock, '127.0.0.1')
|
||||
@@ -300,7 +262,7 @@ class WebSockifyServerTestCase(unittest.TestCase):
|
||||
|
||||
server = self._get_server(handler_class=FakeHandler, daemon=True,
|
||||
idle_timeout=1, ssl_ciphers=test_ciphers)
|
||||
sock = FakeSocket("\x16some ssl data")
|
||||
sock = FakeSocket(b"\x16some ssl data")
|
||||
|
||||
def fake_select(rlist, wlist, xlist, timeout=None):
|
||||
return ([sock], [], [])
|
||||
@@ -321,16 +283,10 @@ class WebSockifyServerTestCase(unittest.TestCase):
|
||||
def set_ciphers(self, ciphers_to_set):
|
||||
fake_create_default_context.CIPHERS = ciphers_to_set
|
||||
|
||||
self.stubs.Set(select, 'select', fake_select)
|
||||
if (hasattr(ssl, 'create_default_context')):
|
||||
# for recent versions of python
|
||||
self.stubs.Set(ssl, 'create_default_context', fake_create_default_context)
|
||||
server.do_handshake(sock, '127.0.0.1')
|
||||
self.assertEqual(fake_create_default_context.CIPHERS, test_ciphers)
|
||||
else:
|
||||
# for fallback for old versions of python
|
||||
# not supperted, nothing to test
|
||||
pass
|
||||
patch('select.select').start().side_effect = fake_select
|
||||
patch('ssl.create_default_context').start().side_effect = fake_create_default_context
|
||||
server.do_handshake(sock, '127.0.0.1')
|
||||
self.assertEqual(fake_create_default_context.CIPHERS, test_ciphers)
|
||||
|
||||
def test_do_handshake_ssl_sets_opions(self):
|
||||
test_options = 0xCAFEBEEF
|
||||
@@ -341,7 +297,7 @@ class WebSockifyServerTestCase(unittest.TestCase):
|
||||
|
||||
server = self._get_server(handler_class=FakeHandler, daemon=True,
|
||||
idle_timeout=1, ssl_options=test_options)
|
||||
sock = FakeSocket("\x16some ssl data")
|
||||
sock = FakeSocket(b"\x16some ssl data")
|
||||
|
||||
def fake_select(rlist, wlist, xlist, timeout=None):
|
||||
return ([sock], [], [])
|
||||
@@ -365,16 +321,10 @@ class WebSockifyServerTestCase(unittest.TestCase):
|
||||
fake_create_default_context.OPTIONS = val
|
||||
options = property(get_options, set_options)
|
||||
|
||||
self.stubs.Set(select, 'select', fake_select)
|
||||
if (hasattr(ssl, 'create_default_context')):
|
||||
# for recent versions of python
|
||||
self.stubs.Set(ssl, 'create_default_context', fake_create_default_context)
|
||||
server.do_handshake(sock, '127.0.0.1')
|
||||
self.assertEqual(fake_create_default_context.OPTIONS, test_options)
|
||||
else:
|
||||
# for fallback for old versions of python
|
||||
# not supperted, nothing to test
|
||||
pass
|
||||
patch('select.select').start().side_effect = fake_select
|
||||
patch('ssl.create_default_context').start().side_effect = fake_create_default_context
|
||||
server.do_handshake(sock, '127.0.0.1')
|
||||
self.assertEqual(fake_create_default_context.OPTIONS, test_options)
|
||||
|
||||
def test_fallback_sigchld_handler(self):
|
||||
# TODO(directxman12): implement this
|
||||
@@ -387,11 +337,9 @@ class WebSockifyServerTestCase(unittest.TestCase):
|
||||
def fake_select(rlist, wlist, xlist, timeout=None):
|
||||
raise Exception("fake error")
|
||||
|
||||
self.stubs.Set(websockifyserver.WebSockifyServer, 'socket',
|
||||
lambda *args, **kwargs: sock)
|
||||
self.stubs.Set(websockifyserver.WebSockifyServer, 'daemonize',
|
||||
lambda *args, **kwargs: None)
|
||||
self.stubs.Set(select, 'select', fake_select)
|
||||
patch('websockify.websockifyserver.WebSockifyServer.socket').start()
|
||||
patch('websockify.websockifyserver.WebSockifyServer.daemonize').start()
|
||||
patch('select.select').start().side_effect = fake_select
|
||||
server.start_server()
|
||||
|
||||
def test_start_server_keyboardinterrupt(self):
|
||||
@@ -401,11 +349,9 @@ class WebSockifyServerTestCase(unittest.TestCase):
|
||||
def fake_select(rlist, wlist, xlist, timeout=None):
|
||||
raise KeyboardInterrupt
|
||||
|
||||
self.stubs.Set(websockifyserver.WebSockifyServer, 'socket',
|
||||
lambda *args, **kwargs: sock)
|
||||
self.stubs.Set(websockifyserver.WebSockifyServer, 'daemonize',
|
||||
lambda *args, **kwargs: None)
|
||||
self.stubs.Set(select, 'select', fake_select)
|
||||
patch('websockify.websockifyserver.WebSockifyServer.socket').start()
|
||||
patch('websockify.websockifyserver.WebSockifyServer.daemonize').start()
|
||||
patch('select.select').start().side_effect = fake_select
|
||||
server.start_server()
|
||||
|
||||
def test_start_server_systemexit(self):
|
||||
@@ -415,11 +361,9 @@ class WebSockifyServerTestCase(unittest.TestCase):
|
||||
def fake_select(rlist, wlist, xlist, timeout=None):
|
||||
sys.exit()
|
||||
|
||||
self.stubs.Set(websockifyserver.WebSockifyServer, 'socket',
|
||||
lambda *args, **kwargs: sock)
|
||||
self.stubs.Set(websockifyserver.WebSockifyServer, 'daemonize',
|
||||
lambda *args, **kwargs: None)
|
||||
self.stubs.Set(select, 'select', fake_select)
|
||||
patch('websockify.websockifyserver.WebSockifyServer.socket').start()
|
||||
patch('websockify.websockifyserver.WebSockifyServer.daemonize').start()
|
||||
patch('select.select').start().side_effect = fake_select
|
||||
server.start_server()
|
||||
|
||||
def test_socket_set_keepalive_options(self):
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
# and then run "tox" from this directory.
|
||||
|
||||
[tox]
|
||||
envlist = py24,py26,py27,py33,py34
|
||||
envlist = py34
|
||||
|
||||
[testenv]
|
||||
commands = nosetests {posargs}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
class BasePlugin(object):
|
||||
class BasePlugin():
|
||||
def __init__(self, src=None):
|
||||
self.source = src
|
||||
|
||||
@@ -15,7 +15,7 @@ class AuthenticationError(Exception):
|
||||
if log_msg is None:
|
||||
log_msg = response_msg
|
||||
|
||||
super(AuthenticationError, self).__init__('%s %s' % (self.code, log_msg))
|
||||
super().__init__('%s %s' % (self.code, log_msg))
|
||||
|
||||
|
||||
class InvalidOriginError(AuthenticationError):
|
||||
@@ -23,13 +23,13 @@ class InvalidOriginError(AuthenticationError):
|
||||
self.expected_origin = expected
|
||||
self.actual_origin = actual
|
||||
|
||||
super(InvalidOriginError, self).__init__(
|
||||
super().__init__(
|
||||
response_msg='Invalid Origin',
|
||||
log_msg="Invalid Origin Header: Expected one of "
|
||||
"%s, got '%s'" % (expected, actual))
|
||||
|
||||
|
||||
class BasicHTTPAuth(object):
|
||||
class BasicHTTPAuth():
|
||||
"""Verifies Basic Auth headers. Specify src as username:password"""
|
||||
|
||||
def __init__(self, src=None):
|
||||
@@ -76,7 +76,7 @@ class BasicHTTPAuth(object):
|
||||
raise AuthenticationError(response_code=401,
|
||||
response_headers={'WWW-Authenticate': 'Basic realm="Websockify"'})
|
||||
|
||||
class ExpectOrigin(object):
|
||||
class ExpectOrigin():
|
||||
def __init__(self, src=None):
|
||||
if src is None:
|
||||
self.source = []
|
||||
@@ -88,7 +88,7 @@ class ExpectOrigin(object):
|
||||
if origin is None or origin not in self.source:
|
||||
raise InvalidOriginError(expected=self.source, actual=origin)
|
||||
|
||||
class ClientCertCNAuth(object):
|
||||
class ClientCertCNAuth():
|
||||
"""Verifies client by SSL certificate. Specify src as whitespace separated list of common names."""
|
||||
|
||||
def __init__(self, src=None):
|
||||
|
||||
@@ -44,7 +44,7 @@ class WebsockifySysLogHandler(handlers.SysLogHandler):
|
||||
self._legacy = True
|
||||
self._head_fmt = self._legacy_head_fmt
|
||||
|
||||
handlers.SysLogHandler.__init__(self, address, facility, socktype)
|
||||
super().__init__(address, facility, socktype)
|
||||
|
||||
|
||||
def emit(self, record):
|
||||
|
||||
+181
-24
@@ -1,8 +1,14 @@
|
||||
from __future__ import print_function
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import re
|
||||
import json
|
||||
|
||||
class BasePlugin(object):
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BasePlugin():
|
||||
def __init__(self, src):
|
||||
self.source = src
|
||||
|
||||
@@ -15,7 +21,7 @@ class ReadOnlyTokenFile(BasePlugin):
|
||||
# token: host:port
|
||||
# or a directory of such files
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ReadOnlyTokenFile, self).__init__(*args, **kwargs)
|
||||
super().__init__(*args, **kwargs)
|
||||
self._targets = None
|
||||
|
||||
def _load_targets(self):
|
||||
@@ -31,10 +37,10 @@ class ReadOnlyTokenFile(BasePlugin):
|
||||
for line in [l.strip() for l in open(f).readlines()]:
|
||||
if line and not line.startswith('#'):
|
||||
try:
|
||||
tok, target = line.split(': ')
|
||||
tok, target = re.split(':\s', line)
|
||||
self._targets[tok] = target.strip().rsplit(':', 1)
|
||||
except ValueError:
|
||||
print >>sys.stderr, "Syntax error in %s on line %d" % (self.source, index)
|
||||
logger.error("Syntax error in %s on line %d" % (self.source, index))
|
||||
index += 1
|
||||
|
||||
def lookup(self, token):
|
||||
@@ -57,7 +63,7 @@ class TokenFile(ReadOnlyTokenFile):
|
||||
def lookup(self, token):
|
||||
self._load_targets()
|
||||
|
||||
return super(TokenFile, self).lookup(token)
|
||||
return super().lookup(token)
|
||||
|
||||
|
||||
class BaseTokenAPI(BasePlugin):
|
||||
@@ -98,16 +104,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:
|
||||
@@ -116,7 +122,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,31 +135,182 @@ class JWTTokenApi(BasePlugin):
|
||||
|
||||
parsed = json.loads(token.claims)
|
||||
|
||||
if 'nbf' in parsed:
|
||||
# Not Before is present, so we need to check it
|
||||
if time.time() < parsed['nbf']:
|
||||
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']:
|
||||
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(object):
|
||||
|
||||
class TokenRedis(BasePlugin):
|
||||
"""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:
|
||||
|
||||
redis-cli set my-token '{"host": "127.0.0.1:5000"}'
|
||||
|
||||
Verify with:
|
||||
|
||||
redis-cli --raw get my-token
|
||||
|
||||
Spawn a test "server" using netcat
|
||||
|
||||
nc -l 5000 -v
|
||||
|
||||
Note: This Token Plugin depends on the 'redis' module, so you have
|
||||
to install it before using this plugin:
|
||||
|
||||
pip install redis
|
||||
"""
|
||||
def __init__(self, src):
|
||||
self._server, self._port = src.split(":")
|
||||
try:
|
||||
import redis
|
||||
except ImportError:
|
||||
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()
|
||||
|
||||
def lookup(self, token):
|
||||
try:
|
||||
import redis
|
||||
import simplejson
|
||||
except ImportError as e:
|
||||
print("package redis or simplejson not found, are you sure you've installed them correctly?", file=sys.stderr)
|
||||
return None
|
||||
except ImportError:
|
||||
logger.error("package redis not found, are you sure you've installed them correctly?")
|
||||
sys.exit()
|
||||
|
||||
client = redis.Redis(host=self._server,port=self._port)
|
||||
logger.info("resolving token '%s'" % token)
|
||||
client = redis.Redis(host=self._server, port=self._port,
|
||||
db=self._db, password=self._password)
|
||||
stuff = client.get(token)
|
||||
if stuff is None:
|
||||
return None
|
||||
else:
|
||||
combo = simplejson.loads(stuff.decode("utf-8"))
|
||||
pair = combo["host"]
|
||||
return pair.split(':')
|
||||
responseStr = stuff.decode("utf-8").strip()
|
||||
logger.debug("response from redis : %s" % responseStr)
|
||||
if responseStr.startswith("{"):
|
||||
try:
|
||||
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]
|
||||
|
||||
|
||||
class UnixDomainSocketDirectory(BasePlugin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._dir_path = os.path.abspath(self.source)
|
||||
|
||||
def lookup(self, token):
|
||||
try:
|
||||
import stat
|
||||
|
||||
if not os.path.isdir(self._dir_path):
|
||||
return None
|
||||
|
||||
uds_path = os.path.abspath(os.path.join(self._dir_path, token))
|
||||
if not uds_path.startswith(self._dir_path):
|
||||
return None
|
||||
|
||||
if not os.path.exists(uds_path):
|
||||
return None
|
||||
|
||||
if not stat.S_ISSOCK(os.stat(uds_path).st_mode):
|
||||
return None
|
||||
|
||||
return [ 'unix_socket', uds_path ]
|
||||
except Exception as e:
|
||||
logger.error("Error finding unix domain socket: %s" % str(e))
|
||||
return None
|
||||
|
||||
+138
-92
@@ -22,6 +22,7 @@ import ssl
|
||||
import struct
|
||||
from base64 import b64encode
|
||||
from hashlib import sha1
|
||||
from urllib.parse import urlparse
|
||||
|
||||
try:
|
||||
import numpy
|
||||
@@ -30,25 +31,10 @@ except ImportError:
|
||||
warnings.warn("no 'numpy' module, HyBi protocol will be slower")
|
||||
numpy = None
|
||||
|
||||
# python 3.0 differences
|
||||
try:
|
||||
from urllib.parse import urlparse
|
||||
except ImportError:
|
||||
from urlparse import urlparse
|
||||
|
||||
# SSLWant*Error is 2.7.9+
|
||||
try:
|
||||
class WebSocketWantReadError(ssl.SSLWantReadError):
|
||||
pass
|
||||
class WebSocketWantWriteError(ssl.SSLWantWriteError):
|
||||
pass
|
||||
except AttributeError:
|
||||
class WebSocketWantReadError(OSError):
|
||||
def __init__(self):
|
||||
OSError.__init__(self, errno.EWOULDBLOCK)
|
||||
class WebSocketWantWriteError(OSError):
|
||||
def __init__(self):
|
||||
OSError.__init__(self, errno.EWOULDBLOCK)
|
||||
class WebSocketWantReadError(ssl.SSLWantReadError):
|
||||
pass
|
||||
class WebSocketWantWriteError(ssl.SSLWantWriteError):
|
||||
pass
|
||||
|
||||
class WebSocket(object):
|
||||
"""WebSocket protocol socket like class.
|
||||
@@ -87,11 +73,13 @@ class WebSocket(object):
|
||||
|
||||
self._state = "new"
|
||||
|
||||
self._partial_msg = ''.encode("ascii")
|
||||
self._partial_msg = b''
|
||||
|
||||
self._recv_buffer = ''.encode("ascii")
|
||||
self._recv_buffer = b''
|
||||
self._recv_queue = []
|
||||
self._send_buffer = ''.encode("ascii")
|
||||
self._send_buffer = b''
|
||||
|
||||
self._previous_sendmsg = None
|
||||
|
||||
self._sent_close = False
|
||||
self._received_close = False
|
||||
@@ -164,27 +152,25 @@ class WebSocket(object):
|
||||
self._key = ''
|
||||
for i in range(16):
|
||||
self._key += chr(random.randrange(256))
|
||||
if sys.hexversion >= 0x3000000:
|
||||
self._key = bytes(self._key, "latin-1")
|
||||
self._key = b64encode(self._key).decode("ascii")
|
||||
self._key = b64encode(self._key.encode("latin-1")).decode("ascii")
|
||||
|
||||
path = uri.path
|
||||
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"
|
||||
|
||||
@@ -196,10 +182,10 @@ class WebSocket(object):
|
||||
if not self._recv():
|
||||
raise Exception("Socket closed unexpectedly")
|
||||
|
||||
if self._recv_buffer.find('\r\n\r\n'.encode("ascii")) == -1:
|
||||
if self._recv_buffer.find(b'\r\n\r\n') == -1:
|
||||
raise WebSocketWantReadError
|
||||
|
||||
(request, self._recv_buffer) = self._recv_buffer.split('\r\n'.encode("ascii"), 1)
|
||||
(request, self._recv_buffer) = self._recv_buffer.split(b'\r\n', 1)
|
||||
request = request.decode("latin-1")
|
||||
|
||||
words = request.split()
|
||||
@@ -208,7 +194,7 @@ class WebSocket(object):
|
||||
if words[1] != "101":
|
||||
raise Exception("WebSocket request denied: %s" % " ".join(words[1:]))
|
||||
|
||||
(headers, self._recv_buffer) = self._recv_buffer.split('\r\n\r\n'.encode("ascii"), 1)
|
||||
(headers, self._recv_buffer) = self._recv_buffer.split(b'\r\n\r\n', 1)
|
||||
headers = headers.decode('latin-1') + '\r\n'
|
||||
headers = email.message_from_string(headers)
|
||||
|
||||
@@ -254,8 +240,8 @@ class WebSocket(object):
|
||||
the value "websocket" in such cases.
|
||||
|
||||
WebSocketWantWriteError can be raised if the response cannot be
|
||||
sent right away. Repeated calls to accept() does not need to
|
||||
retain the arguments.
|
||||
sent right away. accept() must be called again once more space
|
||||
is available using the same arguments.
|
||||
"""
|
||||
|
||||
# This is a state machine in order to handle
|
||||
@@ -297,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"
|
||||
|
||||
@@ -419,8 +405,12 @@ class WebSocket(object):
|
||||
data from other calls, or split it over multiple messages.
|
||||
|
||||
WebSocketWantWriteError can be raised if there is insufficient
|
||||
space in the underlying socket.
|
||||
space in the underlying socket. send() must be called again
|
||||
once more space is available using the same arguments.
|
||||
"""
|
||||
if len(bytes) == 0:
|
||||
return 0
|
||||
|
||||
return self.sendmsg(bytes)
|
||||
|
||||
def sendmsg(self, msg):
|
||||
@@ -431,23 +421,93 @@ class WebSocket(object):
|
||||
single WebSocket message.
|
||||
|
||||
WebSocketWantWriteError can be raised if there is insufficient
|
||||
space in the underlying socket.
|
||||
space in the underlying socket. sendmsg() must be called again
|
||||
once more space is available using the same arguments.
|
||||
"""
|
||||
if not self._sent_close:
|
||||
# Only called to flush?
|
||||
if msg:
|
||||
self._sendmsg(0x2, msg)
|
||||
if not isinstance(msg, bytes):
|
||||
raise TypeError
|
||||
|
||||
if self._sent_close:
|
||||
return 0
|
||||
|
||||
if self._previous_sendmsg is not None:
|
||||
if self._previous_sendmsg != msg:
|
||||
raise ValueError
|
||||
|
||||
self._flush()
|
||||
self._previous_sendmsg = None
|
||||
|
||||
return len(msg)
|
||||
|
||||
try:
|
||||
self._sendmsg(0x2, msg)
|
||||
except WebSocketWantWriteError:
|
||||
self._previous_sendmsg = msg
|
||||
raise
|
||||
|
||||
self._flush()
|
||||
return len(msg)
|
||||
|
||||
def ping(self, data=''.encode('ascii')):
|
||||
"""Write a ping message to the WebSocket."""
|
||||
self._sendmsg(0x9, data)
|
||||
def send_response(self, code, message):
|
||||
self._queue_str("HTTP/1.1 %d %s\r\n" % (code, message))
|
||||
|
||||
def pong(self, data=''.encode('ascii')):
|
||||
"""Write a pong message to the WebSocket."""
|
||||
self._sendmsg(0xA, data)
|
||||
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
|
||||
|
||||
WebSocketWantWriteError can be raised if there is insufficient
|
||||
space in the underlying socket. ping() must be called again once
|
||||
more space is available using the same arguments.
|
||||
"""
|
||||
if not isinstance(data, bytes):
|
||||
raise TypeError
|
||||
|
||||
if self._previous_sendmsg is not None:
|
||||
if self._previous_sendmsg != data:
|
||||
raise ValueError
|
||||
|
||||
self._flush()
|
||||
self._previous_sendmsg = None
|
||||
|
||||
return
|
||||
|
||||
try:
|
||||
self._sendmsg(0x9, data)
|
||||
except WebSocketWantWriteError:
|
||||
self._previous_sendmsg = data
|
||||
raise
|
||||
|
||||
def pong(self, data=b''):
|
||||
"""Write a pong message to the WebSocket
|
||||
|
||||
WebSocketWantWriteError can be raised if there is insufficient
|
||||
space in the underlying socket. pong() must be called again once
|
||||
more space is available using the same arguments.
|
||||
"""
|
||||
if not isinstance(data, bytes):
|
||||
raise TypeError
|
||||
|
||||
if self._previous_sendmsg is not None:
|
||||
if self._previous_sendmsg != data:
|
||||
raise ValueError
|
||||
|
||||
self._flush()
|
||||
self._previous_sendmsg = None
|
||||
|
||||
return
|
||||
|
||||
try:
|
||||
self._sendmsg(0xA, data)
|
||||
except WebSocketWantWriteError:
|
||||
self._previous_sendmsg = data
|
||||
raise
|
||||
|
||||
def shutdown(self, how, code=1000, reason=None):
|
||||
"""Gracefully terminate the WebSocket connection.
|
||||
@@ -459,7 +519,9 @@ class WebSocket(object):
|
||||
ignored.
|
||||
|
||||
WebSocketWantWriteError can be raised if there is insufficient
|
||||
space in the underlying socket for the close message.
|
||||
space in the underlying socket for the close message. shutdown()
|
||||
must be called again once more space is available using the same
|
||||
arguments.
|
||||
|
||||
The how argument is currently ignored.
|
||||
"""
|
||||
@@ -476,7 +538,7 @@ class WebSocket(object):
|
||||
|
||||
self._sent_close = True
|
||||
|
||||
msg = ''.encode('ascii')
|
||||
msg = b''
|
||||
if code is not None:
|
||||
msg += struct.pack(">H", code)
|
||||
if reason is not None:
|
||||
@@ -491,7 +553,9 @@ class WebSocket(object):
|
||||
a close message to the peer.
|
||||
|
||||
WebSocketWantWriteError can be raised if there is insufficient
|
||||
space in the underlying socket for the close message.
|
||||
space in the underlying socket for the close message. close()
|
||||
must be called again once more space is available using the same
|
||||
arguments.
|
||||
"""
|
||||
self.shutdown(socket.SHUT_RDWR, code, reason)
|
||||
self._close()
|
||||
@@ -503,16 +567,9 @@ class WebSocket(object):
|
||||
while True:
|
||||
try:
|
||||
data = self.socket.recv(4096)
|
||||
except (socket.error, OSError):
|
||||
exc = sys.exc_info()[1]
|
||||
if hasattr(exc, 'errno'):
|
||||
err = exc.errno
|
||||
else:
|
||||
err = exc[0]
|
||||
|
||||
if err == errno.EWOULDBLOCK:
|
||||
except OSError as exc:
|
||||
if exc.errno == errno.EWOULDBLOCK:
|
||||
raise WebSocketWantReadError
|
||||
|
||||
raise
|
||||
|
||||
if len(data) == 0:
|
||||
@@ -569,7 +626,7 @@ class WebSocket(object):
|
||||
|
||||
if frame["fin"]:
|
||||
msg = self._partial_msg
|
||||
self._partial_msg = ''.decode("ascii")
|
||||
self._partial_msg = b''
|
||||
return msg
|
||||
elif frame["opcode"] == 0x1:
|
||||
self.shutdown(socket.SHUT_RDWR, 1003, "Unsupported: Text frames are not supported")
|
||||
@@ -609,7 +666,7 @@ class WebSocket(object):
|
||||
continue
|
||||
|
||||
if code is None:
|
||||
self.close_code = code = 1005
|
||||
self.close_code = 1005
|
||||
self.close_reason = "No close status code specified by peer"
|
||||
else:
|
||||
self.close_code = code
|
||||
@@ -644,16 +701,9 @@ class WebSocket(object):
|
||||
|
||||
try:
|
||||
sent = self.socket.send(self._send_buffer)
|
||||
except (socket.error, OSError):
|
||||
exc = sys.exc_info()[1]
|
||||
if hasattr(exc, 'errno'):
|
||||
err = exc.errno
|
||||
else:
|
||||
err = exc[0]
|
||||
|
||||
if err == errno.EWOULDBLOCK:
|
||||
except OSError as exc:
|
||||
if exc.errno == errno.EWOULDBLOCK:
|
||||
raise WebSocketWantWriteError
|
||||
|
||||
raise
|
||||
|
||||
self._send_buffer = self._send_buffer[sent:]
|
||||
@@ -679,11 +729,9 @@ class WebSocket(object):
|
||||
def _sendmsg(self, opcode, msg):
|
||||
# Sends a standard data message
|
||||
if self.client:
|
||||
mask = ''
|
||||
mask = b''
|
||||
for i in range(4):
|
||||
mask += chr(random.randrange(256))
|
||||
if sys.hexversion >= 0x3000000:
|
||||
mask = bytes(mask, "latin-1")
|
||||
mask += random.randrange(256)
|
||||
frame = self._encode_hybi(opcode, msg, mask)
|
||||
else:
|
||||
frame = self._encode_hybi(opcode, msg)
|
||||
@@ -705,7 +753,7 @@ class WebSocket(object):
|
||||
plen = len(buf)
|
||||
pstart = 0
|
||||
pend = plen
|
||||
b = c = ''.encode('ascii')
|
||||
b = c = b''
|
||||
if plen >= 4:
|
||||
dtype=numpy.dtype('<u4')
|
||||
if sys.byteorder == 'big':
|
||||
@@ -713,7 +761,7 @@ class WebSocket(object):
|
||||
mask = numpy.frombuffer(mask, dtype, count=1)
|
||||
data = numpy.frombuffer(buf, dtype, count=int(plen / 4))
|
||||
#b = numpy.bitwise_xor(data, mask).data
|
||||
b = numpy.bitwise_xor(data, mask).tostring()
|
||||
b = numpy.bitwise_xor(data, mask).tobytes()
|
||||
|
||||
if plen % 4:
|
||||
dtype=numpy.dtype('B')
|
||||
@@ -722,17 +770,15 @@ class WebSocket(object):
|
||||
mask = numpy.frombuffer(mask, dtype, count=(plen % 4))
|
||||
data = numpy.frombuffer(buf, dtype,
|
||||
offset=plen - (plen % 4), count=(plen % 4))
|
||||
c = numpy.bitwise_xor(data, mask).tostring()
|
||||
c = numpy.bitwise_xor(data, mask).tobytes()
|
||||
return b + c
|
||||
else:
|
||||
# Slower fallback
|
||||
if sys.hexversion < 0x3000000:
|
||||
mask = [ ord(c) for c in mask ]
|
||||
data = array.array('B')
|
||||
data.fromstring(buf)
|
||||
data.frombytes(buf)
|
||||
for i in range(len(data)):
|
||||
data[i] ^= mask[i % 4]
|
||||
return data.tostring()
|
||||
return data.tobytes()
|
||||
|
||||
def _encode_hybi(self, opcode, buf, mask_key=None, fin=True):
|
||||
""" Encode a HyBi style WebSocket frame.
|
||||
|
||||
@@ -11,25 +11,14 @@ as taken from http://docs.python.org/dev/library/ssl.html#certificates
|
||||
|
||||
'''
|
||||
|
||||
import signal, socket, optparse, time, os, sys, subprocess, logging, errno, ssl
|
||||
try:
|
||||
from socketserver import ThreadingMixIn
|
||||
except ImportError:
|
||||
from SocketServer import ThreadingMixIn
|
||||
|
||||
try:
|
||||
from http.server import HTTPServer
|
||||
except ImportError:
|
||||
from BaseHTTPServer import HTTPServer
|
||||
import signal, socket, optparse, time, os, sys, subprocess, logging, errno, ssl, stat
|
||||
from socketserver import ThreadingMixIn
|
||||
from http.server import HTTPServer
|
||||
|
||||
import select
|
||||
from websockify import websockifyserver
|
||||
from websockify import auth_plugins as auth
|
||||
try:
|
||||
from urllib.parse import parse_qs, urlparse
|
||||
except ImportError:
|
||||
from cgi import parse_qs
|
||||
from urlparse import urlparse
|
||||
from urllib.parse import parse_qs, urlparse
|
||||
|
||||
class ProxyRequestHandler(websockifyserver.WebSockifyRequestHandler):
|
||||
|
||||
@@ -46,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
|
||||
@@ -83,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,
|
||||
@@ -118,12 +107,14 @@ Traffic Legend:
|
||||
connect=True,
|
||||
use_ssl=self.server.ssl_target,
|
||||
unix_socket=self.server.unix_target)
|
||||
except Exception:
|
||||
self.log_message("Failed to connect to %s:%s",
|
||||
self.server.target_host, self.server.target_port)
|
||||
except Exception as e:
|
||||
self.log_message("Failed to connect to %s:%s: %s",
|
||||
self.server.target_host, self.server.target_port, e)
|
||||
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:
|
||||
tsock.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)
|
||||
|
||||
@@ -233,6 +224,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",
|
||||
@@ -256,6 +259,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)
|
||||
@@ -293,6 +306,7 @@ class WebSocketProxy(websockifyserver.WebSockifyServer):
|
||||
wsdir = os.path.dirname(sys.argv[0])
|
||||
rebinder_path = [os.path.join(wsdir, "..", "lib"),
|
||||
os.path.join(wsdir, "..", "lib", "websockify"),
|
||||
os.path.join(wsdir, ".."),
|
||||
wsdir]
|
||||
self.rebinder = None
|
||||
|
||||
@@ -313,12 +327,15 @@ 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)})
|
||||
|
||||
websockifyserver.WebSockifyServer.__init__(self, RequestHandlerClass, *args, **kwargs)
|
||||
super().__init__(RequestHandlerClass, *args, **kwargs)
|
||||
|
||||
def run_wrap_cmd(self):
|
||||
self.msg("Starting '%s'", " ".join(self.wrap_cmd))
|
||||
@@ -394,35 +411,15 @@ def _subprocess_setup():
|
||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||
|
||||
|
||||
try :
|
||||
# First try SSL options for Python 3.4 and above
|
||||
SSL_OPTIONS = {
|
||||
'default': ssl.OP_ALL,
|
||||
'tlsv1_1': ssl.PROTOCOL_TLS | ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 |
|
||||
ssl.OP_NO_TLSv1,
|
||||
'tlsv1_2': ssl.PROTOCOL_TLS | ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 |
|
||||
ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1,
|
||||
'tlsv1_3': ssl.PROTOCOL_TLS | ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 |
|
||||
ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1 | ssl.OP_NO_TLSv1_2,
|
||||
}
|
||||
except AttributeError:
|
||||
try:
|
||||
# Python 3.3 uses a different scheme for SSL options
|
||||
# tlsv1_3 is not supported on older Python versions
|
||||
SSL_OPTIONS = {
|
||||
'default': ssl.OP_ALL,
|
||||
'tlsv1_1': ssl.PROTOCOL_TLSv1 | ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 |
|
||||
ssl.OP_NO_TLSv1,
|
||||
'tlsv1_2': ssl.PROTOCOL_TLSv1 | ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 |
|
||||
ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1,
|
||||
}
|
||||
except AttributeError:
|
||||
# Python 2.6 does not support TLS v1.2, and uses a different scheme
|
||||
# for SSL options
|
||||
SSL_OPTIONS = {
|
||||
'default': ssl.PROTOCOL_SSLv23,
|
||||
'tlsv1_1': ssl.PROTOCOL_TLSv1,
|
||||
}
|
||||
SSL_OPTIONS = {
|
||||
'default': ssl.OP_ALL,
|
||||
'tlsv1_1': ssl.PROTOCOL_SSLv23 | ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 |
|
||||
ssl.OP_NO_TLSv1,
|
||||
'tlsv1_2': ssl.PROTOCOL_SSLv23 | ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 |
|
||||
ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1,
|
||||
'tlsv1_3': ssl.PROTOCOL_SSLv23 | ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 |
|
||||
ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1 | ssl.OP_NO_TLSv1_2,
|
||||
}
|
||||
|
||||
def select_ssl_version(version):
|
||||
"""Returns SSL options for the most secure TSL version available on this
|
||||
@@ -433,7 +430,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",
|
||||
@@ -443,18 +440,21 @@ 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 += " [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)
|
||||
@@ -497,6 +497,10 @@ def websockify_init():
|
||||
parser.add_option("--ssl-ciphers", action="store",
|
||||
help="list of ciphers allowed for connection. For a list of "
|
||||
"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",
|
||||
help="connect to unix socket target", metavar="FILE")
|
||||
parser.add_option("--inetd",
|
||||
@@ -547,6 +551,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()
|
||||
|
||||
@@ -582,7 +588,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
|
||||
|
||||
@@ -615,13 +622,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
|
||||
@@ -647,6 +656,16 @@ def websockify_init():
|
||||
|
||||
if opts.inetd:
|
||||
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:
|
||||
if len(args) < 1:
|
||||
parser.error("Too few arguments")
|
||||
@@ -768,14 +787,13 @@ class LibProxyServer(ThreadingMixIn, HTTPServer):
|
||||
if web:
|
||||
os.chdir(web)
|
||||
|
||||
HTTPServer.__init__(self, (listen_host, listen_port),
|
||||
RequestHandlerClass)
|
||||
super().__init__((listen_host, listen_port), RequestHandlerClass)
|
||||
|
||||
|
||||
def process_request(self, request, client_address):
|
||||
"""Override process_request to implement a counter"""
|
||||
self.handler_id += 1
|
||||
ThreadingMixIn.process_request(self, request, client_address)
|
||||
super().process_request(request, client_address)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -8,15 +8,27 @@ Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
|
||||
'''
|
||||
|
||||
import sys
|
||||
|
||||
# python 3.0 differences
|
||||
try:
|
||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
except ImportError:
|
||||
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
|
||||
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
|
||||
|
||||
@@ -30,7 +42,7 @@ class WebSocketRequestHandlerMixIn:
|
||||
use for the WebSocket connection.
|
||||
"""
|
||||
|
||||
SocketClass = WebSocket
|
||||
SocketClass = HttpWebSocket
|
||||
|
||||
def handle_one_request(self):
|
||||
"""Extended request handler
|
||||
@@ -42,12 +54,7 @@ class WebSocketRequestHandlerMixIn:
|
||||
self._real_do_GET = self.do_GET
|
||||
self.do_GET = self._websocket_do_GET
|
||||
try:
|
||||
# super() only works for new style classes
|
||||
if issubclass(WebSocketRequestHandlerMixIn, object):
|
||||
super(WebSocketRequestHandlerMixIn, self).handle_one_request()
|
||||
else:
|
||||
# Assume handle_one_request() hasn't been overriden
|
||||
BaseHTTPRequestHandler.handle_one_request(self)
|
||||
super().handle_one_request()
|
||||
finally:
|
||||
self.do_GET = self._real_do_GET
|
||||
|
||||
@@ -69,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:
|
||||
@@ -77,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
|
||||
|
||||
@@ -14,19 +14,7 @@ as taken from http://docs.python.org/dev/library/ssl.html#certificates
|
||||
|
||||
import os, sys, time, errno, signal, socket, select, logging
|
||||
import multiprocessing
|
||||
|
||||
# Imports that vary by python version
|
||||
|
||||
# python 3.0 differences
|
||||
if sys.hexversion > 0x3000000:
|
||||
s2b = lambda s: s.encode('latin_1')
|
||||
else:
|
||||
s2b = lambda s: s # No-op
|
||||
|
||||
try:
|
||||
from http.server import SimpleHTTPRequestHandler
|
||||
except ImportError:
|
||||
from SimpleHTTPServer import SimpleHTTPRequestHandler
|
||||
from http.server import SimpleHTTPRequestHandler
|
||||
|
||||
# Degraded functionality if these imports are missing
|
||||
for mod, msg in [('ssl', 'TLS/SSL/wss is disabled'),
|
||||
@@ -41,12 +29,12 @@ 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 specifiy a sub-protocol
|
||||
# Handle old websockify clients that still specify a sub-protocol
|
||||
if 'binary' in protocols:
|
||||
return 'binary'
|
||||
else:
|
||||
@@ -96,7 +84,7 @@ class WebSockifyRequestHandler(WebSocketRequestHandlerMixIn, SimpleHTTPRequestHa
|
||||
if self.logger is None:
|
||||
self.logger = WebSockifyServer.get_logger()
|
||||
|
||||
SimpleHTTPRequestHandler.__init__(self, req, addr, server)
|
||||
super().__init__(req, addr, server)
|
||||
|
||||
def log_message(self, format, *args):
|
||||
self.logger.info("%s - - [%s] %s" % (self.client_address[0], self.log_date_time_string(), format % args))
|
||||
@@ -146,20 +134,14 @@ class WebSockifyRequestHandler(WebSocketRequestHandlerMixIn, SimpleHTTPRequestHa
|
||||
self.rec.write("'{{{0}{{{1}',\n".format(tdelta, bufstr))
|
||||
self.send_parts.append(buf)
|
||||
|
||||
# Flush any previously queued data
|
||||
try:
|
||||
self.request.sendmsg('')
|
||||
except WebSocketWantWriteError:
|
||||
return True
|
||||
|
||||
while self.send_parts:
|
||||
# Send pending frames
|
||||
buf = self.send_parts.pop(0)
|
||||
try:
|
||||
self.request.sendmsg(buf)
|
||||
self.request.sendmsg(self.send_parts[0])
|
||||
except WebSocketWantWriteError:
|
||||
self.print_traffic("<.")
|
||||
return True
|
||||
self.send_parts.pop(0)
|
||||
self.print_traffic("<")
|
||||
|
||||
return False
|
||||
@@ -218,7 +200,7 @@ class WebSockifyRequestHandler(WebSocketRequestHandlerMixIn, SimpleHTTPRequestHa
|
||||
self.validate_connection()
|
||||
self.auth_connection()
|
||||
|
||||
WebSocketRequestHandlerMixIn.handle_upgrade(self)
|
||||
super().handle_upgrade()
|
||||
|
||||
def handle_websocket(self):
|
||||
# Indicate to server that a Websocket upgrade was done
|
||||
@@ -268,15 +250,15 @@ class WebSockifyRequestHandler(WebSocketRequestHandlerMixIn, SimpleHTTPRequestHa
|
||||
self.auth_connection()
|
||||
|
||||
if self.only_upgrade:
|
||||
self.send_error(405, "Method Not Allowed")
|
||||
self.send_error(405)
|
||||
else:
|
||||
SimpleHTTPRequestHandler.do_GET(self)
|
||||
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 SimpleHTTPRequestHandler.list_directory(self, path)
|
||||
return super().list_directory(path)
|
||||
|
||||
def new_websocket_client(self):
|
||||
""" Do something with a WebSockets client connection. """
|
||||
@@ -295,15 +277,15 @@ class WebSockifyRequestHandler(WebSocketRequestHandlerMixIn, SimpleHTTPRequestHa
|
||||
self.auth_connection()
|
||||
|
||||
if self.only_upgrade:
|
||||
self.send_error(405, "Method Not Allowed")
|
||||
self.send_error(405)
|
||||
else:
|
||||
SimpleHTTPRequestHandler.do_HEAD(self)
|
||||
super().do_HEAD()
|
||||
|
||||
def finish(self):
|
||||
if self.rec:
|
||||
self.rec.write("'EOF'];\n")
|
||||
self.rec.close()
|
||||
SimpleHTTPRequestHandler.finish(self)
|
||||
super().finish()
|
||||
|
||||
def handle(self):
|
||||
# When using run_once, we have a single process, so
|
||||
@@ -312,14 +294,14 @@ class WebSockifyRequestHandler(WebSocketRequestHandlerMixIn, SimpleHTTPRequestHa
|
||||
if self.run_once:
|
||||
self.handle_one_request()
|
||||
else:
|
||||
SimpleHTTPRequestHandler.handle(self)
|
||||
super().handle()
|
||||
|
||||
def log_request(self, code='-', size='-'):
|
||||
if self.verbose:
|
||||
SimpleHTTPRequestHandler.log_request(self, code, size)
|
||||
super().log_request(code, size)
|
||||
|
||||
|
||||
class WebSockifyServer(object):
|
||||
class WebSockifyServer():
|
||||
"""
|
||||
WebSockets server class.
|
||||
As an alternative, the standard library SocketServer can be used
|
||||
@@ -343,37 +325,40 @@ class WebSockifyServer(object):
|
||||
file_only=False,
|
||||
run_once=False, timeout=0, idle_timeout=0, traffic=False,
|
||||
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
|
||||
self.RequestHandlerClass = RequestHandlerClass
|
||||
self.verbose = verbose
|
||||
self.listen_fd = listen_fd
|
||||
self.listen_host = listen_host
|
||||
self.listen_port = listen_port
|
||||
self.prefer_ipv6 = source_is_ipv6
|
||||
self.ssl_only = ssl_only
|
||||
self.ssl_ciphers = ssl_ciphers
|
||||
self.ssl_options = ssl_options
|
||||
self.verify_client = verify_client
|
||||
self.daemon = daemon
|
||||
self.run_once = run_once
|
||||
self.timeout = timeout
|
||||
self.idle_timeout = idle_timeout
|
||||
self.traffic = traffic
|
||||
self.file_only = file_only
|
||||
self.web_auth = web_auth
|
||||
self.verbose = verbose
|
||||
self.listen_fd = listen_fd
|
||||
self.unix_listen = unix_listen
|
||||
self.unix_listen_mode = unix_listen_mode
|
||||
self.listen_host = listen_host
|
||||
self.listen_port = listen_port
|
||||
self.prefer_ipv6 = source_is_ipv6
|
||||
self.ssl_only = ssl_only
|
||||
self.ssl_ciphers = ssl_ciphers
|
||||
self.ssl_options = ssl_options
|
||||
self.verify_client = verify_client
|
||||
self.daemon = daemon
|
||||
self.run_once = run_once
|
||||
self.timeout = timeout
|
||||
self.idle_timeout = idle_timeout
|
||||
self.traffic = traffic
|
||||
self.file_only = file_only
|
||||
self.web_auth = web_auth
|
||||
|
||||
self.launch_time = time.time()
|
||||
self.ws_connection = False
|
||||
self.handler_id = 1
|
||||
self.terminating = False
|
||||
self.launch_time = time.time()
|
||||
self.ws_connection = False
|
||||
self.handler_id = 1
|
||||
self.terminating = False
|
||||
|
||||
self.logger = self.get_logger()
|
||||
self.tcp_keepalive = tcp_keepalive
|
||||
self.tcp_keepcnt = tcp_keepcnt
|
||||
self.tcp_keepidle = tcp_keepidle
|
||||
self.tcp_keepintvl = tcp_keepintvl
|
||||
self.logger = self.get_logger()
|
||||
self.tcp_keepalive = tcp_keepalive
|
||||
self.tcp_keepcnt = tcp_keepcnt
|
||||
self.tcp_keepidle = tcp_keepidle
|
||||
self.tcp_keepintvl = tcp_keepintvl
|
||||
|
||||
# keyfile path must be None if not specified
|
||||
self.key = None
|
||||
@@ -405,6 +390,8 @@ class WebSockifyServer(object):
|
||||
self.msg("WebSocket server settings:")
|
||||
if self.listen_fd != None:
|
||||
self.msg(" - Listen for inetd connections")
|
||||
elif self.unix_listen != None:
|
||||
self.msg(" - Listen on unix socket %s", self.unix_listen)
|
||||
else:
|
||||
self.msg(" - Listen on %s:%s",
|
||||
self.listen_host, self.listen_port)
|
||||
@@ -439,8 +426,9 @@ class WebSockifyServer(object):
|
||||
|
||||
@staticmethod
|
||||
def socket(host, port=None, connect=False, prefer_ipv6=False,
|
||||
unix_socket=None, use_ssl=False, tcp_keepalive=True,
|
||||
tcp_keepcnt=None, tcp_keepidle=None, tcp_keepintvl=None):
|
||||
unix_socket=None, unix_socket_mode=None, unix_socket_listen=False,
|
||||
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
|
||||
address. Create a socket. Bind to it if listen is set,
|
||||
otherwise connect to it. Return the socket.
|
||||
@@ -488,8 +476,22 @@ class WebSockifyServer(object):
|
||||
sock.bind(addrs[0][4])
|
||||
sock.listen(100)
|
||||
else:
|
||||
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
sock.connect(unix_socket)
|
||||
if unix_socket_listen:
|
||||
# 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
|
||||
|
||||
@@ -559,7 +561,7 @@ class WebSockifyServer(object):
|
||||
if not handshake:
|
||||
raise self.EClose("")
|
||||
|
||||
elif handshake[0] in ("\x16", "\x80", 22, 128):
|
||||
elif handshake[0] in (22, 128):
|
||||
# SSL wrap the connection
|
||||
if not ssl:
|
||||
raise self.EClose("SSL connection but no 'ssl' module")
|
||||
@@ -568,32 +570,21 @@ class WebSockifyServer(object):
|
||||
% self.cert)
|
||||
retsock = None
|
||||
try:
|
||||
if (hasattr(ssl, 'create_default_context')
|
||||
and callable(ssl.create_default_context)):
|
||||
# create new-style SSL wrapping for extended features
|
||||
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
|
||||
if self.ssl_ciphers is not None:
|
||||
context.set_ciphers(self.ssl_ciphers)
|
||||
context.options = self.ssl_options
|
||||
context.load_cert_chain(certfile=self.cert, keyfile=self.key, password=self.key_password)
|
||||
if self.verify_client:
|
||||
context.verify_mode = ssl.CERT_REQUIRED
|
||||
if self.cafile:
|
||||
context.load_verify_locations(cafile=self.cafile)
|
||||
else:
|
||||
context.set_default_verify_paths()
|
||||
retsock = context.wrap_socket(
|
||||
sock,
|
||||
server_side=True)
|
||||
else:
|
||||
if self.verify_client:
|
||||
raise self.EClose("Client certificate verification requested, but this Python is too old.")
|
||||
# new-style SSL wrapping is not needed, using to old style
|
||||
retsock = ssl.wrap_socket(
|
||||
sock,
|
||||
server_side=True,
|
||||
certfile=self.cert,
|
||||
keyfile=self.key)
|
||||
# create new-style SSL wrapping for extended features
|
||||
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
|
||||
if self.ssl_ciphers is not None:
|
||||
context.set_ciphers(self.ssl_ciphers)
|
||||
context.options = self.ssl_options
|
||||
context.load_cert_chain(certfile=self.cert, keyfile=self.key, password=self.key_password)
|
||||
if self.verify_client:
|
||||
context.verify_mode = ssl.CERT_REQUIRED
|
||||
if self.cafile:
|
||||
context.load_verify_locations(cafile=self.cafile)
|
||||
else:
|
||||
context.set_default_verify_paths()
|
||||
retsock = context.wrap_socket(
|
||||
sock,
|
||||
server_side=True)
|
||||
except ssl.SSLError:
|
||||
_, x, _ = sys.exc_info()
|
||||
if x.args[0] == ssl.SSL_ERROR_EOF:
|
||||
@@ -729,10 +720,11 @@ class WebSockifyServer(object):
|
||||
|
||||
if self.listen_fd != None:
|
||||
lsock = socket.fromfd(self.listen_fd, socket.AF_INET, socket.SOCK_STREAM)
|
||||
if sys.hexversion < 0x3000000:
|
||||
# For python 2 we have to wrap the "raw" socket into a socket object,
|
||||
# otherwise ssl wrap_socket doesn't work.
|
||||
lsock = socket.socket(_sock=lsock)
|
||||
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:
|
||||
lsock = self.socket(self.listen_host, self.listen_port, False,
|
||||
self.prefer_ipv6,
|
||||
@@ -799,6 +791,9 @@ class WebSockifyServer(object):
|
||||
ready = select.select([lsock], [], [], 1)[0]
|
||||
if lsock in ready:
|
||||
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:
|
||||
continue
|
||||
except self.Terminate:
|
||||
|
||||
Reference in New Issue
Block a user