54 Commits

Author SHA1 Message Date
Pierre Ossman 71d55fcaa8 Fix bad classifiers array 2021-07-22 17:20:45 +02:00
Pierre Ossman 1c35fdd72b websockify 0.10.0 2021-07-22 17:11:08 +02:00
Pierre Ossman eca301c05b Fix patching of open() for Python 3.4
It doesn't handle builtins automatically, so follow the recommendations
from that time.
2021-05-03 14:34:07 +02:00
Pierre Ossman b9b269c73f Add unit test for token file with tab 2021-04-16 13:02:07 +02:00
Pierre Ossman da130946eb Merge branch 'sisou/split-tokens-tab' of https://github.com/sisou/websockify 2021-04-16 13:00:36 +02:00
Pierre Ossman 100a776409 Add unit tests for ReadOnlyTokenFile 2021-04-16 12:59:17 +02:00
Pierre Ossman 984dcc62d3 Move JWT token plugins tests to separate file
Let's try to match the test units with the modules we have.
2021-04-16 11:27:56 +02:00
Samuel Mannehed c5d365dd1d Merge pull request #479 from llopisdon/patch-1
Update README.md
2021-03-22 09:23:15 +01:00
Don E. Llopis 27e0ce71ed Update README.md
Fixed typo and clarified sentence.
2021-03-20 10:17:55 -04:00
Pierre Ossman 24c3f75ede Remove setuptools version restriction
We're fine with current versions now.
2021-02-08 15:51:34 +01:00
Pierre Ossman 8b724074e0 Use older PROTOCOL_SSLv23 instead of PROTOCOL_TLS
We need to support Python older than 3.6, so use the older name.
2021-02-08 15:47:29 +01:00
Pierre Ossman 55f1fe2603 Explicitly include "six" for jwt tests
It is not properly listed as a dependency for jwcrypto, so we need to
pull it in manually.
2021-02-08 15:46:43 +01:00
Pierre Ossman f1e33fa4c7 Use older numpy for older Python versions
The latest version refuses to run on anything older than Python 3.7.
2021-02-08 15:33:37 +01:00
Pierre Ossman 72704b3fe8 Update classifiers to just Python 3
We no longer support anything older than Python 3.4.
2021-02-08 14:55:49 +01:00
Pierre Ossman 4afdcb00ab Update references in egg manifest
We changed things when we purged all the non-Python stuff out of the
tree.
2021-02-08 14:55:15 +01:00
Pierre Ossman 982e475936 Run all tests even on failure
Issues might be specific to certain versions of Python, so let all tests
run.
2021-02-08 14:42:41 +01:00
Pierre Ossman 6012052686 Use Ubuntu 18.04 for tests
We want to test on Python 3.4, which requires us to use an older Ubuntu
image for tests.
2021-02-08 11:11:39 +01:00
Pierre Ossman e4e85f8082 Replace Travis with Github actions 2021-02-08 11:02:48 +01:00
Pierre Ossman f7b0867471 Merge branch 'fix467_redis' of https://github.com/PexMor/websockify 2021-02-08 10:42:01 +01:00
PexMor 9dab8b360b add details on format of redis and leave port as number 2021-02-07 20:39:02 +01:00
Alexander E. Patrakov 4afafcd152 Simplify Windows instructions
Instead of a Websockify-specific tool that needs to be compiled, and
that doesn't work on Windows 10 anyway (the service takes forever to
start), let's switch to the industry standard SrvAny tool. This also
gets rid of .Net Framework requirement and lets one run modern
versions of Websockify easily.
2021-01-30 17:11:20 +05:00
Pierre Ossman b22a6b0ce0 Add unit tests for jwt token time checks 2021-01-29 13:11:07 +01:00
Pierre Ossman 980237c9cd Merge branch 'new_branch' of https://github.com/jprietove/websockify 2021-01-29 13:10:49 +01:00
Pierre Ossman fadb25e026 Use assertRaises() as a context manager
Makes the code a lot easier to read.
2021-01-29 13:10:16 +01:00
Pierre Ossman 3f17696dc6 Use patch() as a decorator in tests
Cleaner and more robust.
2021-01-29 13:09:19 +01:00
Pierre Ossman a82eb10b48 Remove Python version check
We require Python 3 now, so no need for this check.
2021-01-29 12:58:26 +01:00
Javier Prieto 1f618c8f41 Added JWT nbf, not before 2021-01-27 16:50:19 +01:00
Javier Prieto e9367b03be Added exp claim for JWT token 2021-01-27 15:16:08 +01:00
Tim Gates adc278657a docs: fix simple typo, acccept -> accept
There is a small typo in README.md.

Should read `accept` rather than `acccept`.
2020-12-25 20:37:15 +11:00
Pierre Ossman 108f993eb3 Explicitly request Python 3
It varies between systems if /usr/bin/python is Python 2.x or Python
3.x. Since we now only work with Python 3.x we should be more explicit
in our scripts and documentation.
2020-12-15 16:10:43 +01:00
Pierre Ossman 33710b3972 Use frombytes() instead of fromstring()
Both the Python library and NumPy have changed the names of these
functions to better match the new bytes data type it actually uses.
2020-12-14 16:08:16 +01:00
Pierre Ossman 96eda1a5c7 Remove support for older Python
All active distributions should now support at least Python 3.4, so
let's clean things up by removing older compatibility code.
2020-12-14 13:48:54 +01:00
Pierre Ossman e80739b296 Add more search paths for rebind.so
If we're run as a module then sys.argv[0] will be the main module, not
some startup script.
2020-12-04 13:57:21 +01:00
Samuel Mannehed 4c2edaabc2 Merge pull request #459 from nwtgck/patch-1
fix a typo
2020-11-18 09:15:42 +01:00
Ryo Ota d2155697fc fix a typo 2020-11-18 08:53:09 +09:00
JasonYang 2b44803f83 fixed a bug in the function of send_frames. 2020-11-10 13:14:54 +01:00
Pierre Ossman 01ef6a6a55 Require same arguments on insufficient socket space
This matches the behaviour of SSLSocket, which we are trying to mimic.
It also closely matches the behaviour of normal Socket which can be
assumed to not have sent anything if an error occurs. We might actually
send some data, but the caller cannot really see that and must call us
again as if no data was sent.
2020-09-30 14:58:19 +02:00
Pierre Ossman 6caf23c067 Add type checking when sendning data
We use this in various ways so add an early check to make things clear
in case anything is called incorrectly.
2020-09-30 14:53:19 +02:00
Pierre Ossman d72ace2ae6 Allow sending empty messages
This is perfectly valid in the protocol, and may be meningful to some
applications. However send() is still stream oriented so it will ignore
an empty buffer.
2020-09-30 14:51:07 +02:00
Samuel Mannehed 536a548db5 Merge pull request #441 from RangeForce/python-gitignore
Add build and dist directories to .gitignore
2020-09-02 02:19:12 +02:00
Chris Kerr 997b3897e7 Add build and dist directories to .gitignore 2020-09-01 10:26:46 +03:00
Samuel Mannehed 7d4841c49a Merge pull request #440 from gamtiq/patch-1
Fix a typo in README
2020-08-25 16:51:00 +02:00
Denis Sikuler 9851dd9012 Fix a typo in README 2020-08-24 15:36:35 +03:00
Pierre Ossman 9b1780f068 Fix bad reset of fragmented frame
The Python 2/3 binary data type compatibility hack was backwards here.
2020-08-21 10:58:55 +02:00
Pierre Ossman e5b6dfe17f Merge branch 'sisou/fix-print-statement' of https://github.com/sisou/websockify 2020-08-21 10:51:37 +02:00
Pierre Ossman 992e09eac4 Convert tests from mox to mock
mox is deprecated upstream in favour of mock
2020-08-21 10:50:11 +02:00
Sören Schwert 663b39cd38 Split token file by any whitespace after the colon, not just a space
With the current parser logic, only tokens and servers that are separated by _exactly_ a colon and a space `: ` are detected as tokens. But when formatting one's token file with tabs, this breaks.

