From 5bde6cea4db1cc1e54c358eaec75529ee23f9e4c Mon Sep 17 00:00:00 2001 From: Giuseppe Corbelli Date: Wed, 6 Mar 2019 10:54:19 +0100 Subject: [PATCH 1/6] [websocketproxy] Added a websockify.spec file to be used with pyinstaller to build a frozen distribution Explicit excludes have been disabled and comments added on how to proceed to re-enable them (see PR #384 discussion) --- websockify.spec | 102 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 websockify.spec diff --git a/websockify.spec b/websockify.spec new file mode 100644 index 0000000..3f1d176 --- /dev/null +++ b/websockify.spec @@ -0,0 +1,102 @@ +# -*- mode: python -*- +# To be used with https://www.pyinstaller.org to build a "frozen" executable distribution +# Tested with pyinstaller 3.4, python 3.7.2, linux x86_64 +# NOT tested with SSL + +# Set to True to bring in numpy if available (increases output package size by ~45MB on linux64/python3.7). +# If False it will be left out +USE_NUMPY=True + +# Get debug messages by pyinstaller bootloader +DEBUG=False + +# Explicitly excluded modules +excludes = [] + +# It is possible to reduce the resulting package size (~3MB on linux64/python3.7) +# by excluding unused encodings. +# By default (as of pyinstaller 3.4/python 3.7) all encodings are pulled in: +# the following list can be used to reduce the encodings pulled in to the bare minimum. +# You should include the target platform encoding or call locale.setlocale(locale.LC_ALL, 'C') +# early in websockify_init() +# +# These are required: +# - 'encodings.base64_codec' This is required when payload is b64 encoded, don't exclude +# - 'encodings' Father package must also be included +# - 'encodings.aliases' Required, see initstdio() function in cPython sources +# - 'encodings.utf_8' +# - 'encodings.latin_1' +# - 'encodings.ascii' Required by some cascaded import from http +# - encodings.idna' Required by +# excludes.extend(('encodings.undefined', 'encodings.utf_32_be', 'encodings.utf_32', +# 'encodings.utf_16_le', 'encodings.utf_16_be', 'encodings.utf_16', 'encodings.utf_32_le', +# 'encodings.zlib_codec', 'encodings.euc_jis_2004', 'encodings.ptcp154', 'encodings.cp874', +# 'encodings.cp424', +# 'encodings.iso2022_jp_2', 'encodings.euc_jp', 'encodings.mac_arabic', 'encodings.shift_jis', +# 'encodings.utf_7', 'encodings.cp866', 'encodings.cp855', 'encodings.rot_13', 'encodings.cp1006', +# 'encodings.johab', 'encodings.cp865', 'encodings.mac_cyrillic', 'encodings.cp737', 'encodings.kz1048', +# 'encodings.cp1256', 'encodings.cp1252', 'encodings.hp_roman8', 'encodings.cp1026', 'encodings.iso8859_6', +# 'encodings.hz', 'encodings.shift_jisx0213', 'encodings.cp500', 'encodings.palmos', 'encodings.euc_jisx0213', +# 'encodings.cp864', 'encodings.cp875', 'encodings.mac_iceland', 'encodings.cp856', 'encodings.big5', +# 'encodings.iso2022_jp_ext', 'encodings.charmap', 'encodings.iso8859_7', 'encodings.cp852', +# 'encodings.mac_croatian', 'encodings.bz2_codec', 'encodings.cp863', 'encodings.iso8859_14', +# 'encodings.cp65001', 'encodings.cp1254', 'encodings.iso2022_jp_2004', 'encodings.cp932', +# 'encodings.raw_unicode_escape', 'encodings.mac_romanian', 'encodings.gb18030', 'encodings.cp1257', +# 'encodings.mac_latin2', 'encodings.iso2022_kr', 'encodings.shift_jis_2004', 'encodings.cp850', +# 'encodings.iso2022_jp_1', 'encodings.cp862', 'encodings.iso8859_15', 'encodings.hex_codec', +# 'encodings.cp857', 'encodings.iso8859_4', 'encodings.mac_roman', 'encodings.cp1250', +# 'encodings.iso8859_9', 'encodings.mbcs', 'encodings.mac_greek', +# 'encodings.cp1125', 'encodings.koi8_u', 'encodings.cp273', 'encodings.big5hkscs', 'encodings.cp1140', +# 'encodings.utf_8_sig', 'encodings.iso8859_13', 'encodings.tis_620', 'encodings.cp037', +# 'encodings.iso2022_jp_3', 'encodings.cp861', 'encodings.mac_farsi', 'encodings.iso8859_1', +# 'encodings.cp869', 'encodings.iso8859_8', 'encodings.unicode_internal', +# 'encodings.iso8859_3', 'encodings.cp720', 'encodings.koi8_r', 'encodings.cp437', 'encodings.cp858', +# 'encodings.euc_kr', 'encodings.iso8859_2', 'encodings.cp1251', 'encodings.cp950', 'encodings.gbk', +# 'encodings.cp775', 'encodings.unicode_escape', 'encodings.quopri_codec', 'encodings.cp860', +# 'encodings.koi8_t', 'encodings.uu_codec', 'encodings.cp1253', 'encodings.iso8859_5', +# 'encodings.mac_centeuro', 'encodings.iso8859_11', 'encodings.iso8859_16', 'encodings.iso8859_10', +# 'encodings.gb2312', 'encodings.iso2022_jp', 'encodings.mac_turkish', +# 'encodings.cp1255', 'encodings.cp949', 'encodings.cp1258', 'encodings.punycode')) + +# Also some other modules may be safely left out (save ~2MB on linux64/python3.7) +# excludes.extend(('bz2', 'curses', 'decimal', 'grp', 'gzip', 'json', 'lzma', 'pdb', 'pkg_resources', +# 'plistlib', 'pyexpat', 'readline', 'termios', 'uuid', 'xml', 'zlib')) + +block_cipher = None +CONSOLE=True + +if not USE_NUMPY: + # Apparently unittes is required??? by numpy + excludes.extend(("numpy", "unittest")) + +hiddenimports=[] + +a = Analysis( + ['run'], + pathex=[], + binaries=[], + datas=[("docs", "docs"),], + hiddenimports=hiddenimports, + hookspath=[], + runtime_hooks=[], + excludes=excludes, + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher +) +pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) +exe = EXE( + pyz, + a.scripts, + # Must remain True or it will assume a single-file deploy + exclude_binaries=True, + name='websockify', + debug=DEBUG, + strip=False, + upx=False, + console=CONSOLE +) +coll = COLLECT( + exe, a.binaries, a.zipfiles, a.datas, + strip=False, upx=False, name='websockify' +) From f5dbb83fecbdbfe94af3e2918fab630d37caca06 Mon Sep 17 00:00:00 2001 From: Tommy Brunn Date: Sat, 2 Mar 2019 17:21:28 +0100 Subject: [PATCH 2/6] Add option for cert key password --- tests/test_websockifyserver.py | 6 +++--- websockify/websocketproxy.py | 2 ++ websockify/websockifyserver.py | 5 +++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/test_websockifyserver.py b/tests/test_websockifyserver.py index 7ce82da..b9312dc 100644 --- a/tests/test_websockifyserver.py +++ b/tests/test_websockifyserver.py @@ -271,7 +271,7 @@ class WebSockifyServerTestCase(unittest.TestCase): def __init__(self, purpose): self.verify_mode = None self.options = 0 - def load_cert_chain(self, certfile, keyfile): + def load_cert_chain(self, certfile, keyfile, password): pass def set_default_verify_paths(self): pass @@ -310,7 +310,7 @@ class WebSockifyServerTestCase(unittest.TestCase): def __init__(self, purpose): self.verify_mode = None self.options = 0 - def load_cert_chain(self, certfile, keyfile): + def load_cert_chain(self, certfile, keyfile, password): pass def set_default_verify_paths(self): pass @@ -351,7 +351,7 @@ class WebSockifyServerTestCase(unittest.TestCase): def __init__(self, purpose): self.verify_mode = None self._options = 0 - def load_cert_chain(self, certfile, keyfile): + def load_cert_chain(self, certfile, keyfile, password): pass def set_default_verify_paths(self): pass diff --git a/websockify/websocketproxy.py b/websockify/websocketproxy.py index 16b00d8..d69dad6 100644 --- a/websockify/websocketproxy.py +++ b/websockify/websocketproxy.py @@ -478,6 +478,8 @@ def websockify_init(): help="SSL certificate file") parser.add_option("--key", default=None, help="SSL key file (if separate from cert)") + parser.add_option("--password", default=None, + help="SSL key password") parser.add_option("--ssl-only", action="store_true", help="disallow non-encrypted client connections") parser.add_option("--ssl-target", action="store_true", diff --git a/websockify/websockifyserver.py b/websockify/websockifyserver.py index fe01f97..c379d64 100644 --- a/websockify/websockifyserver.py +++ b/websockify/websockifyserver.py @@ -340,7 +340,7 @@ class WebSockifyServer(object): def __init__(self, RequestHandlerClass, listen_fd=None, listen_host='', listen_port=None, source_is_ipv6=False, - verbose=False, cert='', key='', ssl_only=None, + verbose=False, cert='', key='', password=None, ssl_only=None, verify_client=False, cafile=None, daemon=False, record='', web='', web_auth=False, file_only=False, @@ -380,6 +380,7 @@ class WebSockifyServer(object): # keyfile path must be None if not specified self.key = None + self.password = password # Make paths settings absolute self.cert = os.path.abspath(cert) @@ -577,7 +578,7 @@ class WebSockifyServer(object): if self.ssl_ciphers is not None: context.set_ciphers(self.ssl_ciphers) context.options = self.ssl_options - context.load_cert_chain(certfile=self.cert, keyfile=self.key) + context.load_cert_chain(certfile=self.cert, keyfile=self.key, password=self.password) if self.verify_client: context.verify_mode = ssl.CERT_REQUIRED if self.cafile: From 39ae9b6250a3e218b3961db96e90b2f4191e54bb Mon Sep 17 00:00:00 2001 From: Tommy Brunn Date: Mon, 4 Mar 2019 09:31:01 +0100 Subject: [PATCH 3/6] Rename certificate key password option --- websockify/websocketproxy.py | 2 +- websockify/websockifyserver.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/websockify/websocketproxy.py b/websockify/websocketproxy.py index d69dad6..6e89fa6 100644 --- a/websockify/websocketproxy.py +++ b/websockify/websocketproxy.py @@ -478,7 +478,7 @@ def websockify_init(): help="SSL certificate file") parser.add_option("--key", default=None, help="SSL key file (if separate from cert)") - parser.add_option("--password", default=None, + parser.add_option("--key-password", default=None, help="SSL key password") parser.add_option("--ssl-only", action="store_true", help="disallow non-encrypted client connections") diff --git a/websockify/websockifyserver.py b/websockify/websockifyserver.py index c379d64..9d9cfb9 100644 --- a/websockify/websockifyserver.py +++ b/websockify/websockifyserver.py @@ -340,7 +340,7 @@ class WebSockifyServer(object): def __init__(self, RequestHandlerClass, listen_fd=None, listen_host='', listen_port=None, source_is_ipv6=False, - verbose=False, cert='', key='', password=None, ssl_only=None, + verbose=False, cert='', key='', key_password=None, ssl_only=None, verify_client=False, cafile=None, daemon=False, record='', web='', web_auth=False, file_only=False, @@ -380,7 +380,7 @@ class WebSockifyServer(object): # keyfile path must be None if not specified self.key = None - self.password = password + self.key_password = key_password # Make paths settings absolute self.cert = os.path.abspath(cert) @@ -578,7 +578,7 @@ class WebSockifyServer(object): if self.ssl_ciphers is not None: context.set_ciphers(self.ssl_ciphers) context.options = self.ssl_options - context.load_cert_chain(certfile=self.cert, keyfile=self.key, password=self.password) + context.load_cert_chain(certfile=self.cert, keyfile=self.key, password=self.key_password) if self.verify_client: context.verify_mode = ssl.CERT_REQUIRED if self.cafile: From fb6405bffbddabdbc5aae65a0ae18e1a3e54c0bb Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Tue, 2 Apr 2019 17:02:08 +0200 Subject: [PATCH 4/6] Allow main script to be imported We should only start the server if we are the main module, and not imported some other way. This is important for multiprocessing to work correctly on Windows. --- run | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/run b/run index 9ad217c..9ea6996 100755 --- a/run +++ b/run @@ -2,4 +2,5 @@ import websockify -websockify.websocketproxy.websockify_init() +if __name__ == '__main__': + websockify.websocketproxy.websockify_init() From 05c4ac96704ec420f44449d5f788b901d8753aa2 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Tue, 2 Apr 2019 17:03:08 +0200 Subject: [PATCH 5/6] Use ThreadingMixIn for the simple server ForkingMixIn isn't available on Windows. This is the simple server without features, so use ThreadingMixIn to keep things consistent. --- websockify/websocketproxy.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/websockify/websocketproxy.py b/websockify/websocketproxy.py index 6e89fa6..525904e 100644 --- a/websockify/websocketproxy.py +++ b/websockify/websocketproxy.py @@ -13,9 +13,9 @@ as taken from http://docs.python.org/dev/library/ssl.html#certificates import signal, socket, optparse, time, os, sys, subprocess, logging, errno, ssl try: - from socketserver import ForkingMixIn + from socketserver import ThreadingMixIn except ImportError: - from SocketServer import ForkingMixIn + from SocketServer import ThreadingMixIn try: from http.server import HTTPServer @@ -726,7 +726,7 @@ def websockify_init(): server.start_server() -class LibProxyServer(ForkingMixIn, HTTPServer): +class LibProxyServer(ThreadingMixIn, HTTPServer): """ Just like WebSocketProxy, but uses standard Python SocketServer framework. From dc3d28cbb6209d52ef5a417dfc33c4fcedd35859 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Tue, 2 Apr 2019 17:04:19 +0200 Subject: [PATCH 6/6] Re-enable Windows support It works well enough now with the recent fixes and a modern Python. --- websockify/websockifyserver.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/websockify/websockifyserver.py b/websockify/websockifyserver.py index 9d9cfb9..0ec3bb0 100644 --- a/websockify/websockifyserver.py +++ b/websockify/websockifyserver.py @@ -40,9 +40,6 @@ for mod, msg in [('ssl', 'TLS/SSL/wss is disabled'), if sys.platform == 'win32': # make sockets pickle-able/inheritable import multiprocessing.reduction - # the multiprocesssing module behaves much differently on Windows, - # and we have yet to fix all the bugs - sys.exit("Windows is not supported at this time") from websockify.websocket import WebSocket, WebSocketWantReadError, WebSocketWantWriteError from websockify.websocketserver import WebSocketRequestHandlerMixIn