diff options
-rw-r--r-- | libmproxy/protocol2/http.py | 96 | ||||
-rw-r--r-- | libmproxy/protocol2/http_proxy.py | 5 | ||||
-rw-r--r-- | libmproxy/protocol2/layer.py | 48 | ||||
-rw-r--r-- | libmproxy/protocol2/messages.py | 3 | ||||
-rw-r--r-- | libmproxy/protocol2/reverse_proxy.py | 3 | ||||
-rw-r--r-- | libmproxy/protocol2/root_context.py | 12 | ||||
-rw-r--r-- | libmproxy/protocol2/socks_proxy.py | 4 | ||||
-rw-r--r-- | libmproxy/protocol2/tls.py | 9 | ||||
-rw-r--r-- | libmproxy/protocol2/transparent_proxy.py | 2 | ||||
-rw-r--r-- | libmproxy/proxy/connection.py | 7 | ||||
-rw-r--r-- | libmproxy/proxy/server.py | 11 |
11 files changed, 127 insertions, 73 deletions
diff --git a/libmproxy/protocol2/http.py b/libmproxy/protocol2/http.py index cabec806..eadde3b3 100644 --- a/libmproxy/protocol2/http.py +++ b/libmproxy/protocol2/http.py @@ -2,22 +2,24 @@ from __future__ import (absolute_import, print_function, division) from .. import version from ..exceptions import InvalidCredentials, HttpException, ProtocolException -from .layer import Layer, ServerConnectionMixin +from .layer import Layer from libmproxy import utils from .messages import SetServer, Connect, Reconnect, Kill from libmproxy.protocol import KILL from libmproxy.protocol.http import HTTPFlow from libmproxy.protocol.http_wrappers import HTTPResponse, HTTPRequest -from libmproxy.protocol2.tls import TlsLayer from netlib import tcp from netlib.http import status_codes, http1, HttpErrorConnClosed from netlib.http.semantics import CONTENT_MISSING from netlib import odict -from netlib.tcp import NetLibError +from netlib.tcp import NetLibError, Address from netlib.http.http1 import HTTP1Protocol from netlib.http.http2 import HTTP2Protocol +# TODO: The HTTP2 layer is missing multiplexing, which requires a major rewrite. + + class Http1Layer(Layer): def __init__(self, ctx, mode): super(Http1Layer, self).__init__(ctx) @@ -26,7 +28,6 @@ class Http1Layer(Layer): self.server_protocol = HTTP1Protocol(self.server_conn) def __call__(self): - from .http import HttpLayer layer = HttpLayer(self, self.mode) for message in layer(): yield message @@ -40,11 +41,12 @@ class Http2Layer(Layer): self.server_protocol = HTTP2Protocol(self.server_conn, is_server=False) def __call__(self): - from .http import HttpLayer + # FIXME: Handle Reconnect etc. layer = HttpLayer(self, self.mode) for message in layer(): yield message + def make_error_response(status_code, message, headers=None): response = status_codes.RESPONSES.get(status_code, "Unknown") body = """ @@ -73,6 +75,7 @@ def make_error_response(status_code, message, headers=None): def make_connect_request(address): + address = Address.wrap(address) return HTTPRequest( "authority", "CONNECT", None, address.host, address.port, None, (1, 1), odict.ODictCaseless(), "" @@ -93,6 +96,22 @@ def make_connect_response(httpversion): ) +class ConnectServerConnection(object): + """ + "Fake" ServerConnection to represent state after a CONNECT request to an upstream proxy. + """ + def __init__(self, address, ctx): + self.address = tcp.Address.wrap(address) + self._ctx = ctx + + @property + def via(self): + return self._ctx.server_conn + + def __getattr__(self, item): + return getattr(self.via, item) + + class HttpLayer(Layer): """ HTTP 1 Layer @@ -122,12 +141,8 @@ class HttpLayer(Layer): # Regular Proxy Mode: Handle CONNECT if self.mode == "regular" and request.form_in == "authority": - yield SetServer((request.host, request.port), False, None) - self.send_to_client(make_connect_response(request.httpversion)) - layer = self.ctx.next_layer(self) - for message in layer(): - if not self._handle_server_message(message): - yield message + for message in self.handle_regular_mode_connect(request): + yield message return # Make sure that the incoming request matches our expectations @@ -149,12 +164,50 @@ class HttpLayer(Layer): if self.check_close_connection(flow): return + # Upstream Proxy Mode: Handle CONNECT if flow.request.form_in == "authority" and flow.response.code == 200: - raise NotImplementedError("Upstream mode CONNECT not implemented") + for message in self.handle_upstream_mode_connect(flow.request.copy()): + yield message + return + except (HttpErrorConnClosed, NetLibError) as e: make_error_response(502, repr(e)) raise ProtocolException(repr(e), e) + def handle_regular_mode_connect(self, request): + yield SetServer((request.host, request.port), False, None) + self.send_to_client(make_connect_response(request.httpversion)) + layer = self.ctx.next_layer(self) + for message in layer(): + yield message + + def handle_upstream_mode_connect(self, connect_request): + layer = self.ctx.next_layer(self) + self.server_conn = ConnectServerConnection((connect_request.host, connect_request.port), self.ctx) + + for message in layer(): + if message == Connect: + if not self.server_conn: + yield message + self.send_to_server(connect_request) + else: + pass # swallow the message + elif message == Reconnect: + yield message + self.send_to_server(connect_request) + elif message == SetServer: + if message.depth == 1: + if self.ctx.server_conn: + yield Reconnect() + connect_request.host = message.address.host + connect_request.port = message.address.port + self.server_conn.address = message.address + else: + message.depth -= 1 + yield message + else: + yield message + def check_close_connection(self, flow): """ Checks if the connection should be closed depending on the HTTP @@ -236,9 +289,9 @@ class HttpLayer(Layer): if flow is None or flow == KILL: yield Kill() - if flow.response.stream: + if flow.response.stream and isinstance(self.server_protocol, http1.HTTP1Protocol): flow.response.content = CONTENT_MISSING - elif isinstance(self.server_protocol, http1.HTTP1Protocol): + else: flow.response.content = self.server_protocol.read_http_body( self.server_conn, flow.response.headers, @@ -274,11 +327,11 @@ class HttpLayer(Layer): if flow.request.form_in == "authority": flow.request.scheme = "http" # pseudo value else: - flow.request.host = self.ctx.server_address.host - flow.request.port = self.ctx.server_address.port + flow.request.host = self.ctx.server_conn.address.host + flow.request.port = self.ctx.server_conn.address.port flow.request.scheme = "https" if self.server_conn.tls_established else "http" - # TODO: Expose ChangeServer functionality to inline scripts somehow? (yield_from_callback?) + # TODO: Expose SetServer functionality to inline scripts somehow? (yield_from_callback?) request_reply = self.channel.ask("request", flow) if request_reply is None or request_reply == KILL: yield Kill() @@ -292,25 +345,26 @@ class HttpLayer(Layer): tls = (flow.request.scheme == "https") if self.mode == "regular" or self.mode == "transparent": # If there's an existing connection that doesn't match our expectations, kill it. - if self.server_address != address or tls != self.server_conn.ssl_established: + if address != self.server_conn.address or tls != self.server_conn.ssl_established: yield SetServer(address, tls, address.host) # Establish connection is neccessary. if not self.server_conn: yield Connect() - # ChangeServer is not guaranteed to work with TLS: + # SetServer is not guaranteed to work with TLS: # If there's not TlsLayer below which could catch the exception, # TLS will not be established. if tls and not self.server_conn.tls_established: raise ProtocolException("Cannot upgrade to SSL, no TLS layer on the protocol stack.") else: + if not self.server_conn: + yield Connect() if tls: raise HttpException("Cannot change scheme in upstream proxy mode.") """ # This is a very ugly (untested) workaround to solve a very ugly problem. - # FIXME: Check if connected first. - if self.server_conn.tls_established and not ssl: + if self.server_conn and self.server_conn.tls_established and not ssl: yield Reconnect() elif ssl and not hasattr(self, "connected_to") or self.connected_to != address: if self.server_conn.tls_established: diff --git a/libmproxy/protocol2/http_proxy.py b/libmproxy/protocol2/http_proxy.py index 7f5957ac..19b5f7ef 100644 --- a/libmproxy/protocol2/http_proxy.py +++ b/libmproxy/protocol2/http_proxy.py @@ -1,7 +1,7 @@ from __future__ import (absolute_import, print_function, division) from .layer import Layer, ServerConnectionMixin -from .http import Http1Layer, HttpLayer +from .http import Http1Layer class HttpProxy(Layer, ServerConnectionMixin): @@ -14,8 +14,7 @@ class HttpProxy(Layer, ServerConnectionMixin): class HttpUpstreamProxy(Layer, ServerConnectionMixin): def __init__(self, ctx, server_address): - super(HttpUpstreamProxy, self).__init__(ctx) - self.server_address = server_address + super(HttpUpstreamProxy, self).__init__(ctx, server_address=server_address) def __call__(self): layer = Http1Layer(self, "upstream") diff --git a/libmproxy/protocol2/layer.py b/libmproxy/protocol2/layer.py index 31b74552..67f3d549 100644 --- a/libmproxy/protocol2/layer.py +++ b/libmproxy/protocol2/layer.py @@ -44,8 +44,8 @@ class _LayerCodeCompletion(object): Dummy class that provides type hinting in PyCharm, which simplifies development a lot. """ - def __init__(self): - super(_LayerCodeCompletion, self).__init__() + def __init__(self, *args, **kwargs): + super(_LayerCodeCompletion, self).__init__(*args, **kwargs) if True: return self.config = None @@ -57,14 +57,13 @@ class _LayerCodeCompletion(object): class Layer(_LayerCodeCompletion): - def __init__(self, ctx): + def __init__(self, ctx, *args, **kwargs): """ Args: ctx: The (read-only) higher layer. """ - super(Layer, self).__init__() + super(Layer, self).__init__(*args, **kwargs) self.ctx = ctx - print("%s -> %s" % (repr(ctx), repr(self))) def __call__(self): """ @@ -104,10 +103,9 @@ class ServerConnectionMixin(object): Mixin that provides a layer with the capabilities to manage a server connection. """ - def __init__(self): + def __init__(self, server_address=None): super(ServerConnectionMixin, self).__init__() - self._server_address = None - self.server_conn = None + self.server_conn = ServerConnection(server_address) def _handle_server_message(self, message): if message == Reconnect: @@ -117,44 +115,38 @@ class ServerConnectionMixin(object): elif message == Connect: self._connect() return True - elif message == SetServer and message.depth == 1: - if self.server_conn: - self._disconnect() - self.server_address = message.address - return True + elif message == SetServer: + if message.depth == 1: + if self.server_conn: + self._disconnect() + self.log("Set new server address: " + repr(message.address), "debug") + self.server_conn.address = message.address + return True + else: + message.depth -= 1 elif message == Kill: self._disconnect() return False - @property - def server_address(self): - return self._server_address - - @server_address.setter - def server_address(self, address): - self._server_address = tcp.Address.wrap(address) - self.log("Set new server address: " + repr(self.server_address), "debug") - def _disconnect(self): """ Deletes (and closes) an existing server connection. """ - self.log("serverdisconnect", "debug", [repr(self.server_address)]) + self.log("serverdisconnect", "debug", [repr(self.server_conn.address)]) self.server_conn.finish() self.server_conn.close() # self.channel.tell("serverdisconnect", self) - self.server_conn = None + self.server_conn = ServerConnection(None) def _connect(self): - if not self.server_address: + if not self.server_conn.address: raise ProtocolException("Cannot connect to server, no server address given.") - self.log("serverconnect", "debug", [repr(self.server_address)]) - self.server_conn = ServerConnection(self.server_address) + self.log("serverconnect", "debug", [repr(self.server_conn.address)]) try: self.server_conn.connect() except tcp.NetLibError as e: - raise ProtocolException("Server connection to '%s' failed: %s" % (self.server_address, e), e) + raise ProtocolException("Server connection to '%s' failed: %s" % (self.server_conn.address, e), e) def yield_from_callback(fun): diff --git a/libmproxy/protocol2/messages.py b/libmproxy/protocol2/messages.py index 17e12f11..f5907537 100644 --- a/libmproxy/protocol2/messages.py +++ b/libmproxy/protocol2/messages.py @@ -2,6 +2,7 @@ This module contains all valid messages layers can send to the underlying layers. """ from __future__ import (absolute_import, print_function, division) +from netlib.tcp import Address class _Message(object): @@ -33,7 +34,7 @@ class SetServer(_Message): """ def __init__(self, address, server_tls, sni, depth=1): - self.address = address + self.address = Address.wrap(address) self.server_tls = server_tls self.sni = sni diff --git a/libmproxy/protocol2/reverse_proxy.py b/libmproxy/protocol2/reverse_proxy.py index bb414ec3..2ee3d9d8 100644 --- a/libmproxy/protocol2/reverse_proxy.py +++ b/libmproxy/protocol2/reverse_proxy.py @@ -7,8 +7,7 @@ from .tls import TlsLayer class ReverseProxy(Layer, ServerConnectionMixin): def __init__(self, ctx, server_address, client_tls, server_tls): - super(ReverseProxy, self).__init__(ctx) - self.server_address = server_address + super(ReverseProxy, self).__init__(ctx, server_address=server_address) self._client_tls = client_tls self._server_tls = server_tls diff --git a/libmproxy/protocol2/root_context.py b/libmproxy/protocol2/root_context.py index a68560c2..6ba6ca9a 100644 --- a/libmproxy/protocol2/root_context.py +++ b/libmproxy/protocol2/root_context.py @@ -1,9 +1,11 @@ from __future__ import (absolute_import, print_function, division) +from .messages import Kill from .rawtcp import RawTcpLayer from .tls import TlsLayer from .http import Http1Layer, Http2Layer, HttpLayer + class RootContext(object): """ The outmost context provided to the root layer. @@ -37,21 +39,17 @@ class RootContext(object): # TODO: build is_http2_magic check here, maybe this is an easy way to detect h2c if not d: - return + return iter([]) if is_tls_client_hello: return TlsLayer(top_layer, True, True) - elif isinstance(top_layer, TlsLayer): - if top_layer.client_conn.get_alpn_proto_negotiated() == 'h2': - return Http2Layer(top_layer, 'regular') # TODO: regular correct here? - else: - return Http1Layer(top_layer, 'regular') # TODO: regular correct here? + elif isinstance(top_layer, TlsLayer) and top_layer.client_conn.get_alpn_proto_negotiated() == 'h2': + return Http2Layer(top_layer, 'transparent') elif isinstance(top_layer, TlsLayer) and isinstance(top_layer.ctx, Http1Layer): return Http1Layer(top_layer, "transparent") else: return RawTcpLayer(top_layer) - @property def layers(self): return [] diff --git a/libmproxy/protocol2/socks_proxy.py b/libmproxy/protocol2/socks_proxy.py index c89477ca..c6126a42 100644 --- a/libmproxy/protocol2/socks_proxy.py +++ b/libmproxy/protocol2/socks_proxy.py @@ -5,7 +5,7 @@ from ..proxy import ProxyError, Socks5ProxyMode from .layer import Layer, ServerConnectionMixin -class Socks5Proxy(Layer, ServerConnectionMixin): +class Socks5Proxy(ServerConnectionMixin, Layer): def __call__(self): try: s5mode = Socks5ProxyMode(self.config.ssl_ports) @@ -14,7 +14,7 @@ class Socks5Proxy(Layer, ServerConnectionMixin): # TODO: Unmonkeypatch raise ProtocolException(str(e), e) - self.server_address = address + self.server_conn.address = address layer = self.ctx.next_layer(self) for message in layer(): diff --git a/libmproxy/protocol2/tls.py b/libmproxy/protocol2/tls.py index 8e367728..970abe62 100644 --- a/libmproxy/protocol2/tls.py +++ b/libmproxy/protocol2/tls.py @@ -124,7 +124,7 @@ class TlsLayer(Layer): if old_upstream_sni != self.sni_for_upstream_connection: # Perform reconnect - if self._server_tls: + if self.server_conn and self._server_tls: self.yield_from_callback(Reconnect()) if self.client_sni: @@ -151,9 +151,11 @@ class TlsLayer(Layer): alpn_preference = netlib.http.http2.HTTP2Protocol.ALPN_PROTO_H2 ### + # TODO: Not if self.client_alpn_protos != options: # Perform reconnect - if self._server_tls: + # TODO: Avoid double reconnect. + if self.server_conn and self._server_tls: self.yield_from_callback(Reconnect()) self.client_alpn_protos = options @@ -219,7 +221,7 @@ class TlsLayer(Layer): host = self.server_conn.address.host sans = set() # Incorporate upstream certificate - if self.server_conn.tls_established and (not self.config.no_upstream_cert): + if self.server_conn and self.server_conn.tls_established and (not self.config.no_upstream_cert): upstream_cert = self.server_conn.cert sans.update(upstream_cert.altnames) if upstream_cert.cn: @@ -231,4 +233,5 @@ class TlsLayer(Layer): if self._sni_from_server_change: sans.add(self._sni_from_server_change) + sans.discard(host) return self.config.certstore.get_cert(host, list(sans)) diff --git a/libmproxy/protocol2/transparent_proxy.py b/libmproxy/protocol2/transparent_proxy.py index f073e2f8..4ed4c14b 100644 --- a/libmproxy/protocol2/transparent_proxy.py +++ b/libmproxy/protocol2/transparent_proxy.py @@ -13,7 +13,7 @@ class TransparentProxy(Layer, ServerConnectionMixin): def __call__(self): try: - self.server_address = self.resolver.original_addr(self.client_conn.connection) + 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) diff --git a/libmproxy/proxy/connection.py b/libmproxy/proxy/connection.py index f33e84cd..f92b53aa 100644 --- a/libmproxy/proxy/connection.py +++ b/libmproxy/proxy/connection.py @@ -96,6 +96,9 @@ class ServerConnection(tcp.TCPClient, stateobject.StateObject): self.timestamp_ssl_setup = None self.protocol = None + def __nonzero__(self): + return bool(self.connection) + def __repr__(self): if self.ssl_established and self.sni: ssl = "[ssl: {0}] ".format(self.sni) @@ -132,8 +135,8 @@ class ServerConnection(tcp.TCPClient, stateobject.StateObject): 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), + 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 diff --git a/libmproxy/proxy/server.py b/libmproxy/proxy/server.py index ffca55ee..e23a7d72 100644 --- a/libmproxy/proxy/server.py +++ b/libmproxy/proxy/server.py @@ -80,7 +80,12 @@ class ConnectionHandler2: self.config, self.channel ) - root_layer = protocol2.HttpProxy(root_context) + + # FIXME: properly parse config + if self.config.mode == "upstream": + root_layer = protocol2.HttpUpstreamProxy(root_context, ("localhost", 8081)) + else: + root_layer = protocol2.HttpProxy(root_context) try: for message in root_layer(): @@ -302,7 +307,7 @@ class ConnectionHandler: 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']), + (ssl_cert_err['depth'], ssl_cert_err['errno']), "error") self.log("Ignoring server verification error, continuing with connection", "error") except tcp.NetLibError as v: @@ -318,7 +323,7 @@ class ConnectionHandler: 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']), + (ssl_cert_err['depth'], ssl_cert_err['errno']), "error") self.log("Aborting connection attempt", "error") raise e |