This commit changes the split characters to be a regular expression that matches all forms of whitespace, including spaces and tabs.
2020-07-28 14:40:42 +02:00
Sören Schwert 57be17d7f5 Convert old print statement into function
For Python 3 compatibility.
2020-07-28 14:35:49 +02:00
Pierre Ossman 86a20b23f5 Update test requirements
Travis are dropping older Python environments, so update things to
something that reflects what current distributions are using (e.g. Red
Hat Enterprise Linux 7).
2020-04-08 09:18:09 +02:00
Feng Ye 7bd955a379 Add UnixDomainSocketDirectory token plugin 2020-03-18 10:40:18 +08:00
Pierre Ossman 6d48b1507e Make sure port is a simple string (again)
The redis token plugin also needs to make sure the port string isn't
unicode for compatibility with Python 2.x.
2020-02-27 15:55:35 +01:00
Pierre Ossman 76897b5093 Log connection errors better 2020-02-27 15:54:29 +01:00
Samuel Mannehed 4ecff119c4 Merge pull request #405 from andersk/shell
run: Fix shell scripting bugs
2019-09-26 08:42:17 +02:00
Anders Kaseorg 5052f823d7 run: Fix shell scripting bugs
* Use double quotes around `"$@"` to fix invocation with arguments
  including spaces.
* Use double quotes around `"$(dirname "$0")"` to fix invocation
  inside a directory path including spaces.
