Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
perkinslr committed May 8, 2020
1 parent 1940c06 commit 5afa684
Show file tree
Hide file tree
Showing 21 changed files with 736 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
*__pycache__
*pyc
*pyo
*so
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ReverseProxy/tlsheader.so: tlsheader.c
gcc tlsheader.c -o ReverseProxy/tlsheader.so -fPIC -shared
2 changes: 2 additions & 0 deletions ReverseProxy/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .reactor import reactor
from . import _click
23 changes: 23 additions & 0 deletions ReverseProxy/_click.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import click
import functools
click.core._verify_python3_env = lambda *x: None

_main = click.core.BaseCommand.main


@functools.partial(setattr, click.core.BaseCommand, 'main')
def main(self, args: []=None, prog_name: str=None, complete_var: str=None, standalone_mode: bool=True, **extra):
if args and prog_name is None:
prog_name = args[0]
args = args[1:]
else:
args = None

if not standalone_mode:
ctx = self.make_context(prog_name, args)
with ctx:
self.invoke(ctx)
return ctx

return _main(self, args, prog_name, complete_var, standalone_mode, **extra)

25 changes: 25 additions & 0 deletions ReverseProxy/_proxy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import socket
import array


def send_fd(sock, fd, msg=b"0"):
return sock.sendmsg([msg], [(socket.SOL_SOCKET, socket.SCM_RIGHTS, array.array("i", [fd]))])


def recv_fd(sock, msglen=1):
fds = array.array("i")
msg, ancdata, flags, addr = sock.recvmsg(msglen, socket.CMSG_LEN(fds.itemsize))
for cmsg_level, cmsg_type, cmsg_data in ancdata:
if (cmsg_level == socket.SOL_SOCKET and cmsg_type == socket.SCM_RIGHTS):
# Append data, ignoring any truncated integers at the end.
fds.fromstring(cmsg_data[:len(cmsg_data) - (len(cmsg_data) % fds.itemsize)])
return list(fds)[0]


def send_fd_to(path, sock):
if not isinstance(sock, int):
sock = sock.fileno()
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.connect(path)
send_fd(s, sock)

Empty file added ReverseProxy/client/__init__.py
Empty file.
23 changes: 23 additions & 0 deletions ReverseProxy/client/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import sys

from twisted.internet.protocol import ServerFactory

from .configuration import Configuration
from .click import cli
from ..reactor import reactor
from .receiver import FDReceiverProtocol
import click.core
click.core.Exit = SystemExit

def __main__(argv=sys.argv):
from twisted.python.log import startLogging
startLogging(sys.stderr)
configuration: Configuration = cli.main(argv, standalone_mode=False).obj
reactor.listenUNIX(configuration.listenPort, ServerFactory.forProtocol(lambda: FDReceiverProtocol(configuration)))
return reactor.run()

if __name__=='__main__':
argv = sys.argv
argv[0] = __package__
raise SystemExit(__main__(argv))

41 changes: 41 additions & 0 deletions ReverseProxy/client/click.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from click import group, option, pass_context, pass_obj
from twisted.protocols import tls

from twisted.python.filepath import FilePath
import importlib.util


from .configuration import Configuration
from .ssl import makeContextFactory


@group(invoke_without_command=True, chain=True)
@option("--listenPort", type=str, required=True)
@pass_context
def cli(context, listenport):
context.obj = Configuration(listenport)


@cli.command()
@pass_obj
@option("--privkey", type=str, required=True)
@option("--cert", type=str, required=True)
@option("--chain", type=str, required=True)
def ssl(conf: Configuration, privkey, cert, chain):
conf.certificate = FilePath(cert)
conf.chain = FilePath(chain)
conf.privateKey = FilePath(privkey)
conf.sslFactory = makeContextFactory(conf)
if conf.factory:
conf.tlsFactory = tls.TLSMemoryBIOFactory(conf.sslFactory, False, conf.factory)


@cli.command()
@pass_obj
@option("--application", type=str, required=True)
def launch(conf: Configuration, application: str):
module, attribute = application.rsplit('.', 1)
mod = importlib.import_module(module)
conf.factory = getattr(mod, attribute)
if conf.sslFactory:
conf.tlsFactory = tls.TLSMemoryBIOFactory(conf.sslFactory, False, conf.factory)
12 changes: 12 additions & 0 deletions ReverseProxy/client/configuration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import attr


@attr.s()
class Configuration(object):
listenPort: str = attr.ib()
sslFactory = attr.ib(None)
privateKey = attr.ib(None)
certificate = attr.ib(None)
chain = attr.ib(None)
factory = attr.ib(None)
tlsFactory = attr.ib(None)
32 changes: 32 additions & 0 deletions ReverseProxy/client/receiver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from twisted.internet.interfaces import IFileDescriptorReceiver
from twisted.internet.protocol import Protocol
from zope.interface import implementer

import socket

from .. import reactor
from ..parsers import is_ssl


@implementer(IFileDescriptorReceiver)
class FDReceiverProtocol(Protocol):
descriptor = None

def __init__(self, conf):
self.conf = conf

