aboutsummaryrefslogtreecommitdiffstats
path: root/libmproxy/proxy
diff options
context:
space:
mode:
Diffstat (limited to 'libmproxy/proxy')
-rw-r--r--libmproxy/proxy/__init__.py11
-rw-r--r--libmproxy/proxy/config.py221
-rw-r--r--libmproxy/proxy/connection.py188
-rw-r--r--libmproxy/proxy/modes/__init__.py12
-rw-r--r--libmproxy/proxy/modes/http_proxy.py26
-rw-r--r--libmproxy/proxy/modes/reverse_proxy.py17
-rw-r--r--libmproxy/proxy/modes/socks_proxy.py60
-rw-r--r--libmproxy/proxy/modes/transparent_proxy.py24
-rw-r--r--libmproxy/proxy/primitives.py178
-rw-r--r--libmproxy/proxy/root_context.py93
-rw-r--r--libmproxy/proxy/server.py404
11 files changed, 376 insertions, 858 deletions
diff --git a/libmproxy/proxy/__init__.py b/libmproxy/proxy/__init__.py
index f33d323b..d5297cb1 100644
--- a/libmproxy/proxy/__init__.py
+++ b/libmproxy/proxy/__init__.py
@@ -1,2 +1,9 @@
-from .primitives import *
-from .config import ProxyConfig, process_proxy_options
+from __future__ import (absolute_import, print_function, division)
+
+from .server import ProxyServer, DummyServer
+from .config import ProxyConfig
+
+__all__ = [
+ "ProxyServer", "DummyServer",
+ "ProxyConfig",
+]
diff --git a/libmproxy/proxy/config.py b/libmproxy/proxy/config.py
index ec91a6e0..2a1b84cb 100644
--- a/libmproxy/proxy/config.py
+++ b/libmproxy/proxy/config.py
@@ -1,26 +1,31 @@
-from __future__ import absolute_import
+from __future__ import (absolute_import, print_function, division)
+import collections
import os
import re
from OpenSSL import SSL
-import netlib
-from netlib import http, certutils, tcp
+from netlib import certutils, tcp
from netlib.http import authentication
+from netlib.tcp import Address, sslversion_choices
-from .. import utils, platform, version
-from .primitives import RegularProxyMode, SpoofMode, SSLSpoofMode, TransparentProxyMode, UpstreamProxyMode, ReverseProxyMode, Socks5ProxyMode
+from .. import utils, platform
-TRANSPARENT_SSL_PORTS = [443, 8443]
CONF_BASENAME = "mitmproxy"
CA_DIR = "~/.mitmproxy"
+# We manually need to specify this, otherwise OpenSSL may select a non-HTTP2 cipher by default.
+# https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=apache-2.2.15&openssl=1.0.2&hsts=yes&profile=old
+DEFAULT_CLIENT_CIPHERS = "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA"
+
class HostMatcher(object):
- def __init__(self, patterns=[]):
+ def __init__(self, patterns=tuple()):
self.patterns = list(patterns)
self.regexes = [re.compile(p, re.IGNORECASE) for p in self.patterns]
def __call__(self, address):
+ if not address:
+ return False
address = tcp.Address.wrap(address)
host = "%s:%s" % (address.host, address.port)
if any(rex.search(host) for rex in self.regexes):
@@ -32,61 +37,44 @@ class HostMatcher(object):
return bool(self.patterns)
+ServerSpec = collections.namedtuple("ServerSpec", "scheme address")
+
+
class ProxyConfig:
def __init__(
self,
host='',
port=8080,
- server_version=version.NAMEVERSION,
cadir=CA_DIR,
clientcerts=None,
no_upstream_cert=False,
body_size_limit=None,
- mode=None,
+ mode="regular",
upstream_server=None,
- http_form_in=None,
- http_form_out=None,
authenticator=None,
- ignore_hosts=[],
- tcp_hosts=[],
+ ignore_hosts=tuple(),
+ tcp_hosts=tuple(),
ciphers_client=None,
ciphers_server=None,
- certs=[],
- ssl_version_client=tcp.SSL_DEFAULT_METHOD,
- ssl_version_server=tcp.SSL_DEFAULT_METHOD,
- ssl_ports=TRANSPARENT_SSL_PORTS,
- spoofed_ssl_port=None,
+ certs=tuple(),
+ ssl_version_client="secure",
+ ssl_version_server="secure",
ssl_verify_upstream_cert=False,
- ssl_upstream_trusted_cadir=None,
- ssl_upstream_trusted_ca=None
+ ssl_verify_upstream_trusted_cadir=None,
+ ssl_verify_upstream_trusted_ca=None,
):
self.host = host
self.port = port
- self.server_version = server_version
self.ciphers_client = ciphers_client
self.ciphers_server = ciphers_server
self.clientcerts = clientcerts
self.no_upstream_cert = no_upstream_cert
self.body_size_limit = body_size_limit
-
- if mode == "transparent":
- self.mode = TransparentProxyMode(platform.resolver(), ssl_ports)
- elif mode == "socks5":
- self.mode = Socks5ProxyMode(ssl_ports)
- elif mode == "reverse":
- self.mode = ReverseProxyMode(upstream_server)
- elif mode == "upstream":
- self.mode = UpstreamProxyMode(upstream_server)
- elif mode == "spoof":
- self.mode = SpoofMode()
- elif mode == "sslspoof":
- self.mode = SSLSpoofMode(spoofed_ssl_port)
+ self.mode = mode
+ if upstream_server:
+ self.upstream_server = ServerSpec(upstream_server[0], Address.wrap(upstream_server[1]))
else:
- self.mode = RegularProxyMode()
-
- # Handle manual overrides of the http forms
- self.mode.http_form_in = http_form_in or self.mode.http_form_in
- self.mode.http_form_out = http_form_out or self.mode.http_form_out
+ self.upstream_server = None
self.check_ignore = HostMatcher(ignore_hosts)
self.check_tcp = HostMatcher(tcp_hosts)
@@ -94,41 +82,33 @@ class ProxyConfig:
self.cadir = os.path.expanduser(cadir)
self.certstore = certutils.CertStore.from_store(
self.cadir,
- CONF_BASENAME)
+ CONF_BASENAME
+ )
for spec, cert in certs:
self.certstore.add_cert_file(spec, cert)
- self.ssl_ports = ssl_ports
- if isinstance(ssl_version_client, int):
- self.openssl_method_client = ssl_version_client
- else:
- self.openssl_method_client = tcp.SSL_VERSIONS[ssl_version_client]
- if isinstance(ssl_version_server, int):
- self.openssl_method_server = ssl_version_server
- else:
- self.openssl_method_server = tcp.SSL_VERSIONS[ssl_version_server]
+ self.openssl_method_client, self.openssl_options_client = \
+ sslversion_choices[ssl_version_client]
+ self.openssl_method_server, self.openssl_options_server = \
+ sslversion_choices[ssl_version_server]
if ssl_verify_upstream_cert:
self.openssl_verification_mode_server = SSL.VERIFY_PEER
else:
self.openssl_verification_mode_server = SSL.VERIFY_NONE
- self.openssl_trusted_cadir_server = ssl_upstream_trusted_cadir
- self.openssl_trusted_ca_server = ssl_upstream_trusted_ca
-
- self.openssl_options_client = tcp.SSL_DEFAULT_OPTIONS
- self.openssl_options_server = tcp.SSL_DEFAULT_OPTIONS
+ self.openssl_trusted_cadir_server = ssl_verify_upstream_trusted_cadir
+ self.openssl_trusted_ca_server = ssl_verify_upstream_trusted_ca
def process_proxy_options(parser, options):
body_size_limit = utils.parse_size(options.body_size_limit)
c = 0
- mode, upstream_server, spoofed_ssl_port = None, None, None
+ mode, upstream_server = "regular", None
if options.transparent_proxy:
c += 1
if not platform.resolver:
- return parser.error(
- "Transparent mode not supported on this platform.")
+ return parser.error("Transparent mode not supported on this platform.")
mode = "transparent"
if options.socks_proxy:
c += 1
@@ -141,32 +121,33 @@ def process_proxy_options(parser, options):
c += 1
mode = "upstream"
upstream_server = options.upstream_proxy
- if options.spoof_mode:
- c += 1
- mode = "spoof"
- if options.ssl_spoof_mode:
- c += 1
- mode = "sslspoof"
- spoofed_ssl_port = options.spoofed_ssl_port
if c > 1:
return parser.error(
"Transparent, SOCKS5, reverse and upstream proxy mode "
- "are mutually exclusive.")
+ "are mutually exclusive. Read the docs on proxy modes to understand why."
+ )
if options.clientcerts:
options.clientcerts = os.path.expanduser(options.clientcerts)
- if not os.path.exists(
- options.clientcerts) or not os.path.isdir(
- options.clientcerts):
+ if not os.path.exists(options.clientcerts) or not os.path.isdir(options.clientcerts):
return parser.error(
"Client certificate directory does not exist or is not a directory: %s" %
- options.clientcerts)
+ options.clientcerts
+ )
+
+ if options.auth_nonanonymous or options.auth_singleuser or options.auth_htpasswd:
+
+ if options.socks_proxy:
+ return parser.error(
+ "Proxy Authentication not supported in SOCKS mode. "
+ "https://github.com/mitmproxy/mitmproxy/issues/738"
+ )
- if (options.auth_nonanonymous or options.auth_singleuser or options.auth_htpasswd):
if options.auth_singleuser:
if len(options.auth_singleuser.split(':')) != 2:
return parser.error(
- "Invalid single-user specification. Please use the format username:password")
+ "Invalid single-user specification. Please use the format username:password"
+ )
username, password = options.auth_singleuser.split(':')
password_manager = authentication.PassManSingleUser(username, password)
elif options.auth_nonanonymous:
@@ -191,12 +172,6 @@ def process_proxy_options(parser, options):
parser.error("Certificate file does not exist: %s" % parts[1])
certs.append(parts)
- ssl_ports = options.ssl_ports
- if options.ssl_ports != TRANSPARENT_SSL_PORTS:
- # arparse appends to default value by default, strip that off.
- # see http://bugs.python.org/issue16399
- ssl_ports = ssl_ports[len(TRANSPARENT_SSL_PORTS):]
-
return ProxyConfig(
host=options.addr,
port=options.port,
@@ -206,99 +181,15 @@ def process_proxy_options(parser, options):
body_size_limit=body_size_limit,
mode=mode,
upstream_server=upstream_server,
- http_form_in=options.http_form_in,
- http_form_out=options.http_form_out,
ignore_hosts=options.ignore_hosts,
tcp_hosts=options.tcp_hosts,
authenticator=authenticator,
ciphers_client=options.ciphers_client,
ciphers_server=options.ciphers_server,
- certs=certs,
+ certs=tuple(certs),
ssl_version_client=options.ssl_version_client,
ssl_version_server=options.ssl_version_server,
- ssl_ports=ssl_ports,
- spoofed_ssl_port=spoofed_ssl_port,
ssl_verify_upstream_cert=options.ssl_verify_upstream_cert,
- ssl_upstream_trusted_cadir=options.ssl_upstream_trusted_cadir,
- ssl_upstream_trusted_ca=options.ssl_upstream_trusted_ca
- )
-
-
-def ssl_option_group(parser):
- group = parser.add_argument_group("SSL")
- group.add_argument(
- "--cert",
- dest='certs',
- default=[],
- type=str,
- metavar="SPEC",
- action="append",
- help='Add an SSL certificate. SPEC is of the form "[domain=]path". '
- 'The domain may include a wildcard, and is equal to "*" if not specified. '
- 'The file at path is a certificate in PEM format. If a private key is included in the PEM, '
- 'it is used, else the default key in the conf dir is used. '
- 'The PEM file should contain the full certificate chain, with the leaf certificate as the first entry. '
- 'Can be passed multiple times.')
- group.add_argument(
- "--ciphers-client", action="store",
- type=str, dest="ciphers_client", default=None,
- help="Set supported ciphers for client connections. (OpenSSL Syntax)"
- )
- group.add_argument(
- "--ciphers-server", action="store",
- type=str, dest="ciphers_server", default=None,
- help="Set supported ciphers for server connections. (OpenSSL Syntax)"
- )
- group.add_argument(
- "--client-certs", action="store",
- type=str, dest="clientcerts", default=None,
- help="Client certificate directory."
- )
- group.add_argument(
- "--no-upstream-cert", default=False,
- action="store_true", dest="no_upstream_cert",
- help="Don't connect to upstream server to look up certificate details."
- )
- group.add_argument(
- "--verify-upstream-cert", default=False,
- action="store_true", dest="ssl_verify_upstream_cert",
- help="Verify upstream server SSL/TLS certificates and fail if invalid "
- "or not present."
- )
- group.add_argument(
- "--upstream-trusted-cadir", default=None, action="store",
- dest="ssl_upstream_trusted_cadir",
- help="Path to a directory of trusted CA certificates for upstream "
- "server verification prepared using the c_rehash tool."
- )
- group.add_argument(
- "--upstream-trusted-ca", default=None, action="store",
- dest="ssl_upstream_trusted_ca",
- help="Path to a PEM formatted trusted CA certificate."
- )
- group.add_argument(
- "--ssl-port",
- action="append",
- type=int,
- dest="ssl_ports",
- default=list(TRANSPARENT_SSL_PORTS),
- metavar="PORT",
- help="Can be passed multiple times. Specify destination ports which are assumed to be SSL. "
- "Defaults to %s." %
- str(TRANSPARENT_SSL_PORTS))
- group.add_argument(
- "--ssl-version-client", dest="ssl_version_client", type=str, default=tcp.SSL_DEFAULT_VERSION,
- choices=tcp.SSL_VERSIONS.keys(),
- help=""""
- Use a specified protocol for client connections:
- TLSv1.2, TLSv1.1, TLSv1, SSLv3, SSLv2, SSLv23.
- Default to SSLv23."""
- )
- group.add_argument(
- "--ssl-version-server", dest="ssl_version_server", type=str, default=tcp.SSL_DEFAULT_VERSION,
- choices=tcp.SSL_VERSIONS.keys(),
- help=""""
- Use a specified protocol for server connections:
- TLSv1.2, TLSv1.1, TLSv1, SSLv3, SSLv2, SSLv23.
- Default to SSLv23."""
- )
+ ssl_verify_upstream_trusted_cadir=options.ssl_verify_upstream_trusted_cadir,
+ ssl_verify_upstream_trusted_ca=options.ssl_verify_upstream_trusted_ca
+ ) \ No newline at end of file
diff --git a/libmproxy/proxy/connection.py b/libmproxy/proxy/connection.py
deleted file mode 100644
index 9e03157a..00000000
--- a/libmproxy/proxy/connection.py
+++ /dev/null
@@ -1,188 +0,0 @@
-from __future__ import absolute_import
-import copy
-import os
-from netlib import tcp, certutils
-from .. import stateobject, utils
-
-
-class ClientConnection(tcp.BaseHandler, stateobject.StateObject):
- def __init__(self, client_connection, address, server):
- # Eventually, this object is restored from state. We don't have a
- # connection then.
- if client_connection:
- tcp.BaseHandler.__init__(self, client_connection, address, server)
- else:
- self.connection = None
- self.server = None
- self.wfile = None
- self.rfile = None
- self.address = None
- self.clientcert = None
- self.ssl_established = None
-
- self.timestamp_start = utils.timestamp()
- self.timestamp_end = None
- self.timestamp_ssl_setup = None
- self.protocol = None
-
- def __repr__(self):
- return "<ClientConnection: {ssl}{host}:{port}>".format(
- ssl="[ssl] " if self.ssl_established else "",
- host=self.address.host,
- port=self.address.port
- )
-
- _stateobject_attributes = dict(
- ssl_established=bool,
- timestamp_start=float,
- timestamp_end=float,
- timestamp_ssl_setup=float
- )
-
- def get_state(self, short=False):
- d = super(ClientConnection, self).get_state(short)
- d.update(
- address={
- "address": self.address(),
- "use_ipv6": self.address.use_ipv6},
- clientcert=self.cert.to_pem() if self.clientcert else None)
- return d
-
- def load_state(self, state):
- super(ClientConnection, self).load_state(state)
- self.address = tcp.Address(
- **state["address"]) if state["address"] else None
- self.clientcert = certutils.SSLCert.from_pem(
- state["clientcert"]) if state["clientcert"] else None
-
- def copy(self):
- return copy.copy(self)
-
- def send(self, message):
- if isinstance(message, list):
- message = b''.join(message)
- self.wfile.write(message)
- self.wfile.flush()
-
- @classmethod
- def from_state(cls, state):
- f = cls(None, tuple(), None)
- f.load_state(state)
- return f
-
- def convert_to_ssl(self, *args, **kwargs):
- # TODO: read ALPN from server and select same proto for client conn
- # alpn_select = 'h2'
- # def alpn_select_callback(conn_, options):
- # if alpn_select in options:
- # return bytes(alpn_select)
- # else: # pragma no cover
- # return options[0]
- # tcp.BaseHandler.convert_to_ssl(self, alpn_select=alpn_select_callback, *args, **kwargs)
-
- tcp.BaseHandler.convert_to_ssl(self, *args, **kwargs)
- self.timestamp_ssl_setup = utils.timestamp()
-
- def finish(self):
- tcp.BaseHandler.finish(self)
- self.timestamp_end = utils.timestamp()
-
-
-class ServerConnection(tcp.TCPClient, stateobject.StateObject):
- def __init__(self, address):
- tcp.TCPClient.__init__(self, address)
-
- self.state = [] # a list containing (conntype, state) tuples
- self.timestamp_start = None
- self.timestamp_end = None
- self.timestamp_tcp_setup = None
- self.timestamp_ssl_setup = None
- self.protocol = None
-
- def __repr__(self):
- if self.ssl_established and self.sni:
- ssl = "[ssl: {0}] ".format(self.sni)
- elif self.ssl_established:
- ssl = "[ssl] "
- else:
- ssl = ""
- return "<ServerConnection: {ssl}{host}:{port}>".format(
- ssl=ssl,
- host=self.address.host,
- port=self.address.port
- )
-
- _stateobject_attributes = dict(
- state=list,
- timestamp_start=float,
- timestamp_end=float,
- timestamp_tcp_setup=float,
- timestamp_ssl_setup=float,
- address=tcp.Address,
- source_address=tcp.Address,
- cert=certutils.SSLCert,
- ssl_established=bool,
- sni=str
- )
- _stateobject_long_attributes = {"cert"}
-
- def get_state(self, short=False):
- d = super(ServerConnection, self).get_state(short)
- d.update(
- address={"address": self.address(),
- "use_ipv6": self.address.use_ipv6},
- source_address= ({"address": self.source_address(),
- "use_ipv6": self.source_address.use_ipv6} if self.source_address else None),
- cert=self.cert.to_pem() if self.cert else None
- )
- return d
-
- def load_state(self, state):
- super(ServerConnection, self).load_state(state)
-
- self.address = tcp.Address(
- **state["address"]) if state["address"] else None
- self.source_address = tcp.Address(
- **state["source_address"]) if state["source_address"] else None
- self.cert = certutils.SSLCert.from_pem(
- state["cert"]) if state["cert"] else None
-
- @classmethod
- def from_state(cls, state):
- f = cls(tuple())
- f.load_state(state)
- return f
-
- def copy(self):
- return copy.copy(self)
-
- def connect(self):
- self.timestamp_start = utils.timestamp()
- tcp.TCPClient.connect(self)
- self.timestamp_tcp_setup = utils.timestamp()
-
- def send(self, message):
- if isinstance(message, list):
- message = b''.join(message)
- self.wfile.write(message)
- self.wfile.flush()
-
- def establish_ssl(self, clientcerts, sni, **kwargs):
- clientcert = None
- if clientcerts:
- path = os.path.join(
- clientcerts,
- self.address.host.encode("idna")) + ".pem"
- if os.path.exists(path):
- clientcert = path
-
- # TODO: read ALPN from client and use same list for server conn
- # self.convert_to_ssl(cert=clientcert, sni=sni, alpn_protos=[netlib.http.http2.HTTP2Protocol.ALPN_PROTO_H2], **kwargs)
-
- self.convert_to_ssl(cert=clientcert, sni=sni, **kwargs)
- self.sni = sni
- self.timestamp_ssl_setup = utils.timestamp()
-
- def finish(self):
- tcp.TCPClient.finish(self)
- self.timestamp_end = utils.timestamp()
diff --git a/libmproxy/proxy/modes/__init__.py b/libmproxy/proxy/modes/__init__.py
new file mode 100644
index 00000000..f014ed98
--- /dev/null
+++ b/libmproxy/proxy/modes/__init__.py
@@ -0,0 +1,12 @@
+from __future__ import (absolute_import, print_function, division)
+from .http_proxy import HttpProxy, HttpUpstreamProxy
+from .reverse_proxy import ReverseProxy
+from .socks_proxy import Socks5Proxy
+from .transparent_proxy import TransparentProxy
+
+__all__ = [
+ "HttpProxy", "HttpUpstreamProxy",
+ "ReverseProxy",
+ "Socks5Proxy",
+ "TransparentProxy"
+]
diff --git a/libmproxy/proxy/modes/http_proxy.py b/libmproxy/proxy/modes/http_proxy.py
new file mode 100644
index 00000000..90c54cc6
--- /dev/null
+++ b/libmproxy/proxy/modes/http_proxy.py
@@ -0,0 +1,26 @@
+from __future__ import (absolute_import, print_function, division)
+
+from ...protocol import Layer, ServerConnectionMixin
+
+
+class HttpProxy(Layer, ServerConnectionMixin):
+ def __call__(self):
+ layer = self.ctx.next_layer(self)
+ try:
+ layer()
+ finally:
+ if self.server_conn:
+ self._disconnect()
+
+
+class HttpUpstreamProxy(Layer, ServerConnectionMixin):
+ def __init__(self, ctx, server_address):
+ super(HttpUpstreamProxy, self).__init__(ctx, server_address=server_address)
+
+ def __call__(self):
+ layer = self.ctx.next_layer(self)
+ try:
+ layer()
+ finally:
+ if self.server_conn:
+ self._disconnect()
diff --git a/libmproxy/proxy/modes/reverse_proxy.py b/libmproxy/proxy/modes/reverse_proxy.py
new file mode 100644
index 00000000..b57ac5eb
--- /dev/null
+++ b/libmproxy/proxy/modes/reverse_proxy.py
@@ -0,0 +1,17 @@
+from __future__ import (absolute_import, print_function, division)
+
+from ...protocol import Layer, ServerConnectionMixin
+
+
+class ReverseProxy(Layer, ServerConnectionMixin):
+ def __init__(self, ctx, server_address, server_tls):
+ super(ReverseProxy, self).__init__(ctx, server_address=server_address)
+ self.server_tls = server_tls
+
+ def __call__(self):
+ layer = self.ctx.next_layer(self)
+ try:
+ layer()
+ finally:
+ if self.server_conn:
+ self._disconnect()
diff --git a/libmproxy/proxy/modes/socks_proxy.py b/libmproxy/proxy/modes/socks_proxy.py
new file mode 100644
index 00000000..ebaf939e
--- /dev/null
+++ b/libmproxy/proxy/modes/socks_proxy.py
@@ -0,0 +1,60 @@
+from __future__ import (absolute_import, print_function, division)
+
+from netlib import socks
+from netlib.tcp import NetLibError
+
+from ...exceptions import Socks5Exception
+from ...protocol import Layer, ServerConnectionMixin
+
+
+class Socks5Proxy(Layer, ServerConnectionMixin):
+ def __call__(self):
+ try:
+ # Parse Client Greeting
+ client_greet = socks.ClientGreeting.from_file(self.client_conn.rfile, fail_early=True)
+ client_greet.assert_socks5()
+ if socks.METHOD.NO_AUTHENTICATION_REQUIRED not in client_greet.methods:
+ raise socks.SocksError(
+ socks.METHOD.NO_ACCEPTABLE_METHODS,
+ "mitmproxy only supports SOCKS without authentication"
+ )
+
+ # Send Server Greeting
+ server_greet = socks.ServerGreeting(
+ socks.VERSION.SOCKS5,
+ socks.METHOD.NO_AUTHENTICATION_REQUIRED
+ )
+ server_greet.to_file(self.client_conn.wfile)
+ self.client_conn.wfile.flush()
+
+ # Parse Connect Request
+ connect_request = socks.Message.from_file(self.client_conn.rfile)
+ connect_request.assert_socks5()
+ if connect_request.msg != socks.CMD.CONNECT:
+ raise socks.SocksError(
+ socks.REP.COMMAND_NOT_SUPPORTED,
+ "mitmproxy only supports SOCKS5 CONNECT."
+ )
+
+ # We always connect lazily, but we need to pretend to the client that we connected.
+ connect_reply = socks.Message(
+ socks.VERSION.SOCKS5,
+ socks.REP.SUCCEEDED,
+ connect_request.atyp,
+ # dummy value, we don't have an upstream connection yet.
+ connect_request.addr
+ )
+ connect_reply.to_file(self.client_conn.wfile)
+ self.client_conn.wfile.flush()
+
+ except (socks.SocksError, NetLibError) as e:
+ raise Socks5Exception("SOCKS5 mode failure: %s" % repr(e), e)
+
+ self.server_conn.address = connect_request.addr
+
+ layer = self.ctx.next_layer(self)
+ try:
+ layer()
+ finally:
+ if self.server_conn:
+ self._disconnect()
diff --git a/libmproxy/proxy/modes/transparent_proxy.py b/libmproxy/proxy/modes/transparent_proxy.py
new file mode 100644
index 00000000..96ad86c4
--- /dev/null
+++ b/libmproxy/proxy/modes/transparent_proxy.py
@@ -0,0 +1,24 @@
+from __future__ import (absolute_import, print_function, division)
+
+from ... import platform
+from ...exceptions import ProtocolException
+from ...protocol import Layer, ServerConnectionMixin
+
+
+class TransparentProxy(Layer, ServerConnectionMixin):
+ def __init__(self, ctx):
+ super(TransparentProxy, self).__init__(ctx)
+ self.resolver = platform.resolver()
+
+ def __call__(self):
+ try:
+ self.server_conn.address = self.resolver.original_addr(self.client_conn.connection)
+ except Exception as e:
+ raise ProtocolException("Transparent mode failure: %s" % repr(e), e)
+
+ layer = self.ctx.next_layer(self)
+ try:
+ layer()
+ finally:
+ if self.server_conn:
+ self._disconnect()
diff --git a/libmproxy/proxy/primitives.py b/libmproxy/proxy/primitives.py
deleted file mode 100644
index 923f84ca..00000000
--- a/libmproxy/proxy/primitives.py
+++ /dev/null
@@ -1,178 +0,0 @@
-from __future__ import absolute_import
-from netlib import socks, tcp
-
-
-class ProxyError(Exception):
- def __init__(self, code, message, headers=None):
- super(ProxyError, self).__init__(message)
- self.code, self.headers = code, headers
-
-
-class ProxyServerError(Exception):
- pass
-
-
-class ProxyMode(object):
- http_form_in = None
- http_form_out = None
-
- def get_upstream_server(self, client_conn):
- """
- Returns the address of the server to connect to.
- Returns None if the address needs to be determined on the protocol level (regular proxy mode)
- """
- raise NotImplementedError() # pragma: nocover
-
- @property
- def name(self):
- return self.__class__.__name__.replace("ProxyMode", "").lower()
-
- def __str__(self):
- return self.name
-
- def __eq__(self, other):
- """
- Allow comparisions with "regular" etc.
- """
- if isinstance(other, ProxyMode):
- return self is other
- else:
- return self.name == other
-
- def __ne__(self, other):
- return not self.__eq__(other)
-
-
-class RegularProxyMode(ProxyMode):
- http_form_in = "absolute"
- http_form_out = "relative"
-
- def get_upstream_server(self, client_conn):
- return None
-
-
-class SpoofMode(ProxyMode):
- http_form_in = "relative"
- http_form_out = "relative"
-
- def get_upstream_server(self, client_conn):
- return None
-
- @property
- def name(self):
- return "spoof"
-
-
-class SSLSpoofMode(ProxyMode):
- http_form_in = "relative"
- http_form_out = "relative"
-
- def __init__(self, sslport):
- self.sslport = sslport
-
- def get_upstream_server(self, client_conn):
- return None
-
- @property
- def name(self):
- return "sslspoof"
-
-
-class TransparentProxyMode(ProxyMode):
- http_form_in = "relative"
- http_form_out = "relative"
-
- def __init__(self, resolver, sslports):
- self.resolver = resolver
- self.sslports = sslports
-
- def get_upstream_server(self, client_conn):
- try:
- dst = self.resolver.original_addr(client_conn.connection)
- except Exception as e:
- raise ProxyError(502, "Transparent mode failure: %s" % str(e))
-
- if dst[1] in self.sslports:
- ssl = True
- else:
- ssl = False
- return [ssl, ssl] + list(dst)
-
-
-class Socks5ProxyMode(ProxyMode):
- http_form_in = "relative"
- http_form_out = "relative"
-
- def __init__(self, sslports):
- self.sslports = sslports
-
- def get_upstream_server(self, client_conn):
- try:
- # Parse Client Greeting
- client_greet = socks.ClientGreeting.from_file(client_conn.rfile, fail_early=True)
- client_greet.assert_socks5()
- if socks.METHOD.NO_AUTHENTICATION_REQUIRED not in client_greet.methods:
- raise socks.SocksError(
- socks.METHOD.NO_ACCEPTABLE_METHODS,
- "mitmproxy only supports SOCKS without authentication"
- )
-
- # Send Server Greeting
- server_greet = socks.ServerGreeting(
- socks.VERSION.SOCKS5,
- socks.METHOD.NO_AUTHENTICATION_REQUIRED
- )
- server_greet.to_file(client_conn.wfile)
- client_conn.wfile.flush()
-
- # Parse Connect Request
- connect_request = socks.Message.from_file(client_conn.rfile)
- connect_request.assert_socks5()
- if connect_request.msg != socks.CMD.CONNECT:
- raise socks.SocksError(
- socks.REP.COMMAND_NOT_SUPPORTED,
- "mitmproxy only supports SOCKS5 CONNECT."
- )
-
- # We do not connect here yet, as the clientconnect event has not
- # been handled yet.
-
- connect_reply = socks.Message(
- socks.VERSION.SOCKS5,
- socks.REP.SUCCEEDED,
- connect_request.atyp,
- # dummy value, we don't have an upstream connection yet.
- connect_request.addr
- )
- connect_reply.to_file(client_conn.wfile)
- client_conn.wfile.flush()
-
- ssl = bool(connect_request.addr.port in self.sslports)
- return ssl, ssl, connect_request.addr.host, connect_request.addr.port
-
- except (socks.SocksError, tcp.NetLibError) as e:
- raise ProxyError(502, "SOCKS5 mode failure: %s" % str(e))
-
-
-class _ConstDestinationProxyMode(ProxyMode):
- def __init__(self, dst):
- self.dst = dst
-
- def get_upstream_server(self, client_conn):
- return self.dst
-
-
-class ReverseProxyMode(_ConstDestinationProxyMode):
- http_form_in = "relative"
- http_form_out = "relative"
-
-
-class UpstreamProxyMode(_ConstDestinationProxyMode):
- http_form_in = "absolute"
- http_form_out = "absolute"
-
-
-class Log:
- def __init__(self, msg, level="info"):
- self.msg = msg
- self.level = level
diff --git a/libmproxy/proxy/root_context.py b/libmproxy/proxy/root_context.py
new file mode 100644
index 00000000..35909612
--- /dev/null
+++ b/libmproxy/proxy/root_context.py
@@ -0,0 +1,93 @@
+from __future__ import (absolute_import, print_function, division)
+
+from netlib.http.http1 import HTTP1Protocol
+from netlib.http.http2 import HTTP2Protocol
+
+from ..protocol import (
+ RawTCPLayer, TlsLayer, Http1Layer, Http2Layer, is_tls_record_magic, ServerConnectionMixin
+)
+from .modes import HttpProxy, HttpUpstreamProxy, ReverseProxy
+
+
+class RootContext(object):
+ """
+ The outmost context provided to the root layer.
+ As a consequence, every layer has .client_conn, .channel, .next_layer() and .config.
+ """
+
+ def __init__(self, client_conn, config, channel):
+ self.client_conn = client_conn # Client Connection
+ self.channel = channel # provides .ask() method to communicate with FlowMaster
+ self.config = config # Proxy Configuration
+
+ def next_layer(self, top_layer):
+ """
+ This function determines the next layer in the protocol stack.
+
+ Arguments:
+ top_layer: the current top layer.
+
+ Returns:
+ The next layer
+ """
+
+ # 1. Check for --ignore.
+ if self.config.check_ignore(top_layer.server_conn.address):
+ return RawTCPLayer(top_layer, logging=False)
+
+ d = top_layer.client_conn.rfile.peek(3)
+ client_tls = is_tls_record_magic(d)
+
+ # 2. Always insert a TLS layer, even if there's neither client nor server tls.
+ # An inline script may upgrade from http to https,
+ # in which case we need some form of TLS layer.
+ if isinstance(top_layer, ReverseProxy):
+ return TlsLayer(top_layer, client_tls, top_layer.server_tls)
+ if isinstance(top_layer, ServerConnectionMixin):
+ return TlsLayer(top_layer, client_tls, client_tls)
+
+ # 3. In Http Proxy mode and Upstream Proxy mode, the next layer is fixed.
+ if isinstance(top_layer, TlsLayer):
+ if isinstance(top_layer.ctx, HttpProxy):
+ return Http1Layer(top_layer, "regular")
+ if isinstance(top_layer.ctx, HttpUpstreamProxy):
+ return Http1Layer(top_layer, "upstream")
+
+ # 4. Check for other TLS cases (e.g. after CONNECT).
+ if client_tls:
+ return TlsLayer(top_layer, True, True)
+
+ # 4. Check for --tcp
+ if self.config.check_tcp(top_layer.server_conn.address):
+ return RawTCPLayer(top_layer)
+
+ # 5. Check for TLS ALPN (HTTP1/HTTP2)
+ if isinstance(top_layer, TlsLayer):
+ alpn = top_layer.client_conn.get_alpn_proto_negotiated()
+ if alpn == HTTP2Protocol.ALPN_PROTO_H2:
+ return Http2Layer(top_layer, 'transparent')
+ if alpn == HTTP1Protocol.ALPN_PROTO_HTTP1:
+ return Http1Layer(top_layer, 'transparent')
+
+ # 6. Assume HTTP1 by default
+ return Http1Layer(top_layer, 'transparent')
+
+ # In a future version, we want to implement TCP passthrough as the last fallback,
+ # but we don't have the UI part ready for that.
+ #
+ # d = top_layer.client_conn.rfile.peek(3)
+ # is_ascii = (
+ # len(d) == 3 and
+ # # better be safe here and don't expect uppercase...
+ # all(x in string.ascii_letters for x in d)
+ # )
+ # # TODO: This could block if there are not enough bytes available?
+ # d = top_layer.client_conn.rfile.peek(len(HTTP2Protocol.CLIENT_CONNECTION_PREFACE))
+ # is_http2_magic = (d == HTTP2Protocol.CLIENT_CONNECTION_PREFACE)
+
+ @property
+ def layers(self):
+ return []
+
+ def __repr__(self):
+ return "RootContext"
diff --git a/libmproxy/proxy/server.py b/libmproxy/proxy/server.py
index 2f6ee061..e9e8df09 100644
--- a/libmproxy/proxy/server.py
+++ b/libmproxy/proxy/server.py
@@ -1,13 +1,17 @@
-from __future__ import absolute_import
+from __future__ import (absolute_import, print_function, division)
+import traceback
+import sys
import socket
-from OpenSSL import SSL
from netlib import tcp
-from .primitives import ProxyServerError, Log, ProxyError
-from .connection import ClientConnection, ServerConnection
-from ..protocol.handle import protocol_handler
-from .. import version
+from netlib.http.http1 import HTTP1Protocol
+from netlib.tcp import NetLibError
+from ..exceptions import ProtocolException, ServerException
+from ..protocol import Log, Kill
+from ..models import ClientConnection, make_error_response
+from .modes import HttpUpstreamProxy, HttpProxy, ReverseProxy, TransparentProxy, Socks5Proxy
+from .root_context import RootContext
class DummyServer:
@@ -33,9 +37,9 @@ class ProxyServer(tcp.TCPServer):
"""
self.config = config
try:
- tcp.TCPServer.__init__(self, (config.host, config.port))
- except socket.error as v:
- raise ProxyServerError('Error starting proxy server: ' + repr(v))
+ super(ProxyServer, self).__init__((config.host, config.port))
+ except socket.error as e:
+ raise ServerException('Error starting proxy server: ' + repr(e), e)
self.channel = None
def start_slave(self, klass, channel):
@@ -47,340 +51,90 @@ class ProxyServer(tcp.TCPServer):
def handle_client_connection(self, conn, client_address):
h = ConnectionHandler(
- self.config,
conn,
client_address,
- self,
- self.channel)
+ self.config,
+ self.channel
+ )
h.handle()
- h.finish()
-class ConnectionHandler:
- def __init__(
- self,
- config,
- client_connection,
- client_address,
- server,
- channel):
+class ConnectionHandler(object):
+ def __init__(self, client_conn, client_address, config, channel):
self.config = config
"""@type: libmproxy.proxy.config.ProxyConfig"""
self.client_conn = ClientConnection(
- client_connection,
+ client_conn,
client_address,
- server)
+ None)
"""@type: libmproxy.proxy.connection.ClientConnection"""
- self.server_conn = None
- """@type: libmproxy.proxy.connection.ServerConnection"""
self.channel = channel
+ """@type: libmproxy.controller.Channel"""
- self.conntype = "http"
+ def _create_root_layer(self):
+ root_context = RootContext(
+ self.client_conn,
+ self.config,
+ self.channel
+ )
+
+ mode = self.config.mode
+ if mode == "upstream":
+ return HttpUpstreamProxy(
+ root_context,
+ self.config.upstream_server.address
+ )
+ elif mode == "transparent":
+ return TransparentProxy(root_context)
+ elif mode == "reverse":
+ server_tls = self.config.upstream_server.scheme == "https"
+ return ReverseProxy(
+ root_context,
+ self.config.upstream_server.address,
+ server_tls
+ )
+ elif mode == "socks5":
+ return Socks5Proxy(root_context)
+ elif mode == "regular":
+ return HttpProxy(root_context)
+ elif callable(mode): # pragma: nocover
+ return mode(root_context)
+ else: # pragma: nocover
+ raise ValueError("Unknown proxy mode: %s" % mode)
def handle(self):
- try:
- self.log("clientconnect", "info")
-
- # Can we already identify the target server and connect to it?
- client_ssl, server_ssl = False, False
- conn_kwargs = dict()
- upstream_info = self.config.mode.get_upstream_server(
- self.client_conn)
- if upstream_info:
- self.set_server_address(upstream_info[2:])
- client_ssl, server_ssl = upstream_info[:2]
- if self.config.check_ignore(self.server_conn.address):
- self.log(
- "Ignore host: %s:%s" %
- self.server_conn.address(),
- "info")
- self.conntype = "tcp"
- conn_kwargs["log"] = False
- client_ssl, server_ssl = False, False
- else:
- # No upstream info from the metadata: upstream info in the
- # protocol (e.g. HTTP absolute-form)
- pass
-
- self.channel.ask("clientconnect", self)
-
- # Check for existing connection: If an inline script already established a
- # connection, do not apply client_ssl or server_ssl.
- if self.server_conn and not self.server_conn.connection:
- self.establish_server_connection()
- if client_ssl or server_ssl:
- self.establish_ssl(client=client_ssl, server=server_ssl)
-
- if self.config.check_tcp(self.server_conn.address):
- self.log(
- "Generic TCP mode for host: %s:%s" %
- self.server_conn.address(),
- "info")
- self.conntype = "tcp"
-
- elif not self.server_conn and self.config.mode == "sslspoof":
- port = self.config.mode.sslport
- self.set_server_address(("-", port))
- self.establish_ssl(client=True)
- host = self.client_conn.connection.get_servername()
- if host:
- self.set_server_address((host, port))
- self.establish_server_connection()
- self.establish_ssl(server=True, sni=host)
-
- # Delegate handling to the protocol handler
- protocol_handler(
- self.conntype)(
- self,
- **conn_kwargs).handle_messages()
-
- self.log("clientdisconnect", "info")
- self.channel.tell("clientdisconnect", self)
-
- except ProxyError as e:
- protocol_handler(self.conntype)(self, **conn_kwargs).handle_error(e)
- except Exception:
- import traceback
- import sys
-
- self.log(traceback.format_exc(), "error")
- print >> sys.stderr, traceback.format_exc()
- print >> sys.stderr, "mitmproxy has crashed!"
- print >> sys.stderr, "Please lodge a bug report at: https://github.com/mitmproxy/mitmproxy"
- finally:
- # Make sure that we close the server connection in any case.
- # The client connection is closed by the ProxyServer and does not
- # have be handled here.
- self.del_server_connection()
-
- def del_server_connection(self):
- """
- Deletes (and closes) an existing server connection.
- """
- if self.server_conn and self.server_conn.connection:
- self.server_conn.finish()
- self.server_conn.close()
- self.log(
- "serverdisconnect", "debug", [
- "%s:%s" %
- (self.server_conn.address.host, self.server_conn.address.port)])
- self.channel.tell("serverdisconnect", self)
- self.server_conn = None
-
- def set_server_address(self, addr):
- """
- Sets a new server address with the given priority.
- Does not re-establish either connection or SSL handshake.
- """
- address = tcp.Address.wrap(addr)
-
- # Don't reconnect to the same destination.
- if self.server_conn and self.server_conn.address == address:
- return
+ self.log("clientconnect", "info")
- if self.server_conn:
- self.del_server_connection()
+ root_layer = self._create_root_layer()
+ root_layer = self.channel.ask("clientconnect", root_layer)
+ if root_layer == Kill:
+ def root_layer():
+ raise Kill()
- self.log(
- "Set new server address: %s:%s" %
- (address.host, address.port), "debug")
- self.server_conn = ServerConnection(address)
-
- def establish_server_connection(self, ask=True):
- """
- Establishes a new server connection.
- If there is already an existing server connection, the function returns immediately.
-
- By default, this function ".ask"s the proxy master. This is deadly if this function is already called from the
- master (e.g. via change_server), because this navigates us in a simple deadlock (the master is single-threaded).
- In these scenarios, ask=False can be passed to suppress the call to the master.
- """
- if self.server_conn.connection:
- return
- self.log(
- "serverconnect", "debug", [
- "%s:%s" %
- self.server_conn.address()[
- :2]])
- if ask:
- self.channel.ask("serverconnect", self)
try:
- self.server_conn.connect()
- except tcp.NetLibError as v:
- raise ProxyError(502, v)
-
- def establish_ssl(self, client=False, server=False, sni=None):
- """
- Establishes SSL on the existing connection(s) to the server or the client,
- as specified by the parameters.
- """
-
- # Logging
- if client or server:
- subs = []
- if client:
- subs.append("with client")
- if server:
- subs.append("with server (sni: %s)" % sni)
- self.log("Establish SSL", "debug", subs)
-
- if server:
- if not self.server_conn or not self.server_conn.connection:
- raise ProxyError(502, "No server connection.")
- if self.server_conn.ssl_established:
- raise ProxyError(502, "SSL to Server already established.")
+ root_layer()
+ except Kill:
+ self.log("Connection killed", "info")
+ except ProtocolException as e:
+ self.log(e, "info")
+ # If an error propagates to the topmost level,
+ # we send an HTTP error response, which is both
+ # understandable by HTTP clients and humans.
try:
- self.server_conn.establish_ssl(
- self.config.clientcerts,
- sni,
- method=self.config.openssl_method_server,
- options=self.config.openssl_options_server,
- verify_options=self.config.openssl_verification_mode_server,
- ca_path=self.config.openssl_trusted_cadir_server,
- ca_pemfile=self.config.openssl_trusted_ca_server,
- cipher_list=self.config.ciphers_server,
- )
- ssl_cert_err = self.server_conn.ssl_verification_error
- if ssl_cert_err is not None:
- self.log(
- "SSL verification failed for upstream server at depth %s with error: %s" %
- (ssl_cert_err['depth'], ssl_cert_err['errno']),
- "error")
- self.log("Ignoring server verification error, continuing with connection", "error")
- except tcp.NetLibError as v:
- e = ProxyError(502, repr(v))
- # Workaround for https://github.com/mitmproxy/mitmproxy/issues/427
- # The upstream server may reject connections without SNI, which means we need to
- # establish SSL with the client first, hope for a SNI (which triggers a reconnect which replaces the
- # ServerConnection object) and see whether that worked.
- if client and "handshake failure" in e.message:
- self.server_conn.may_require_sni = e
- else:
- ssl_cert_err = self.server_conn.ssl_verification_error
- if ssl_cert_err is not None:
- self.log(
- "SSL verification failed for upstream server at depth %s with error: %s" %
- (ssl_cert_err['depth'], ssl_cert_err['errno']),
- "error")
- self.log("Aborting connection attempt", "error")
- raise e
- if client:
- if self.client_conn.ssl_established:
- raise ProxyError(502, "SSL to Client already established.")
- cert, key, chain_file = self.find_cert()
- try:
- self.client_conn.convert_to_ssl(
- cert, key,
- method=self.config.openssl_method_client,
- options=self.config.openssl_options_client,
- handle_sni=self.handle_sni,
- cipher_list=self.config.ciphers_client,
- dhparams=self.config.certstore.dhparams,
- chain_file=chain_file
- )
- except tcp.NetLibError as v:
- raise ProxyError(400, repr(v))
-
- # Workaround for #427 part 2
- if server and hasattr(self.server_conn, "may_require_sni"):
- raise self.server_conn.may_require_sni
-
- def server_reconnect(self, new_sni=False):
- address = self.server_conn.address
- had_ssl = self.server_conn.ssl_established
- state = self.server_conn.state
- sni = new_sni or self.server_conn.sni
- self.log("(server reconnect follows)", "debug")
- self.del_server_connection()
- self.set_server_address(address)
- self.establish_server_connection()
-
- for s in state:
- protocol_handler(s[0])(self).handle_server_reconnect(s[1])
- self.server_conn.state = state
-
- # Receiving new_sni where had_ssl is False is a weird case that happens when the workaround for
- # https://github.com/mitmproxy/mitmproxy/issues/427 is active. In this
- # case, we want to establish SSL as well.
- if had_ssl or new_sni:
- self.establish_ssl(server=True, sni=sni)
+ error_response = make_error_response(502, repr(e))
+ self.client_conn.send(HTTP1Protocol().assemble(error_response))
+ except NetLibError:
+ pass
+ except Exception:
+ self.log(traceback.format_exc(), "error")
+ print(traceback.format_exc(), file=sys.stderr)
+ print("mitmproxy has crashed!", file=sys.stderr)
+ print("Please lodge a bug report at: https://github.com/mitmproxy/mitmproxy", file=sys.stderr)
- def finish(self):
+ self.log("clientdisconnect", "info")
+ self.channel.tell("clientdisconnect", root_layer)
self.client_conn.finish()
- def log(self, msg, level, subs=()):
- full_msg = [
- "%s:%s: %s" %
- (self.client_conn.address.host,
- self.client_conn.address.port,
- msg)]
- for i in subs:
- full_msg.append(" -> " + i)
- full_msg = "\n".join(full_msg)
- self.channel.tell("log", Log(full_msg, level))
-
- def find_cert(self):
- host = self.server_conn.address.host
- sans = []
- if self.server_conn.ssl_established and (
- not self.config.no_upstream_cert):
- upstream_cert = self.server_conn.cert
- sans.extend(upstream_cert.altnames)
- if upstream_cert.cn:
- sans.append(host)
- host = upstream_cert.cn.decode("utf8").encode("idna")
- if self.server_conn.sni:
- sans.append(self.server_conn.sni)
- # for ssl spoof mode
- if hasattr(self.client_conn, "sni"):
- sans.append(self.client_conn.sni)
-
- ret = self.config.certstore.get_cert(host, sans)
- if not ret:
- raise ProxyError(502, "Unable to generate dummy cert.")
- return ret
-
- def handle_sni(self, connection):
- """
- This callback gets called during the SSL handshake with the client.
- The client has just sent the Sever Name Indication (SNI). We now connect upstream to
- figure out which certificate needs to be served.
- """
- try:
- sn = connection.get_servername()
- if not sn:
- return
- sni = sn.decode("utf8").encode("idna")
- # for ssl spoof mode
- self.client_conn.sni = sni
-
- if sni != self.server_conn.sni:
- self.log("SNI received: %s" % sni, "debug")
- # We should only re-establish upstream SSL if one of the following conditions is true:
- # - We established SSL with the server previously
- # - We initially wanted to establish SSL with the server,
- # but the server refused to negotiate without SNI.
- if self.server_conn.ssl_established or hasattr(
- self.server_conn,
- "may_require_sni"):
- # reconnect to upstream server with SNI
- self.server_reconnect(sni)
- # Now, change client context to reflect changed certificate:
- cert, key, chain_file = self.find_cert()
- new_context = self.client_conn.create_ssl_context(
- cert, key,
- method=self.config.openssl_method_client,
- options=self.config.openssl_options_client,
- cipher_list=self.config.ciphers_client,
- dhparams=self.config.certstore.dhparams,
- chain_file=chain_file
- )
- connection.set_context(new_context)
- # An unhandled exception in this method will core dump PyOpenSSL, so
- # make dang sure it doesn't happen.
- except: # pragma: no cover
- import traceback
- self.log(
- "Error in handle_sni:\r\n" +
- traceback.format_exc(),
- "error")
+ def log(self, msg, level):
+ msg = "{}: {}".format(repr(self.client_conn.address), msg)
+ self.channel.tell("log", Log(msg, level)) \ No newline at end of file