├── doc ├── source │ ├── _static │ ├── record_layer.rst │ ├── network_io.rst │ ├── relay.rst │ ├── index.rst │ ├── server.rst │ ├── client.rst │ ├── __init__.rst │ ├── fteproxy.rst │ └── conf.py ├── make.bat └── Makefile ├── fteproxy ├── VERSION ├── tests │ ├── __init__.py │ ├── test_record_layer.py │ └── test_relay.py ├── defs │ ├── 20131224.json │ └── __init__.py ├── server.py ├── client.py ├── network_io.py ├── record_layer.py ├── conf.py ├── relay.py ├── __init__.py ├── cli.py └── regex2dfa.py ├── requirements.txt ├── unittests ├── MANIFEST.in ├── examples ├── chat │ ├── README │ ├── client.py │ └── server.py └── netcat │ ├── netcat_simple.sh │ └── netcat_verbose.sh ├── .gitignore ├── .travis.yml ├── bin └── fteproxy ├── fteproxy.spec ├── setup_tbb.py ├── README ├── setup.py ├── BUILDING.md ├── README.md ├── systemtests ├── COPYING └── LICENSE /doc/source/_static: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /fteproxy/VERSION: -------------------------------------------------------------------------------- 1 | 0.2.19 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | fte 2 | pyptlib 3 | obfsproxy 4 | twisted 5 | -------------------------------------------------------------------------------- /unittests: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ./bin/fteproxy --mode test --quiet 4 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include fteproxy/VERSION 3 | include fteproxy/defs/* 4 | -------------------------------------------------------------------------------- /examples/chat/README: -------------------------------------------------------------------------------- 1 | Based on code from: http://docs.python.org/2/library/socket.html 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | dist 3 | fteproxy/*.so 4 | thirdparty/re2 5 | fteproxy/*.pyc 6 | fteproxy/tests/*.pyc 7 | fteproxy/defs/*.pyc 8 | fteproxy.egg-info 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: 2 | - python 3 | - cpp 4 | compiler: 5 | - gcc 6 | - clang 7 | before_install: 8 | - sudo apt-get update -qq 9 | - sudo apt-get install -y libgmp-dev 10 | python: 11 | - "2.7" 12 | install: 13 | - "pip install -r requirements.txt" 14 | script: 15 | - "python setup.py test" 16 | -------------------------------------------------------------------------------- /examples/netcat/netcat_simple.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # start fteproxy client 4 | ./bin/fteproxy --quiet & 5 | 6 | # start fteproxy server 7 | ./bin/fteproxy --mode server --quiet & 8 | 9 | # start server-side netcat listener 10 | netcat -k -l -p 8081 11 | 12 | # start client-side netcat pusher in another window 13 | # nc localhost 8079 14 | -------------------------------------------------------------------------------- /doc/source/record_layer.rst: -------------------------------------------------------------------------------- 1 | :mod:`fteproxy.record_layer` Module 2 | *********************************** 3 | 4 | Overview 5 | -------- 6 | 7 | This is a class used internally by fteproxy and should not be invoked directly. 8 | 9 | Interface 10 | --------- 11 | 12 | .. automodule:: fteproxy.record_layer 13 | :members: 14 | :undoc-members: 15 | :show-inheritance: 16 | -------------------------------------------------------------------------------- /fteproxy/tests/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | 6 | import unittest 7 | import test_record_layer 8 | import test_relay 9 | 10 | 11 | def suite(): 12 | suite = unittest.TestSuite() 13 | suite.addTests(test_record_layer.suite()) 14 | suite.addTests(test_relay.suite()) 15 | return suite 16 | 17 | if __name__ == '__main__': 18 | unittest.TextTestRunner(verbosity=2).run(suite()) 19 | -------------------------------------------------------------------------------- /bin/fteproxy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import sys 5 | import os 6 | 7 | if hasattr(sys, "frozen"): 8 | sys.path.append( 9 | os.path.abspath(os.path.join(os.path.dirname(sys.executable)))) 10 | else: 11 | sys.path.append( 12 | os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) 13 | 14 | import fteproxy.cli 15 | 16 | if __name__ == '__main__': 17 | fteproxy.cli.main() 18 | -------------------------------------------------------------------------------- /doc/source/network_io.rst: -------------------------------------------------------------------------------- 1 | :mod:`fteproxy.network_io` Module 2 | ********************************* 3 | 4 | Overview 5 | -------- 6 | 7 | A class that aids in socket I/O. 8 | The primary purpose of this class is to encapsulate the different error conditions 9 | that can occur with a socket read: exceptions and a return value of ''. 10 | 11 | Interface 12 | --------- 13 | 14 | .. automodule:: fteproxy.network_io 15 | :members: 16 | :undoc-members: 17 | :show-inheritance: 18 | -------------------------------------------------------------------------------- /doc/source/relay.rst: -------------------------------------------------------------------------------- 1 | :mod:`fteproxy.relay` Module 2 | **************************** 3 | 4 | Overview 5 | -------- 6 | 7 | The classes ``fteproxy.relay.worker`` and ``fteproxy.relay.listener`` are never invoked 8 | directly and are helper classes used internally within fteproxy. 9 | 10 | ``fteproxy.relay.worker`` contains logic that is common to ``fteproxy.relay.client`` and 11 | ``fteproxy.relay.server``. 12 | 13 | Interface 14 | --------- 15 | 16 | .. automodule:: fteproxy.relay 17 | :members: 18 | :undoc-members: 19 | :show-inheritance: 20 | -------------------------------------------------------------------------------- /fteproxy/defs/20131224.json: -------------------------------------------------------------------------------- 1 | { 2 | "manual-http-request": { 3 | "regex": "^GET\\ \\/([a-zA-Z0-9\\.\\/]*) HTTP/1\\.1\\r\\n\\r\\n$" 4 | }, 5 | "manual-http-response": { 6 | "regex": "^HTTP/1\\.1\\ 200 OK\\r\\nContent-Type:\\ ([a-zA-Z0-9]+)\\r\\n\\r\\n\\C*$" 7 | }, 8 | "manual-ssh-request": { 9 | "regex": "^SSH\\-2\\.0\\C*$" 10 | }, 11 | "manual-ssh-response": { 12 | "regex": "^SSH\\-2\\.0\\C*$" 13 | }, 14 | "dummy-request": { 15 | "regex": "^\\C+$" 16 | }, 17 | "dummy-response": { 18 | "regex": "^\\C+$" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /fteproxy.spec: -------------------------------------------------------------------------------- 1 | # -*- mode: python -*- 2 | a = Analysis(['./bin/fteproxy'], 3 | pathex=['/Users/kpdyer/sandbox/kpdyer/fteproxy'], 4 | hiddenimports=[], 5 | hookspath=None, 6 | runtime_hooks=None) 7 | pyz = PYZ(a.pure) 8 | exe = EXE(pyz, 9 | a.scripts, 10 | exclude_binaries=True, 11 | name='fteproxy', 12 | debug=False, 13 | strip=None, 14 | upx=True, 15 | console=True ) 16 | coll = COLLECT(exe, 17 | a.binaries, 18 | a.zipfiles, 19 | a.datas, 20 | strip=None, 21 | upx=True, 22 | name='fteproxy') 23 | -------------------------------------------------------------------------------- /fteproxy/server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | 6 | 7 | import fteproxy.relay 8 | 9 | 10 | class listener(fteproxy.relay.listener): 11 | 12 | def onNewIncomingConnection(self, socket): 13 | """On an incoming data stream we wrap it with ``fteproxy.wrap_socket``, with no parameters. 14 | By default we want the regular expressions to be negotiated in-band, specified by the client. 15 | """ 16 | K1 = fteproxy.conf.getValue('runtime.fteproxy.encrypter.key')[:16] 17 | K2 = fteproxy.conf.getValue('runtime.fteproxy.encrypter.key')[16:] 18 | socket = fteproxy.wrap_socket(socket, K1=K1, K2=K2) 19 | 20 | return socket 21 | -------------------------------------------------------------------------------- /examples/chat/client.py: -------------------------------------------------------------------------------- 1 | # FTE-Powered echo client program 2 | import socket 3 | import fteproxy 4 | 5 | client_server_regex = '^(0|1)+$' 6 | server_client_regex = '^(A|B)+$' 7 | 8 | HOST = '127.0.0.1' # The remote host 9 | PORT = 50007 # The same port as used by the server 10 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 11 | s = fteproxy.wrap_socket(s, 12 | outgoing_regex=client_server_regex, 13 | outgoing_fixed_slice=256, 14 | incoming_regex=server_client_regex, 15 | incoming_fixed_slice=256) 16 | s.connect((HOST, PORT)) 17 | s.sendall('Hello, world') 18 | data = s.recv(1024) 19 | s.close() 20 | print 'Received', repr(data) 21 | -------------------------------------------------------------------------------- /examples/chat/server.py: -------------------------------------------------------------------------------- 1 | # FTE-Powered echo server program 2 | import socket 3 | import fteproxy 4 | 5 | client_server_regex = '^(0|1)+$' 6 | server_client_regex = '^(A|B)+$' 7 | 8 | HOST = '' # Symbolic name meaning all available interfaces 9 | PORT = 50007 # Arbitrary non-privileged port 10 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 11 | s = fteproxy.wrap_socket(s, 12 | outgoing_regex=server_client_regex, 13 | outgoing_fixed_slice=256, 14 | incoming_regex=client_server_regex, 15 | incoming_fixed_slice=256) 16 | s.bind((HOST, PORT)) 17 | s.listen(1) 18 | conn, addr = s.accept() 19 | print 'Connected by', addr 20 | while 1: 21 | data = conn.recv(1024) 22 | if not data: 23 | break 24 | conn.sendall(data) 25 | conn.close() 26 | -------------------------------------------------------------------------------- /setup_tbb.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | 4 | 5 | from setuptools import setup 6 | from setuptools import Extension 7 | 8 | import glob 9 | import sys 10 | import os 11 | if os.name == 'nt': 12 | import py2exe 13 | 14 | with open('fteproxy/VERSION') as fh: 15 | FTEPROXY_RELEASE = fh.read().strip() 16 | 17 | setup(name='fteproxy', 18 | console=['./bin/fteproxy'], 19 | zipfile="fteproxy.zip", 20 | options={"py2exe": { 21 | "dll_excludes": ["w9xpopen.exe"], 22 | "includes": ["twisted", "pyptlib", "Crypto", "txsocksx", "parsley", "obfsproxy", "fte"], 23 | } 24 | }, 25 | version=FTEPROXY_RELEASE, 26 | description='fteproxy', 27 | author='Kevin P. Dyer', 28 | author_email='kpdyer@gmail.com', 29 | url='https://github.com/kpdyer/fteproxy', 30 | packages=['fteproxy', 'fteproxy.defs', 'fteproxy.tests'], 31 | install_requires=['pyptlib', 'obfsproxy', 'twisted', 'fte'] 32 | ) 33 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Requires: Twisted (https://twistedmatrix.com/), obfsproxy (https://pypi.python.org/pypi/obfsproxy), pyptlib (https://pypi.python.org/pypi/pyptlib) and fte (https://pypi.python.org/pypi/fte). 2 | 3 | fteproxy provides transport-layer protection to resist keyword filtering, censorship and discriminatory routing policies. Its job is to relay datastreams, such as web browsing traffic, by encoding streams as messages that match a user-specified regular expression. See https://fteproxy.org/ for more information. 4 | 5 | Format-Transforming Encryption (FTE) is a cryptographic primitive explored in the paper *Protocol Misidentification Made Easy with Format-Transforming Encryption* [1]. FTE allows a user to specify the format of their ouput ciphertexts using regular expressions. The libfte library implements the primitive presented in [1]. 6 | 7 | [1] Protocol Misidentification Made Easy with Format-Transforming Encryption, Kevin P. Dyer, Scott E. Coull, Thomas Ristenpart and Thomas Shrimpton, https://kpdyer.com/publications/ccs2013-fte.pdf 8 | -------------------------------------------------------------------------------- /examples/netcat/netcat_verbose.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # the IP:port our fteproxy client listens on 4 | CLIENT_IP=127.0.0.1 5 | CLIENT_PORT=8079 6 | 7 | # the IP:port our fteproxy server listens on 8 | SERVER_IP=127.0.0.1 9 | SERVER_PORT=8080 10 | 11 | # the IP:port where our fteproxy forwards all connections 12 | # in this test, it's the IP:port the server-side netcat will bind to 13 | PROXY_IP=127.0.0.1 14 | PROXY_PORT=8081 15 | 16 | # start fteproxy client 17 | ./bin/fteproxy --mode client --quiet \ 18 | --client_ip $CLIENT_IP --client_port $CLIENT_PORT \ 19 | --server_ip $SERVER_IP --server_port $SERVER_PORT & 20 | 21 | # start fteproxy server 22 | ./bin/fteproxy --mode server --quiet \ 23 | --server_ip $SERVER_IP --server_port $SERVER_PORT \ 24 | --proxy_ip $PROXY_IP --proxy_port $PROXY_PORT & 25 | 26 | # start server-side netcat listener 27 | netcat -k -l -p $PROXY_PORT 28 | 29 | # start client-side netcat pusher in another window 30 | # nc $CLIENT_IP $CLIENT_PORT 31 | -------------------------------------------------------------------------------- /fteproxy/defs/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | 6 | import os 7 | import json 8 | 9 | import fteproxy.conf 10 | 11 | 12 | class InvalidRegexName(Exception): 13 | pass 14 | 15 | _definitions = None 16 | 17 | 18 | def load_definitions(): 19 | global _definitions 20 | 21 | if _definitions == None: 22 | def_dir = os.path.join(fteproxy.conf.getValue('general.defs_dir')) 23 | def_file = fteproxy.conf.getValue('fteproxy.defs.release') + '.json' 24 | def_abspath = os.path.join(def_dir, def_file) 25 | 26 | with open(def_abspath) as fh: 27 | _definitions = json.load(fh) 28 | 29 | return _definitions 30 | 31 | 32 | def getRegex(format_name): 33 | definitions = load_definitions() 34 | try: 35 | regex = definitions[format_name]['regex'] 36 | except KeyError: 37 | raise InvalidRegexName(format_name) 38 | 39 | return regex 40 | 41 | 42 | def getFixedSlice(format_name): 43 | definitions = load_definitions() 44 | try: 45 | fixed_slice = definitions[format_name]['fixed_slice'] 46 | except KeyError: 47 | fixed_slice = fteproxy.conf.getValue('fteproxy.default_fixed_slice') 48 | 49 | return fixed_slice 50 | -------------------------------------------------------------------------------- /doc/source/index.rst: -------------------------------------------------------------------------------- 1 | fteproxy Documentation 2 | ======================= 3 | 4 | Format-Transforming Encryption (FTE) is a strategy for communicating using 5 | strings defined by compact regular expressions. 6 | fteproxy is well-positioned to circumvent regex-based DPI systems. 7 | 8 | ``fteproxy`` Command Line Application 9 | ------------------------------------- 10 | 11 | The ``fteproxy`` command-line application can be used to run and fteproxy proxy client 12 | or server. 13 | 14 | .. toctree:: 15 | :maxdepth: 2 16 | 17 | fteproxy.rst 18 | 19 | 20 | fteproxy Wrapper for Socket Objects 21 | ----------------------------------- 22 | 23 | The fteproxy socket wrapper is useful for rapidly integrating fteproxy into existing 24 | socket-powered applications. 25 | 26 | .. toctree:: 27 | :maxdepth: 2 28 | 29 | __init__.rst 30 | 31 | 32 | fteproxy API 33 | ----------------------------------- 34 | 35 | 36 | .. toctree:: 37 | :maxdepth: 2 38 | 39 | client.rst 40 | server.rst 41 | network_io.rst 42 | relay.rst 43 | record_layer.rst 44 | 45 | 46 | Indices and tables 47 | ================== 48 | 49 | * :ref:`genindex` 50 | * :ref:`modindex` 51 | * :ref:`search` 52 | 53 | Indices and tables 54 | ================== 55 | 56 | * :ref:`genindex` 57 | * :ref:`modindex` 58 | * :ref:`search` 59 | -------------------------------------------------------------------------------- /fteproxy/client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | 6 | import fteproxy.relay 7 | 8 | 9 | class listener(fteproxy.relay.listener): 10 | 11 | def onNewOutgoingConnection(self, socket): 12 | """On an outgoing data stream we wrap it with ``fteproxy.wrap_socket``, with 13 | the languages specified in the ``runtime.state.upstream_language`` and 14 | ``runtime.state.downstream_language`` configuration parameters. 15 | """ 16 | 17 | outgoing_language = fteproxy.conf.getValue( 18 | 'runtime.state.upstream_language') 19 | incoming_language = fteproxy.conf.getValue( 20 | 'runtime.state.downstream_language') 21 | 22 | outgoing_regex = fteproxy.defs.getRegex(outgoing_language) 23 | outgoing_fixed_slice = fteproxy.defs.getFixedSlice(outgoing_language) 24 | 25 | incoming_regex = fteproxy.defs.getRegex(incoming_language) 26 | incoming_fixed_slice = fteproxy.defs.getFixedSlice(incoming_language) 27 | 28 | K1 = fteproxy.conf.getValue('runtime.fteproxy.encrypter.key')[:16] 29 | K2 = fteproxy.conf.getValue('runtime.fteproxy.encrypter.key')[16:] 30 | socket = fteproxy.wrap_socket(socket, 31 | outgoing_regex, outgoing_fixed_slice, 32 | incoming_regex, incoming_fixed_slice, 33 | K1, K2) 34 | 35 | return socket 36 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup 4 | from setuptools import Extension 5 | 6 | import glob 7 | import sys 8 | import os 9 | if os.name == 'nt': 10 | import py2exe 11 | 12 | with open('fteproxy/VERSION') as fh: 13 | FTEPROXY_RELEASE = fh.read().strip() 14 | 15 | package_data_files = [] 16 | package_data_files += ['VERSION'] 17 | for filename in glob.glob('fteproxy/defs/*.json'): 18 | jsonFile = filename.split('/')[-1] 19 | package_data_files += ['defs/'+jsonFile] 20 | package_data = {'fteproxy': package_data_files} 21 | 22 | with open('README') as file: 23 | long_description = file.read() 24 | 25 | setup(name='fteproxy', 26 | long_description=long_description, 27 | console=['./bin/fteproxy'], 28 | test_suite='fteproxy.tests.suite', 29 | zipfile="fteproxy.zip", 30 | package_data=package_data, 31 | options={"py2exe": { 32 | "bundle_files": 2, 33 | "compressed": True, 34 | "dll_excludes": ["w9xpopen.exe"], 35 | } 36 | }, 37 | version=FTEPROXY_RELEASE, 38 | description='fteproxy', 39 | author='Kevin P. Dyer', 40 | author_email='kpdyer@gmail.com', 41 | url='https://fteproxy.org/', 42 | packages=['fteproxy', 'fteproxy.defs', 'fteproxy.tests'], 43 | install_requires=['fte','twisted','pyptlib','obfsproxy'], 44 | entry_points = { 45 | 'console_scripts': [ 46 | 'fteproxy = fteproxy.cli:main' 47 | ] 48 | }, 49 | ) 50 | -------------------------------------------------------------------------------- /doc/source/server.rst: -------------------------------------------------------------------------------- 1 | :mod:`fteproxy.server` Module 2 | ***************************** 3 | 4 | Overview 5 | -------- 6 | It's the responsibility of the server listener to broker incoming/ 7 | outgoing connections on the server-side of an fteproxy setup. 8 | Incoming connections are encapsulated by FTE, as they 9 | are from an fteproxy client application. Outgoing connections 10 | will be destined to some sort of proxy, such as a SOCKS server or Tor bridge. 11 | 12 | The ``fteproxy.server.listener`` class extends ``fteproxy.relay.listener``. 13 | See ``fteproxy.relay.listener`` for more information, which is also the base class 14 | for ``fteproxy.client.listener``. The ``fteproxy.relay.listener`` class extends 15 | ``threading.Thread``, hence we invoke the fteproxy server via fteproxy.listener.server.start(). 16 | 17 | 18 | Interface 19 | --------- 20 | 21 | .. automodule:: fteproxy.server 22 | :members: 23 | :undoc-members: 24 | :show-inheritance: 25 | 26 | Examples 27 | -------- 28 | 29 | Start the fteproxy server with default configuration parameters. 30 | 31 | .. code-block:: python 32 | 33 | import fteproxy.server.listener 34 | 35 | server = fteproxy.server.listener() 36 | server.start() 37 | server.join(10) # run for 10 seconds 38 | server.stop() 39 | 40 | Start the fteproxy server listening on server-side port ``127.0.0.1:8888``. 41 | 42 | .. code-block:: python 43 | 44 | import fteproxy.conf 45 | import fteproxy.server.listener 46 | 47 | fteproxy.conf.setValue('runtime.server.ip', '127.0.0.1') 48 | fteproxy.conf.setValue('runtime.server.port', 8888) 49 | 50 | server = fteproxy.server.listener() 51 | server.start() 52 | server.join(10) # run for 10 seconds 53 | server.stop() 54 | -------------------------------------------------------------------------------- /BUILDING.md: -------------------------------------------------------------------------------- 1 | fteproxy Build Instructions 2 | =========================== 3 | 4 | Ubuntu/Debian 5 | ------------- 6 | 7 | Install the following packages. 8 | ``` 9 | sudo apt-get install python-dev python-pip upx git 10 | sudo pip install --upgrade fte pyptlib obfsproxy twisted pyinstaller 11 | ``` 12 | 13 | Note: If you are on Ubuntu 13.10, there is a bug with pyinstaller/pycrypto, such that binary packages do not build properly. Please install development version 52fa29ce of pyinstaller [3], instead of using pip. See [4] for more details. 14 | 15 | Then, clone and build fteproxy. 16 | ``` 17 | git clone https://github.com/kpdyer/fteproxy.git 18 | cd fteproxy 19 | make 20 | make test 21 | make dist 22 | ``` 23 | 24 | OSX 25 | --- 26 | 27 | Install homebrew [1], if you don't have it already. 28 | 29 | ``` 30 | ruby -e "$(curl -fsSL https://raw.github.com/Homebrew/homebrew/go/install)" 31 | ``` 32 | 33 | Install the following packages. 34 | ``` 35 | brew install --build-from-source python gmp git upx 36 | sudo pip install --upgrade fte pyptlib obfsproxy twisted pyinstaller 37 | ``` 38 | 39 | Then, clone and build fteproxy. 40 | ``` 41 | git clone https://github.com/kpdyer/fteproxy.git 42 | cd fteproxy 43 | make 44 | make test 45 | make dist 46 | ``` 47 | 48 | Note: if on OSX 10.9, you may experience a ```clang: warning: argument unused during compilation: '-mno-fused-madd'``` error. This can be resolved by setting the following evnironmental variables: 49 | 50 | ``` 51 | export CFLAGS=-Qunused-arguments 52 | export CPPFLAGS=-Qunused-arguments 53 | ``` 54 | 55 | Windows 56 | ------- 57 | 58 | If you must build fteproxy on Windows, please see [2] for guidance. 59 | 60 | 61 | ### References 62 | 63 | * [1] http://brew.sh/ 64 | * [2] https://github.com/kpdyer/fteproxy-builder/blob/master/build/windows-i386/build_fteproxy.sh 65 | * [3] https://github.com/pyinstaller/pyinstaller 66 | * [4] https://github.com/kpdyer/fteproxy/issues/124 67 | -------------------------------------------------------------------------------- /fteproxy/network_io.py: -------------------------------------------------------------------------------- 1 | import select 2 | import socket 3 | 4 | 5 | def sendall_to_socket(sock, data): 6 | """Given a socket ``sock`` and ``msg`` does a best effort to send 7 | ``msg`` on ``sock`` as quickly as possible. 8 | """ 9 | 10 | return sock.sendall(data) 11 | 12 | 13 | def recvall_from_socket(sock, 14 | bufsize=2 ** 18, 15 | select_timeout=0.1): 16 | """Give ``sock``, does a best effort to pull data from ``sock``. 17 | By default, fails quickly if ``sock`` is closed or has no data ready. 18 | The return value ``is_alive`` reports if ``sock`` is still alive. 19 | The return value ``retval`` is the data extracted from the socket. 20 | Unlike normal raw sockets, it may be the case that ``retval`` is '', and 21 | ``is_alive`` is ``true``. 22 | """ 23 | 24 | retval = '' 25 | is_alive = False 26 | 27 | try: 28 | ready = select.select([sock], [], [sock], select_timeout) 29 | if ready[0]: 30 | _data = sock.recv(bufsize) 31 | if _data: 32 | retval += _data 33 | is_alive = True 34 | else: 35 | is_alive = False 36 | else: 37 | # select.timeout 38 | is_alive = True 39 | except socket.timeout: 40 | is_alive = True 41 | except socket.error: 42 | is_alive = (len(retval) > 0) 43 | except select.error: 44 | is_alive = (len(retval) > 0) 45 | finally: 46 | if retval: is_alive = True 47 | 48 | return [is_alive, retval] 49 | 50 | 51 | def close_socket(sock, lock=None): 52 | """Given socket ``sock`` closes the socket for reading and writing. 53 | If the optional ``lock`` parameter is provided, protects all accesses 54 | to ``sock`` with ``lock``. 55 | """ 56 | 57 | try: 58 | if lock is not None: 59 | with lock: 60 | sock.close() 61 | else: 62 | sock.close() 63 | except: 64 | pass 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | fteproxy 2 | ======== 3 | 4 | [![Build Status](https://travis-ci.org/kpdyer/fteproxy.svg?branch=master)](https://travis-ci.org/kpdyer/fteproxy) 5 | 6 | * homepage: https://fteproxy.org 7 | * source code: https://github.com/kpdyer/fteproxy 8 | * publication: https://kpdyer.com/publications/ccs2013-fte.pdf 9 | 10 | Overview 11 | -------- 12 | 13 | fteproxy provides transport-layer protection to resist keyword filtering, censorship and discrimantory routing policies. 14 | Its job is to relay datastreams, such as web browsing traffic, by encoding the stream into messages that satisfy a user-specified regular expression. 15 | 16 | fteproxy is powered by Format-Transforming Encryption [1] and was presented at CCS 2013. 17 | 18 | [1] [Protocol Misidentification Made Easy with Format-Transforming Encryption](https://kpdyer.com/publications/ccs2013-fte.pdf), Kevin P. Dyer, Scott E. Coull, Thomas Ristenpart and Thomas Shrimpton 19 | 20 | Quick Start 21 | ----------- 22 | 23 | On Linux/OSX, use pip to install fteproxy. 24 | 25 | ```console 26 | pip install fteproxy 27 | ``` 28 | 29 | On Windows, download pre-compiled binaries, located at: https://fteproxy.org/download 30 | 31 | Dependencies 32 | -------- 33 | 34 | Dependencies for building from source: 35 | * Python 2.7.x: https://python.org/ 36 | * fte 0.0.x: https://pypi.python.org/pypi/fte 37 | * pyptlib 0.0.x: https://gitweb.torproject.org/pluggable-transports/pyptlib.git 38 | * obfsproxy 0.2.x: https://gitweb.torproject.org/pluggable-transports/obfsproxy.git 39 | * Twisted 13.2.x: https://twistedmatrix.com/ 40 | 41 | Running 42 | ------- 43 | 44 | For platform-specific examples of how to install dependencies see BUILDING.md. 45 | 46 | There is nothing to build for fteproxy --- it is Python-only project. To run fteproxy, you need to do only the following. 47 | 48 | ``` 49 | git clone https://github.com/kpdyer/fteproxy.git 50 | cd fteproxy 51 | ./bin/fteproxy 52 | ``` 53 | 54 | Documentation 55 | ------------- 56 | 57 | See: https://fteproxy.org/documentation 58 | 59 | Author 60 | ------ 61 | 62 | Please contact Kevin P. Dyer (kpdyer@gmail.com), if you have any questions. 63 | -------------------------------------------------------------------------------- /doc/source/client.rst: -------------------------------------------------------------------------------- 1 | :mod:`fteproxy.client` Module 2 | ***************************** 3 | 4 | Overview 5 | -------- 6 | It's the responsibility of the client listener to broker incoming/ 7 | outgoing connections on the client-side of an fteproxy setup. 8 | Incoming connections are not encapsulated by FTE, as they 9 | are from a client-side application, such as Firefox. Outgoing connections 10 | will be destined to an fteproxy server. They will be encapsulated by fteproxy. 11 | 12 | The ``fteproxy.client.listener`` class extends ``fteproxy.relay.listener``. 13 | See ``fteproxy.relay.listener`` for more information, which is also the base class 14 | for ``fteproxy.server.listener``. The ``fteproxy.relay.listener`` class extends 15 | ``threading.Thread``, hence we invoke the fteproxy client via fteproxy.listener.client.start(). 16 | 17 | 18 | Interface 19 | --------- 20 | 21 | .. automodule:: fteproxy.client 22 | :members: 23 | :undoc-members: 24 | :show-inheritance: 25 | 26 | Examples 27 | -------- 28 | 29 | Start the fteproxy client with default configuration parameters. 30 | 31 | .. code-block:: python 32 | 33 | import fteproxy.client.listener 34 | 35 | client = fteproxy.client.listener() 36 | client.start() 37 | client.join(10) # run for 10 seconds 38 | client.stop() 39 | 40 | Start the fteproxy client listening on client-side port ``127.0.0.1:8888``. 41 | 42 | .. code-block:: python 43 | 44 | import fteproxy.conf 45 | import fteproxy.client.listener 46 | 47 | fteproxy.conf.setValue('runtime.client.ip', '127.0.0.1') 48 | fteproxy.conf.setValue('runtime.client.port', 8888) 49 | 50 | client = fteproxy.client.listener() 51 | client.start() 52 | client.join(10) # run for 10 seconds 53 | client.stop() 54 | 55 | Start the fteproxy client and connect to remote server ``myfteserver:80``. 56 | 57 | .. code-block:: python 58 | 59 | import fteproxy.conf 60 | import fteproxy.client.listener 61 | 62 | fteproxy.conf.setValue('runtime.server.ip', 'myfteserver') 63 | fteproxy.conf.setValue('runtime.server.port', 80) 64 | 65 | client = fteproxy.client.listener() 66 | client.start() 67 | client.join(10) # run for 10 seconds 68 | client.stop() 69 | -------------------------------------------------------------------------------- /doc/source/__init__.rst: -------------------------------------------------------------------------------- 1 | ``fteproxy.wrap_socket`` 2 | ************************ 3 | 4 | 5 | Overview 6 | -------- 7 | 8 | The ``fteproxy.wrap_socket`` function is useful for rapidly integrating FTE 9 | into existing applications. The interface is inspired by ``ssl.wrap_socket`` [1]. 10 | 11 | .. [1] http://docs.python.org/2/library/ssl.html 12 | 13 | Interface 14 | --------- 15 | 16 | .. autofunction:: fteproxy.wrap_socket 17 | 18 | Example 19 | ------- 20 | 21 | An example FTE-powered client-server chat application. 22 | The highlighted lines are the only lines introduced to the example chat 23 | application at for Python's socket module [2]. 24 | 25 | .. [2] http://docs.python.org/2/library/socket.html 26 | 27 | .. code-block:: python 28 | :emphasize-lines: 3,5,6,11,12,13,14,15 29 | 30 | # FTE-Powered echo server program 31 | import socket 32 | import fteproxy 33 | 34 | client_server_regex = '^(0|1)+$' 35 | server_client_regex = '^(A|B)+$' 36 | 37 | HOST = '' # Symbolic name meaning all available interfaces 38 | PORT = 50007 # Arbitrary non-privileged port 39 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 40 | s = fteproxy.wrap_socket(s, 41 | outgoing_regex=server_client_regex, 42 | outgoing_fixed_slice=256, 43 | incoming_regex=client_server_regex, 44 | incoming_fixed_slice=256) 45 | s.bind((HOST, PORT)) 46 | s.listen(1) 47 | conn, addr = s.accept() 48 | print 'Connected by', addr 49 | while 1: 50 | data = conn.recv(1024) 51 | if not data: break 52 | conn.sendall(data) 53 | conn.close() 54 | 55 | .. code-block:: python 56 | :emphasize-lines: 3,5,6,11,12,13,14,15 57 | 58 | # FTE-Powered echo client program 59 | import socket 60 | import fteproxy 61 | 62 | client_server_regex = '^(0|1)+$' 63 | server_client_regex = '^(A|B)+$' 64 | 65 | HOST = '127.0.0.1' # The remote host 66 | PORT = 50007 # The same port as used by the server 67 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 68 | s = fteproxy.wrap_socket(s, 69 | outgoing_regex=client_server_regex, 70 | outgoing_fixed_slice=256, 71 | incoming_regex=server_client_regex, 72 | incoming_fixed_slice=256) 73 | s.connect((HOST, PORT)) 74 | s.sendall('Hello, world') 75 | data = s.recv(1024) 76 | s.close() 77 | print 'Received', repr(data) 78 | -------------------------------------------------------------------------------- /fteproxy/record_layer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | 6 | import fte.encoder 7 | 8 | import fteproxy.conf 9 | 10 | 11 | MAX_CELL_SIZE = fteproxy.conf.getValue('runtime.fteproxy.record_layer.max_cell_size') 12 | 13 | 14 | class Encoder: 15 | 16 | def __init__( 17 | self, 18 | encoder, 19 | ): 20 | self._encoder = encoder 21 | self._buffer = '' 22 | 23 | def push(self, data): 24 | """Push data onto the FIFO buffer.""" 25 | 26 | self._buffer += data 27 | 28 | def pop(self): 29 | """Pop data off the FIFO buffer. We pop at most 30 | ``runtime.fteproxy.record_layer.max_cell_size`` 31 | bytes. The returned value is encrypted and encoded 32 | with ``encoder`` specified in ``__init__``. 33 | """ 34 | retval = '' 35 | 36 | while len(self._buffer) > 0: 37 | plaintext = self._buffer[:MAX_CELL_SIZE] 38 | covertext = self._encoder.encode(plaintext) 39 | self._buffer = self._buffer[MAX_CELL_SIZE:] 40 | retval += covertext 41 | 42 | return retval 43 | 44 | 45 | class Decoder: 46 | 47 | def __init__( 48 | self, 49 | decoder, 50 | ): 51 | self._decoder = decoder 52 | self._buffer = '' 53 | 54 | def push(self, data): 55 | """Push data onto the FIFO buffer.""" 56 | 57 | self._buffer += data 58 | 59 | def pop(self, oneCell=False): 60 | """Pop data off the FIFO buffer. 61 | The returned value is decoded with ``_decoder`` then decrypted 62 | with ``_decrypter`` specified in ``__init__``. 63 | """ 64 | 65 | retval = '' 66 | 67 | while len(self._buffer) > 0: 68 | try: 69 | msg, buffer = self._decoder.decode(self._buffer) 70 | retval += msg 71 | self._buffer = buffer 72 | except fte.encoder.DecodeFailureError as e: 73 | fteproxy.info("fteproxy.encoder.DecodeFailure: "+str(e)) 74 | break 75 | except fte.encrypter.RecoverableDecryptionError as e: 76 | fteproxy.info("fteproxy.encrypter.RecoverableDecryptionError: "+str(e)) 77 | break 78 | except fte.encrypter.UnrecoverableDecryptionError as e: 79 | fteproxy.fatal_error("fteproxy.encrypter.UnrecoverableDecryptionError: "+str(e)) 80 | # exit 81 | except Exception as e: 82 | fteproxy.warn("fteproxy.record_layer exception: "+str(e)) 83 | break 84 | finally: 85 | if oneCell: 86 | break 87 | 88 | return retval 89 | -------------------------------------------------------------------------------- /fteproxy/tests/test_record_layer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | 6 | import unittest 7 | 8 | import fteproxy.record_layer 9 | import fteproxy.regex2dfa 10 | 11 | import fte.encoder 12 | 13 | START = 0 14 | ITERATIONS = 2048 15 | STEP = 64 16 | 17 | 18 | class Tests(unittest.TestCase): 19 | 20 | @classmethod 21 | def setUp(self): 22 | fteproxy.conf.setValue('runtime.mode', 'client') 23 | self.record_layers_info = [] 24 | self.record_layers_outgoing = [] 25 | self.record_layers_incoming = [] 26 | definitions = fteproxy.defs.load_definitions() 27 | for language in definitions.keys(): 28 | regex = fteproxy.defs.getRegex(language) 29 | fixed_slice = fteproxy.defs.getFixedSlice(language) 30 | regex_encoder = fte.encoder.DfaEncoder(fteproxy.regex2dfa.regex2dfa(regex), fixed_slice) 31 | encoder = fteproxy.record_layer.Encoder( 32 | encoder=regex_encoder) 33 | decoder = fteproxy.record_layer.Decoder( 34 | decoder=regex_encoder) 35 | self.record_layers_info.append(language) 36 | self.record_layers_outgoing.append(encoder) 37 | self.record_layers_incoming.append(decoder) 38 | 39 | def testReclayer_basic(self): 40 | for i in range(len(self.record_layers_outgoing)): 41 | record_layer_outgoing = self.record_layers_outgoing[i] 42 | record_layer_incoming = self.record_layers_incoming[i] 43 | for j in range(START, ITERATIONS, STEP): 44 | P = 'X' * j + 'Y' 45 | record_layer_outgoing.push(P) 46 | while True: 47 | data = record_layer_outgoing.pop() 48 | if not data: 49 | break 50 | record_layer_incoming.push(data) 51 | Y = '' 52 | while True: 53 | data = record_layer_incoming.pop() 54 | if not data: 55 | break 56 | Y += data 57 | self.assertEquals(P, Y, (self.record_layers_info[i], 58 | P, Y)) 59 | 60 | def testReclayer_concat(self): 61 | for i in range(len(self.record_layers_outgoing)): 62 | record_layer_outgoing = self.record_layers_outgoing[i] 63 | record_layer_incoming = self.record_layers_incoming[i] 64 | for j in range(START, ITERATIONS, STEP): 65 | ptxt = '' 66 | X = '' 67 | P = 'X' * j + 'Y' 68 | ptxt += P 69 | record_layer_outgoing.push(P) 70 | while True: 71 | data = record_layer_outgoing.pop() 72 | if not data: 73 | break 74 | X += data 75 | record_layer_incoming.push(X) 76 | Y = '' 77 | while True: 78 | data = record_layer_incoming.pop() 79 | if not data: 80 | break 81 | Y += data 82 | self.assertEquals(ptxt, Y, self.record_layers_info[i]) 83 | 84 | 85 | def suite(): 86 | loader = unittest.TestLoader() 87 | suite = unittest.TestSuite() 88 | suite.addTest(loader.loadTestsFromTestCase(Tests)) 89 | return suite 90 | -------------------------------------------------------------------------------- /fteproxy/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | 6 | import os 7 | import sys 8 | import tempfile 9 | 10 | 11 | def getValue(key): 12 | return conf[key] 13 | 14 | 15 | def setValue(key, value): 16 | conf[key] = value 17 | 18 | 19 | def we_are_frozen(): 20 | # All of the modules are built-in to the interpreter, e.g., by py2exe 21 | return hasattr(sys, "frozen") 22 | 23 | 24 | def module_path(): 25 | if we_are_frozen(): 26 | return os.path.dirname(sys.executable) 27 | else: 28 | return os.path.dirname(__file__) 29 | 30 | 31 | conf = {} 32 | 33 | 34 | """The base path for the location of the fteproxy.* modules.""" 35 | if we_are_frozen(): 36 | conf['general.base_dir'] = module_path() 37 | else: 38 | conf['general.base_dir'] = os.path.join(module_path(), '..') 39 | 40 | 41 | """Directory containing binary executables""" 42 | if we_are_frozen(): 43 | conf['general.bin_dir'] = os.path.join(module_path()) 44 | else: 45 | conf['general.bin_dir'] = os.path.join(module_path(), '..', 'bin') 46 | 47 | 48 | """The path for fte *.json definition files.""" 49 | if we_are_frozen(): 50 | conf['general.defs_dir'] = os.path.join(module_path(), 'fteproxy', 'defs') 51 | else: 52 | conf['general.defs_dir'] = os.path.join(module_path(), '..', 'fteproxy', 'defs') 53 | 54 | 55 | """The location that we store *.pid files, such that we can kill fteproxy from the command line.""" 56 | conf['general.pid_dir'] = tempfile.gettempdir() 57 | 58 | 59 | """Our runtime mode: client|server|test""" 60 | conf['runtime.mode'] = None 61 | 62 | 63 | """Our loglevel = 0|1|2|3""" 64 | conf['runtime.loglevel'] = 3 65 | 66 | 67 | """The maximum number of queued connections for sockets""" 68 | conf['runtime.fteproxy.relay.backlog'] = 100 69 | 70 | 71 | """Our client-side ip:port to listen for incoming connections""" 72 | conf['runtime.client.ip'] = '127.0.0.1' 73 | conf['runtime.client.port'] = 8079 74 | 75 | 76 | """Our server-side ip:port to listen for connections from fteproxy clients""" 77 | conf['runtime.server.ip'] = '127.0.0.1' 78 | conf['runtime.server.port'] = 8080 79 | 80 | 81 | """Our proxy server, where the fteproxy server forwards outgoing connections.""" 82 | conf['runtime.proxy.ip'] = '127.0.0.1' 83 | conf['runtime.proxy.port'] = 8081 84 | 85 | 86 | """The default socket timeout.""" 87 | conf['runtime.fteproxy.relay.socket_timeout'] = 30 88 | 89 | 90 | """The default socket accept timeout.""" 91 | conf['runtime.fteproxy.relay.accept_timeout'] = 0.1 92 | 93 | 94 | """The default penalty after polling for network data, and not recieving anything.""" 95 | conf['runtime.fteproxy.relay.throttle'] = 0.01 96 | 97 | 98 | """The default timeout when establishing a new fteproxy socket.""" 99 | conf['runtime.fteproxy.negotiate.timeout'] = 5 100 | 101 | 102 | """The maximum number of bytes to segment for an outgoing message.""" 103 | conf['runtime.fteproxy.record_layer.max_cell_size'] = 2 ** 15 104 | 105 | 106 | """The default client-to-server language.""" 107 | conf['runtime.state.upstream_language'] = 'manual-http-request' 108 | 109 | 110 | """The default server-to-client language.""" 111 | conf['runtime.state.downstream_language'] = 'manual-http-response' 112 | 113 | 114 | """The default AE scheme key.""" 115 | conf['runtime.fteproxy.encrypter.key'] = '\xFF' * 16 + '\x00' * 16 116 | 117 | 118 | """The default fixed_slice parameter to use for buildTable.""" 119 | conf['fteproxy.default_fixed_slice'] = 2 ** 8 120 | 121 | 122 | """The default definitions file to use.""" 123 | conf['fteproxy.defs.release'] = '20131224' 124 | -------------------------------------------------------------------------------- /doc/source/fteproxy.rst: -------------------------------------------------------------------------------- 1 | ``fteproxy`` 2 | ************ 3 | 4 | The ``fteproxy`` command-line application is used to start an fteproxy client or 5 | server. 6 | 7 | Parameters 8 | ---------- 9 | 10 | The ``fteproxy`` command-line application usage: 11 | 12 | .. code-block:: none 13 | 14 | usage: fteproxy [-h] [--version] [--mode (client|server|test)] [--stop] 15 | [--upstream-format UPSTREAM_FORMAT] 16 | [--downstream-format DOWNSTREAM_FORMAT] 17 | [--client_ip CLIENT_IP] [--client_port CLIENT_PORT] 18 | [--server_ip SERVER_IP] [--server_port SERVER_PORT] 19 | [--proxy_ip PROXY_IP] [--proxy_port PROXY_PORT] [--quiet] 20 | [--release RELEASE] [--managed] [--key KEY] 21 | 22 | optional arguments: 23 | -h, --help show this help message and exit 24 | --version Output the version of fteproxy, then quit. (default: 25 | False) 26 | --mode (client|server|test) 27 | Relay mode: client or server (default: client) 28 | --stop Shutdown daemon process (default: False) 29 | --upstream-format UPSTREAM_FORMAT 30 | Client-to-server language format (default: manual- 31 | http-request) 32 | --downstream-format DOWNSTREAM_FORMAT 33 | Server-to-client language format (default: manual- 34 | http-response) 35 | --client_ip CLIENT_IP 36 | Client-side listening IP (default: 127.0.0.1) 37 | --client_port CLIENT_PORT 38 | Client-side listening port (default: 8079) 39 | --server_ip SERVER_IP 40 | Server-side listening IP (default: 127.0.0.1) 41 | --server_port SERVER_PORT 42 | Server-side listening port (default: 8080) 43 | --proxy_ip PROXY_IP Forwarding-proxy (SOCKS) listening IP (default: 44 | 127.0.0.1) 45 | --proxy_port PROXY_PORT 46 | Forwarding-proxy (SOCKS) listening port (default: 47 | 8081) 48 | --quiet Be completely silent. Print nothing. (default: False) 49 | --release RELEASE Definitions file to use, specified as YYYYMMDD 50 | (default: 20131224) 51 | --managed Start in pluggable transport managed mode, for use 52 | with Tor. (default: False) 53 | --key KEY Cryptographic key, hex, must be exactly 64 characters 54 | (default: FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000000 55 | 00000000000000000000) 56 | 57 | Example Usage 58 | ------------- 59 | 60 | Starting the ``fteproxy`` client with the default configuration parameters. 61 | 62 | .. code-block:: none 63 | 64 | fteproxy --mode client 65 | 66 | Starting the ``fteproxy`` client, binding to ``127.0.0.1:8888``. 67 | 68 | .. code-block:: none 69 | 70 | fteproxy --mode client --client_ip 127.0.0.1 --client_port 8888 71 | 72 | Starting the ``fteproxy`` client, using ``XXX`` for upstream communications 73 | and ``YYY`` for downstream communications. 74 | 75 | .. code-block:: none 76 | 77 | fteproxy --mode client --upstream-format XXX --downstream-format YYY 78 | 79 | Starting the ``fteproxy`` server, binding to port ``8888`` on all interfaces. 80 | 81 | .. code-block:: none 82 | 83 | fteproxy --mode server --client_ip 0.0.0.0 --client_port 8888 84 | 85 | Start ``fteproxy``, execute all unit tests, then exit. 86 | 87 | .. code-block:: none 88 | 89 | fteproxy --mode test 90 | -------------------------------------------------------------------------------- /fteproxy/tests/test_relay.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | 6 | 7 | import time 8 | import socket 9 | import random 10 | import unittest 11 | 12 | import fteproxy.network_io 13 | import fteproxy.relay 14 | import fteproxy.client 15 | import fteproxy.server 16 | 17 | LOCAL_INTERFACE = '127.0.0.1' 18 | 19 | 20 | class Tests(unittest.TestCase): 21 | 22 | def setUp(self): 23 | time.sleep(1) 24 | self._server = fteproxy.server.listener(LOCAL_INTERFACE, 25 | fteproxy.conf.getValue( 26 | 'runtime.server.port'), 27 | LOCAL_INTERFACE, 28 | fteproxy.conf.getValue('runtime.proxy.port')) 29 | self._client = fteproxy.client.listener(LOCAL_INTERFACE, 30 | fteproxy.conf.getValue( 31 | 'runtime.client.port'), 32 | LOCAL_INTERFACE, 33 | fteproxy.conf.getValue('runtime.server.port')) 34 | 35 | self._server.start() 36 | self._client.start() 37 | 38 | time.sleep(1) 39 | 40 | def tearDown(self): 41 | self._server.stop() 42 | self._client.stop() 43 | 44 | def testTenSerialStreams(self): 45 | for i in range(10): 46 | self._testStream() 47 | 48 | def _testStream(self): 49 | uniq_id = str(random.choice(range(2 ** 10))) 50 | expected_msg = 'Hello, world' * 100 + uniq_id 51 | actual_msg = '' 52 | 53 | proxy_socket = None 54 | client_socket = None 55 | server_conn = None 56 | try: 57 | proxy_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 58 | proxy_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 59 | proxy_socket.bind( 60 | (LOCAL_INTERFACE, fteproxy.conf.getValue('runtime.proxy.port'))) 61 | proxy_socket.listen(fteproxy.conf.getValue('runtime.fteproxy.relay.backlog')) 62 | 63 | client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 64 | client_socket.connect((LOCAL_INTERFACE, 65 | fteproxy.conf.getValue('runtime.client.port'))) 66 | 67 | server_conn, addr = proxy_socket.accept() 68 | server_conn.settimeout(1) 69 | 70 | client_socket.sendall(expected_msg) 71 | while True: 72 | try: 73 | data = server_conn.recv(1024) 74 | if not data: 75 | break 76 | actual_msg += data 77 | assert expected_msg.startswith(actual_msg) 78 | if actual_msg == expected_msg: 79 | break 80 | except socket.timeout: 81 | continue 82 | except socket.error: 83 | break 84 | except Exception as e: 85 | fteproxy.fatal_error("failed to transmit data: " + str(e)) 86 | finally: 87 | if proxy_socket: 88 | fteproxy.network_io.close_socket(proxy_socket) 89 | if server_conn: 90 | fteproxy.network_io.close_socket(server_conn) 91 | if client_socket: 92 | fteproxy.network_io.close_socket(client_socket) 93 | 94 | self.assertEquals(expected_msg, actual_msg) 95 | 96 | 97 | def suite(): 98 | loader = unittest.TestLoader() 99 | suite = unittest.TestSuite() 100 | suite.addTest(loader.loadTestsFromTestCase(Tests)) 101 | return suite 102 | -------------------------------------------------------------------------------- /doc/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source 10 | set I18NSPHINXOPTS=%SPHINXOPTS% source 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. linkcheck to check all external links for integrity 37 | echo. doctest to run all doctests embedded in the documentation if enabled 38 | goto end 39 | ) 40 | 41 | if "%1" == "clean" ( 42 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 43 | del /q /s %BUILDDIR%\* 44 | goto end 45 | ) 46 | 47 | if "%1" == "html" ( 48 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 49 | if errorlevel 1 exit /b 1 50 | echo. 51 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 52 | goto end 53 | ) 54 | 55 | if "%1" == "dirhtml" ( 56 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 57 | if errorlevel 1 exit /b 1 58 | echo. 59 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 60 | goto end 61 | ) 62 | 63 | if "%1" == "singlehtml" ( 64 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 68 | goto end 69 | ) 70 | 71 | if "%1" == "pickle" ( 72 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished; now you can process the pickle files. 76 | goto end 77 | ) 78 | 79 | if "%1" == "json" ( 80 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished; now you can process the JSON files. 84 | goto end 85 | ) 86 | 87 | if "%1" == "htmlhelp" ( 88 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can run HTML Help Workshop with the ^ 92 | .hhp project file in %BUILDDIR%/htmlhelp. 93 | goto end 94 | ) 95 | 96 | if "%1" == "qthelp" ( 97 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 98 | if errorlevel 1 exit /b 1 99 | echo. 100 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 101 | .qhcp project file in %BUILDDIR%/qthelp, like this: 102 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Format-TransformingEncryptionfteproxy.qhcp 103 | echo.To view the help file: 104 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Format-TransformingEncryptionfteproxy.ghc 105 | goto end 106 | ) 107 | 108 | if "%1" == "devhelp" ( 109 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 110 | if errorlevel 1 exit /b 1 111 | echo. 112 | echo.Build finished. 113 | goto end 114 | ) 115 | 116 | if "%1" == "epub" ( 117 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 118 | if errorlevel 1 exit /b 1 119 | echo. 120 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 121 | goto end 122 | ) 123 | 124 | if "%1" == "latex" ( 125 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 129 | goto end 130 | ) 131 | 132 | if "%1" == "text" ( 133 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The text files are in %BUILDDIR%/text. 137 | goto end 138 | ) 139 | 140 | if "%1" == "man" ( 141 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 145 | goto end 146 | ) 147 | 148 | if "%1" == "texinfo" ( 149 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 150 | if errorlevel 1 exit /b 1 151 | echo. 152 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 153 | goto end 154 | ) 155 | 156 | if "%1" == "gettext" ( 157 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 158 | if errorlevel 1 exit /b 1 159 | echo. 160 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 161 | goto end 162 | ) 163 | 164 | if "%1" == "changes" ( 165 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 166 | if errorlevel 1 exit /b 1 167 | echo. 168 | echo.The overview file is in %BUILDDIR%/changes. 169 | goto end 170 | ) 171 | 172 | if "%1" == "linkcheck" ( 173 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 174 | if errorlevel 1 exit /b 1 175 | echo. 176 | echo.Link check complete; look for any errors in the above output ^ 177 | or in %BUILDDIR%/linkcheck/output.txt. 178 | goto end 179 | ) 180 | 181 | if "%1" == "doctest" ( 182 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 183 | if errorlevel 1 exit /b 1 184 | echo. 185 | echo.Testing of doctests in the sources finished, look at the ^ 186 | results in %BUILDDIR%/doctest/output.txt. 187 | goto end 188 | ) 189 | 190 | :end 191 | -------------------------------------------------------------------------------- /fteproxy/relay.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | 6 | import time 7 | import socket 8 | import threading 9 | 10 | import fteproxy.conf 11 | import fteproxy.network_io 12 | 13 | 14 | class worker(threading.Thread): 15 | 16 | """``fteproxy.relay.worker`` is responsible for relaying data between two sockets. Given ``socket1`` and 17 | ``socket2``, the worker will forward all data 18 | from ``socket1`` to ``socket2``, and ``socket2`` to ``socket1``. This class is a subclass of 19 | threading.Thread and does not start relaying until start() is called. The run 20 | method terminates when either ``socket1`` or ``socket2`` is detected to be closed. 21 | """ 22 | 23 | def __init__(self, socket1, socket2): 24 | threading.Thread.__init__(self) 25 | self._socket1 = socket1 26 | self._socket2 = socket2 27 | self._running = False 28 | 29 | def run(self): 30 | """It's the responsibility of run to forward data from ``socket1`` to 31 | ``socket2`` and from ``socket2`` to ``socket1``. The ``run()`` method 32 | terminates and closes both sockets if ``fteproxy.network_io.recvall_from_socket`` 33 | returns a negative result for ``success``. 34 | """ 35 | 36 | self._running = True 37 | try: 38 | throttle = fteproxy.conf.getValue('runtime.fteproxy.relay.throttle') 39 | while self._running: 40 | [success, _data] = fteproxy.network_io.recvall_from_socket( 41 | self._socket1) 42 | if not success: 43 | break 44 | if _data: 45 | fteproxy.network_io.sendall_to_socket(self._socket2, _data) 46 | else: 47 | time.sleep(throttle) 48 | except Exception as e: 49 | fteproxy.warn("fteproxy.worker terminated prematurely: " + str(e)) 50 | finally: 51 | fteproxy.network_io.close_socket(self._socket1) 52 | fteproxy.network_io.close_socket(self._socket2) 53 | 54 | def stop(self): 55 | """Terminate the thread and stop listening on ``local_ip:local_port``. 56 | """ 57 | self._running = False 58 | 59 | 60 | class listener(threading.Thread): 61 | 62 | """It's the responsibility of ``fteproxy.relay.listener`` to bind to 63 | ``local_ip:local_port``. Once bound it will then relay all incoming connections 64 | to ``remote_ip:remote_port``. 65 | All new incoming connections are wrapped with ``onNewIncomingConnection``. 66 | All new outgoing connections are wrapped with ``onNewOutgoingConnection``. 67 | By default the functions ``onNewIncomingConnection`` and 68 | ``onNewOutgoingConnection`` are the identity function. 69 | """ 70 | 71 | def __init__(self, local_ip, local_port, 72 | remote_ip, remote_port): 73 | threading.Thread.__init__(self) 74 | 75 | self._running = False 76 | self._local_ip = local_ip 77 | self._local_port = local_port 78 | self._remote_ip = remote_ip 79 | self._remote_port = remote_port 80 | 81 | def _instantiateSocket(self): 82 | try: 83 | self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 84 | self._sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 85 | self._sock.bind((self._local_ip, self._local_port)) 86 | self._sock.listen(fteproxy.conf.getValue('runtime.fteproxy.relay.backlog')) 87 | self._sock.settimeout( 88 | fteproxy.conf.getValue('runtime.fteproxy.relay.accept_timeout')) 89 | except Exception as e: 90 | fteproxy.fatal_error('Failed to bind to ' + 91 | str((self._local_ip, self._local_port)) + ': ' + str(e)) 92 | 93 | def run(self): 94 | """Bind to ``local_ip:local_port`` and forward all connections to 95 | ``remote_ip:remote_port``. 96 | """ 97 | self._instantiateSocket() 98 | 99 | self._running = True 100 | while self._running: 101 | try: 102 | conn, addr = self._sock.accept() 103 | 104 | new_stream = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 105 | new_stream.connect((self._remote_ip, self._remote_port)) 106 | 107 | conn = self.onNewIncomingConnection(conn) 108 | new_stream = self.onNewOutgoingConnection(new_stream) 109 | 110 | conn.settimeout( 111 | fteproxy.conf.getValue('runtime.fteproxy.relay.socket_timeout')) 112 | new_stream.settimeout( 113 | fteproxy.conf.getValue('runtime.fteproxy.relay.socket_timeout')) 114 | 115 | w1 = worker(conn, new_stream) 116 | w2 = worker(new_stream, conn) 117 | w1.start() 118 | w2.start() 119 | except socket.timeout: 120 | continue 121 | except socket.error as e: 122 | fteproxy.warn('socket.error in fteproxy.listener: ' + str(e)) 123 | continue 124 | except Exception as e: 125 | fteproxy.warn('exception in fteproxy.listener: ' + str(e)) 126 | break 127 | 128 | def stop(self): 129 | """Terminate the thread and stop listening on ``local_ip:local_port``. 130 | """ 131 | self._running = False 132 | fteproxy.network_io.close_socket(self._sock) 133 | 134 | def onNewIncomingConnection(self, socket): 135 | """``onNewIncomingConnection`` returns the socket unmodified, by default we do not need to 136 | perform any modifications to incoming data streams. 137 | """ 138 | 139 | return socket 140 | 141 | def onNewOutgoingConnection(self, socket): 142 | """``onNewOutgoingConnection`` returns the socket unmodified, by default we do not need to 143 | perform any modifications to incoming data streams. 144 | """ 145 | 146 | return socket 147 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 16 | 17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 18 | 19 | help: 20 | @echo "Please use \`make ' where is one of" 21 | @echo " html to make standalone HTML files" 22 | @echo " dirhtml to make HTML files named index.html in directories" 23 | @echo " singlehtml to make a single large HTML file" 24 | @echo " pickle to make pickle files" 25 | @echo " json to make JSON files" 26 | @echo " htmlhelp to make HTML files and a HTML help project" 27 | @echo " qthelp to make HTML files and a qthelp project" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 31 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 32 | @echo " text to make text files" 33 | @echo " man to make manual pages" 34 | @echo " texinfo to make Texinfo files" 35 | @echo " info to make Texinfo files and run them through makeinfo" 36 | @echo " gettext to make PO message catalogs" 37 | @echo " changes to make an overview of all changed/added/deprecated items" 38 | @echo " linkcheck to check all external links for integrity" 39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 40 | 41 | clean: 42 | -rm -rf $(BUILDDIR)/* 43 | 44 | html: 45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 48 | 49 | dirhtml: 50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 51 | @echo 52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 53 | 54 | singlehtml: 55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 56 | @echo 57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 58 | 59 | pickle: 60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 61 | @echo 62 | @echo "Build finished; now you can process the pickle files." 63 | 64 | json: 65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 66 | @echo 67 | @echo "Build finished; now you can process the JSON files." 68 | 69 | htmlhelp: 70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 71 | @echo 72 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 73 | ".hhp project file in $(BUILDDIR)/htmlhelp." 74 | 75 | qthelp: 76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 77 | @echo 78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Format-TransformingEncryptionfteproxy.qhcp" 81 | @echo "To view the help file:" 82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Format-TransformingEncryptionfteproxy.qhc" 83 | 84 | devhelp: 85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 86 | @echo 87 | @echo "Build finished." 88 | @echo "To view the help file:" 89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/Format-TransformingEncryptionFTE" 90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Format-TransformingEncryptionFTE" 91 | @echo "# devhelp" 92 | 93 | epub: 94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 95 | @echo 96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 97 | 98 | latex: 99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 100 | @echo 101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 103 | "(use \`make latexpdf' here to do that automatically)." 104 | 105 | latexpdf: 106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 107 | @echo "Running LaTeX files through pdflatex..." 108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 110 | 111 | text: 112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 113 | @echo 114 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 115 | 116 | man: 117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 118 | @echo 119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 120 | 121 | texinfo: 122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 123 | @echo 124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 125 | @echo "Run \`make' in that directory to run these through makeinfo" \ 126 | "(use \`make info' here to do that automatically)." 127 | 128 | info: 129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 130 | @echo "Running Texinfo files through makeinfo..." 131 | make -C $(BUILDDIR)/texinfo info 132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 133 | 134 | gettext: 135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 136 | @echo 137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 138 | 139 | changes: 140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 141 | @echo 142 | @echo "The overview file is in $(BUILDDIR)/changes." 143 | 144 | linkcheck: 145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 146 | @echo 147 | @echo "Link check complete; look for any errors in the above output " \ 148 | "or in $(BUILDDIR)/linkcheck/output.txt." 149 | 150 | doctest: 151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 152 | @echo "Testing of doctests in the sources finished, look at the " \ 153 | "results in $(BUILDDIR)/doctest/output.txt." 154 | -------------------------------------------------------------------------------- /systemtests: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | 6 | 7 | import os 8 | import time 9 | import socket 10 | import string 11 | import random 12 | 13 | import fteproxy.conf 14 | import fteproxy.defs 15 | import fteproxy.network_io 16 | 17 | 18 | BYTES_TO_SEND = 2 ** 18 19 | TRIALS = 2 ** 8 20 | BLOCK_SIZE = 2 ** 12 21 | TIMEOUT = 120 22 | 23 | BIND_IP = '127.0.0.1' 24 | CLIENT_PORT = 8079 25 | SERVER_PORT = 8080 26 | PROXY_PORT = 8081 27 | 28 | 29 | def executeCmd(cmd): 30 | os.system(cmd) 31 | 32 | 33 | def main(): 34 | """Start an fteproxy server. 35 | Iterate through the FTE formats available to fteproxy. 36 | For each format, spawn a client using that format, connect to the fteproxy server and pump data through the connection. 37 | Verify the data successfully traversed the client and server, upon success output performance statistics. 38 | """ 39 | 40 | def_dir = os.path.join(fteproxy.conf.getValue('general.defs_dir')) 41 | def_file = fteproxy.conf.getValue('fteproxy.defs.release') + '.json' 42 | def_abspath = os.path.normpath(os.path.join(def_dir, def_file)) 43 | 44 | print 'Testing formats in', def_abspath 45 | 46 | start_server() 47 | 48 | languages = fteproxy.defs.load_definitions().keys() 49 | for language in languages: 50 | if language.endswith('request'): 51 | language_name = language[:-8] 52 | elapsed, bytesSent = doTest(language_name) 53 | throughput = ((1.0 * bytesSent / elapsed) / (2 ** 20)) * \ 54 | (2 ** 3) 55 | throughput = round(throughput, 2) 56 | upstream_regex = fteproxy.defs.getRegex(language_name + '-request') 57 | downstream_regex = fteproxy.defs.getRegex(language_name + '-response') 58 | 59 | print ' + SUCCESS, format_name="' + language_name + '"' 60 | print ' - client-server regex: "' + upstream_regex + '"' 61 | print ' - server-client regex: "' + downstream_regex + '"' 62 | print ' - test duration: ' + str(round(elapsed, 2)) + " seconds" 63 | print ' - goodput: ' + str(throughput) + 'Mbps' 64 | 65 | stop_server() 66 | 67 | print "****** ALL TESTS COMPLETE, SUCCESS!!" 68 | 69 | 70 | def random_string(size): 71 | """Return a random alphanumeric string of length ``size``.""" 72 | chars = string.ascii_uppercase + string.digits 73 | return ''.join(random.choice(chars) for x in range(size)) 74 | 75 | 76 | def doTest(language_name): 77 | """Given the ``language_name``, start the fteproxy client and pump 78 | ``BYTES_TO_SEND*TIRALS`` bytes via the fteproxy client and server. 79 | """ 80 | 81 | elapsed = -1 82 | bytesSent = 0 83 | 84 | try: 85 | start_client(language_name) 86 | 87 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 88 | sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 89 | sock.bind((BIND_IP, PROXY_PORT)) 90 | sock.listen(100) 91 | 92 | new_stream = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 93 | new_stream.connect((BIND_IP, CLIENT_PORT)) 94 | 95 | conn, addr = sock.accept() 96 | 97 | conn.settimeout(0.1) 98 | 99 | startTimer = time.time() 100 | expected_msg = random_string(BYTES_TO_SEND) 101 | elapsed = -1 102 | for i in range(TRIALS): 103 | bytesSent += BYTES_TO_SEND 104 | actual_msg = '' 105 | expected_buffer = expected_msg 106 | while True: 107 | if expected_buffer: 108 | bytessent = new_stream.send(expected_buffer[:BLOCK_SIZE]) 109 | expected_buffer = expected_buffer[bytessent:] 110 | else: 111 | break 112 | while True: 113 | time.sleep(0.00001) 114 | [is_alive, data] = fteproxy.network_io.recvall_from_socket(conn) 115 | actual_msg += data 116 | assert expected_msg.startswith(actual_msg) 117 | if (actual_msg == expected_msg): 118 | break 119 | if elapsed > TIMEOUT or not is_alive: 120 | raise Exception( 121 | "!!!!!!!!!!! System tests failed to execute properly") 122 | elapsed = time.time() - startTimer 123 | 124 | fteproxy.network_io.close_socket(conn) 125 | fteproxy.network_io.close_socket(new_stream) 126 | fteproxy.network_io.close_socket(sock) 127 | finally: 128 | stop_client() 129 | 130 | return elapsed, bytesSent 131 | 132 | 133 | def start_client(language_name): 134 | """Start our fteproxy client, block until it's ready.""" 135 | 136 | 137 | executeCmd("./bin/fteproxy --quiet --mode client" 138 | + " --upstream-format " + language_name + "-request" 139 | + " --downstream-format " + language_name + "-response" 140 | + " --client_ip " + BIND_IP 141 | + " --client_port " + str(CLIENT_PORT) 142 | + " --server_ip " + BIND_IP + " --server_port " + str(SERVER_PORT) + " &") 143 | 144 | waitForListener(BIND_IP, CLIENT_PORT) 145 | 146 | 147 | def start_server(): 148 | """Start our fteproxy server, block until it's ready.""" 149 | 150 | executeCmd("./bin/fteproxy --quiet --mode server" 151 | + " --server_ip " + BIND_IP 152 | + " --server_port " + str(SERVER_PORT) 153 | + " --proxy_ip " + BIND_IP + " --proxy_port " + str(PROXY_PORT) + " &") 154 | 155 | waitForListener(BIND_IP, SERVER_PORT) 156 | 157 | 158 | def stop_client(): 159 | """Stop our fteproxy client.""" 160 | 161 | executeCmd("./bin/fteproxy --quiet --mode client --stop") 162 | 163 | time.sleep(1) 164 | 165 | 166 | def stop_server(): 167 | """Stop our fteproxy server.""" 168 | 169 | executeCmd("./bin/fteproxy --quiet --mode server --stop") 170 | 171 | time.sleep(1) 172 | 173 | 174 | class ListenerNotReady(Exception): 175 | pass 176 | 177 | 178 | def waitForListener(ip, port, maxWait=60): 179 | """Block until a there's a process listening on ip:port. 180 | Return ``true`` on success, raise ``ListenerNotReady`` exception otherwise. 181 | """ 182 | 183 | time.sleep(5) 184 | 185 | startTime = time.time() 186 | 187 | success = False 188 | new_stream = None 189 | while not success and (time.time() - startTime) < maxWait: 190 | time.sleep(0.1) 191 | try: 192 | new_stream = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 193 | new_stream.settimeout(0.1) 194 | new_stream.connect((ip, port)) 195 | success = True 196 | except: 197 | continue 198 | finally: 199 | if new_stream: 200 | new_stream.close() 201 | new_stream = None 202 | 203 | if not success: 204 | raise ListenerNotReady() 205 | 206 | time.sleep(1) 207 | 208 | return success 209 | 210 | 211 | if __name__ == '__main__': 212 | main() 213 | -------------------------------------------------------------------------------- /doc/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Format-Transforming Encryption (FTE) documentation build configuration file, created by 4 | # sphinx-quickstart on Wed Oct 23 04:52:22 2013. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | sys.path.append('..') 20 | sys.path.append('../..') 21 | sys.path.append('../../..') 22 | 23 | # -- General configuration ----------------------------------------------- 24 | 25 | # If your documentation needs a minimal Sphinx version, state it here. 26 | #needs_sphinx = '1.0' 27 | 28 | # Add any Sphinx extension module names here, as strings. They can be extensions 29 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 30 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 31 | 'sphinx.ext.intersphinx', 'sphinx.ext.coverage', 'sphinx.ext.pngmath'] 32 | 33 | # Add any paths that contain templates here, relative to this directory. 34 | templates_path = ['_templates'] 35 | 36 | # The suffix of source filenames. 37 | source_suffix = '.rst' 38 | 39 | # The encoding of source files. 40 | #source_encoding = 'utf-8-sig' 41 | 42 | # The master toctree document. 43 | master_doc = 'index' 44 | 45 | # General information about the project. 46 | project = u'fteproxy' 47 | copyright = u'2013, Kevin P. Dyer' 48 | 49 | # The version info for the project you're documenting, acts as replacement for 50 | # |version| and |release|, also used in various other places throughout the 51 | # built documents. 52 | # 53 | # The short X.Y version. 54 | with open('../../fteproxy/VERSION') as fh: 55 | FTEPROXY_RELEASE = fh.read().strip() 56 | version = FTEPROXY_RELEASE 57 | # The full version, including alpha/beta/rc tags. 58 | release = '' 59 | 60 | # The language for content autogenerated by Sphinx. Refer to documentation 61 | # for a list of supported languages. 62 | #language = None 63 | 64 | # There are two options for replacing |today|: either, you set today to some 65 | # non-false value, then it is used: 66 | #today = '' 67 | # Else, today_fmt is used as the format for a strftime call. 68 | #today_fmt = '%B %d, %Y' 69 | 70 | # List of patterns, relative to source directory, that match files and 71 | # directories to ignore when looking for source files. 72 | exclude_patterns = [] 73 | 74 | # The reST default role (used for this markup: `text`) to use for all documents. 75 | #default_role = None 76 | 77 | # If true, '()' will be appended to :func: etc. cross-reference text. 78 | #add_function_parentheses = True 79 | 80 | # If true, the current module name will be prepended to all description 81 | # unit titles (such as .. function::). 82 | #add_module_names = True 83 | 84 | # If true, sectionauthor and moduleauthor directives will be shown in the 85 | # output. They are ignored by default. 86 | #show_authors = False 87 | 88 | # The name of the Pygments (syntax highlighting) style to use. 89 | pygments_style = 'sphinx' 90 | 91 | # A list of ignored prefixes for module index sorting. 92 | #modindex_common_prefix = [] 93 | 94 | 95 | # -- Options for HTML output --------------------------------------------- 96 | 97 | # The theme to use for HTML and HTML Help pages. See the documentation for 98 | # a list of builtin themes. 99 | html_theme = 'default' 100 | 101 | # Theme options are theme-specific and customize the look and feel of a theme 102 | # further. For a list of options available for each theme, see the 103 | # documentation. 104 | #html_theme_options = {} 105 | 106 | # Add any paths that contain custom themes here, relative to this directory. 107 | #html_theme_path = [] 108 | 109 | # The name for this set of Sphinx documents. If None, it defaults to 110 | # " v documentation". 111 | #html_title = None 112 | 113 | # A shorter title for the navigation bar. Default is the same as html_title. 114 | #html_short_title = None 115 | 116 | # The name of an image file (relative to this directory) to place at the top 117 | # of the sidebar. 118 | #html_logo = None 119 | 120 | # The name of an image file (within the static path) to use as favicon of the 121 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 122 | # pixels large. 123 | #html_favicon = None 124 | 125 | # Add any paths that contain custom static files (such as style sheets) here, 126 | # relative to this directory. They are copied after the builtin static files, 127 | # so a file named "default.css" will overwrite the builtin "default.css". 128 | html_static_path = ['_static'] 129 | 130 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 131 | # using the given strftime format. 132 | #html_last_updated_fmt = '%b %d, %Y' 133 | 134 | # If true, SmartyPants will be used to convert quotes and dashes to 135 | # typographically correct entities. 136 | #html_use_smartypants = True 137 | 138 | # Custom sidebar templates, maps document names to template names. 139 | #html_sidebars = {} 140 | 141 | # Additional templates that should be rendered to pages, maps page names to 142 | # template names. 143 | #html_additional_pages = {} 144 | 145 | # If false, no module index is generated. 146 | #html_domain_indices = True 147 | 148 | # If false, no index is generated. 149 | #html_use_index = True 150 | 151 | # If true, the index is split into individual pages for each letter. 152 | #html_split_index = False 153 | 154 | # If true, links to the reST sources are added to the pages. 155 | #html_show_sourcelink = True 156 | 157 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 158 | #html_show_sphinx = True 159 | 160 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 161 | #html_show_copyright = True 162 | 163 | # If true, an OpenSearch description file will be output, and all pages will 164 | # contain a tag referring to it. The value of this option must be the 165 | # base URL from which the finished HTML is served. 166 | #html_use_opensearch = '' 167 | 168 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 169 | #html_file_suffix = None 170 | 171 | # Output file base name for HTML help builder. 172 | htmlhelp_basename = 'Format-TransformingEncryptionFTEdoc' 173 | 174 | 175 | # -- Options for LaTeX output -------------------------------------------- 176 | 177 | latex_elements = { 178 | # The paper size ('letterpaper' or 'a4paper'). 179 | #'papersize': 'letterpaper', 180 | 181 | # The font size ('10pt', '11pt' or '12pt'). 182 | #'pointsize': '10pt', 183 | 184 | # Additional stuff for the LaTeX preamble. 185 | #'preamble': '', 186 | } 187 | 188 | # Grouping the document tree into LaTeX files. List of tuples 189 | # (source start file, target name, title, author, documentclass [howto/manual]). 190 | latex_documents = [ 191 | ('index', 'Format-TransformingEncryptionfteproxy.tex', u'fteproxy Documentation', 192 | u'Kevin P. Dyer', 'manual'), 193 | ] 194 | 195 | # The name of an image file (relative to this directory) to place at the top of 196 | # the title page. 197 | #latex_logo = None 198 | 199 | # For "manual" documents, if this is true, then toplevel headings are parts, 200 | # not chapters. 201 | #latex_use_parts = False 202 | 203 | # If true, show page references after internal links. 204 | #latex_show_pagerefs = False 205 | 206 | # If true, show URL addresses after external links. 207 | #latex_show_urls = False 208 | 209 | # Documents to append as an appendix to all manuals. 210 | #latex_appendices = [] 211 | 212 | # If false, no module index is generated. 213 | #latex_domain_indices = True 214 | 215 | 216 | # -- Options for manual page output -------------------------------------- 217 | 218 | # One entry per manual page. List of tuples 219 | # (source start file, name, description, authors, manual section). 220 | man_pages = [ 221 | ('index', 'format-transformingencryptionfte', u'fteproxy Documentation', 222 | [u'Kevin P. Dyer'], 1) 223 | ] 224 | 225 | # If true, show URL addresses after external links. 226 | #man_show_urls = False 227 | 228 | 229 | # -- Options for Texinfo output ------------------------------------------ 230 | 231 | # Grouping the document tree into Texinfo files. List of tuples 232 | # (source start file, target name, title, author, 233 | # dir menu entry, description, category) 234 | texinfo_documents = [ 235 | ('index', 'Format-TransformingEncryptionFTE', u'fteproxy Documentation', 236 | u'Kevin P. Dyer', 'Format-TransformingEncryptionFTE', 'One line description of project.', 237 | 'Miscellaneous'), 238 | ] 239 | 240 | # Documents to append as an appendix to all manuals. 241 | #texinfo_appendices = [] 242 | 243 | # If false, no module index is generated. 244 | #texinfo_domain_indices = True 245 | 246 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 247 | #texinfo_show_urls = 'footnote' 248 | 249 | 250 | # Example configuration for intersphinx: refer to the Python standard library. 251 | intersphinx_mapping = {'http://docs.python.org/': None} 252 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /fteproxy/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | 6 | import sys 7 | import socket 8 | import string 9 | import traceback 10 | 11 | import fteproxy.conf 12 | import fteproxy.defs 13 | import fteproxy.record_layer 14 | import fteproxy.regex2dfa 15 | 16 | import fte.encoder 17 | 18 | 19 | class InvalidRoleException(Exception): 20 | pass 21 | 22 | 23 | class NegotiationFailedException(Exception): 24 | pass 25 | 26 | 27 | class ChannelNotReadyException(Exception): 28 | pass 29 | 30 | 31 | class NegotiateTimeoutException(Exception): 32 | 33 | """Raised when negotiation fails to complete after """ + str(fteproxy.conf.getValue('runtime.fteproxy.negotiate.timeout')) + """ seconds. 34 | """ 35 | pass 36 | 37 | 38 | def fatal_error(msg): 39 | if fteproxy.conf.getValue('runtime.loglevel') in [1,2,3]: 40 | print 'ERROR:', msg 41 | sys.exit(1) 42 | 43 | 44 | def warn(msg): 45 | if fteproxy.conf.getValue('runtime.loglevel') in [2,3]: 46 | print 'WARN:', msg 47 | 48 | 49 | def info(msg): 50 | if fteproxy.conf.getValue('runtime.loglevel') in [3]: 51 | print 'INFO:', msg 52 | 53 | 54 | class NegotiateCell(object): 55 | _CELL_SIZE = 64 56 | _PADDING_LEN = 32 57 | _PADDING_CHAR = '\x00' 58 | _DATE_FORMAT = 'YYYYMMDD' 59 | 60 | def __init__(self): 61 | self._def_file = "" 62 | self._language = "" 63 | 64 | def setDefFile(self, def_file): 65 | self._def_file = def_file 66 | 67 | def getDefFile(self): 68 | return self._def_file 69 | 70 | def setLanguage(self, language): 71 | self._language = language 72 | 73 | def getLanguage(self): 74 | return self._language 75 | 76 | def toString(self): 77 | retval = '' 78 | retval += self._def_file 79 | retval += self._language 80 | retval = string.rjust( 81 | retval, NegotiateCell._CELL_SIZE, NegotiateCell._PADDING_CHAR) 82 | assert retval[:NegotiateCell._PADDING_LEN] == NegotiateCell._PADDING_CHAR * \ 83 | NegotiateCell._PADDING_LEN 84 | return retval 85 | 86 | def fromString(self, negotiate_cell_str): 87 | assert len(negotiate_cell_str) == NegotiateCell._CELL_SIZE 88 | assert negotiate_cell_str[ 89 | :NegotiateCell._PADDING_LEN] == NegotiateCell._PADDING_CHAR * NegotiateCell._PADDING_LEN 90 | negotiate_cell_str = negotiate_cell_str.strip( 91 | NegotiateCell._PADDING_CHAR) 92 | # 8==len(YYYYMMDD) 93 | def_file = negotiate_cell_str[:len(NegotiateCell._DATE_FORMAT)] 94 | language = negotiate_cell_str[len(NegotiateCell._DATE_FORMAT):] 95 | negotiate_cell = NegotiateCell() 96 | negotiate_cell.setDefFile(def_file) 97 | negotiate_cell.setLanguage(language) 98 | return negotiate_cell 99 | 100 | 101 | class NegotiationManager(object): 102 | 103 | def __init__(self, K1, K2): 104 | self._negotiationComplete = False 105 | self._K1 = K1 106 | self._K2 = K2 107 | 108 | def getNegotiationComplete(self): 109 | return self._negotiationComplete 110 | 111 | def _acceptNegotiation(self, data): 112 | 113 | languages = fteproxy.defs.load_definitions() 114 | for incoming_language in languages.keys(): 115 | try: 116 | if incoming_language.endswith('response'): 117 | continue 118 | 119 | incoming_regex = fteproxy.defs.getRegex(incoming_language) 120 | incoming_fixed_slice = fteproxy.defs.getFixedSlice( 121 | incoming_language) 122 | 123 | incoming_decoder = fte.encoder.DfaEncoder(fteproxy.regex2dfa.regex2dfa(incoming_regex), 124 | incoming_fixed_slice, self._K1, self._K2) 125 | decoder = fteproxy.record_layer.Decoder(decoder=incoming_decoder) 126 | 127 | decoder.push(data) 128 | negotiate_cell = decoder.pop(oneCell=True) 129 | NegotiateCell().fromString(negotiate_cell) 130 | 131 | return [negotiate_cell, decoder._buffer] 132 | except Exception as e: 133 | fteproxy.info('Failed to decode first message as '+incoming_language+': '+str(e)) 134 | 135 | raise NegotiationFailedException() 136 | 137 | def _init_encoders(self, 138 | outgoing_regex, outgoing_fixed_slice, 139 | incoming_regex, incoming_fixed_slice): 140 | 141 | encoder = None 142 | decoder = None 143 | 144 | if outgoing_regex != None and outgoing_fixed_slice != -1: 145 | outgoing_encoder = fte.encoder.DfaEncoder(fteproxy.regex2dfa.regex2dfa(outgoing_regex), 146 | outgoing_fixed_slice, self._K1, self._K2) 147 | encoder = fteproxy.record_layer.Encoder(encoder=outgoing_encoder) 148 | 149 | if incoming_regex != None and incoming_fixed_slice != -1: 150 | incoming_decoder = fte.encoder.DfaEncoder(fteproxy.regex2dfa.regex2dfa(incoming_regex), 151 | incoming_fixed_slice, self._K1, self._K2) 152 | decoder = fteproxy.record_layer.Decoder(decoder=incoming_decoder) 153 | 154 | return [encoder, decoder] 155 | 156 | def _makeNegotiationCell(self, encoder): 157 | negotiate_cell = NegotiateCell() 158 | def_file = fteproxy.conf.getValue('fteproxy.defs.release') 159 | negotiate_cell.setDefFile(def_file) 160 | language = fteproxy.conf.getValue('runtime.state.upstream_language') 161 | language = language[:-len('-request')] 162 | negotiate_cell.setLanguage(language) 163 | encoder.push(negotiate_cell.toString()) 164 | data = encoder.pop() 165 | return data 166 | 167 | def makeClientNegotiationCell(self, 168 | outgoing_regex, outgoing_fixed_slice, 169 | incoming_regex, incoming_fixed_slice): 170 | [encoder, decoder] = self._init_encoders( 171 | outgoing_regex, outgoing_fixed_slice, incoming_regex, incoming_fixed_slice) 172 | return self._makeNegotiationCell(encoder) 173 | 174 | def doServerSideNegotiation(self, data): 175 | [negotiate_cell, remaining_buffer] = self._acceptNegotiation(data) 176 | 177 | negotiate = NegotiateCell().fromString(negotiate_cell) 178 | 179 | outgoing_language = negotiate.getLanguage() + '-response' 180 | incoming_language = negotiate.getLanguage() + '-request' 181 | 182 | outgoing_regex = fteproxy.defs.getRegex(outgoing_language) 183 | outgoing_fixed_slice = fteproxy.defs.getFixedSlice(outgoing_language) 184 | incoming_regex = fteproxy.defs.getRegex(incoming_language) 185 | incoming_fixed_slice = fteproxy.defs.getFixedSlice(incoming_language) 186 | 187 | [encoder, decoder] = self._init_encoders( 188 | outgoing_regex, outgoing_fixed_slice, incoming_regex, incoming_fixed_slice) 189 | 190 | decoder.push(remaining_buffer) 191 | 192 | return [encoder, decoder] 193 | 194 | 195 | class FTEHelper(object): 196 | 197 | def _processRecv(self, data): 198 | retval = data 199 | if self._isServer and not self._negotiationComplete: 200 | try: 201 | self._preNegotiationBuffer_incoming += data 202 | [encoder, decoder] = self._negotiation_manager.doServerSideNegotiation( 203 | self._preNegotiationBuffer_incoming) 204 | self._encoder = encoder 205 | self._decoder = decoder 206 | self._preNegotiationBuffer_incoming = '' 207 | self._negotiationComplete = True 208 | retval = '' 209 | except Exception as e: 210 | raise ChannelNotReadyException() 211 | 212 | return retval 213 | 214 | def _processSend(self): 215 | retval = '' 216 | if self._isClient and not self._negotiationComplete: 217 | [encoder, decoder] = self._negotiation_manager._init_encoders( 218 | self._outgoing_regex, 219 | self._outgoing_fixed_slice, 220 | self._incoming_regex, 221 | self._incoming_fixed_slice) 222 | self._encoder = encoder 223 | self._decoder = decoder 224 | negotiation_cell = self._negotiation_manager.makeClientNegotiationCell( 225 | self._outgoing_regex, self._outgoing_fixed_slice, 226 | self._incoming_regex, self._incoming_fixed_slice) 227 | retval = negotiation_cell 228 | self._negotiationComplete = True 229 | return retval 230 | 231 | 232 | class _FTESocketWrapper(FTEHelper, object): 233 | 234 | def __init__(self, _socket, 235 | outgoing_regex=None, outgoing_fixed_slice=-1, 236 | incoming_regex=None, incoming_fixed_slice=-1, 237 | K1=None, K2=None): 238 | 239 | self._socket = _socket 240 | self._outgoing_regex = outgoing_regex 241 | self._outgoing_fixed_slice = outgoing_fixed_slice 242 | self._incoming_regex = incoming_regex 243 | self._incoming_fixed_slice = incoming_fixed_slice 244 | self._K1 = K1 245 | self._K2 = K2 246 | 247 | self._negotiation_manager = NegotiationManager(K1, K2) 248 | self._negotiationComplete = False 249 | self._isServer = (outgoing_regex is None and incoming_regex is None) 250 | self._isClient = ( 251 | outgoing_regex is not None and incoming_regex is not None) 252 | self._incoming_buffer = '' 253 | self._preNegotiationBuffer_outgoing = '' 254 | self._preNegotiationBuffer_incoming = '' 255 | 256 | def fileno(self): 257 | return self._socket.fileno() 258 | 259 | def recv(self, bufsize): 260 | # 261 | # Required to deal with case when client attempts to recv 262 | # before sending. This checks to ensure that a negotiate 263 | # cell is sent no matter what the client does first. 264 | to_send = self._processSend() 265 | if to_send: 266 | numbytes = self._socket.send(to_send) 267 | assert numbytes == len(to_send) 268 | # 269 | 270 | try: 271 | while True: 272 | data = self._socket.recv(bufsize) 273 | noData = (data == '') 274 | data = self._processRecv(data) 275 | 276 | if noData and not self._incoming_buffer and not self._decoder._buffer: 277 | return '' 278 | 279 | self._decoder.push(data) 280 | 281 | while True: 282 | frag = self._decoder.pop() 283 | if not frag: 284 | break 285 | self._incoming_buffer += frag 286 | 287 | if self._incoming_buffer: 288 | break 289 | 290 | retval = self._incoming_buffer 291 | self._incoming_buffer = '' 292 | except ChannelNotReadyException: 293 | raise socket.timeout 294 | 295 | return retval 296 | 297 | def send(self, data): 298 | to_send = self._processSend() 299 | if to_send: 300 | self._socket.sendall(to_send) 301 | 302 | self._encoder.push(data) 303 | while True: 304 | to_send = self._encoder.pop() 305 | if not to_send: 306 | break 307 | self._socket.sendall(to_send) 308 | return len(data) 309 | 310 | def sendall(self, data): 311 | return self.send(data) 312 | 313 | def gettimeout(self): 314 | return self._socket.gettimeout() 315 | 316 | def settimeout(self, val): 317 | return self._socket.settimeout(val) 318 | 319 | def shutdown(self, flags): 320 | return self._socket.shutdown(flags) 321 | 322 | def close(self): 323 | return self._socket.close() 324 | 325 | def connect(self, addr): 326 | return self._socket.connect(addr) 327 | 328 | def accept(self): 329 | conn, addr = self._socket.accept() 330 | conn = _FTESocketWrapper(conn, 331 | self._outgoing_regex, self._outgoing_fixed_slice, 332 | self._incoming_regex, self._incoming_fixed_slice, 333 | self._K1, self._K2) 334 | 335 | return conn, addr 336 | 337 | def bind(self, addr): 338 | return self._socket.bind(addr) 339 | 340 | def listen(self, N): 341 | return self._socket.listen(N) 342 | 343 | 344 | def wrap_socket(sock, 345 | outgoing_regex=None, outgoing_fixed_slice=-1, 346 | incoming_regex=None, incoming_fixed_slice=-1, 347 | K1=None, K2=None): 348 | """``fteproxy.wrap_socket`` turns an existing socket into an fteproxy socket. 349 | 350 | The input parameter ``sock`` is the socket to wrap. 351 | The parameter ``outgoing_regex`` specifies the format of the messages 352 | to send via the socket. The ``outgoing_fixed_slice`` parameter specifies the 353 | maximum length of the strings in ``outgoing_regex``. 354 | The parameters ``incoming_regex`` and ``incoming_fixed_slice`` are defined 355 | similarly. 356 | The optional parameters ``K1`` and ``K2`` specify 128-bit keys to be used 357 | in FTE's underlying AE scheme. If specified, these values must be 16-byte 358 | hex strings. 359 | """ 360 | 361 | assert K1 == None or len(K1) == 16 362 | assert K2 == None or len(K2) == 16 363 | 364 | socket_wrapped = _FTESocketWrapper( 365 | sock, 366 | outgoing_regex, outgoing_fixed_slice, 367 | incoming_regex, incoming_fixed_slice, 368 | K1, K2) 369 | return socket_wrapped 370 | 371 | 372 | import obfsproxy.network.network as network 373 | import obfsproxy.network.socks as socks 374 | import obfsproxy.network.extended_orport as extended_orport 375 | import obfsproxy.transports.base 376 | 377 | import twisted.internet 378 | 379 | 380 | class FTETransport(FTEHelper, obfsproxy.transports.base.BaseTransport): 381 | 382 | def __init__(self, pt_config=None): 383 | self.circuit = None 384 | self._isClient = (fteproxy.conf.getValue('runtime.mode') == 'client') 385 | self._isServer = not self._isClient 386 | if self._isClient: 387 | outgoing_language = fteproxy.conf.getValue( 388 | 'runtime.state.upstream_language') 389 | incoming_language = fteproxy.conf.getValue( 390 | 'runtime.state.downstream_language') 391 | self._outgoing_regex = fteproxy.defs.getRegex(outgoing_language) 392 | self._outgoing_fixed_slice = fteproxy.defs.getFixedSlice( 393 | outgoing_language) 394 | self._incoming_regex = fteproxy.defs.getRegex(incoming_language) 395 | self._incoming_fixed_slice = fteproxy.defs.getFixedSlice( 396 | incoming_language) 397 | else: 398 | self._outgoing_regex = None 399 | self._outgoing_fixed_slice = -1 400 | self._incoming_regex = None 401 | self._incoming_fixed_slice = -1 402 | 403 | self._encoder = None 404 | self._decoder = None 405 | 406 | self._negotiation_manager = NegotiationManager(K1, K2) 407 | self._negotiationComplete = False 408 | self._incoming_buffer = '' 409 | self._preNegotiationBuffer_outgoing = '' 410 | self._preNegotiationBuffer_incoming = '' 411 | 412 | def receivedDownstream(self, data, circuit=None): 413 | """decode fteproxy stream""" 414 | 415 | circuit = self.circuit if circuit == None else circuit 416 | 417 | try: 418 | data = data.read() 419 | data = self._processRecv(data) 420 | 421 | if self._decoder: 422 | self._decoder.push(data) 423 | 424 | while True: 425 | frag = self._decoder.pop() 426 | if not frag: 427 | break 428 | circuit.upstream.write(frag) 429 | 430 | except ChannelNotReadyException: 431 | pass 432 | 433 | def receivedUpstream(self, data, circuit=None): 434 | """encode fteproxy stream""" 435 | 436 | circuit = self.circuit if circuit == None else circuit 437 | 438 | to_send = self._processSend() 439 | if to_send: 440 | circuit.downstream.write(to_send) 441 | 442 | data = data.read() 443 | if self._encoder: 444 | self._encoder.push(data) 445 | while True: 446 | to_send = self._encoder.pop() 447 | if not to_send: 448 | break 449 | circuit.downstream.write(to_send) 450 | 451 | 452 | class FTETransportClient(FTETransport): 453 | pass 454 | 455 | 456 | class FTETransportServer(FTETransport): 457 | pass 458 | 459 | 460 | def launch_transport_listener(transport, bindaddr, role, remote_addrport, pt_config, ext_or_cookie_file=None): 461 | """ 462 | Launch a listener for 'transport' in role 'role' (socks/client/server/ext_server). 463 | 464 | If 'bindaddr' is set, then listen on bindaddr. Otherwise, listen 465 | on an ephemeral port on localhost. 466 | 'remote_addrport' is the TCP/IP address of the other end of the 467 | circuit. It's not used if we are in 'socks' role. 468 | 469 | 'pt_config' contains configuration options (such as the state location) 470 | which are of interest to the pluggable transport. 471 | 472 | 'ext_or_cookie_file' is the filesystem path where the Extended 473 | ORPort Authentication cookie is stored. It's only used in 474 | 'ext_server' mode. 475 | 476 | Return a tuple (addr, port) representing where we managed to bind. 477 | 478 | Throws obfsproxy.transports.transports.TransportNotFound if the 479 | transport could not be found. 480 | 481 | Throws twisted.internet.error.CannotListenError if the listener 482 | could not be set up. 483 | """ 484 | 485 | listen_host = bindaddr[0] if bindaddr else 'localhost' 486 | listen_port = int(bindaddr[1]) if bindaddr else 0 487 | 488 | if role == 'socks': 489 | transport_class = FTETransportClient 490 | if hasattr(socks, "OBFSSOCKSv5Factory"): 491 | # obfsproxy >= 0.2.7 provides SOCKS5. 492 | factory = socks.OBFSSOCKSv5Factory(transport_class, pt_config) 493 | pt_config.fte_client_socks_version = 5 494 | elif hasattr(socks, "SOCKSv4Factory"): 495 | # obfsproxy < 0.2.7 provides SOCKS4. 496 | factory = socks.SOCKSv4Factory(transport_class, pt_config) 497 | pt_config.fte_client_socks_version = 4 498 | else: 499 | # This will only happen if the obfsproxy people change the socks 500 | # code again. This really is a dependency issue, so raise an 501 | # ImportError. 502 | raise ImportError("Failed to setup an obfsproxy SOCKS server factory") 503 | elif role == 'ext_server': 504 | assert(remote_addrport and ext_or_cookie_file) 505 | transport_class = FTETransportServer 506 | factory = extended_orport.ExtORPortServerFactory( 507 | remote_addrport, ext_or_cookie_file, transport, transport_class, pt_config) 508 | elif role == 'client': 509 | assert(remote_addrport) 510 | transport_class = FTETransportClient 511 | factory = network.StaticDestinationServerFactory( 512 | remote_addrport, role, transport_class, pt_config) 513 | elif role == 'server': 514 | assert(remote_addrport) 515 | transport_class = FTETransportServer 516 | factory = network.StaticDestinationServerFactory( 517 | remote_addrport, role, transport_class, pt_config) 518 | else: 519 | raise InvalidRoleException() 520 | 521 | addrport = twisted.internet.reactor.listenTCP( 522 | listen_port, factory, interface=listen_host) 523 | 524 | return (addrport.getHost().host, addrport.getHost().port) 525 | -------------------------------------------------------------------------------- /fteproxy/cli.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | 6 | 7 | import sys 8 | import os 9 | import signal 10 | import glob 11 | import argparse 12 | import threading 13 | 14 | import fte.encoder 15 | 16 | import fteproxy.conf 17 | import fteproxy.server 18 | import fteproxy.client 19 | import fteproxy.regex2dfa 20 | 21 | # do_managed_* 22 | 23 | from twisted.internet import reactor, error 24 | 25 | import obfsproxy.network.network as network 26 | import obfsproxy.common.transport_config as transport_config 27 | import obfsproxy.transports.transports as transports 28 | import obfsproxy.common.log as logging 29 | 30 | from pyptlib.client import ClientTransportPlugin 31 | from pyptlib.server import ServerTransportPlugin 32 | from pyptlib.config import EnvError 33 | 34 | import pprint 35 | 36 | # unit tests 37 | 38 | import unittest 39 | 40 | import fteproxy.tests.test_record_layer 41 | import fteproxy.tests.test_relay 42 | 43 | FTE_PT_NAME = 'fte' 44 | 45 | VERSION_FILE = os.path.join( 46 | fteproxy.conf.getValue('general.base_dir'), 'fteproxy', 'VERSION') 47 | with open(VERSION_FILE) as fh: 48 | FTEPROXY_VERSION = fh.read().strip() 49 | 50 | 51 | class FTEMain(threading.Thread): 52 | 53 | def __init__(self, args): 54 | threading.Thread.__init__(self) 55 | self._args = args 56 | 57 | def run(self): 58 | try: 59 | self._client = None 60 | self._server = None 61 | 62 | if not self._args.quiet and not self._args.managed: 63 | print """fteproxy Copyright (C) 2012-2014 Kevin P. Dyer 64 | This program comes with ABSOLUTELY NO WARRANTY. 65 | This is free software, and you are welcome to redistribute it under certain conditions. 66 | """ 67 | 68 | if self._args.mode == 'test': 69 | test() 70 | if self._args.stop: 71 | FTEMain.do_stop(self) 72 | 73 | try: 74 | pid_file = get_pid_file() 75 | 76 | with open(pid_file, 'w') as f: 77 | f.write(str(os.getpid())) 78 | except IOError: 79 | fteproxy.warn('Failed to write PID file to disk: '+pid_file) 80 | 81 | if fteproxy.conf.getValue('runtime.mode') == 'client': 82 | FTEMain.do_client(self) 83 | elif fteproxy.conf.getValue('runtime.mode') == 'server': 84 | FTEMain.do_server(self) 85 | 86 | except Exception as e: 87 | import traceback 88 | traceback.print_exc(e) 89 | fteproxy.fatal_error("FTEMain terminated unexpectedly: "+str(e)) 90 | 91 | def stop(self): 92 | if self._client is not None: 93 | self._client.stop() 94 | if self._server is not None: 95 | self._server.stop() 96 | 97 | def do_stop(self): 98 | if self._args.mode is not 'test': 99 | pid_files_path = \ 100 | os.path.join(fteproxy.conf.getValue('general.pid_dir'), 101 | '.' + self._args.mode + '-*.pid') 102 | pid_files = glob.glob(pid_files_path) 103 | for pid_file in pid_files: 104 | with open(pid_file) as f: 105 | pid = int(f.read()) 106 | try: 107 | os.kill(pid, signal.SIGINT) 108 | except OSError: 109 | fteproxy.warn('failed to remove PID file: '+pid_file) 110 | os.unlink(pid_file) 111 | sys.exit(0) 112 | 113 | def init_listener(self, mode): 114 | server_ip = fteproxy.conf.getValue('runtime.server.ip') 115 | server_port = fteproxy.conf.getValue('runtime.server.port') 116 | if mode is 'client': 117 | client_ip = fteproxy.conf.getValue('runtime.client.ip') 118 | client_port = fteproxy.conf.getValue('runtime.client.port') 119 | return fteproxy.client.listener(client_ip, client_port, 120 | server_ip, server_port) 121 | elif mode is 'server': 122 | proxy_ip = fteproxy.conf.getValue('runtime.proxy.ip') 123 | proxy_port = fteproxy.conf.getValue('runtime.proxy.port') 124 | return fteproxy.server.listener(server_ip, server_port, 125 | proxy_ip, proxy_port) 126 | else: 127 | fteproxy.fatal_error('Unexpected mode in init_listener: ' + mode) 128 | 129 | def init_DfaEncoder(self, stream_format): 130 | 131 | K1 = fteproxy.conf.getValue('runtime.fteproxy.encrypter.key')[:16] 132 | K2 = fteproxy.conf.getValue('runtime.fteproxy.encrypter.key')[16:] 133 | 134 | try: 135 | regex = fteproxy.defs.getRegex(stream_format) 136 | except fteproxy.defs.InvalidRegexName: 137 | fteproxy.fatal_error('Invalid format name ' + stream_format) 138 | 139 | fixed_slice = fteproxy.defs.getFixedSlice(stream_format) 140 | fte.encoder.DfaEncoder(fteproxy.regex2dfa.regex2dfa(regex), 141 | fixed_slice, K1, K2) 142 | 143 | def do_client(self): 144 | 145 | FTEMain.init_DfaEncoder(self, self._args.downstream_format) 146 | FTEMain.init_DfaEncoder(self, self._args.upstream_format) 147 | 148 | if self._args.managed: 149 | do_managed_client() 150 | else: 151 | 152 | if not self._args.quiet: 153 | print 'Client ready!' 154 | 155 | self._client = FTEMain.init_listener(self, 'client') 156 | self._client.daemon = True 157 | self._client.start() 158 | self._client.join() 159 | 160 | def do_server(self): 161 | 162 | languages = fteproxy.defs.load_definitions() 163 | for language in languages.keys(): 164 | FTEMain.init_DfaEncoder(self, language) 165 | 166 | if self._args.managed: 167 | do_managed_server() 168 | else: 169 | self._server = FTEMain.init_listener(self, 'server') 170 | self._server.daemon = True 171 | self._server.start() 172 | if not self._args.quiet: 173 | print 'Server ready!' 174 | self._server.join() 175 | 176 | 177 | def get_pid_file(): 178 | pid_file = os.path.join(fteproxy.conf.getValue('general.pid_dir'), 179 | '.' + fteproxy.conf.getValue('runtime.mode') 180 | + '-' + str(os.getpid()) + '.pid') 181 | return pid_file 182 | 183 | 184 | def do_managed_client(): 185 | 186 | log = logging.get_obfslogger() 187 | 188 | """Start the managed-proxy protocol as a client.""" 189 | 190 | should_start_event_loop = False 191 | 192 | ptclient = ClientTransportPlugin() 193 | try: 194 | ptclient.init([FTE_PT_NAME]) 195 | except EnvError, err: 196 | log.warning("Client managed-proxy protocol failed (%s)." % err) 197 | return 198 | 199 | log.debug("pyptlib gave us the following data:\n'%s'", 200 | pprint.pformat(ptclient.getDebugData())) 201 | 202 | # Apply the proxy settings if any 203 | proxy = ptclient.config.getProxy() 204 | if proxy: 205 | # Ensure that we have all the neccecary dependencies 206 | try: 207 | network.ensure_outgoing_proxy_dependencies() 208 | except network.OutgoingProxyDepFailure, err: 209 | ptclient.reportProxyError(str(err)) 210 | return 211 | 212 | ptclient.reportProxySuccess() 213 | 214 | for transport in ptclient.getTransports(): 215 | # Will hold configuration parameters for the pluggable transport 216 | # module. 217 | pt_config = transport_config.TransportConfig() 218 | pt_config.setStateLocation(ptclient.config.getStateLocation()) 219 | pt_config.fte_client_socks_version = -1 220 | pt_config.setProxy(proxy) 221 | 222 | try: 223 | addrport = fteproxy.launch_transport_listener( 224 | transport, None, 'socks', None, pt_config) 225 | except transports.TransportNotFound: 226 | log.warning("Could not find transport '%s'" % transport) 227 | ptclient.reportMethodError(transport, "Could not find transport.") 228 | continue 229 | except error.CannotListenError: 230 | log.warning("Could not set up listener for '%s'." % transport) 231 | ptclient.reportMethodError(transport, "Could not set up listener.") 232 | continue 233 | 234 | should_start_event_loop = True 235 | log.debug("Successfully launched '%s' at '%s'" % 236 | (transport, log.safe_addr_str(str(addrport)))) 237 | if pt_config.fte_client_socks_version == 5: 238 | ptclient.reportMethodSuccess(transport, "socks5", addrport, None, None) 239 | elif pt_config.fte_client_socks_version == 4: 240 | ptclient.reportMethodSuccess(transport, "socks4", addrport, None, None) 241 | else: 242 | # This should *never* happen unless launch_transport_listener() 243 | # decides to report a SOCKS version from the future. 244 | log.warning("Listener SOCKS version unknown.") 245 | ptclient.reportMethodError(transport, 246 | "Listener SOCKS version unknown.") 247 | should_start_event_loop = False 248 | 249 | ptclient.reportMethodsEnd() 250 | 251 | if should_start_event_loop: 252 | log.info("Starting up the event loop.") 253 | reactor.run() 254 | else: 255 | log.info("No transports launched. Nothing to do.") 256 | 257 | 258 | def do_managed_server(): 259 | 260 | log = logging.get_obfslogger() 261 | 262 | """Start the managed-proxy protocol as a server.""" 263 | 264 | should_start_event_loop = False 265 | 266 | ptserver = ServerTransportPlugin() 267 | try: 268 | ptserver.init([FTE_PT_NAME]) 269 | except EnvError, err: 270 | log.warning("Server managed-proxy protocol failed (%s)." % err) 271 | return 272 | 273 | log.debug("pyptlib gave us the following data:\n'%s'", 274 | pprint.pformat(ptserver.getDebugData())) 275 | 276 | ext_orport = ptserver.config.getExtendedORPort() 277 | authcookie = ptserver.config.getAuthCookieFile() 278 | orport = ptserver.config.getORPort() 279 | server_transport_options = ptserver.config.getServerTransportOptions() 280 | for transport, transport_bindaddr in ptserver.getBindAddresses().items(): 281 | 282 | # Will hold configuration parameters for the pluggable transport 283 | # module. 284 | pt_config = transport_config.TransportConfig() 285 | pt_config.setStateLocation(ptserver.config.getStateLocation()) 286 | transport_options = "" 287 | if server_transport_options and transport in server_transport_options: 288 | transport_options = server_transport_options[transport] 289 | pt_config.setServerTransportOptions(transport_options) 290 | 291 | try: 292 | if ext_orport: 293 | addrport = fteproxy.launch_transport_listener(transport, 294 | transport_bindaddr, 295 | 'ext_server', 296 | ext_orport, 297 | pt_config, 298 | ext_or_cookie_file=authcookie) 299 | else: 300 | addrport = fteproxy.launch_transport_listener(transport, 301 | transport_bindaddr, 302 | 'server', 303 | orport, 304 | pt_config) 305 | except transports.TransportNotFound: 306 | log.warning("Could not find transport '%s'" % transport) 307 | ptserver.reportMethodError(transport, "Could not find transport.") 308 | continue 309 | except error.CannotListenError: 310 | log.warning("Could not set up listener for '%s'." % transport) 311 | ptserver.reportMethodError(transport, "Could not set up listener.") 312 | continue 313 | 314 | should_start_event_loop = True 315 | 316 | # Include server transport options in the log message if we got 'em 317 | extra_log = "" 318 | if transport_options: 319 | extra_log = " (server transport options: '%s')" % str( 320 | transport_options) 321 | log.debug("Successfully launched '%s' at '%s'%s" % 322 | (transport, log.safe_addr_str(str(addrport)), extra_log)) 323 | 324 | # Report success for this transport. 325 | # (We leave the 'options' as None and let pyptlib handle the 326 | # SMETHOD argument sending.) 327 | ptserver.reportMethodSuccess(transport, addrport, None) 328 | 329 | ptserver.reportMethodsEnd() 330 | 331 | if should_start_event_loop: 332 | log.info("Starting up the event loop.") 333 | reactor.run() 334 | else: 335 | log.info("No transports launched. Nothing to do.") 336 | 337 | 338 | def test(): 339 | try: 340 | suite_record_layer = unittest.TestLoader().loadTestsFromTestCase( 341 | fteproxy.tests.test_record_layer.Tests) 342 | suite_relay = unittest.TestLoader().loadTestsFromTestCase( 343 | fteproxy.tests.test_relay.Tests) 344 | suites = [ 345 | suite_relay, 346 | suite_record_layer, 347 | ] 348 | alltests = unittest.TestSuite(suites) 349 | unittest.TextTestRunner(verbosity=2).run(alltests) 350 | sys.exit(0) 351 | except Exception as e: 352 | fteproxy.warn("Unit tests failed: "+str(e)) 353 | 354 | 355 | def get_args(): 356 | 357 | class setConfValue(argparse.Action): 358 | def __call__(self, parser, namespace, values, options_string): 359 | args_to_conf = { 360 | "--quiet": "runtime.loglevel", 361 | "--managed": "runtime.loglevel", 362 | "--mode": "runtime.mode", 363 | "--client_ip": "runtime.client.ip", 364 | "--client_port": "runtime.client.port", 365 | "--server_ip": "runtime.server.ip", 366 | "--server_port": "runtime.server.port", 367 | "--proxy_ip": "runtime.proxy.ip", 368 | "--proxy_port": "runtime.proxy.port", 369 | "--downstream-format": "runtime.state.downstream_language", 370 | "--upstream-format": "runtime.state.upstream_language", 371 | "--release": "fteproxy.defs.release", 372 | "--key": "runtime.fteproxy.encrypter.key", 373 | } 374 | 375 | if self.dest is "key": 376 | if len(self.dest) != 64: 377 | fteproxy.warn('Invalid key length: ' + str(len(self.dest)) 378 | + ', should be 64') 379 | sys.exit(1) 380 | try: 381 | values = self.dest.decode('hex') 382 | except: 383 | fteproxy.warn('Invalid key format, must contain only 0-9a-fA-F') 384 | sys.exit(1) 385 | 386 | if self.dest in ['quiet', 'managed']: 387 | fteproxy.conf.setValue(args_to_conf[options_string], 0) 388 | setattr(namespace, self.dest, True) 389 | else: 390 | setattr(namespace, self.dest, values) 391 | if "port" in self.dest: 392 | values = int(values) 393 | fteproxy.conf.setValue(args_to_conf[options_string], values) 394 | 395 | parser = argparse.ArgumentParser(prog='fteproxy', 396 | formatter_class=argparse.ArgumentDefaultsHelpFormatter) 397 | parser.add_argument('--version', action='version', version=FTEPROXY_VERSION, 398 | help='Output the version of fteproxy, then quit.') 399 | parser.add_argument('--mode', action=setConfValue, default='client', 400 | metavar='(client|server|test)', 401 | choices=['client', 'server', 'test'], 402 | help='Relay mode: client or server') 403 | parser.add_argument('--stop', action='store_true', 404 | help='Shutdown daemon process') 405 | parser.add_argument('--upstream-format', action=setConfValue, 406 | help='Client-to-server language format', 407 | default=fteproxy.conf.getValue('runtime.state.upstream_language' 408 | )) 409 | parser.add_argument('--downstream-format', action=setConfValue, 410 | help='Server-to-client language format', 411 | default=fteproxy.conf.getValue('runtime.state.downstream_language' 412 | )) 413 | parser.add_argument('--client_ip', action=setConfValue, 414 | help='Client-side listening IP', 415 | default=fteproxy.conf.getValue('runtime.client.ip' 416 | )) 417 | parser.add_argument('--client_port', action=setConfValue, 418 | help='Client-side listening port', 419 | default=fteproxy.conf.getValue('runtime.client.port' 420 | )) 421 | parser.add_argument('--server_ip', action=setConfValue, 422 | help='Server-side listening IP', 423 | default=fteproxy.conf.getValue('runtime.server.ip' 424 | )) 425 | parser.add_argument('--server_port', action=setConfValue, 426 | help='Server-side listening port', 427 | default=fteproxy.conf.getValue('runtime.server.port' 428 | )) 429 | parser.add_argument('--proxy_ip', action=setConfValue, 430 | help='Forwarding-proxy (SOCKS) listening IP', 431 | default=fteproxy.conf.getValue('runtime.proxy.ip' 432 | )) 433 | parser.add_argument('--proxy_port', action=setConfValue, 434 | help='Forwarding-proxy (SOCKS) listening port', 435 | default=fteproxy.conf.getValue('runtime.proxy.port' 436 | )) 437 | parser.add_argument('--quiet', action=setConfValue, default=False, 438 | help='Be completely silent. Print nothing.', nargs=0) 439 | parser.add_argument('--release', action=setConfValue, 440 | help='Definitions file to use, specified as YYYYMMDD', 441 | default=fteproxy.conf.getValue('fteproxy.defs.release')) 442 | parser.add_argument('--managed', action=setConfValue, nargs=0, 443 | help="Start in pluggable transport managed mode, for use with Tor.", 444 | default=False) 445 | parser.add_argument('--key', action=setConfValue, 446 | help='Cryptographic key, hex, must be exactly 64 characters', 447 | default=fteproxy.conf.getValue('runtime.fteproxy.encrypter.key' 448 | ).encode('hex')) 449 | args = parser.parse_args(sys.argv[1:]) 450 | 451 | if args.stop and not args.mode: 452 | parser.error('--mode keyword is required with --stop') 453 | 454 | if not args.mode: # set client mode in conf if not set 455 | fteproxy.conf.setValue('runtime.mode', 'client') 456 | 457 | return args 458 | 459 | 460 | def main(): 461 | global running 462 | running = True 463 | 464 | def signal_handler(signal, frame): 465 | global running 466 | running = False 467 | signal.signal(signal.SIGINT, signal_handler) 468 | 469 | try: 470 | args = get_args() 471 | main = FTEMain(args) 472 | if args.managed: 473 | main.run() 474 | else: 475 | main.daemon = True 476 | main.start() 477 | while running and main.is_alive(): 478 | main.join(timeout=0.5) 479 | main.stop() 480 | except KeyboardInterrupt: 481 | pass 482 | except Exception as e: 483 | fteproxy.fatal_error("Main thread terminated unexpectedly: "+str(e)) 484 | finally: 485 | if fteproxy.conf.getValue('runtime.mode'): 486 | pid_file = get_pid_file() 487 | 488 | if pid_file and os.path.exists(pid_file): 489 | os.unlink(pid_file) 490 | -------------------------------------------------------------------------------- /fteproxy/regex2dfa.py: -------------------------------------------------------------------------------- 1 | 2 | _regex2dfa = {} 3 | 4 | _regex2dfa["^\\C+$"] = "0 1 0 0\n" + \ 5 | "0 1 1 1\n" + \ 6 | "0 1 2 2\n" + \ 7 | "0 1 3 3\n" + \ 8 | "0 1 4 4\n" + \ 9 | "0 1 5 5\n" + \ 10 | "0 1 6 6\n" + \ 11 | "0 1 7 7\n" + \ 12 | "0 1 8 8\n" + \ 13 | "0 1 9 9\n" + \ 14 | "0 1 10 10\n" + \ 15 | "0 1 11 11\n" + \ 16 | "0 1 12 12\n" + \ 17 | "0 1 13 13\n" + \ 18 | "0 1 14 14\n" + \ 19 | "0 1 15 15\n" + \ 20 | "0 1 16 16\n" + \ 21 | "0 1 17 17\n" + \ 22 | "0 1 18 18\n" + \ 23 | "0 1 19 19\n" + \ 24 | "0 1 20 20\n" + \ 25 | "0 1 21 21\n" + \ 26 | "0 1 22 22\n" + \ 27 | "0 1 23 23\n" + \ 28 | "0 1 24 24\n" + \ 29 | "0 1 25 25\n" + \ 30 | "0 1 26 26\n" + \ 31 | "0 1 27 27\n" + \ 32 | "0 1 28 28\n" + \ 33 | "0 1 29 29\n" + \ 34 | "0 1 30 30\n" + \ 35 | "0 1 31 31\n" + \ 36 | "0 1 32 32\n" + \ 37 | "0 1 33 33\n" + \ 38 | "0 1 34 34\n" + \ 39 | "0 1 35 35\n" + \ 40 | "0 1 36 36\n" + \ 41 | "0 1 37 37\n" + \ 42 | "0 1 38 38\n" + \ 43 | "0 1 39 39\n" + \ 44 | "0 1 40 40\n" + \ 45 | "0 1 41 41\n" + \ 46 | "0 1 42 42\n" + \ 47 | "0 1 43 43\n" + \ 48 | "0 1 44 44\n" + \ 49 | "0 1 45 45\n" + \ 50 | "0 1 46 46\n" + \ 51 | "0 1 47 47\n" + \ 52 | "0 1 48 48\n" + \ 53 | "0 1 49 49\n" + \ 54 | "0 1 50 50\n" + \ 55 | "0 1 51 51\n" + \ 56 | "0 1 52 52\n" + \ 57 | "0 1 53 53\n" + \ 58 | "0 1 54 54\n" + \ 59 | "0 1 55 55\n" + \ 60 | "0 1 56 56\n" + \ 61 | "0 1 57 57\n" + \ 62 | "0 1 58 58\n" + \ 63 | "0 1 59 59\n" + \ 64 | "0 1 60 60\n" + \ 65 | "0 1 61 61\n" + \ 66 | "0 1 62 62\n" + \ 67 | "0 1 63 63\n" + \ 68 | "0 1 64 64\n" + \ 69 | "0 1 65 65\n" + \ 70 | "0 1 66 66\n" + \ 71 | "0 1 67 67\n" + \ 72 | "0 1 68 68\n" + \ 73 | "0 1 69 69\n" + \ 74 | "0 1 70 70\n" + \ 75 | "0 1 71 71\n" + \ 76 | "0 1 72 72\n" + \ 77 | "0 1 73 73\n" + \ 78 | "0 1 74 74\n" + \ 79 | "0 1 75 75\n" + \ 80 | "0 1 76 76\n" + \ 81 | "0 1 77 77\n" + \ 82 | "0 1 78 78\n" + \ 83 | "0 1 79 79\n" + \ 84 | "0 1 80 80\n" + \ 85 | "0 1 81 81\n" + \ 86 | "0 1 82 82\n" + \ 87 | "0 1 83 83\n" + \ 88 | "0 1 84 84\n" + \ 89 | "0 1 85 85\n" + \ 90 | "0 1 86 86\n" + \ 91 | "0 1 87 87\n" + \ 92 | "0 1 88 88\n" + \ 93 | "0 1 89 89\n" + \ 94 | "0 1 90 90\n" + \ 95 | "0 1 91 91\n" + \ 96 | "0 1 92 92\n" + \ 97 | "0 1 93 93\n" + \ 98 | "0 1 94 94\n" + \ 99 | "0 1 95 95\n" + \ 100 | "0 1 96 96\n" + \ 101 | "0 1 97 97\n" + \ 102 | "0 1 98 98\n" + \ 103 | "0 1 99 99\n" + \ 104 | "0 1 100 100\n" + \ 105 | "0 1 101 101\n" + \ 106 | "0 1 102 102\n" + \ 107 | "0 1 103 103\n" + \ 108 | "0 1 104 104\n" + \ 109 | "0 1 105 105\n" + \ 110 | "0 1 106 106\n" + \ 111 | "0 1 107 107\n" + \ 112 | "0 1 108 108\n" + \ 113 | "0 1 109 109\n" + \ 114 | "0 1 110 110\n" + \ 115 | "0 1 111 111\n" + \ 116 | "0 1 112 112\n" + \ 117 | "0 1 113 113\n" + \ 118 | "0 1 114 114\n" + \ 119 | "0 1 115 115\n" + \ 120 | "0 1 116 116\n" + \ 121 | "0 1 117 117\n" + \ 122 | "0 1 118 118\n" + \ 123 | "0 1 119 119\n" + \ 124 | "0 1 120 120\n" + \ 125 | "0 1 121 121\n" + \ 126 | "0 1 122 122\n" + \ 127 | "0 1 123 123\n" + \ 128 | "0 1 124 124\n" + \ 129 | "0 1 125 125\n" + \ 130 | "0 1 126 126\n" + \ 131 | "0 1 127 127\n" + \ 132 | "0 1 128 128\n" + \ 133 | "0 1 129 129\n" + \ 134 | "0 1 130 130\n" + \ 135 | "0 1 131 131\n" + \ 136 | "0 1 132 132\n" + \ 137 | "0 1 133 133\n" + \ 138 | "0 1 134 134\n" + \ 139 | "0 1 135 135\n" + \ 140 | "0 1 136 136\n" + \ 141 | "0 1 137 137\n" + \ 142 | "0 1 138 138\n" + \ 143 | "0 1 139 139\n" + \ 144 | "0 1 140 140\n" + \ 145 | "0 1 141 141\n" + \ 146 | "0 1 142 142\n" + \ 147 | "0 1 143 143\n" + \ 148 | "0 1 144 144\n" + \ 149 | "0 1 145 145\n" + \ 150 | "0 1 146 146\n" + \ 151 | "0 1 147 147\n" + \ 152 | "0 1 148 148\n" + \ 153 | "0 1 149 149\n" + \ 154 | "0 1 150 150\n" + \ 155 | "0 1 151 151\n" + \ 156 | "0 1 152 152\n" + \ 157 | "0 1 153 153\n" + \ 158 | "0 1 154 154\n" + \ 159 | "0 1 155 155\n" + \ 160 | "0 1 156 156\n" + \ 161 | "0 1 157 157\n" + \ 162 | "0 1 158 158\n" + \ 163 | "0 1 159 159\n" + \ 164 | "0 1 160 160\n" + \ 165 | "0 1 161 161\n" + \ 166 | "0 1 162 162\n" + \ 167 | "0 1 163 163\n" + \ 168 | "0 1 164 164\n" + \ 169 | "0 1 165 165\n" + \ 170 | "0 1 166 166\n" + \ 171 | "0 1 167 167\n" + \ 172 | "0 1 168 168\n" + \ 173 | "0 1 169 169\n" + \ 174 | "0 1 170 170\n" + \ 175 | "0 1 171 171\n" + \ 176 | "0 1 172 172\n" + \ 177 | "0 1 173 173\n" + \ 178 | "0 1 174 174\n" + \ 179 | "0 1 175 175\n" + \ 180 | "0 1 176 176\n" + \ 181 | "0 1 177 177\n" + \ 182 | "0 1 178 178\n" + \ 183 | "0 1 179 179\n" + \ 184 | "0 1 180 180\n" + \ 185 | "0 1 181 181\n" + \ 186 | "0 1 182 182\n" + \ 187 | "0 1 183 183\n" + \ 188 | "0 1 184 184\n" + \ 189 | "0 1 185 185\n" + \ 190 | "0 1 186 186\n" + \ 191 | "0 1 187 187\n" + \ 192 | "0 1 188 188\n" + \ 193 | "0 1 189 189\n" + \ 194 | "0 1 190 190\n" + \ 195 | "0 1 191 191\n" + \ 196 | "0 1 192 192\n" + \ 197 | "0 1 193 193\n" + \ 198 | "0 1 194 194\n" + \ 199 | "0 1 195 195\n" + \ 200 | "0 1 196 196\n" + \ 201 | "0 1 197 197\n" + \ 202 | "0 1 198 198\n" + \ 203 | "0 1 199 199\n" + \ 204 | "0 1 200 200\n" + \ 205 | "0 1 201 201\n" + \ 206 | "0 1 202 202\n" + \ 207 | "0 1 203 203\n" + \ 208 | "0 1 204 204\n" + \ 209 | "0 1 205 205\n" + \ 210 | "0 1 206 206\n" + \ 211 | "0 1 207 207\n" + \ 212 | "0 1 208 208\n" + \ 213 | "0 1 209 209\n" + \ 214 | "0 1 210 210\n" + \ 215 | "0 1 211 211\n" + \ 216 | "0 1 212 212\n" + \ 217 | "0 1 213 213\n" + \ 218 | "0 1 214 214\n" + \ 219 | "0 1 215 215\n" + \ 220 | "0 1 216 216\n" + \ 221 | "0 1 217 217\n" + \ 222 | "0 1 218 218\n" + \ 223 | "0 1 219 219\n" + \ 224 | "0 1 220 220\n" + \ 225 | "0 1 221 221\n" + \ 226 | "0 1 222 222\n" + \ 227 | "0 1 223 223\n" + \ 228 | "0 1 224 224\n" + \ 229 | "0 1 225 225\n" + \ 230 | "0 1 226 226\n" + \ 231 | "0 1 227 227\n" + \ 232 | "0 1 228 228\n" + \ 233 | "0 1 229 229\n" + \ 234 | "0 1 230 230\n" + \ 235 | "0 1 231 231\n" + \ 236 | "0 1 232 232\n" + \ 237 | "0 1 233 233\n" + \ 238 | "0 1 234 234\n" + \ 239 | "0 1 235 235\n" + \ 240 | "0 1 236 236\n" + \ 241 | "0 1 237 237\n" + \ 242 | "0 1 238 238\n" + \ 243 | "0 1 239 239\n" + \ 244 | "0 1 240 240\n" + \ 245 | "0 1 241 241\n" + \ 246 | "0 1 242 242\n" + \ 247 | "0 1 243 243\n" + \ 248 | "0 1 244 244\n" + \ 249 | "0 1 245 245\n" + \ 250 | "0 1 246 246\n" + \ 251 | "0 1 247 247\n" + \ 252 | "0 1 248 248\n" + \ 253 | "0 1 249 249\n" + \ 254 | "0 1 250 250\n" + \ 255 | "0 1 251 251\n" + \ 256 | "0 1 252 252\n" + \ 257 | "0 1 253 253\n" + \ 258 | "0 1 254 254\n" + \ 259 | "0 1 255 255\n" + \ 260 | "1 1 0 0\n" + \ 261 | "1 1 1 1\n" + \ 262 | "1 1 2 2\n" + \ 263 | "1 1 3 3\n" + \ 264 | "1 1 4 4\n" + \ 265 | "1 1 5 5\n" + \ 266 | "1 1 6 6\n" + \ 267 | "1 1 7 7\n" + \ 268 | "1 1 8 8\n" + \ 269 | "1 1 9 9\n" + \ 270 | "1 1 10 10\n" + \ 271 | "1 1 11 11\n" + \ 272 | "1 1 12 12\n" + \ 273 | "1 1 13 13\n" + \ 274 | "1 1 14 14\n" + \ 275 | "1 1 15 15\n" + \ 276 | "1 1 16 16\n" + \ 277 | "1 1 17 17\n" + \ 278 | "1 1 18 18\n" + \ 279 | "1 1 19 19\n" + \ 280 | "1 1 20 20\n" + \ 281 | "1 1 21 21\n" + \ 282 | "1 1 22 22\n" + \ 283 | "1 1 23 23\n" + \ 284 | "1 1 24 24\n" + \ 285 | "1 1 25 25\n" + \ 286 | "1 1 26 26\n" + \ 287 | "1 1 27 27\n" + \ 288 | "1 1 28 28\n" + \ 289 | "1 1 29 29\n" + \ 290 | "1 1 30 30\n" + \ 291 | "1 1 31 31\n" + \ 292 | "1 1 32 32\n" + \ 293 | "1 1 33 33\n" + \ 294 | "1 1 34 34\n" + \ 295 | "1 1 35 35\n" + \ 296 | "1 1 36 36\n" + \ 297 | "1 1 37 37\n" + \ 298 | "1 1 38 38\n" + \ 299 | "1 1 39 39\n" + \ 300 | "1 1 40 40\n" + \ 301 | "1 1 41 41\n" + \ 302 | "1 1 42 42\n" + \ 303 | "1 1 43 43\n" + \ 304 | "1 1 44 44\n" + \ 305 | "1 1 45 45\n" + \ 306 | "1 1 46 46\n" + \ 307 | "1 1 47 47\n" + \ 308 | "1 1 48 48\n" + \ 309 | "1 1 49 49\n" + \ 310 | "1 1 50 50\n" + \ 311 | "1 1 51 51\n" + \ 312 | "1 1 52 52\n" + \ 313 | "1 1 53 53\n" + \ 314 | "1 1 54 54\n" + \ 315 | "1 1 55 55\n" + \ 316 | "1 1 56 56\n" + \ 317 | "1 1 57 57\n" + \ 318 | "1 1 58 58\n" + \ 319 | "1 1 59 59\n" + \ 320 | "1 1 60 60\n" + \ 321 | "1 1 61 61\n" + \ 322 | "1 1 62 62\n" + \ 323 | "1 1 63 63\n" + \ 324 | "1 1 64 64\n" + \ 325 | "1 1 65 65\n" + \ 326 | "1 1 66 66\n" + \ 327 | "1 1 67 67\n" + \ 328 | "1 1 68 68\n" + \ 329 | "1 1 69 69\n" + \ 330 | "1 1 70 70\n" + \ 331 | "1 1 71 71\n" + \ 332 | "1 1 72 72\n" + \ 333 | "1 1 73 73\n" + \ 334 | "1 1 74 74\n" + \ 335 | "1 1 75 75\n" + \ 336 | "1 1 76 76\n" + \ 337 | "1 1 77 77\n" + \ 338 | "1 1 78 78\n" + \ 339 | "1 1 79 79\n" + \ 340 | "1 1 80 80\n" + \ 341 | "1 1 81 81\n" + \ 342 | "1 1 82 82\n" + \ 343 | "1 1 83 83\n" + \ 344 | "1 1 84 84\n" + \ 345 | "1 1 85 85\n" + \ 346 | "1 1 86 86\n" + \ 347 | "1 1 87 87\n" + \ 348 | "1 1 88 88\n" + \ 349 | "1 1 89 89\n" + \ 350 | "1 1 90 90\n" + \ 351 | "1 1 91 91\n" + \ 352 | "1 1 92 92\n" + \ 353 | "1 1 93 93\n" + \ 354 | "1 1 94 94\n" + \ 355 | "1 1 95 95\n" + \ 356 | "1 1 96 96\n" + \ 357 | "1 1 97 97\n" + \ 358 | "1 1 98 98\n" + \ 359 | "1 1 99 99\n" + \ 360 | "1 1 100 100\n" + \ 361 | "1 1 101 101\n" + \ 362 | "1 1 102 102\n" + \ 363 | "1 1 103 103\n" + \ 364 | "1 1 104 104\n" + \ 365 | "1 1 105 105\n" + \ 366 | "1 1 106 106\n" + \ 367 | "1 1 107 107\n" + \ 368 | "1 1 108 108\n" + \ 369 | "1 1 109 109\n" + \ 370 | "1 1 110 110\n" + \ 371 | "1 1 111 111\n" + \ 372 | "1 1 112 112\n" + \ 373 | "1 1 113 113\n" + \ 374 | "1 1 114 114\n" + \ 375 | "1 1 115 115\n" + \ 376 | "1 1 116 116\n" + \ 377 | "1 1 117 117\n" + \ 378 | "1 1 118 118\n" + \ 379 | "1 1 119 119\n" + \ 380 | "1 1 120 120\n" + \ 381 | "1 1 121 121\n" + \ 382 | "1 1 122 122\n" + \ 383 | "1 1 123 123\n" + \ 384 | "1 1 124 124\n" + \ 385 | "1 1 125 125\n" + \ 386 | "1 1 126 126\n" + \ 387 | "1 1 127 127\n" + \ 388 | "1 1 128 128\n" + \ 389 | "1 1 129 129\n" + \ 390 | "1 1 130 130\n" + \ 391 | "1 1 131 131\n" + \ 392 | "1 1 132 132\n" + \ 393 | "1 1 133 133\n" + \ 394 | "1 1 134 134\n" + \ 395 | "1 1 135 135\n" + \ 396 | "1 1 136 136\n" + \ 397 | "1 1 137 137\n" + \ 398 | "1 1 138 138\n" + \ 399 | "1 1 139 139\n" + \ 400 | "1 1 140 140\n" + \ 401 | "1 1 141 141\n" + \ 402 | "1 1 142 142\n" + \ 403 | "1 1 143 143\n" + \ 404 | "1 1 144 144\n" + \ 405 | "1 1 145 145\n" + \ 406 | "1 1 146 146\n" + \ 407 | "1 1 147 147\n" + \ 408 | "1 1 148 148\n" + \ 409 | "1 1 149 149\n" + \ 410 | "1 1 150 150\n" + \ 411 | "1 1 151 151\n" + \ 412 | "1 1 152 152\n" + \ 413 | "1 1 153 153\n" + \ 414 | "1 1 154 154\n" + \ 415 | "1 1 155 155\n" + \ 416 | "1 1 156 156\n" + \ 417 | "1 1 157 157\n" + \ 418 | "1 1 158 158\n" + \ 419 | "1 1 159 159\n" + \ 420 | "1 1 160 160\n" + \ 421 | "1 1 161 161\n" + \ 422 | "1 1 162 162\n" + \ 423 | "1 1 163 163\n" + \ 424 | "1 1 164 164\n" + \ 425 | "1 1 165 165\n" + \ 426 | "1 1 166 166\n" + \ 427 | "1 1 167 167\n" + \ 428 | "1 1 168 168\n" + \ 429 | "1 1 169 169\n" + \ 430 | "1 1 170 170\n" + \ 431 | "1 1 171 171\n" + \ 432 | "1 1 172 172\n" + \ 433 | "1 1 173 173\n" + \ 434 | "1 1 174 174\n" + \ 435 | "1 1 175 175\n" + \ 436 | "1 1 176 176\n" + \ 437 | "1 1 177 177\n" + \ 438 | "1 1 178 178\n" + \ 439 | "1 1 179 179\n" + \ 440 | "1 1 180 180\n" + \ 441 | "1 1 181 181\n" + \ 442 | "1 1 182 182\n" + \ 443 | "1 1 183 183\n" + \ 444 | "1 1 184 184\n" + \ 445 | "1 1 185 185\n" + \ 446 | "1 1 186 186\n" + \ 447 | "1 1 187 187\n" + \ 448 | "1 1 188 188\n" + \ 449 | "1 1 189 189\n" + \ 450 | "1 1 190 190\n" + \ 451 | "1 1 191 191\n" + \ 452 | "1 1 192 192\n" + \ 453 | "1 1 193 193\n" + \ 454 | "1 1 194 194\n" + \ 455 | "1 1 195 195\n" + \ 456 | "1 1 196 196\n" + \ 457 | "1 1 197 197\n" + \ 458 | "1 1 198 198\n" + \ 459 | "1 1 199 199\n" + \ 460 | "1 1 200 200\n" + \ 461 | "1 1 201 201\n" + \ 462 | "1 1 202 202\n" + \ 463 | "1 1 203 203\n" + \ 464 | "1 1 204 204\n" + \ 465 | "1 1 205 205\n" + \ 466 | "1 1 206 206\n" + \ 467 | "1 1 207 207\n" + \ 468 | "1 1 208 208\n" + \ 469 | "1 1 209 209\n" + \ 470 | "1 1 210 210\n" + \ 471 | "1 1 211 211\n" + \ 472 | "1 1 212 212\n" + \ 473 | "1 1 213 213\n" + \ 474 | "1 1 214 214\n" + \ 475 | "1 1 215 215\n" + \ 476 | "1 1 216 216\n" + \ 477 | "1 1 217 217\n" + \ 478 | "1 1 218 218\n" + \ 479 | "1 1 219 219\n" + \ 480 | "1 1 220 220\n" + \ 481 | "1 1 221 221\n" + \ 482 | "1 1 222 222\n" + \ 483 | "1 1 223 223\n" + \ 484 | "1 1 224 224\n" + \ 485 | "1 1 225 225\n" + \ 486 | "1 1 226 226\n" + \ 487 | "1 1 227 227\n" + \ 488 | "1 1 228 228\n" + \ 489 | "1 1 229 229\n" + \ 490 | "1 1 230 230\n" + \ 491 | "1 1 231 231\n" + \ 492 | "1 1 232 232\n" + \ 493 | "1 1 233 233\n" + \ 494 | "1 1 234 234\n" + \ 495 | "1 1 235 235\n" + \ 496 | "1 1 236 236\n" + \ 497 | "1 1 237 237\n" + \ 498 | "1 1 238 238\n" + \ 499 | "1 1 239 239\n" + \ 500 | "1 1 240 240\n" + \ 501 | "1 1 241 241\n" + \ 502 | "1 1 242 242\n" + \ 503 | "1 1 243 243\n" + \ 504 | "1 1 244 244\n" + \ 505 | "1 1 245 245\n" + \ 506 | "1 1 246 246\n" + \ 507 | "1 1 247 247\n" + \ 508 | "1 1 248 248\n" + \ 509 | "1 1 249 249\n" + \ 510 | "1 1 250 250\n" + \ 511 | "1 1 251 251\n" + \ 512 | "1 1 252 252\n" + \ 513 | "1 1 253 253\n" + \ 514 | "1 1 254 254\n" + \ 515 | "1 1 255 255\n" + \ 516 | "1" 517 | _regex2dfa["^GET\\ \\/([a-zA-Z0-9\\.\\/]*) HTTP/1\\.1\\r\\n\\r\\n$"] = "0 1 71 71\n" + \ 518 | "1 2 69 69\n" + \ 519 | "2 3 84 84\n" + \ 520 | "3 4 32 32\n" + \ 521 | "4 5 47 47\n" + \ 522 | "5 6 32 32\n" + \ 523 | "5 5 46 46\n" + \ 524 | "5 5 47 47\n" + \ 525 | "5 5 48 48\n" + \ 526 | "5 5 49 49\n" + \ 527 | "5 5 50 50\n" + \ 528 | "5 5 51 51\n" + \ 529 | "5 5 52 52\n" + \ 530 | "5 5 53 53\n" + \ 531 | "5 5 54 54\n" + \ 532 | "5 5 55 55\n" + \ 533 | "5 5 56 56\n" + \ 534 | "5 5 57 57\n" + \ 535 | "5 5 65 65\n" + \ 536 | "5 5 66 66\n" + \ 537 | "5 5 67 67\n" + \ 538 | "5 5 68 68\n" + \ 539 | "5 5 69 69\n" + \ 540 | "5 5 70 70\n" + \ 541 | "5 5 71 71\n" + \ 542 | "5 5 72 72\n" + \ 543 | "5 5 73 73\n" + \ 544 | "5 5 74 74\n" + \ 545 | "5 5 75 75\n" + \ 546 | "5 5 76 76\n" + \ 547 | "5 5 77 77\n" + \ 548 | "5 5 78 78\n" + \ 549 | "5 5 79 79\n" + \ 550 | "5 5 80 80\n" + \ 551 | "5 5 81 81\n" + \ 552 | "5 5 82 82\n" + \ 553 | "5 5 83 83\n" + \ 554 | "5 5 84 84\n" + \ 555 | "5 5 85 85\n" + \ 556 | "5 5 86 86\n" + \ 557 | "5 5 87 87\n" + \ 558 | "5 5 88 88\n" + \ 559 | "5 5 89 89\n" + \ 560 | "5 5 90 90\n" + \ 561 | "5 5 97 97\n" + \ 562 | "5 5 98 98\n" + \ 563 | "5 5 99 99\n" + \ 564 | "5 5 100 100\n" + \ 565 | "5 5 101 101\n" + \ 566 | "5 5 102 102\n" + \ 567 | "5 5 103 103\n" + \ 568 | "5 5 104 104\n" + \ 569 | "5 5 105 105\n" + \ 570 | "5 5 106 106\n" + \ 571 | "5 5 107 107\n" + \ 572 | "5 5 108 108\n" + \ 573 | "5 5 109 109\n" + \ 574 | "5 5 110 110\n" + \ 575 | "5 5 111 111\n" + \ 576 | "5 5 112 112\n" + \ 577 | "5 5 113 113\n" + \ 578 | "5 5 114 114\n" + \ 579 | "5 5 115 115\n" + \ 580 | "5 5 116 116\n" + \ 581 | "5 5 117 117\n" + \ 582 | "5 5 118 118\n" + \ 583 | "5 5 119 119\n" + \ 584 | "5 5 120 120\n" + \ 585 | "5 5 121 121\n" + \ 586 | "5 5 122 122\n" + \ 587 | "6 7 72 72\n" + \ 588 | "7 8 84 84\n" + \ 589 | "8 9 84 84\n" + \ 590 | "9 10 80 80\n" + \ 591 | "10 11 47 47\n" + \ 592 | "11 12 49 49\n" + \ 593 | "12 13 46 46\n" + \ 594 | "13 14 49 49\n" + \ 595 | "14 15 13 13\n" + \ 596 | "15 16 10 10\n" + \ 597 | "16 17 13 13\n" + \ 598 | "17 18 10 10\n" + \ 599 | "18" 600 | _regex2dfa["^HTTP/1\\.1\\ 200 OK\\r\\nContent-Type:\\ ([a-zA-Z0-9]+)\\r\\n\\r\\n\\C*$"] = "0 1 72 72\n" + \ 601 | "1 2 84 84\n" + \ 602 | "2 3 84 84\n" + \ 603 | "3 4 80 80\n" + \ 604 | "4 5 47 47\n" + \ 605 | "5 6 49 49\n" + \ 606 | "6 7 46 46\n" + \ 607 | "7 8 49 49\n" + \ 608 | "8 9 32 32\n" + \ 609 | "9 10 50 50\n" + \ 610 | "10 11 48 48\n" + \ 611 | "11 12 48 48\n" + \ 612 | "12 13 32 32\n" + \ 613 | "13 14 79 79\n" + \ 614 | "14 15 75 75\n" + \ 615 | "15 16 13 13\n" + \ 616 | "16 17 10 10\n" + \ 617 | "17 18 67 67\n" + \ 618 | "18 19 111 111\n" + \ 619 | "19 20 110 110\n" + \ 620 | "20 21 116 116\n" + \ 621 | "21 22 101 101\n" + \ 622 | "22 23 110 110\n" + \ 623 | "23 24 116 116\n" + \ 624 | "24 25 45 45\n" + \ 625 | "25 26 84 84\n" + \ 626 | "26 27 121 121\n" + \ 627 | "27 28 112 112\n" + \ 628 | "28 29 101 101\n" + \ 629 | "29 30 58 58\n" + \ 630 | "30 31 32 32\n" + \ 631 | "31 32 48 48\n" + \ 632 | "31 32 49 49\n" + \ 633 | "31 32 50 50\n" + \ 634 | "31 32 51 51\n" + \ 635 | "31 32 52 52\n" + \ 636 | "31 32 53 53\n" + \ 637 | "31 32 54 54\n" + \ 638 | "31 32 55 55\n" + \ 639 | "31 32 56 56\n" + \ 640 | "31 32 57 57\n" + \ 641 | "31 32 65 65\n" + \ 642 | "31 32 66 66\n" + \ 643 | "31 32 67 67\n" + \ 644 | "31 32 68 68\n" + \ 645 | "31 32 69 69\n" + \ 646 | "31 32 70 70\n" + \ 647 | "31 32 71 71\n" + \ 648 | "31 32 72 72\n" + \ 649 | "31 32 73 73\n" + \ 650 | "31 32 74 74\n" + \ 651 | "31 32 75 75\n" + \ 652 | "31 32 76 76\n" + \ 653 | "31 32 77 77\n" + \ 654 | "31 32 78 78\n" + \ 655 | "31 32 79 79\n" + \ 656 | "31 32 80 80\n" + \ 657 | "31 32 81 81\n" + \ 658 | "31 32 82 82\n" + \ 659 | "31 32 83 83\n" + \ 660 | "31 32 84 84\n" + \ 661 | "31 32 85 85\n" + \ 662 | "31 32 86 86\n" + \ 663 | "31 32 87 87\n" + \ 664 | "31 32 88 88\n" + \ 665 | "31 32 89 89\n" + \ 666 | "31 32 90 90\n" + \ 667 | "31 32 97 97\n" + \ 668 | "31 32 98 98\n" + \ 669 | "31 32 99 99\n" + \ 670 | "31 32 100 100\n" + \ 671 | "31 32 101 101\n" + \ 672 | "31 32 102 102\n" + \ 673 | "31 32 103 103\n" + \ 674 | "31 32 104 104\n" + \ 675 | "31 32 105 105\n" + \ 676 | "31 32 106 106\n" + \ 677 | "31 32 107 107\n" + \ 678 | "31 32 108 108\n" + \ 679 | "31 32 109 109\n" + \ 680 | "31 32 110 110\n" + \ 681 | "31 32 111 111\n" + \ 682 | "31 32 112 112\n" + \ 683 | "31 32 113 113\n" + \ 684 | "31 32 114 114\n" + \ 685 | "31 32 115 115\n" + \ 686 | "31 32 116 116\n" + \ 687 | "31 32 117 117\n" + \ 688 | "31 32 118 118\n" + \ 689 | "31 32 119 119\n" + \ 690 | "31 32 120 120\n" + \ 691 | "31 32 121 121\n" + \ 692 | "31 32 122 122\n" + \ 693 | "32 33 13 13\n" + \ 694 | "32 32 48 48\n" + \ 695 | "32 32 49 49\n" + \ 696 | "32 32 50 50\n" + \ 697 | "32 32 51 51\n" + \ 698 | "32 32 52 52\n" + \ 699 | "32 32 53 53\n" + \ 700 | "32 32 54 54\n" + \ 701 | "32 32 55 55\n" + \ 702 | "32 32 56 56\n" + \ 703 | "32 32 57 57\n" + \ 704 | "32 32 65 65\n" + \ 705 | "32 32 66 66\n" + \ 706 | "32 32 67 67\n" + \ 707 | "32 32 68 68\n" + \ 708 | "32 32 69 69\n" + \ 709 | "32 32 70 70\n" + \ 710 | "32 32 71 71\n" + \ 711 | "32 32 72 72\n" + \ 712 | "32 32 73 73\n" + \ 713 | "32 32 74 74\n" + \ 714 | "32 32 75 75\n" + \ 715 | "32 32 76 76\n" + \ 716 | "32 32 77 77\n" + \ 717 | "32 32 78 78\n" + \ 718 | "32 32 79 79\n" + \ 719 | "32 32 80 80\n" + \ 720 | "32 32 81 81\n" + \ 721 | "32 32 82 82\n" + \ 722 | "32 32 83 83\n" + \ 723 | "32 32 84 84\n" + \ 724 | "32 32 85 85\n" + \ 725 | "32 32 86 86\n" + \ 726 | "32 32 87 87\n" + \ 727 | "32 32 88 88\n" + \ 728 | "32 32 89 89\n" + \ 729 | "32 32 90 90\n" + \ 730 | "32 32 97 97\n" + \ 731 | "32 32 98 98\n" + \ 732 | "32 32 99 99\n" + \ 733 | "32 32 100 100\n" + \ 734 | "32 32 101 101\n" + \ 735 | "32 32 102 102\n" + \ 736 | "32 32 103 103\n" + \ 737 | "32 32 104 104\n" + \ 738 | "32 32 105 105\n" + \ 739 | "32 32 106 106\n" + \ 740 | "32 32 107 107\n" + \ 741 | "32 32 108 108\n" + \ 742 | "32 32 109 109\n" + \ 743 | "32 32 110 110\n" + \ 744 | "32 32 111 111\n" + \ 745 | "32 32 112 112\n" + \ 746 | "32 32 113 113\n" + \ 747 | "32 32 114 114\n" + \ 748 | "32 32 115 115\n" + \ 749 | "32 32 116 116\n" + \ 750 | "32 32 117 117\n" + \ 751 | "32 32 118 118\n" + \ 752 | "32 32 119 119\n" + \ 753 | "32 32 120 120\n" + \ 754 | "32 32 121 121\n" + \ 755 | "32 32 122 122\n" + \ 756 | "33 34 10 10\n" + \ 757 | "34 35 13 13\n" + \ 758 | "35 36 10 10\n" + \ 759 | "36 36 0 0\n" + \ 760 | "36 36 1 1\n" + \ 761 | "36 36 2 2\n" + \ 762 | "36 36 3 3\n" + \ 763 | "36 36 4 4\n" + \ 764 | "36 36 5 5\n" + \ 765 | "36 36 6 6\n" + \ 766 | "36 36 7 7\n" + \ 767 | "36 36 8 8\n" + \ 768 | "36 36 9 9\n" + \ 769 | "36 36 10 10\n" + \ 770 | "36 36 11 11\n" + \ 771 | "36 36 12 12\n" + \ 772 | "36 36 13 13\n" + \ 773 | "36 36 14 14\n" + \ 774 | "36 36 15 15\n" + \ 775 | "36 36 16 16\n" + \ 776 | "36 36 17 17\n" + \ 777 | "36 36 18 18\n" + \ 778 | "36 36 19 19\n" + \ 779 | "36 36 20 20\n" + \ 780 | "36 36 21 21\n" + \ 781 | "36 36 22 22\n" + \ 782 | "36 36 23 23\n" + \ 783 | "36 36 24 24\n" + \ 784 | "36 36 25 25\n" + \ 785 | "36 36 26 26\n" + \ 786 | "36 36 27 27\n" + \ 787 | "36 36 28 28\n" + \ 788 | "36 36 29 29\n" + \ 789 | "36 36 30 30\n" + \ 790 | "36 36 31 31\n" + \ 791 | "36 36 32 32\n" + \ 792 | "36 36 33 33\n" + \ 793 | "36 36 34 34\n" + \ 794 | "36 36 35 35\n" + \ 795 | "36 36 36 36\n" + \ 796 | "36 36 37 37\n" + \ 797 | "36 36 38 38\n" + \ 798 | "36 36 39 39\n" + \ 799 | "36 36 40 40\n" + \ 800 | "36 36 41 41\n" + \ 801 | "36 36 42 42\n" + \ 802 | "36 36 43 43\n" + \ 803 | "36 36 44 44\n" + \ 804 | "36 36 45 45\n" + \ 805 | "36 36 46 46\n" + \ 806 | "36 36 47 47\n" + \ 807 | "36 36 48 48\n" + \ 808 | "36 36 49 49\n" + \ 809 | "36 36 50 50\n" + \ 810 | "36 36 51 51\n" + \ 811 | "36 36 52 52\n" + \ 812 | "36 36 53 53\n" + \ 813 | "36 36 54 54\n" + \ 814 | "36 36 55 55\n" + \ 815 | "36 36 56 56\n" + \ 816 | "36 36 57 57\n" + \ 817 | "36 36 58 58\n" + \ 818 | "36 36 59 59\n" + \ 819 | "36 36 60 60\n" + \ 820 | "36 36 61 61\n" + \ 821 | "36 36 62 62\n" + \ 822 | "36 36 63 63\n" + \ 823 | "36 36 64 64\n" + \ 824 | "36 36 65 65\n" + \ 825 | "36 36 66 66\n" + \ 826 | "36 36 67 67\n" + \ 827 | "36 36 68 68\n" + \ 828 | "36 36 69 69\n" + \ 829 | "36 36 70 70\n" + \ 830 | "36 36 71 71\n" + \ 831 | "36 36 72 72\n" + \ 832 | "36 36 73 73\n" + \ 833 | "36 36 74 74\n" + \ 834 | "36 36 75 75\n" + \ 835 | "36 36 76 76\n" + \ 836 | "36 36 77 77\n" + \ 837 | "36 36 78 78\n" + \ 838 | "36 36 79 79\n" + \ 839 | "36 36 80 80\n" + \ 840 | "36 36 81 81\n" + \ 841 | "36 36 82 82\n" + \ 842 | "36 36 83 83\n" + \ 843 | "36 36 84 84\n" + \ 844 | "36 36 85 85\n" + \ 845 | "36 36 86 86\n" + \ 846 | "36 36 87 87\n" + \ 847 | "36 36 88 88\n" + \ 848 | "36 36 89 89\n" + \ 849 | "36 36 90 90\n" + \ 850 | "36 36 91 91\n" + \ 851 | "36 36 92 92\n" + \ 852 | "36 36 93 93\n" + \ 853 | "36 36 94 94\n" + \ 854 | "36 36 95 95\n" + \ 855 | "36 36 96 96\n" + \ 856 | "36 36 97 97\n" + \ 857 | "36 36 98 98\n" + \ 858 | "36 36 99 99\n" + \ 859 | "36 36 100 100\n" + \ 860 | "36 36 101 101\n" + \ 861 | "36 36 102 102\n" + \ 862 | "36 36 103 103\n" + \ 863 | "36 36 104 104\n" + \ 864 | "36 36 105 105\n" + \ 865 | "36 36 106 106\n" + \ 866 | "36 36 107 107\n" + \ 867 | "36 36 108 108\n" + \ 868 | "36 36 109 109\n" + \ 869 | "36 36 110 110\n" + \ 870 | "36 36 111 111\n" + \ 871 | "36 36 112 112\n" + \ 872 | "36 36 113 113\n" + \ 873 | "36 36 114 114\n" + \ 874 | "36 36 115 115\n" + \ 875 | "36 36 116 116\n" + \ 876 | "36 36 117 117\n" + \ 877 | "36 36 118 118\n" + \ 878 | "36 36 119 119\n" + \ 879 | "36 36 120 120\n" + \ 880 | "36 36 121 121\n" + \ 881 | "36 36 122 122\n" + \ 882 | "36 36 123 123\n" + \ 883 | "36 36 124 124\n" + \ 884 | "36 36 125 125\n" + \ 885 | "36 36 126 126\n" + \ 886 | "36 36 127 127\n" + \ 887 | "36 36 128 128\n" + \ 888 | "36 36 129 129\n" + \ 889 | "36 36 130 130\n" + \ 890 | "36 36 131 131\n" + \ 891 | "36 36 132 132\n" + \ 892 | "36 36 133 133\n" + \ 893 | "36 36 134 134\n" + \ 894 | "36 36 135 135\n" + \ 895 | "36 36 136 136\n" + \ 896 | "36 36 137 137\n" + \ 897 | "36 36 138 138\n" + \ 898 | "36 36 139 139\n" + \ 899 | "36 36 140 140\n" + \ 900 | "36 36 141 141\n" + \ 901 | "36 36 142 142\n" + \ 902 | "36 36 143 143\n" + \ 903 | "36 36 144 144\n" + \ 904 | "36 36 145 145\n" + \ 905 | "36 36 146 146\n" + \ 906 | "36 36 147 147\n" + \ 907 | "36 36 148 148\n" + \ 908 | "36 36 149 149\n" + \ 909 | "36 36 150 150\n" + \ 910 | "36 36 151 151\n" + \ 911 | "36 36 152 152\n" + \ 912 | "36 36 153 153\n" + \ 913 | "36 36 154 154\n" + \ 914 | "36 36 155 155\n" + \ 915 | "36 36 156 156\n" + \ 916 | "36 36 157 157\n" + \ 917 | "36 36 158 158\n" + \ 918 | "36 36 159 159\n" + \ 919 | "36 36 160 160\n" + \ 920 | "36 36 161 161\n" + \ 921 | "36 36 162 162\n" + \ 922 | "36 36 163 163\n" + \ 923 | "36 36 164 164\n" + \ 924 | "36 36 165 165\n" + \ 925 | "36 36 166 166\n" + \ 926 | "36 36 167 167\n" + \ 927 | "36 36 168 168\n" + \ 928 | "36 36 169 169\n" + \ 929 | "36 36 170 170\n" + \ 930 | "36 36 171 171\n" + \ 931 | "36 36 172 172\n" + \ 932 | "36 36 173 173\n" + \ 933 | "36 36 174 174\n" + \ 934 | "36 36 175 175\n" + \ 935 | "36 36 176 176\n" + \ 936 | "36 36 177 177\n" + \ 937 | "36 36 178 178\n" + \ 938 | "36 36 179 179\n" + \ 939 | "36 36 180 180\n" + \ 940 | "36 36 181 181\n" + \ 941 | "36 36 182 182\n" + \ 942 | "36 36 183 183\n" + \ 943 | "36 36 184 184\n" + \ 944 | "36 36 185 185\n" + \ 945 | "36 36 186 186\n" + \ 946 | "36 36 187 187\n" + \ 947 | "36 36 188 188\n" + \ 948 | "36 36 189 189\n" + \ 949 | "36 36 190 190\n" + \ 950 | "36 36 191 191\n" + \ 951 | "36 36 192 192\n" + \ 952 | "36 36 193 193\n" + \ 953 | "36 36 194 194\n" + \ 954 | "36 36 195 195\n" + \ 955 | "36 36 196 196\n" + \ 956 | "36 36 197 197\n" + \ 957 | "36 36 198 198\n" + \ 958 | "36 36 199 199\n" + \ 959 | "36 36 200 200\n" + \ 960 | "36 36 201 201\n" + \ 961 | "36 36 202 202\n" + \ 962 | "36 36 203 203\n" + \ 963 | "36 36 204 204\n" + \ 964 | "36 36 205 205\n" + \ 965 | "36 36 206 206\n" + \ 966 | "36 36 207 207\n" + \ 967 | "36 36 208 208\n" + \ 968 | "36 36 209 209\n" + \ 969 | "36 36 210 210\n" + \ 970 | "36 36 211 211\n" + \ 971 | "36 36 212 212\n" + \ 972 | "36 36 213 213\n" + \ 973 | "36 36 214 214\n" + \ 974 | "36 36 215 215\n" + \ 975 | "36 36 216 216\n" + \ 976 | "36 36 217 217\n" + \ 977 | "36 36 218 218\n" + \ 978 | "36 36 219 219\n" + \ 979 | "36 36 220 220\n" + \ 980 | "36 36 221 221\n" + \ 981 | "36 36 222 222\n" + \ 982 | "36 36 223 223\n" + \ 983 | "36 36 224 224\n" + \ 984 | "36 36 225 225\n" + \ 985 | "36 36 226 226\n" + \ 986 | "36 36 227 227\n" + \ 987 | "36 36 228 228\n" + \ 988 | "36 36 229 229\n" + \ 989 | "36 36 230 230\n" + \ 990 | "36 36 231 231\n" + \ 991 | "36 36 232 232\n" + \ 992 | "36 36 233 233\n" + \ 993 | "36 36 234 234\n" + \ 994 | "36 36 235 235\n" + \ 995 | "36 36 236 236\n" + \ 996 | "36 36 237 237\n" + \ 997 | "36 36 238 238\n" + \ 998 | "36 36 239 239\n" + \ 999 | "36 36 240 240\n" + \ 1000 | "36 36 241 241\n" + \ 1001 | "36 36 242 242\n" + \ 1002 | "36 36 243 243\n" + \ 1003 | "36 36 244 244\n" + \ 1004 | "36 36 245 245\n" + \ 1005 | "36 36 246 246\n" + \ 1006 | "36 36 247 247\n" + \ 1007 | "36 36 248 248\n" + \ 1008 | "36 36 249 249\n" + \ 1009 | "36 36 250 250\n" + \ 1010 | "36 36 251 251\n" + \ 1011 | "36 36 252 252\n" + \ 1012 | "36 36 253 253\n" + \ 1013 | "36 36 254 254\n" + \ 1014 | "36 36 255 255\n" + \ 1015 | "36" 1016 | _regex2dfa["^SSH\\-2\\.0\\C*$"] = "0 1 83 83\n" + \ 1017 | "1 2 83 83\n" + \ 1018 | "2 3 72 72\n" + \ 1019 | "3 4 45 45\n" + \ 1020 | "4 5 50 50\n" + \ 1021 | "5 6 46 46\n" + \ 1022 | "6 7 48 48\n" + \ 1023 | "7 7 0 0\n" + \ 1024 | "7 7 1 1\n" + \ 1025 | "7 7 2 2\n" + \ 1026 | "7 7 3 3\n" + \ 1027 | "7 7 4 4\n" + \ 1028 | "7 7 5 5\n" + \ 1029 | "7 7 6 6\n" + \ 1030 | "7 7 7 7\n" + \ 1031 | "7 7 8 8\n" + \ 1032 | "7 7 9 9\n" + \ 1033 | "7 7 10 10\n" + \ 1034 | "7 7 11 11\n" + \ 1035 | "7 7 12 12\n" + \ 1036 | "7 7 13 13\n" + \ 1037 | "7 7 14 14\n" + \ 1038 | "7 7 15 15\n" + \ 1039 | "7 7 16 16\n" + \ 1040 | "7 7 17 17\n" + \ 1041 | "7 7 18 18\n" + \ 1042 | "7 7 19 19\n" + \ 1043 | "7 7 20 20\n" + \ 1044 | "7 7 21 21\n" + \ 1045 | "7 7 22 22\n" + \ 1046 | "7 7 23 23\n" + \ 1047 | "7 7 24 24\n" + \ 1048 | "7 7 25 25\n" + \ 1049 | "7 7 26 26\n" + \ 1050 | "7 7 27 27\n" + \ 1051 | "7 7 28 28\n" + \ 1052 | "7 7 29 29\n" + \ 1053 | "7 7 30 30\n" + \ 1054 | "7 7 31 31\n" + \ 1055 | "7 7 32 32\n" + \ 1056 | "7 7 33 33\n" + \ 1057 | "7 7 34 34\n" + \ 1058 | "7 7 35 35\n" + \ 1059 | "7 7 36 36\n" + \ 1060 | "7 7 37 37\n" + \ 1061 | "7 7 38 38\n" + \ 1062 | "7 7 39 39\n" + \ 1063 | "7 7 40 40\n" + \ 1064 | "7 7 41 41\n" + \ 1065 | "7 7 42 42\n" + \ 1066 | "7 7 43 43\n" + \ 1067 | "7 7 44 44\n" + \ 1068 | "7 7 45 45\n" + \ 1069 | "7 7 46 46\n" + \ 1070 | "7 7 47 47\n" + \ 1071 | "7 7 48 48\n" + \ 1072 | "7 7 49 49\n" + \ 1073 | "7 7 50 50\n" + \ 1074 | "7 7 51 51\n" + \ 1075 | "7 7 52 52\n" + \ 1076 | "7 7 53 53\n" + \ 1077 | "7 7 54 54\n" + \ 1078 | "7 7 55 55\n" + \ 1079 | "7 7 56 56\n" + \ 1080 | "7 7 57 57\n" + \ 1081 | "7 7 58 58\n" + \ 1082 | "7 7 59 59\n" + \ 1083 | "7 7 60 60\n" + \ 1084 | "7 7 61 61\n" + \ 1085 | "7 7 62 62\n" + \ 1086 | "7 7 63 63\n" + \ 1087 | "7 7 64 64\n" + \ 1088 | "7 7 65 65\n" + \ 1089 | "7 7 66 66\n" + \ 1090 | "7 7 67 67\n" + \ 1091 | "7 7 68 68\n" + \ 1092 | "7 7 69 69\n" + \ 1093 | "7 7 70 70\n" + \ 1094 | "7 7 71 71\n" + \ 1095 | "7 7 72 72\n" + \ 1096 | "7 7 73 73\n" + \ 1097 | "7 7 74 74\n" + \ 1098 | "7 7 75 75\n" + \ 1099 | "7 7 76 76\n" + \ 1100 | "7 7 77 77\n" + \ 1101 | "7 7 78 78\n" + \ 1102 | "7 7 79 79\n" + \ 1103 | "7 7 80 80\n" + \ 1104 | "7 7 81 81\n" + \ 1105 | "7 7 82 82\n" + \ 1106 | "7 7 83 83\n" + \ 1107 | "7 7 84 84\n" + \ 1108 | "7 7 85 85\n" + \ 1109 | "7 7 86 86\n" + \ 1110 | "7 7 87 87\n" + \ 1111 | "7 7 88 88\n" + \ 1112 | "7 7 89 89\n" + \ 1113 | "7 7 90 90\n" + \ 1114 | "7 7 91 91\n" + \ 1115 | "7 7 92 92\n" + \ 1116 | "7 7 93 93\n" + \ 1117 | "7 7 94 94\n" + \ 1118 | "7 7 95 95\n" + \ 1119 | "7 7 96 96\n" + \ 1120 | "7 7 97 97\n" + \ 1121 | "7 7 98 98\n" + \ 1122 | "7 7 99 99\n" + \ 1123 | "7 7 100 100\n" + \ 1124 | "7 7 101 101\n" + \ 1125 | "7 7 102 102\n" + \ 1126 | "7 7 103 103\n" + \ 1127 | "7 7 104 104\n" + \ 1128 | "7 7 105 105\n" + \ 1129 | "7 7 106 106\n" + \ 1130 | "7 7 107 107\n" + \ 1131 | "7 7 108 108\n" + \ 1132 | "7 7 109 109\n" + \ 1133 | "7 7 110 110\n" + \ 1134 | "7 7 111 111\n" + \ 1135 | "7 7 112 112\n" + \ 1136 | "7 7 113 113\n" + \ 1137 | "7 7 114 114\n" + \ 1138 | "7 7 115 115\n" + \ 1139 | "7 7 116 116\n" + \ 1140 | "7 7 117 117\n" + \ 1141 | "7 7 118 118\n" + \ 1142 | "7 7 119 119\n" + \ 1143 | "7 7 120 120\n" + \ 1144 | "7 7 121 121\n" + \ 1145 | "7 7 122 122\n" + \ 1146 | "7 7 123 123\n" + \ 1147 | "7 7 124 124\n" + \ 1148 | "7 7 125 125\n" + \ 1149 | "7 7 126 126\n" + \ 1150 | "7 7 127 127\n" + \ 1151 | "7 7 128 128\n" + \ 1152 | "7 7 129 129\n" + \ 1153 | "7 7 130 130\n" + \ 1154 | "7 7 131 131\n" + \ 1155 | "7 7 132 132\n" + \ 1156 | "7 7 133 133\n" + \ 1157 | "7 7 134 134\n" + \ 1158 | "7 7 135 135\n" + \ 1159 | "7 7 136 136\n" + \ 1160 | "7 7 137 137\n" + \ 1161 | "7 7 138 138\n" + \ 1162 | "7 7 139 139\n" + \ 1163 | "7 7 140 140\n" + \ 1164 | "7 7 141 141\n" + \ 1165 | "7 7 142 142\n" + \ 1166 | "7 7 143 143\n" + \ 1167 | "7 7 144 144\n" + \ 1168 | "7 7 145 145\n" + \ 1169 | "7 7 146 146\n" + \ 1170 | "7 7 147 147\n" + \ 1171 | "7 7 148 148\n" + \ 1172 | "7 7 149 149\n" + \ 1173 | "7 7 150 150\n" + \ 1174 | "7 7 151 151\n" + \ 1175 | "7 7 152 152\n" + \ 1176 | "7 7 153 153\n" + \ 1177 | "7 7 154 154\n" + \ 1178 | "7 7 155 155\n" + \ 1179 | "7 7 156 156\n" + \ 1180 | "7 7 157 157\n" + \ 1181 | "7 7 158 158\n" + \ 1182 | "7 7 159 159\n" + \ 1183 | "7 7 160 160\n" + \ 1184 | "7 7 161 161\n" + \ 1185 | "7 7 162 162\n" + \ 1186 | "7 7 163 163\n" + \ 1187 | "7 7 164 164\n" + \ 1188 | "7 7 165 165\n" + \ 1189 | "7 7 166 166\n" + \ 1190 | "7 7 167 167\n" + \ 1191 | "7 7 168 168\n" + \ 1192 | "7 7 169 169\n" + \ 1193 | "7 7 170 170\n" + \ 1194 | "7 7 171 171\n" + \ 1195 | "7 7 172 172\n" + \ 1196 | "7 7 173 173\n" + \ 1197 | "7 7 174 174\n" + \ 1198 | "7 7 175 175\n" + \ 1199 | "7 7 176 176\n" + \ 1200 | "7 7 177 177\n" + \ 1201 | "7 7 178 178\n" + \ 1202 | "7 7 179 179\n" + \ 1203 | "7 7 180 180\n" + \ 1204 | "7 7 181 181\n" + \ 1205 | "7 7 182 182\n" + \ 1206 | "7 7 183 183\n" + \ 1207 | "7 7 184 184\n" + \ 1208 | "7 7 185 185\n" + \ 1209 | "7 7 186 186\n" + \ 1210 | "7 7 187 187\n" + \ 1211 | "7 7 188 188\n" + \ 1212 | "7 7 189 189\n" + \ 1213 | "7 7 190 190\n" + \ 1214 | "7 7 191 191\n" + \ 1215 | "7 7 192 192\n" + \ 1216 | "7 7 193 193\n" + \ 1217 | "7 7 194 194\n" + \ 1218 | "7 7 195 195\n" + \ 1219 | "7 7 196 196\n" + \ 1220 | "7 7 197 197\n" + \ 1221 | "7 7 198 198\n" + \ 1222 | "7 7 199 199\n" + \ 1223 | "7 7 200 200\n" + \ 1224 | "7 7 201 201\n" + \ 1225 | "7 7 202 202\n" + \ 1226 | "7 7 203 203\n" + \ 1227 | "7 7 204 204\n" + \ 1228 | "7 7 205 205\n" + \ 1229 | "7 7 206 206\n" + \ 1230 | "7 7 207 207\n" + \ 1231 | "7 7 208 208\n" + \ 1232 | "7 7 209 209\n" + \ 1233 | "7 7 210 210\n" + \ 1234 | "7 7 211 211\n" + \ 1235 | "7 7 212 212\n" + \ 1236 | "7 7 213 213\n" + \ 1237 | "7 7 214 214\n" + \ 1238 | "7 7 215 215\n" + \ 1239 | "7 7 216 216\n" + \ 1240 | "7 7 217 217\n" + \ 1241 | "7 7 218 218\n" + \ 1242 | "7 7 219 219\n" + \ 1243 | "7 7 220 220\n" + \ 1244 | "7 7 221 221\n" + \ 1245 | "7 7 222 222\n" + \ 1246 | "7 7 223 223\n" + \ 1247 | "7 7 224 224\n" + \ 1248 | "7 7 225 225\n" + \ 1249 | "7 7 226 226\n" + \ 1250 | "7 7 227 227\n" + \ 1251 | "7 7 228 228\n" + \ 1252 | "7 7 229 229\n" + \ 1253 | "7 7 230 230\n" + \ 1254 | "7 7 231 231\n" + \ 1255 | "7 7 232 232\n" + \ 1256 | "7 7 233 233\n" + \ 1257 | "7 7 234 234\n" + \ 1258 | "7 7 235 235\n" + \ 1259 | "7 7 236 236\n" + \ 1260 | "7 7 237 237\n" + \ 1261 | "7 7 238 238\n" + \ 1262 | "7 7 239 239\n" + \ 1263 | "7 7 240 240\n" + \ 1264 | "7 7 241 241\n" + \ 1265 | "7 7 242 242\n" + \ 1266 | "7 7 243 243\n" + \ 1267 | "7 7 244 244\n" + \ 1268 | "7 7 245 245\n" + \ 1269 | "7 7 246 246\n" + \ 1270 | "7 7 247 247\n" + \ 1271 | "7 7 248 248\n" + \ 1272 | "7 7 249 249\n" + \ 1273 | "7 7 250 250\n" + \ 1274 | "7 7 251 251\n" + \ 1275 | "7 7 252 252\n" + \ 1276 | "7 7 253 253\n" + \ 1277 | "7 7 254 254\n" + \ 1278 | "7 7 255 255\n" + \ 1279 | "7" 1280 | 1281 | def regex2dfa(regex): 1282 | if regex not in _regex2dfa: 1283 | raise Exception("Regex not supported, please see fte/regex2dfa.py.") 1284 | return _regex2dfa[regex] 1285 | 1286 | --------------------------------------------------------------------------------