diff --git a/libmproxy/cmdline.py b/libmproxy/cmdline.py index 591e87ed3..55377af26 100644 --- a/libmproxy/cmdline.py +++ b/libmproxy/cmdline.py @@ -102,9 +102,9 @@ def parse_setheader(s): return _parse_hook(s) -def parse_server_spec(url, allowed_schemes=("http", "https")): +def parse_server_spec(url): p = netlib.utils.parse_url(url) - if not p or not p[1] or p[0] not in allowed_schemes: + if not p or not p[1] or p[0] not in ("http", "https"): raise configargparse.ArgumentTypeError( "Invalid server specification: %s" % url ) @@ -113,13 +113,6 @@ def parse_server_spec(url, allowed_schemes=("http", "https")): return config.ServerSpec(scheme, address) -def parse_server_spec_special(url): - """ - Provides additional support for http2https and https2http schemes. - """ - return parse_server_spec(url, allowed_schemes=("http", "https", "http2https", "https2http")) - - def get_common_options(options): stickycookie, stickyauth = None, None if options.stickycookie_filt: @@ -297,7 +290,7 @@ def proxy_modes(parser): group.add_argument( "-R", "--reverse", action="store", - type=parse_server_spec_special, + type=parse_server_spec, dest="reverse_proxy", default=None, help=""" diff --git a/libmproxy/protocol2/http_proxy.py b/libmproxy/protocol2/http_proxy.py index b3389eb75..2876c0226 100644 --- a/libmproxy/protocol2/http_proxy.py +++ b/libmproxy/protocol2/http_proxy.py @@ -1,12 +1,11 @@ from __future__ import (absolute_import, print_function, division) from .layer import Layer, ServerConnectionMixin -from .http import Http1Layer class HttpProxy(Layer, ServerConnectionMixin): def __call__(self): - layer = Http1Layer(self, "regular") + layer = self.ctx.next_layer(self) try: layer() finally: @@ -19,7 +18,7 @@ class HttpUpstreamProxy(Layer, ServerConnectionMixin): super(HttpUpstreamProxy, self).__init__(ctx, server_address=server_address) def __call__(self): - layer = Http1Layer(self, "upstream") + layer = self.ctx.next_layer(self) try: layer() finally: diff --git a/libmproxy/protocol2/reverse_proxy.py b/libmproxy/protocol2/reverse_proxy.py index e959db861..c4cabcccc 100644 --- a/libmproxy/protocol2/reverse_proxy.py +++ b/libmproxy/protocol2/reverse_proxy.py @@ -5,16 +5,12 @@ from .tls import TlsLayer class ReverseProxy(Layer, ServerConnectionMixin): - def __init__(self, ctx, server_address, client_tls, server_tls): + def __init__(self, ctx, server_address, server_tls): super(ReverseProxy, self).__init__(ctx, server_address=server_address) - self._client_tls = client_tls - self._server_tls = server_tls + self.server_tls = server_tls def __call__(self): - # Always use a TLS layer here; if someone changes the scheme, there needs to be a - # TLS layer underneath. - layer = TlsLayer(self, self._client_tls, self._server_tls) - + layer = self.ctx.next_layer(self) try: layer() finally: diff --git a/libmproxy/protocol2/root_context.py b/libmproxy/protocol2/root_context.py index 4d69204f6..210ba6ab0 100644 --- a/libmproxy/protocol2/root_context.py +++ b/libmproxy/protocol2/root_context.py @@ -6,7 +6,9 @@ from netlib.http.http2 import HTTP2Protocol from .rawtcp import RawTcpLayer from .tls import TlsLayer, is_tls_record_magic from .http import Http1Layer, Http2Layer - +from .layer import ServerConnectionMixin +from .http_proxy import HttpProxy, HttpUpstreamProxy +from .reverse_proxy import ReverseProxy class RootContext(object): """ @@ -34,18 +36,33 @@ class RootContext(object): if self.config.check_ignore(top_layer.server_conn.address): return RawTcpLayer(top_layer, logging=False) - # 2. Check for TLS - # TLS ClientHello magic, works for SSLv3, TLSv1.0, TLSv1.1, TLSv1.2 - # http://www.moserware.com/2009/06/first-few-milliseconds-of-https.html#client-hello d = top_layer.client_conn.rfile.peek(3) - if is_tls_record_magic(d): + 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) - # 3. Check for --tcp + # 4. Check for --tcp if self.config.check_tcp(top_layer.server_conn.address): return RawTcpLayer(top_layer) - # 4. Check for TLS ALPN (HTTP1/HTTP2) + # 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: @@ -53,7 +70,7 @@ class RootContext(object): if alpn == HTTP1Protocol.ALPN_PROTO_HTTP1: return Http1Layer(top_layer, 'transparent') - # 5. Assume HTTP1 by default + # 6. Assume HTTP1 by default return Http1Layer(top_layer, 'transparent') # In a future version, we want to implement TCP passthrough as the last fallback, diff --git a/libmproxy/protocol2/tls.py b/libmproxy/protocol2/tls.py index 0c02b0ead..041adaaab 100644 --- a/libmproxy/protocol2/tls.py +++ b/libmproxy/protocol2/tls.py @@ -18,6 +18,9 @@ def is_tls_record_magic(d): False, otherwise. """ d = d[:3] + + # TLS ClientHello magic, works for SSLv3, TLSv1.0, TLSv1.1, TLSv1.2 + # http://www.moserware.com/2009/06/first-few-milliseconds-of-https.html#client-hello return ( len(d) == 3 and d[0] == '\x16' and @@ -73,6 +76,16 @@ class TlsLayer(Layer): layer = self.ctx.next_layer(self) layer() + def __repr__(self): + if self._client_tls and self._server_tls: + return "TlsLayer(client and server)" + elif self._client_tls: + return "TlsLayer(client)" + elif self._server_tls: + return "TlsLayer(server)" + else: + return "TlsLayer(inactive)" + def _get_client_hello(self): """ Peek into the socket and read all records that contain the initial client hello message. diff --git a/libmproxy/proxy/config.py b/libmproxy/proxy/config.py index 415ee215d..b360abbd6 100644 --- a/libmproxy/proxy/config.py +++ b/libmproxy/proxy/config.py @@ -24,6 +24,8 @@ class HostMatcher(object): 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): diff --git a/libmproxy/proxy/server.py b/libmproxy/proxy/server.py index 697840149..5abd08771 100644 --- a/libmproxy/proxy/server.py +++ b/libmproxy/proxy/server.py @@ -86,12 +86,10 @@ class ConnectionHandler(object): elif mode == "transparent": return protocol2.TransparentProxy(root_context) elif mode == "reverse": - client_tls = self.config.upstream_server.scheme.startswith("https") - server_tls = self.config.upstream_server.scheme.endswith("https") + server_tls = self.config.upstream_server.scheme == "https" return protocol2.ReverseProxy( root_context, self.config.upstream_server.address, - client_tls, server_tls ) elif mode == "socks5": diff --git a/test/test_cmdline.py b/test/test_cmdline.py index ee2f7044b..1443ee1cc 100644 --- a/test/test_cmdline.py +++ b/test/test_cmdline.py @@ -43,10 +43,6 @@ def test_parse_server_spec(): "http://foo.com") == ("http", ("foo.com", 80)) assert cmdline.parse_server_spec( "https://foo.com") == ("https", ("foo.com", 443)) - assert cmdline.parse_server_spec_special( - "https2http://foo.com") == ("https2http", ("foo.com", 80)) - assert cmdline.parse_server_spec_special( - "http2https://foo.com") == ("http2https", ("foo.com", 443)) tutils.raises( "Invalid server specification", cmdline.parse_server_spec, diff --git a/test/test_server.py b/test/test_server.py index 7b66c5822..b691804bb 100644 --- a/test/test_server.py +++ b/test/test_server.py @@ -490,10 +490,6 @@ class TestHttps2Http(tservers.ReverseProxTest): assert p.request("get:'/p/200'").status_code == 200 assert all("Error in handle_sni" not in msg for msg in self.proxy.log) - def test_http(self): - p = self.pathoc(ssl=False) - assert p.request("get:'/p/200'").status_code == 502 - class TestTransparent(tservers.TransparentProxTest, CommonMixin, TcpMixin): ssl = False