def fileDescriptorReceived(self, descriptor):
# Record the descriptor sent to us
self.descriptor = descriptor
sock = socket.fromfd(descriptor, socket.AF_INET, socket.SOCK_STREAM)
try:
peek = sock.recv(1024, socket.MSG_PEEK)
finally:
sock.close()

if is_ssl(peek) and self.conf.tlsFactory:
reactor.adoptStreamConnection(descriptor, socket.AF_INET, self.conf.tlsFactory)

else:
reactor.adoptStreamConnection(descriptor, socket.AF_INET, self.conf.factory)

30 changes: 30 additions & 0 deletions ReverseProxy/client/ssl.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from twisted.internet import ssl


# noinspection PyMissingConstructor
class OpenSSLContextFactory(ssl.DefaultOpenSSLContextFactory):
def __init__(self, privateKeyFileName, certificateFileName, chainFileName):
self.sslmethod = ssl.SSL.SSLv23_METHOD
self.privateKeyFileName = privateKeyFileName
self.certificateFileName = certificateFileName
self.chainFileName = chainFileName
self.cacheContext()

def cacheContext(self):
if self._context is None:
ctx = ssl.SSL.Context(self.sslmethod)
# Disallow SSLv2! It's insecure! SSLv3 has been around since
# 1996. It's time to move on.
ctx.set_options(ssl.SSL.OP_NO_SSLv2)
ctx.set_options(ssl.SSL.OP_NO_SSLv3)
ctx.set_options(ssl.SSL.OP_NO_TLSv1)
ctx.set_options(ssl.SSL.OP_NO_TLSv1_1)
ctx.use_certificate_chain_file(self.chainFileName)
ctx.use_certificate_file(self.certificateFileName)
ctx.use_privatekey_file(self.privateKeyFileName)

self._context = ctx


def makeContextFactory(conf):
return OpenSSLContextFactory(conf.privateKey.path, conf.certificate.path, conf.chain.path)
51 changes: 51 additions & 0 deletions ReverseProxy/parsers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from twisted.python.filepath import FilePath
import ctypes
tlsheader = ctypes.cdll.LoadLibrary(FilePath(__file__).sibling('tlsheader.so').path)
tlsheader.parseHeader.argtypes = [ctypes.c_char_p, ctypes.c_int, ctypes.c_char_p]


def parse_tls(data):
out = ctypes.create_string_buffer(255)
datalen = len(data)
success = tlsheader.parseHeader(data, datalen, out)
return success, out.value


def parse_http(data):
data = data.replace(b'\r\n', b'\n').split(b'\n\n')[0]

if b'Host' not in data:
return None

host = data.split(b'Host:', 1)[1].split(b'\n')[0].strip().split(b':')[0]
return host


def parse_host(data):
if data[:3].isalpha():
try:
host = parse_http(data)
if host:
return host
success, host = parse_tls(data)
if success > -1:
return host
except:
return None
else:
success, host = parse_tls(data)
if success > -1:
return host
if success == -2:
return None

return parse_http(data)


def is_ssl(data):
success, host = parse_tls(data)
if success > -1 or success == -2:
return True


__all__ = ['parse_host', 'is_ssl']
6 changes: 6 additions & 0 deletions ReverseProxy/reactor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from twisted.internet.epollreactor import EPollReactor
from twisted.internet.main import installReactor
reactor: EPollReactor = EPollReactor()

installReactor(reactor)

Empty file added ReverseProxy/server/__init__.py
Empty file.
22 changes: 22 additions & 0 deletions ReverseProxy/server/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import sys

from .click import cli
from .proxy import run
import click.core
click.core.Exit = SystemExit


def __main__(argv=sys.argv):
from twisted.python.log import startLogging
startLogging(sys.stderr)
configuration = cli.main(argv, standalone_mode=False).obj
return run(configuration)


if __name__=='__main__':
argv = sys.argv
argv[0] = __package__

raise SystemExit(__main__(argv))


18 changes: 18 additions & 0 deletions ReverseProxy/server/click.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from click import group, option, pass_context, pass_obj
from .configuration import Configuration


@group(chain=True)
@option("--listenPort", default=443, type=int)
@option("--listenInterface", default="", type=str)
@pass_context
def cli(context, listenport, listeninterface):
context.obj = Configuration(listenport, listeninterface)


@cli.command()
@pass_obj
@option("--hostname", type=str, required=True)
@option("--socket", type=str, required=True)
def vhost(conf: Configuration, socket: str, hostname: str):
conf.addVHost(hostname, socket)
19 changes: 19 additions & 0 deletions ReverseProxy/server/configuration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import attr
import re


@attr.s()
class VHost(object):
socket: str = attr.ib()
hostname = attr.ib(converter=lambda x: re.compile(bytes(x, 'charmap')))


@attr.s()
class Configuration(object):
listenPort: int = attr.ib()
listenInterface: str = attr.ib()

vhosts: list = attr.ib(default=attr.Factory(list))

def addVHost(self, hostname, socket):
self.vhosts.append(VHost(socket, hostname))
Loading

0 comments on commit 5afa684

Please sign in to comment.