* Use `set -e` to abort in case `cd` fails.
* Use `exec` to avoid forking an unnecessary wrapper process.
* Skip an unnecessary `cd` → `pwd` → `cd` dance, just use `cd`.

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2019-08-17 11:39:46 -07:00
31 changed files with 638 additions and 980 deletions
+35
View File
@@ -0,0 +1,35 @@
name: Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-18.04
strategy:
matrix:
python-version:
- 3.4
- 3.5
- 3.6
- 3.7
- 3.8
- 3.9
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: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install setuptools
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 setup.py nosetests --verbosity=3
+3
View File
@@ -6,4 +6,7 @@
target.cfg target.cfg
target.cfg.d target.cfg.d
.tox .tox
build/
dist/
*.egg-info *.egg-info
-15
View File
@@ -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
+10
View File
@@ -1,6 +1,16 @@
Changes Changes
======= =======
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 0.9.0
----- -----
+1 -2
View File
@@ -1,2 +1 @@
include CHANGES.txt README.md LICENSE.txt include CHANGES.txt README.md COPYING
graft include
+5 -5
View File
@@ -55,9 +55,9 @@ 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 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 and approve. Browsers generally don't give you the "trust certificate?" prompt
by opening a WSS socket with invalid certificate, hence you need to have it by opening a WSS socket with invalid certificate, hence you need to have it
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 If you have a commercial/valid SSL certificate with one or more intermediate
certificates, concat them into one file, server certificate first, then the certificates, concat them into one file, server certificate first, then the
intermediate(s) from the CA, etc. Point to this file with the `--cert` option intermediate(s) from the CA, etc. Point to this file with the `--cert` option
and then also to the key with `--key`. Finally, use `--ssl-only` as needed. and then also to the key with `--key`. Finally, use `--ssl-only` as needed.
@@ -125,7 +125,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 launch a program on the local system and proxy WebSockets traffic to
a normal TCP port owned/bound by the program. 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 which intercepts bind() system calls by the program. The specified
port is moved to a new localhost/loopback free high port. websockify port is moved to a new localhost/loopback free high port. websockify
then proxies WebSockets traffic directed to the original port to the then proxies WebSockets traffic directed to the original port to the
@@ -159,12 +159,12 @@ project demonstrates a simple WebSockets based telnet client (use
### Installing websockify ### Installing websockify
Download one of the releases or the latest development version, extract 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 extracted the files. Normally, this will also install numpy for better
performance, if you don't have it installed already. However, numpy is 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, 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 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 Afterwards, websockify should be available in your path. Run
`websockify --help` to confirm it's installed correctly. `websockify --help` to confirm it's installed correctly.
+61
View File
@@ -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.
-39
View File
@@ -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
+3 -6
View File
@@ -1,7 +1,4 @@
#!/usr/bin/env sh #!/usr/bin/env sh
set -e
BASE_DIR="$(cd $(dirname "$0"); pwd)" cd "$(dirname "$0")"
exec python3 -m websockify "$@"
cd "$BASE_DIR"
python -m websockify $@
+8 -6
View File
@@ -1,6 +1,6 @@
from setuptools import setup, find_packages from setuptools import setup, find_packages
version = '0.9.0' version = '0.10.0'
name = 'websockify' name = 'websockify'
long_description = open("README.md").read() + "\n" + \ long_description = open("README.md").read() + "\n" + \
open("CHANGES.txt").read() + "\n" open("CHANGES.txt").read() + "\n"
@@ -12,12 +12,14 @@ setup(name=name,
long_description_content_type="text/markdown", long_description_content_type="text/markdown",
classifiers=[ classifiers=[
"Programming Language :: Python", "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",
"Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.4" "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', keywords='noVNC websockify',
license='LGPLv3', license='LGPLv3',
+5 -5
View File
@@ -1,6 +1,6 @@
mox3 mock
nose nose
jwcrypto;python_version>="2.7" jwcrypto
enum34;python_version=="3.3" six
redis;python_version>="2.7" redis
simplejson;python_version>="2.7" simplejson
+179
View File
@@ -0,0 +1,179 @@
# 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
from websockify.token_plugins import ReadOnlyTokenFile, JWTTokenApi
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 = 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)
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 = 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)
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 = 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", '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 = 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", '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 = 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", '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 = 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)
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 = 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)
result = plugin.lookup(jwt_token.serialize())
self.assertIsNone(result)
def test_asymmetric_jwe_token_plugin(self):
plugin = JWTTokenApi("./tests/fixtures/private.pem")
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)
result = plugin.lookup(jwt_token.serialize())
self.assertIsNotNone(result)
self.assertEqual(result[0], "remote_host")
self.assertEqual(result[1], "remote_port")
+14 -123
View File
@@ -20,31 +20,18 @@ import sys
import unittest import unittest
import unittest import unittest
import socket 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 websocketproxy
from websockify import token_plugins from websockify import token_plugins
from websockify import auth_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): class FakeSocket(object):
def __init__(self, data=''): def __init__(self, data=b''):
if isinstance(data, bytes): self._data = data
self._data = data
else:
self._data = data.encode('latin_1')
def recv(self, amt, flags=None): def recv(self, amt, flags=None):
res = self._data[0:amt] res = self._data[0:amt]
@@ -74,16 +61,14 @@ class FakeServer(object):
class ProxyRequestHandlerTestCase(unittest.TestCase): class ProxyRequestHandlerTestCase(unittest.TestCase):
def setUp(self): def setUp(self):
super(ProxyRequestHandlerTestCase, self).setUp() super(ProxyRequestHandlerTestCase, self).setUp()
self.stubs = stubout.StubOutForTesting()
self.handler = websocketproxy.ProxyRequestHandler( 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.path = "https://localhost:6080/websockify?token=blah"
self.handler.headers = None self.handler.headers = None
self.stubs.Set(websockifyserver.WebSockifyServer, 'socket', patch('websockify.websockifyserver.WebSockifyServer.socket').start()
staticmethod(lambda *args, **kwargs: None))
def tearDown(self): def tearDown(self):
self.stubs.UnsetAll() patch.stopall()
super(ProxyRequestHandlerTestCase, self).tearDown() super(ProxyRequestHandlerTestCase, self).tearDown()
def test_get_target(self): def test_get_target(self):
@@ -112,128 +97,34 @@ class ProxyRequestHandlerTestCase(unittest.TestCase):
def lookup(self, token): def lookup(self, token):
return None return None
self.assertRaises(FakeServer.EClose, self.handler.get_target, with self.assertRaises(FakeServer.EClose):
TestPlugin(None)) self.handler.get_target(TestPlugin(None))
@patch('websockify.websocketproxy.ProxyRequestHandler.send_auth_error', MagicMock())
def test_token_plugin(self): def test_token_plugin(self):
class TestPlugin(token_plugins.BasePlugin): class TestPlugin(token_plugins.BasePlugin):
def lookup(self, token): def lookup(self, token):
return (self.source + token).split(',') 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.server.token_plugin = TestPlugin("somehost,")
self.handler.validate_connection() self.handler.validate_connection()
self.assertEqual(self.handler.server.target_host, "somehost") self.assertEqual(self.handler.server.target_host, "somehost")
self.assertEqual(self.handler.server.target_port, "blah") self.assertEqual(self.handler.server.target_port, "blah")
if sys.version_info >= (2,7): @patch('websockify.websocketproxy.ProxyRequestHandler.send_auth_error', MagicMock())
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")
def test_auth_plugin(self): def test_auth_plugin(self):
class TestPlugin(auth_plugins.BasePlugin): class TestPlugin(auth_plugins.BasePlugin):
def authenticate(self, headers, target_host, target_port): def authenticate(self, headers, target_host, target_port):
if target_host == self.source: if target_host == self.source:
raise auth_plugins.AuthenticationError(response_msg="some_error") 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.auth_plugin = TestPlugin("somehost")
self.handler.server.target_host = "somehost" self.handler.server.target_host = "somehost"
self.handler.server.target_port = "someport" self.handler.server.target_port = "someport"
self.assertRaises(auth_plugins.AuthenticationError, with self.assertRaises(auth_plugins.AuthenticationError):
self.handler.auth_connection) self.handler.auth_connection()
self.handler.server.target_host = "someotherhost" self.handler.server.target_host = "someotherhost"
self.handler.auth_connection() self.handler.auth_connection()
+58 -114
View File
@@ -22,39 +22,26 @@ import select
import shutil import shutil
import socket import socket
import ssl import ssl
from mox3 import stubout from unittest.mock import patch, MagicMock, ANY
import sys import sys
import tempfile import tempfile
import unittest import unittest
import socket import socket
import signal import signal
from http.server import BaseHTTPRequestHandler
from io import StringIO
from io import BytesIO
from websockify import websockifyserver 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): def raise_oserror(*args, **kwargs):
raise OSError('fake error') raise OSError('fake error')
class FakeSocket(object): class FakeSocket(object):
def __init__(self, data=''): def __init__(self, data=b''):
if isinstance(data, bytes): self._data = data
self._data = data
else:
self._data = data.encode('latin_1')
def recv(self, amt, flags=None): def recv(self, amt, flags=None):
res = self._data[0:amt] res = self._data[0:amt]
@@ -73,22 +60,13 @@ class FakeSocket(object):
class WebSockifyRequestHandlerTestCase(unittest.TestCase): class WebSockifyRequestHandlerTestCase(unittest.TestCase):
def setUp(self): def setUp(self):
super(WebSockifyRequestHandlerTestCase, self).setUp() super(WebSockifyRequestHandlerTestCase, self).setUp()
self.stubs = stubout.StubOutForTesting()
self.tmpdir = tempfile.mkdtemp('-websockify-tests') self.tmpdir = tempfile.mkdtemp('-websockify-tests')
# Mock this out cause it screws tests up # Mock this out cause it screws tests up
self.stubs.Set(os, 'chdir', lambda *args, **kwargs: None) patch('os.chdir').start()
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)
def tearDown(self): def tearDown(self):
"""Called automatically after each test.""" """Called automatically after each test."""
self.stubs.UnsetAll() patch.stopall()
os.rmdir(self.tmpdir) os.rmdir(self.tmpdir)
super(WebSockifyRequestHandlerTestCase, self).tearDown() super(WebSockifyRequestHandlerTestCase, self).tearDown()
@@ -101,47 +79,36 @@ class WebSockifyRequestHandlerTestCase(unittest.TestCase):
record=self.tmpdir, daemon=False, ssl_only=0, idle_timeout=1, record=self.tmpdir, daemon=False, ssl_only=0, idle_timeout=1,
**kwargs) **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) server = self._get_server(web=None)
handler = websockifyserver.WebSockifyRequestHandler( handler = websockifyserver.WebSockifyRequestHandler(
FakeSocket('GET /tmp.txt HTTP/1.1'), '127.0.0.1', server) FakeSocket(b'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)
handler.do_GET() handler.do_GET()
self.assertEqual(handler.last_code, 405) send_error.assert_called_with(405, ANY)
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) server = self._get_server(file_only=True)
handler = websockifyserver.WebSockifyRequestHandler( handler = websockifyserver.WebSockifyRequestHandler(
FakeSocket('GET / HTTP/1.1'), '127.0.0.1', server) FakeSocket(b'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)
handler.path = '/' handler.path = '/'
handler.do_GET() handler.do_GET()
self.assertEqual(handler.last_code, 404) send_error.assert_called_with(404, ANY)
class WebSockifyServerTestCase(unittest.TestCase): class WebSockifyServerTestCase(unittest.TestCase):
def setUp(self): def setUp(self):
super(WebSockifyServerTestCase, self).setUp() super(WebSockifyServerTestCase, self).setUp()
self.stubs = stubout.StubOutForTesting()
self.tmpdir = tempfile.mkdtemp('-websockify-tests') self.tmpdir = tempfile.mkdtemp('-websockify-tests')
# Mock this out cause it screws tests up # Mock this out cause it screws tests up
self.stubs.Set(os, 'chdir', lambda *args, **kwargs: None) patch('os.chdir').start()
def tearDown(self): def tearDown(self):
"""Called automatically after each test.""" """Called automatically after each test."""
self.stubs.UnsetAll() patch.stopall()
os.rmdir(self.tmpdir) os.rmdir(self.tmpdir)
super(WebSockifyServerTestCase, self).tearDown() super(WebSockifyServerTestCase, self).tearDown()
@@ -154,10 +121,10 @@ class WebSockifyServerTestCase(unittest.TestCase):
def test_daemonize_raises_error_while_closing_fds(self): def test_daemonize_raises_error_while_closing_fds(self):
server = self._get_server(daemon=True, ssl_only=1, idle_timeout=1) server = self._get_server(daemon=True, ssl_only=1, idle_timeout=1)
self.stubs.Set(os, 'fork', lambda *args: 0) patch('os.fork').start().return_value = 0
self.stubs.Set(signal, 'signal', lambda *args: None) patch('signal.signal').start()
self.stubs.Set(os, 'setsid', lambda *args: None) patch('os.setsid').start()
self.stubs.Set(os, 'close', raise_oserror) patch('os.close').start().side_effect = raise_oserror
self.assertRaises(OSError, server.daemonize, keepfd=None, chdir='./') self.assertRaises(OSError, server.daemonize, keepfd=None, chdir='./')
def test_daemonize_ignores_ebadf_error_while_closing_fds(self): def test_daemonize_ignores_ebadf_error_while_closing_fds(self):
@@ -165,11 +132,11 @@ class WebSockifyServerTestCase(unittest.TestCase):
raise OSError(errno.EBADF, 'fake error') raise OSError(errno.EBADF, 'fake error')
server = self._get_server(daemon=True, ssl_only=1, idle_timeout=1) server = self._get_server(daemon=True, ssl_only=1, idle_timeout=1)
self.stubs.Set(os, 'fork', lambda *args: 0) patch('os.fork').start().return_value = 0
self.stubs.Set(os, 'setsid', lambda *args: None) patch('signal.signal').start()
self.stubs.Set(signal, 'signal', lambda *args: None) patch('os.setsid').start()
self.stubs.Set(os, 'close', raise_oserror_ebadf) patch('os.close').start().side_effect = raise_oserror_ebadf
self.stubs.Set(os, 'open', raise_oserror) patch('os.open').start().side_effect = raise_oserror
self.assertRaises(OSError, server.daemonize, keepfd=None, chdir='./') self.assertRaises(OSError, server.daemonize, keepfd=None, chdir='./')
def test_handshake_fails_on_not_ready(self): def test_handshake_fails_on_not_ready(self):
@@ -178,7 +145,7 @@ class WebSockifyServerTestCase(unittest.TestCase):
def fake_select(rlist, wlist, xlist, timeout=None): def fake_select(rlist, wlist, xlist, timeout=None):
return ([], [], []) return ([], [], [])
self.stubs.Set(select, 'select', fake_select) patch('select.select').start().side_effect = fake_select
self.assertRaises( self.assertRaises(
websockifyserver.WebSockifyServer.EClose, server.do_handshake, websockifyserver.WebSockifyServer.EClose, server.do_handshake,
FakeSocket(), '127.0.0.1') FakeSocket(), '127.0.0.1')
@@ -191,7 +158,7 @@ class WebSockifyServerTestCase(unittest.TestCase):
def fake_select(rlist, wlist, xlist, timeout=None): def fake_select(rlist, wlist, xlist, timeout=None):
return ([sock], [], []) return ([sock], [], [])
self.stubs.Set(select, 'select', fake_select) patch('select.select').start().side_effect = fake_select
self.assertRaises( self.assertRaises(
websockifyserver.WebSockifyServer.EClose, server.do_handshake, websockifyserver.WebSockifyServer.EClose, server.do_handshake,
sock, '127.0.0.1') sock, '127.0.0.1')
@@ -203,12 +170,12 @@ class WebSockifyServerTestCase(unittest.TestCase):
def test_handshake_ssl_only_without_ssl_raises_error(self): def test_handshake_ssl_only_without_ssl_raises_error(self):
server = self._get_server(daemon=True, ssl_only=1, idle_timeout=1) 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): def fake_select(rlist, wlist, xlist, timeout=None):
return ([sock], [], []) return ([sock], [], [])
self.stubs.Set(select, 'select', fake_select) patch('select.select').start().side_effect = fake_select
self.assertRaises( self.assertRaises(
websockifyserver.WebSockifyServer.EClose, server.do_handshake, websockifyserver.WebSockifyServer.EClose, server.do_handshake,
sock, '127.0.0.1') sock, '127.0.0.1')
@@ -225,12 +192,12 @@ class WebSockifyServerTestCase(unittest.TestCase):
handler_class=FakeHandler, daemon=True, handler_class=FakeHandler, daemon=True,
ssl_only=0, idle_timeout=1) 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): def fake_select(rlist, wlist, xlist, timeout=None):
return ([sock], [], []) 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.assertEqual(server.do_handshake(sock, '127.0.0.1'), sock)
self.assertTrue(FakeHandler.CALLED, True) 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, server = self._get_server(daemon=True, ssl_only=0, idle_timeout=1,
cert='afdsfasdafdsafdsafdsafdas') cert='afdsfasdafdsafdsafdsafdas')
sock = FakeSocket("\x16some ssl data") sock = FakeSocket(b"\x16some ssl data")
def fake_select(rlist, wlist, xlist, timeout=None): def fake_select(rlist, wlist, xlist, timeout=None):
return ([sock], [], []) return ([sock], [], [])
self.stubs.Set(select, 'select', fake_select) patch('select.select').start().side_effect = fake_select
self.assertRaises( self.assertRaises(
websockifyserver.WebSockifyServer.EClose, server.do_handshake, websockifyserver.WebSockifyServer.EClose, server.do_handshake,
sock, '127.0.0.1') sock, '127.0.0.1')
@@ -259,7 +226,7 @@ class WebSockifyServerTestCase(unittest.TestCase):
def test_do_handshake_ssl_error_eof_raises_close_error(self): def test_do_handshake_ssl_error_eof_raises_close_error(self):
server = self._get_server(daemon=True, ssl_only=0, idle_timeout=1) 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): def fake_select(rlist, wlist, xlist, timeout=None):
return ([sock], [], []) return ([sock], [], [])
@@ -280,13 +247,8 @@ class WebSockifyServerTestCase(unittest.TestCase):
def wrap_socket(self, *args, **kwargs): def wrap_socket(self, *args, **kwargs):
raise ssl.SSLError(ssl.SSL_ERROR_EOF) raise ssl.SSLError(ssl.SSL_ERROR_EOF)
self.stubs.Set(select, 'select', fake_select) patch('select.select').start().side_effect = fake_select
if (hasattr(ssl, 'create_default_context')): patch('ssl.create_default_context').start().side_effect = fake_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)
self.assertRaises( self.assertRaises(
websockifyserver.WebSockifyServer.EClose, server.do_handshake, websockifyserver.WebSockifyServer.EClose, server.do_handshake,
sock, '127.0.0.1') sock, '127.0.0.1')
@@ -300,7 +262,7 @@ class WebSockifyServerTestCase(unittest.TestCase):
server = self._get_server(handler_class=FakeHandler, daemon=True, server = self._get_server(handler_class=FakeHandler, daemon=True,
idle_timeout=1, ssl_ciphers=test_ciphers) 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): def fake_select(rlist, wlist, xlist, timeout=None):
return ([sock], [], []) return ([sock], [], [])
@@ -321,16 +283,10 @@ class WebSockifyServerTestCase(unittest.TestCase):
def set_ciphers(self, ciphers_to_set): def set_ciphers(self, ciphers_to_set):
fake_create_default_context.CIPHERS = ciphers_to_set fake_create_default_context.CIPHERS = ciphers_to_set
self.stubs.Set(select, 'select', fake_select) patch('select.select').start().side_effect = fake_select
if (hasattr(ssl, 'create_default_context')): patch('ssl.create_default_context').start().side_effect = fake_create_default_context
# for recent versions of python server.do_handshake(sock, '127.0.0.1')
self.stubs.Set(ssl, 'create_default_context', fake_create_default_context) self.assertEqual(fake_create_default_context.CIPHERS, test_ciphers)
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
def test_do_handshake_ssl_sets_opions(self): def test_do_handshake_ssl_sets_opions(self):
test_options = 0xCAFEBEEF test_options = 0xCAFEBEEF
@@ -341,7 +297,7 @@ class WebSockifyServerTestCase(unittest.TestCase):
server = self._get_server(handler_class=FakeHandler, daemon=True, server = self._get_server(handler_class=FakeHandler, daemon=True,
idle_timeout=1, ssl_options=test_options) 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): def fake_select(rlist, wlist, xlist, timeout=None):
return ([sock], [], []) return ([sock], [], [])
@@ -365,16 +321,10 @@ class WebSockifyServerTestCase(unittest.TestCase):
fake_create_default_context.OPTIONS = val fake_create_default_context.OPTIONS = val
options = property(get_options, set_options) options = property(get_options, set_options)
self.stubs.Set(select, 'select', fake_select) patch('select.select').start().side_effect = fake_select
if (hasattr(ssl, 'create_default_context')): patch('ssl.create_default_context').start().side_effect = fake_create_default_context
# for recent versions of python server.do_handshake(sock, '127.0.0.1')
self.stubs.Set(ssl, 'create_default_context', fake_create_default_context) self.assertEqual(fake_create_default_context.OPTIONS, test_options)
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
def test_fallback_sigchld_handler(self): def test_fallback_sigchld_handler(self):
# TODO(directxman12): implement this # TODO(directxman12): implement this
@@ -387,11 +337,9 @@ class WebSockifyServerTestCase(unittest.TestCase):
def fake_select(rlist, wlist, xlist, timeout=None): def fake_select(rlist, wlist, xlist, timeout=None):
raise Exception("fake error") raise Exception("fake error")
self.stubs.Set(websockifyserver.WebSockifyServer, 'socket', patch('websockify.websockifyserver.WebSockifyServer.socket').start()
lambda *args, **kwargs: sock) patch('websockify.websockifyserver.WebSockifyServer.daemonize').start()
self.stubs.Set(websockifyserver.WebSockifyServer, 'daemonize', patch('select.select').start().side_effect = fake_select
lambda *args, **kwargs: None)
self.stubs.Set(select, 'select', fake_select)
server.start_server() server.start_server()
def test_start_server_keyboardinterrupt(self): def test_start_server_keyboardinterrupt(self):
@@ -401,11 +349,9 @@ class WebSockifyServerTestCase(unittest.TestCase):
def fake_select(rlist, wlist, xlist, timeout=None): def fake_select(rlist, wlist, xlist, timeout=None):
raise KeyboardInterrupt raise KeyboardInterrupt
self.stubs.Set(websockifyserver.WebSockifyServer, 'socket', patch('websockify.websockifyserver.WebSockifyServer.socket').start()
lambda *args, **kwargs: sock) patch('websockify.websockifyserver.WebSockifyServer.daemonize').start()
self.stubs.Set(websockifyserver.WebSockifyServer, 'daemonize', patch('select.select').start().side_effect = fake_select
lambda *args, **kwargs: None)
self.stubs.Set(select, 'select', fake_select)
server.start_server() server.start_server()
def test_start_server_systemexit(self): def test_start_server_systemexit(self):
@@ -415,11 +361,9 @@ class WebSockifyServerTestCase(unittest.TestCase):
def fake_select(rlist, wlist, xlist, timeout=None): def fake_select(rlist, wlist, xlist, timeout=None):
sys.exit() sys.exit()
self.stubs.Set(websockifyserver.WebSockifyServer, 'socket', patch('websockify.websockifyserver.WebSockifyServer.socket').start()
lambda *args, **kwargs: sock) patch('websockify.websockifyserver.WebSockifyServer.daemonize').start()
self.stubs.Set(websockifyserver.WebSockifyServer, 'daemonize', patch('select.select').start().side_effect = fake_select
lambda *args, **kwargs: None)
self.stubs.Set(select, 'select', fake_select)
server.start_server() server.start_server()
def test_socket_set_keepalive_options(self): def test_socket_set_keepalive_options(self):
+1 -1
View File
@@ -4,7 +4,7 @@
# and then run "tox" from this directory. # and then run "tox" from this directory.
[tox] [tox]
envlist = py24,py26,py27,py33,py34 envlist = py34
[testenv] [testenv]
commands = nosetests {posargs} commands = nosetests {posargs}
+6 -6
View File
@@ -1,4 +1,4 @@
class BasePlugin(object): class BasePlugin():
def __init__(self, src=None): def __init__(self, src=None):
self.source = src self.source = src
@@ -15,7 +15,7 @@ class AuthenticationError(Exception):
if log_msg is None: if log_msg is None:
log_msg = response_msg 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): class InvalidOriginError(AuthenticationError):
@@ -23,13 +23,13 @@ class InvalidOriginError(AuthenticationError):
self.expected_origin = expected self.expected_origin = expected
self.actual_origin = actual self.actual_origin = actual
super(InvalidOriginError, self).__init__( super().__init__(
response_msg='Invalid Origin', response_msg='Invalid Origin',
log_msg="Invalid Origin Header: Expected one of " log_msg="Invalid Origin Header: Expected one of "
"%s, got '%s'" % (expected, actual)) "%s, got '%s'" % (expected, actual))
class BasicHTTPAuth(object): class BasicHTTPAuth():
"""Verifies Basic Auth headers. Specify src as username:password""" """Verifies Basic Auth headers. Specify src as username:password"""
def __init__(self, src=None): def __init__(self, src=None):
@@ -76,7 +76,7 @@ class BasicHTTPAuth(object):
raise AuthenticationError(response_code=401, raise AuthenticationError(response_code=401,
response_headers={'WWW-Authenticate': 'Basic realm="Websockify"'}) response_headers={'WWW-Authenticate': 'Basic realm="Websockify"'})
class ExpectOrigin(object): class ExpectOrigin():
def __init__(self, src=None): def __init__(self, src=None):
if src is None: if src is None:
self.source = [] self.source = []
@@ -88,7 +88,7 @@ class ExpectOrigin(object):
if origin is None or origin not in self.source: if origin is None or origin not in self.source:
raise InvalidOriginError(expected=self.source, actual=origin) raise InvalidOriginError(expected=self.source, actual=origin)
class ClientCertCNAuth(object): class ClientCertCNAuth():
"""Verifies client by SSL certificate. Specify src as whitespace separated list of common names.""" """Verifies client by SSL certificate. Specify src as whitespace separated list of common names."""
def __init__(self, src=None): def __init__(self, src=None):
+1 -1
View File
@@ -44,7 +44,7 @@ class WebsockifySysLogHandler(handlers.SysLogHandler):
self._legacy = True self._legacy = True
self._head_fmt = self._legacy_head_fmt self._head_fmt = self._legacy_head_fmt
handlers.SysLogHandler.__init__(self, address, facility, socktype) super().__init__(address, facility, socktype)
def emit(self, record): def emit(self, record):
+88 -15
View File
@@ -1,8 +1,9 @@
from __future__ import print_function
import os import os
import sys import sys
import time
import re
class BasePlugin(object): class BasePlugin():
def __init__(self, src): def __init__(self, src):
self.source = src self.source = src
@@ -15,7 +16,7 @@ class ReadOnlyTokenFile(BasePlugin):
# token: host:port # token: host:port
# or a directory of such files # or a directory of such files
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(ReadOnlyTokenFile, self).__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self._targets = None self._targets = None
def _load_targets(self): def _load_targets(self):
@@ -31,10 +32,10 @@ class ReadOnlyTokenFile(BasePlugin):
for line in [l.strip() for l in open(f).readlines()]: for line in [l.strip() for l in open(f).readlines()]:
if line and not line.startswith('#'): if line and not line.startswith('#'):
try: try:
tok, target = line.split(': ') tok, target = re.split(':\s', line)
self._targets[tok] = target.strip().rsplit(':', 1) self._targets[tok] = target.strip().rsplit(':', 1)
except ValueError: except ValueError:
print >>sys.stderr, "Syntax error in %s on line %d" % (self.source, index) print("Syntax error in %s on line %d" % (self.source, index), file=sys.stderr)
index += 1 index += 1
def lookup(self, token): def lookup(self, token):
@@ -57,7 +58,7 @@ class TokenFile(ReadOnlyTokenFile):
def lookup(self, token): def lookup(self, token):
self._load_targets() self._load_targets()
return super(TokenFile, self).lookup(token) return super().lookup(token)
class BaseTokenAPI(BasePlugin): class BaseTokenAPI(BasePlugin):
@@ -102,7 +103,7 @@ class JWTTokenApi(BasePlugin):
import json import json
key = jwt.JWK() key = jwt.JWK()
try: try:
with open(self.source, 'rb') as key_file: with open(self.source, 'rb') as key_file:
key_data = key_file.read() key_data = key_file.read()
@@ -128,6 +129,18 @@ class JWTTokenApi(BasePlugin):
token = jwt.JWT(key=key, jwt=token.claims) token = jwt.JWT(key=key, jwt=token.claims)
parsed = json.loads(token.claims) parsed = json.loads(token.claims)
if 'nbf' in parsed:
# Not Before is present, so we need to check it
if time.time() < parsed['nbf']:
print('Token can not be used yet!', file=sys.stderr)
return None
if 'exp' in parsed:
# Expiration time is present, so we need to check it
if time.time() > parsed['exp']:
print('Token has expired!', file=sys.stderr)
return None
return (parsed['host'], parsed['port']) return (parsed['host'], parsed['port'])
except Exception as e: except Exception as e:
@@ -137,23 +150,83 @@ class JWTTokenApi(BasePlugin):
print("package jwcrypto not found, are you sure you've installed it correctly?", file=sys.stderr) print("package jwcrypto not found, are you sure you've installed it correctly?", file=sys.stderr)
return None return None
class TokenRedis(object): class TokenRedis():
"""
The TokenRedis plugin expects the format of the data in a form of json.
Prepare data with:
redis-cli set hello '{"host":"127.0.0.1:5000"}'
Verify with:
redis-cli --raw get hello
Spawn a test "server" using netcat
nc -l 5000 -v
Note: you have to install also the 'redis' and 'simplejson' modules
pip install redis simplejson
"""
def __init__(self, src): def __init__(self, src):
self._server, self._port = src.split(":") try:
# import those ahead of time so we provide error earlier
import redis
import simplejson
self._server, self._port = src.split(":")
print("TokenRedis backend initilized (%s:%s)" %
(self._server, self._port), file=sys.stderr)
except ValueError:
print("The provided --token-source='%s' is not in an expected format <host>:<port>" %
src, file=sys.stderr)
sys.exit()
except ImportError:
print("package redis or simplejson not found, are you sure you've installed them correctly?", file=sys.stderr)
sys.exit()
def lookup(self, token): def lookup(self, token):
try: try:
import redis import redis
import simplejson import simplejson
except ImportError as e: except ImportError:
print("package redis or simplejson not found, are you sure you've installed them correctly?", file=sys.stderr) print("package redis or simplejson not found, are you sure you've installed them correctly?", file=sys.stderr)
return None sys.exit()
client = redis.Redis(host=self._server,port=self._port) print("resolving token '%s'" % token, file=sys.stderr)
client = redis.Redis(host=self._server, port=self._port)
stuff = client.get(token) stuff = client.get(token)
if stuff is None: if stuff is None:
return None return None
else: else:
combo = simplejson.loads(stuff.decode("utf-8")) responseStr = stuff.decode("utf-8")
pair = combo["host"] print("response from redis : %s" % responseStr, file=sys.stderr)
return pair.split(':') combo = simplejson.loads(responseStr)
(host, port) = combo["host"].split(':')
print("host: %s, port: %s" % (host,port), file=sys.stderr)
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:
print("Error finding unix domain socket: %s" % str(e), file=sys.stderr)
return None
+110 -76
View File
@@ -22,6 +22,7 @@ import ssl
import struct import struct
from base64 import b64encode from base64 import b64encode
from hashlib import sha1 from hashlib import sha1
from urllib.parse import urlparse
try: try:
import numpy import numpy
@@ -30,25 +31,10 @@ except ImportError:
warnings.warn("no 'numpy' module, HyBi protocol will be slower") warnings.warn("no 'numpy' module, HyBi protocol will be slower")
numpy = None numpy = None
# python 3.0 differences class WebSocketWantReadError(ssl.SSLWantReadError):
try: pass
from urllib.parse import urlparse class WebSocketWantWriteError(ssl.SSLWantWriteError):
except ImportError: pass
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 WebSocket(object): class WebSocket(object):
"""WebSocket protocol socket like class. """WebSocket protocol socket like class.
@@ -87,11 +73,13 @@ class WebSocket(object):
self._state = "new" 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._recv_queue = []
self._send_buffer = ''.encode("ascii") self._send_buffer = b''
self._previous_sendmsg = None
self._sent_close = False self._sent_close = False
self._received_close = False self._received_close = False
@@ -164,9 +152,7 @@ class WebSocket(object):
self._key = '' self._key = ''
for i in range(16): for i in range(16):
self._key += chr(random.randrange(256)) self._key += chr(random.randrange(256))
if sys.hexversion >= 0x3000000: self._key = b64encode(self._key.encode("latin-1")).decode("ascii")
self._key = bytes(self._key, "latin-1")
self._key = b64encode(self._key).decode("ascii")
path = uri.path path = uri.path
if not path: if not path:
@@ -196,10 +182,10 @@ class WebSocket(object):
if not self._recv(): if not self._recv():
raise Exception("Socket closed unexpectedly") 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 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") request = request.decode("latin-1")
words = request.split() words = request.split()
@@ -208,7 +194,7 @@ class WebSocket(object):
if words[1] != "101": if words[1] != "101":
raise Exception("WebSocket request denied: %s" % " ".join(words[1:])) 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 = headers.decode('latin-1') + '\r\n'
headers = email.message_from_string(headers) headers = email.message_from_string(headers)
@@ -254,8 +240,8 @@ class WebSocket(object):
the value "websocket" in such cases. the value "websocket" in such cases.
WebSocketWantWriteError can be raised if the response cannot be WebSocketWantWriteError can be raised if the response cannot be
sent right away. Repeated calls to accept() does not need to sent right away. accept() must be called again once more space
retain the arguments. is available using the same arguments.
""" """
# This is a state machine in order to handle # This is a state machine in order to handle
@@ -419,8 +405,12 @@ class WebSocket(object):
data from other calls, or split it over multiple messages. data from other calls, or split it over multiple messages.
WebSocketWantWriteError can be raised if there is insufficient 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) return self.sendmsg(bytes)
def sendmsg(self, msg): def sendmsg(self, msg):
@@ -431,23 +421,81 @@ class WebSocket(object):
single WebSocket message. single WebSocket message.
WebSocketWantWriteError can be raised if there is insufficient 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: if not isinstance(msg, bytes):
# Only called to flush? raise TypeError
if msg:
self._sendmsg(0x2, msg) 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) return len(msg)
def ping(self, data=''.encode('ascii')): def ping(self, data=b''):
"""Write a ping message to the WebSocket.""" """Write a ping message to the WebSocket
self._sendmsg(0x9, data)
def pong(self, data=''.encode('ascii')): WebSocketWantWriteError can be raised if there is insufficient
"""Write a pong message to the WebSocket.""" space in the underlying socket. ping() must be called again once
self._sendmsg(0xA, data) 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): def shutdown(self, how, code=1000, reason=None):
"""Gracefully terminate the WebSocket connection. """Gracefully terminate the WebSocket connection.
@@ -459,7 +507,9 @@ class WebSocket(object):
ignored. ignored.
WebSocketWantWriteError can be raised if there is insufficient 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. The how argument is currently ignored.
""" """
@@ -476,7 +526,7 @@ class WebSocket(object):
self._sent_close = True self._sent_close = True
msg = ''.encode('ascii') msg = b''
if code is not None: if code is not None:
msg += struct.pack(">H", code) msg += struct.pack(">H", code)
if reason is not None: if reason is not None:
@@ -491,7 +541,9 @@ class WebSocket(object):
a close message to the peer. a close message to the peer.
WebSocketWantWriteError can be raised if there is insufficient 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.shutdown(socket.SHUT_RDWR, code, reason)
self._close() self._close()
@@ -503,16 +555,9 @@ class WebSocket(object):
while True: while True:
try: try:
data = self.socket.recv(4096) data = self.socket.recv(4096)
except (socket.error, OSError): except OSError as exc:
exc = sys.exc_info()[1] if exc.errno == errno.EWOULDBLOCK:
if hasattr(exc, 'errno'):
err = exc.errno
else:
err = exc[0]
if err == errno.EWOULDBLOCK:
raise WebSocketWantReadError raise WebSocketWantReadError
raise raise
if len(data) == 0: if len(data) == 0:
@@ -569,7 +614,7 @@ class WebSocket(object):
if frame["fin"]: if frame["fin"]:
msg = self._partial_msg msg = self._partial_msg
self._partial_msg = ''.decode("ascii") self._partial_msg = b''
return msg return msg
elif frame["opcode"] == 0x1: elif frame["opcode"] == 0x1:
self.shutdown(socket.SHUT_RDWR, 1003, "Unsupported: Text frames are not supported") self.shutdown(socket.SHUT_RDWR, 1003, "Unsupported: Text frames are not supported")
@@ -644,16 +689,9 @@ class WebSocket(object):
try: try:
sent = self.socket.send(self._send_buffer) sent = self.socket.send(self._send_buffer)
except (socket.error, OSError): except OSError as exc:
exc = sys.exc_info()[1] if exc.errno == errno.EWOULDBLOCK:
if hasattr(exc, 'errno'):
err = exc.errno
else:
err = exc[0]
if err == errno.EWOULDBLOCK:
raise WebSocketWantWriteError raise WebSocketWantWriteError
raise raise
self._send_buffer = self._send_buffer[sent:] self._send_buffer = self._send_buffer[sent:]
@@ -679,11 +717,9 @@ class WebSocket(object):
def _sendmsg(self, opcode, msg): def _sendmsg(self, opcode, msg):
# Sends a standard data message # Sends a standard data message
if self.client: if self.client:
mask = '' mask = b''
for i in range(4): for i in range(4):
mask += chr(random.randrange(256)) mask += random.randrange(256)
if sys.hexversion >= 0x3000000:
mask = bytes(mask, "latin-1")
frame = self._encode_hybi(opcode, msg, mask) frame = self._encode_hybi(opcode, msg, mask)
else: else:
frame = self._encode_hybi(opcode, msg) frame = self._encode_hybi(opcode, msg)
@@ -705,7 +741,7 @@ class WebSocket(object):
plen = len(buf) plen = len(buf)
pstart = 0 pstart = 0
pend = plen pend = plen
b = c = ''.encode('ascii') b = c = b''
if plen >= 4: if plen >= 4:
dtype=numpy.dtype('<u4') dtype=numpy.dtype('<u4')
if sys.byteorder == 'big': if sys.byteorder == 'big':
@@ -713,7 +749,7 @@ class WebSocket(object):
mask = numpy.frombuffer(mask, dtype, count=1) mask = numpy.frombuffer(mask, dtype, count=1)
data = numpy.frombuffer(buf, dtype, count=int(plen / 4)) data = numpy.frombuffer(buf, dtype, count=int(plen / 4))
#b = numpy.bitwise_xor(data, mask).data #b = numpy.bitwise_xor(data, mask).data
b = numpy.bitwise_xor(data, mask).tostring() b = numpy.bitwise_xor(data, mask).tobytes()
if plen % 4: if plen % 4:
dtype=numpy.dtype('B') dtype=numpy.dtype('B')
@@ -722,17 +758,15 @@ class WebSocket(object):
mask = numpy.frombuffer(mask, dtype, count=(plen % 4)) mask = numpy.frombuffer(mask, dtype, count=(plen % 4))
data = numpy.frombuffer(buf, dtype, data = numpy.frombuffer(buf, dtype,
offset=plen - (plen % 4), count=(plen % 4)) offset=plen - (plen % 4), count=(plen % 4))
c = numpy.bitwise_xor(data, mask).tostring() c = numpy.bitwise_xor(data, mask).tobytes()
return b + c return b + c
else: else:
# Slower fallback # Slower fallback
if sys.hexversion < 0x3000000:
mask = [ ord(c) for c in mask ]
data = array.array('B') data = array.array('B')
data.fromstring(buf) data.frombytes(buf)
for i in range(len(data)): for i in range(len(data)):
data[i] ^= mask[i % 4] data[i] ^= mask[i % 4]
return data.tostring() return data.tobytes()
def _encode_hybi(self, opcode, buf, mask_key=None, fin=True): def _encode_hybi(self, opcode, buf, mask_key=None, fin=True):
""" Encode a HyBi style WebSocket frame. """ Encode a HyBi style WebSocket frame.
+19 -50
View File
@@ -12,24 +12,13 @@ as taken from http://docs.python.org/dev/library/ssl.html#certificates
''' '''
import signal, socket, optparse, time, os, sys, subprocess, logging, errno, ssl import signal, socket, optparse, time, os, sys, subprocess, logging, errno, ssl
try: from socketserver import ThreadingMixIn
from socketserver import ThreadingMixIn from http.server import HTTPServer
except ImportError:
from SocketServer import ThreadingMixIn
try:
from http.server import HTTPServer
except ImportError:
from BaseHTTPServer import HTTPServer
import select import select
from websockify import websockifyserver from websockify import websockifyserver
from websockify import auth_plugins as auth from websockify import auth_plugins as auth
try: from urllib.parse import parse_qs, urlparse
from urllib.parse import parse_qs, urlparse
except ImportError:
from cgi import parse_qs
from urlparse import urlparse
class ProxyRequestHandler(websockifyserver.WebSockifyRequestHandler): class ProxyRequestHandler(websockifyserver.WebSockifyRequestHandler):
@@ -118,9 +107,9 @@ Traffic Legend:
connect=True, connect=True,
use_ssl=self.server.ssl_target, use_ssl=self.server.ssl_target,
unix_socket=self.server.unix_target) unix_socket=self.server.unix_target)
except Exception: except Exception as e:
self.log_message("Failed to connect to %s:%s", self.log_message("Failed to connect to %s:%s: %s",
self.server.target_host, self.server.target_port) self.server.target_host, self.server.target_port, e)
raise self.CClose(1011, "Failed to connect to downstream server") raise self.CClose(1011, "Failed to connect to downstream server")
self.request.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1) self.request.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)
@@ -293,6 +282,7 @@ class WebSocketProxy(websockifyserver.WebSockifyServer):
wsdir = os.path.dirname(sys.argv[0]) wsdir = os.path.dirname(sys.argv[0])
rebinder_path = [os.path.join(wsdir, "..", "lib"), rebinder_path = [os.path.join(wsdir, "..", "lib"),
os.path.join(wsdir, "..", "lib", "websockify"), os.path.join(wsdir, "..", "lib", "websockify"),
os.path.join(wsdir, ".."),
wsdir] wsdir]
self.rebinder = None self.rebinder = None
@@ -318,7 +308,7 @@ class WebSocketProxy(websockifyserver.WebSockifyServer):
"REBIND_OLD_PORT": str(kwargs['listen_port']), "REBIND_OLD_PORT": str(kwargs['listen_port']),
"REBIND_NEW_PORT": str(self.target_port)}) "REBIND_NEW_PORT": str(self.target_port)})
websockifyserver.WebSockifyServer.__init__(self, RequestHandlerClass, *args, **kwargs) super().__init__(RequestHandlerClass, *args, **kwargs)
def run_wrap_cmd(self): def run_wrap_cmd(self):
self.msg("Starting '%s'", " ".join(self.wrap_cmd)) self.msg("Starting '%s'", " ".join(self.wrap_cmd))
@@ -394,35 +384,15 @@ def _subprocess_setup():
signal.signal(signal.SIGPIPE, signal.SIG_DFL) signal.signal(signal.SIGPIPE, signal.SIG_DFL)
try : SSL_OPTIONS = {
# First try SSL options for Python 3.4 and above 'default': ssl.OP_ALL,
SSL_OPTIONS = { 'tlsv1_1': ssl.PROTOCOL_SSLv23 | ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 |
'default': ssl.OP_ALL, ssl.OP_NO_TLSv1,
'tlsv1_1': ssl.PROTOCOL_TLS | ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 | 'tlsv1_2': ssl.PROTOCOL_SSLv23 | ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 |
ssl.OP_NO_TLSv1, ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1,
'tlsv1_2': ssl.PROTOCOL_TLS | ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 | '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 | ssl.OP_NO_TLSv1_1 | ssl.OP_NO_TLSv1_2,
'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,
}
def select_ssl_version(version): def select_ssl_version(version):
"""Returns SSL options for the most secure TSL version available on this """Returns SSL options for the most secure TSL version available on this
@@ -768,14 +738,13 @@ class LibProxyServer(ThreadingMixIn, HTTPServer):
if web: if web:
os.chdir(web) os.chdir(web)
HTTPServer.__init__(self, (listen_host, listen_port), super().__init__((listen_host, listen_port), RequestHandlerClass)
RequestHandlerClass)
def process_request(self, request, client_address): def process_request(self, request, client_address):
"""Override process_request to implement a counter""" """Override process_request to implement a counter"""
self.handler_id += 1 self.handler_id += 1
ThreadingMixIn.process_request(self, request, client_address) super().process_request(request, client_address)
if __name__ == '__main__': if __name__ == '__main__':
+2 -12
View File
@@ -8,12 +8,7 @@ Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
''' '''
import sys import sys
from http.server import BaseHTTPRequestHandler, HTTPServer
# python 3.0 differences
try:
from http.server import BaseHTTPRequestHandler, HTTPServer
except ImportError:
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
from websockify.websocket import WebSocket, WebSocketWantReadError, WebSocketWantWriteError from websockify.websocket import WebSocket, WebSocketWantReadError, WebSocketWantWriteError
@@ -42,12 +37,7 @@ class WebSocketRequestHandlerMixIn:
self._real_do_GET = self.do_GET self._real_do_GET = self.do_GET
self.do_GET = self._websocket_do_GET self.do_GET = self._websocket_do_GET
try: try:
# super() only works for new style classes super().handle_one_request()
if issubclass(WebSocketRequestHandlerMixIn, object):
super(WebSocketRequestHandlerMixIn, self).handle_one_request()
else:
# Assume handle_one_request() hasn't been overriden
BaseHTTPRequestHandler.handle_one_request(self)
finally: finally:
self.do_GET = self._real_do_GET self.do_GET = self._real_do_GET
+29 -62
View File
@@ -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 os, sys, time, errno, signal, socket, select, logging
import multiprocessing import multiprocessing
from http.server import SimpleHTTPRequestHandler
# 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
# Degraded functionality if these imports are missing # Degraded functionality if these imports are missing
for mod, msg in [('ssl', 'TLS/SSL/wss is disabled'), for mod, msg in [('ssl', 'TLS/SSL/wss is disabled'),
@@ -46,7 +34,7 @@ from websockify.websocketserver import WebSocketRequestHandlerMixIn
class CompatibleWebSocket(WebSocket): class CompatibleWebSocket(WebSocket):
def select_subprotocol(self, protocols): 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: if 'binary' in protocols:
return 'binary' return 'binary'
else: else:
@@ -96,7 +84,7 @@ class WebSockifyRequestHandler(WebSocketRequestHandlerMixIn, SimpleHTTPRequestHa
if self.logger is None: if self.logger is None:
self.logger = WebSockifyServer.get_logger() self.logger = WebSockifyServer.get_logger()
SimpleHTTPRequestHandler.__init__(self, req, addr, server) super().__init__(req, addr, server)
def log_message(self, format, *args): def log_message(self, format, *args):
self.logger.info("%s - - [%s] %s" % (self.client_address[0], self.log_date_time_string(), 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.rec.write("'{{{0}{{{1}',\n".format(tdelta, bufstr))
self.send_parts.append(buf) self.send_parts.append(buf)
# Flush any previously queued data
try:
self.request.sendmsg('')
except WebSocketWantWriteError:
return True
while self.send_parts: while self.send_parts:
# Send pending frames # Send pending frames
buf = self.send_parts.pop(0)
try: try:
self.request.sendmsg(buf) self.request.sendmsg(self.send_parts[0])
except WebSocketWantWriteError: except WebSocketWantWriteError:
self.print_traffic("<.") self.print_traffic("<.")
return True return True
self.send_parts.pop(0)
self.print_traffic("<") self.print_traffic("<")
return False return False
@@ -218,7 +200,7 @@ class WebSockifyRequestHandler(WebSocketRequestHandlerMixIn, SimpleHTTPRequestHa
self.validate_connection() self.validate_connection()
self.auth_connection() self.auth_connection()
WebSocketRequestHandlerMixIn.handle_upgrade(self) super().handle_upgrade()
def handle_websocket(self): def handle_websocket(self):
# Indicate to server that a Websocket upgrade was done # Indicate to server that a Websocket upgrade was done
@@ -270,13 +252,13 @@ class WebSockifyRequestHandler(WebSocketRequestHandlerMixIn, SimpleHTTPRequestHa
if self.only_upgrade: if self.only_upgrade:
self.send_error(405, "Method Not Allowed") self.send_error(405, "Method Not Allowed")
else: else:
SimpleHTTPRequestHandler.do_GET(self) super().do_GET()
def list_directory(self, path): def list_directory(self, path):
if self.file_only: if self.file_only:
self.send_error(404, "No such file") self.send_error(404, "No such file")
else: else:
return SimpleHTTPRequestHandler.list_directory(self, path) return super().list_directory(path)
def new_websocket_client(self): def new_websocket_client(self):
""" Do something with a WebSockets client connection. """ """ Do something with a WebSockets client connection. """
@@ -297,13 +279,13 @@ class WebSockifyRequestHandler(WebSocketRequestHandlerMixIn, SimpleHTTPRequestHa
if self.only_upgrade: if self.only_upgrade:
self.send_error(405, "Method Not Allowed") self.send_error(405, "Method Not Allowed")
else: else:
SimpleHTTPRequestHandler.do_HEAD(self) super().do_HEAD()
def finish(self): def finish(self):
if self.rec: if self.rec:
self.rec.write("'EOF'];\n") self.rec.write("'EOF'];\n")
self.rec.close() self.rec.close()
SimpleHTTPRequestHandler.finish(self) super().finish()
def handle(self): def handle(self):
# When using run_once, we have a single process, so # When using run_once, we have a single process, so
@@ -312,14 +294,14 @@ class WebSockifyRequestHandler(WebSocketRequestHandlerMixIn, SimpleHTTPRequestHa
if self.run_once: if self.run_once:
self.handle_one_request() self.handle_one_request()
else: else:
SimpleHTTPRequestHandler.handle(self) super().handle()
def log_request(self, code='-', size='-'): def log_request(self, code='-', size='-'):
if self.verbose: if self.verbose:
SimpleHTTPRequestHandler.log_request(self, code, size) super().log_request(code, size)
class WebSockifyServer(object): class WebSockifyServer():
""" """
WebSockets server class. WebSockets server class.
As an alternative, the standard library SocketServer can be used As an alternative, the standard library SocketServer can be used
@@ -559,7 +541,7 @@ class WebSockifyServer(object):
if not handshake: if not handshake:
raise self.EClose("") raise self.EClose("")
elif handshake[0] in ("\x16", "\x80", 22, 128): elif handshake[0] in (22, 128):
# SSL wrap the connection # SSL wrap the connection
if not ssl: if not ssl:
raise self.EClose("SSL connection but no 'ssl' module") raise self.EClose("SSL connection but no 'ssl' module")
@@ -568,32 +550,21 @@ class WebSockifyServer(object):
% self.cert) % self.cert)
retsock = None retsock = None
try: try:
if (hasattr(ssl, 'create_default_context') # create new-style SSL wrapping for extended features
and callable(ssl.create_default_context)): context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
# create new-style SSL wrapping for extended features if self.ssl_ciphers is not None:
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) context.set_ciphers(self.ssl_ciphers)
if self.ssl_ciphers is not None: context.options = self.ssl_options
context.set_ciphers(self.ssl_ciphers) context.load_cert_chain(certfile=self.cert, keyfile=self.key, password=self.key_password)
context.options = self.ssl_options if self.verify_client:
context.load_cert_chain(certfile=self.cert, keyfile=self.key, password=self.key_password) context.verify_mode = ssl.CERT_REQUIRED
if self.verify_client: if self.cafile:
context.verify_mode = ssl.CERT_REQUIRED context.load_verify_locations(cafile=self.cafile)
if self.cafile: else:
context.load_verify_locations(cafile=self.cafile) context.set_default_verify_paths()
else: retsock = context.wrap_socket(
context.set_default_verify_paths() sock,
retsock = context.wrap_socket( server_side=True)
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)
except ssl.SSLError: except ssl.SSLError:
_, x, _ = sys.exc_info() _, x, _ = sys.exc_info()
if x.args[0] == ssl.SSL_ERROR_EOF: if x.args[0] == ssl.SSL_ERROR_EOF:
@@ -729,10 +700,6 @@ class WebSockifyServer(object):
if self.listen_fd != None: if self.listen_fd != None:
lsock = socket.fromfd(self.listen_fd, socket.AF_INET, socket.SOCK_STREAM) lsock = socket.fromfd(self.listen_fd, socket.AF_INET, socket.SOCK_STREAM)
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)
else: else:
lsock = self.socket(self.listen_host, self.listen_port, False, lsock = self.socket(self.listen_host, self.listen_port, False,
self.prefer_ipv6, self.prefer_ipv6,