From 78750a8b4da217a2b3f3eac23bea92b6c428fc35 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Mon, 10 Mar 2014 02:32:27 +0100 Subject: [PATCH] lay the foundations for --(in|out)(abs|rel) command line switches, as proposed in https://groups.google.com/forum/#!topic/mitmproxy/nApno2TXS0c --- libmproxy/protocol/http.py | 74 ++++++++++++++++++++--------------- libmproxy/proxy/config.py | 14 ++++--- libmproxy/proxy/primitives.py | 4 +- libmproxy/proxy/server.py | 18 ++------- test/test_protocol_http.py | 4 +- test/tservers.py | 13 ++++-- 6 files changed, 67 insertions(+), 60 deletions(-) diff --git a/libmproxy/protocol/http.py b/libmproxy/protocol/http.py index 77a09e614..c8147a3c9 100644 --- a/libmproxy/protocol/http.py +++ b/libmproxy/protocol/http.py @@ -843,6 +843,12 @@ class HttpAuthenticationError(Exception): class HTTPHandler(ProtocolHandler, TemporaryServerChangeMixin): + def __init__(self, c): + super(HTTPHandler, self).__init__(c) + self.expected_form_in = c.config.http_form_in + self.expected_form_out = c.config.http_form_out + self.skip_authentication = False + def handle_messages(self): while self.handle_flow(): pass @@ -877,13 +883,15 @@ class HTTPHandler(ProtocolHandler, TemporaryServerChangeMixin): flow = HTTPFlow(self.c.client_conn, self.c.server_conn, self.change_server) try: req = HTTPRequest.from_stream(self.c.client_conn.rfile, - body_size_limit=self.c.config.body_size_limit) + body_size_limit=self.c.config.body_size_limit) self.c.log("request", [req._assemble_first_line(req.form_in)]) - self.process_request(flow, req) + send_upstream = self.process_request(flow, req) + if not send_upstream: + return True # Be careful NOT to assign the request to the flow before # process_request completes. This is because the call can raise an - # exception. If the requets object is already attached, this results + # exception. If the request object is already attached, this results # in an Error object that has an attached request that has not been # sent through to the Master. flow.request = req @@ -1004,44 +1012,48 @@ class HTTPHandler(ProtocolHandler, TemporaryServerChangeMixin): Upgrade the connection to SSL after an authority (CONNECT) request has been made. """ self.c.log("Received CONNECT request. Upgrading to SSL...") - self.c.mode = "transparent" - self.c.determine_conntype() + self.expected_form_in = "relative" + self.expected_form_out = "relative" self.c.establish_ssl(server=True, client=True) self.c.log("Upgrade to SSL completed.") - raise ConnectionTypeChange def process_request(self, flow, request): - if self.c.mode == "regular": + + if not self.skip_authentication: self.authenticate(request) - if request.form_in == "authority" and self.c.client_conn.ssl_established: - raise http.HttpError(502, "Must not CONNECT on already encrypted connection") - # If we have a CONNECT request, we might need to intercept if request.form_in == "authority": - directly_addressed_at_mitmproxy = (self.c.mode == "regular" and not self.c.config.forward_proxy) - if directly_addressed_at_mitmproxy: - self.c.set_server_address((request.host, request.port), AddressPriority.FROM_PROTOCOL) - flow.server_conn = self.c.server_conn # Update server_conn attribute on the flow - self.c.client_conn.wfile.write( - 'HTTP/1.1 200 Connection established\r\n' + - ('Proxy-agent: %s\r\n' % self.c.server_version) + - '\r\n' - ) - self.c.client_conn.wfile.flush() - self.ssl_upgrade() # raises ConnectionTypeChange exception + if self.c.client_conn.ssl_established: + raise http.HttpError(400, "Must not CONNECT on already encrypted connection") - if self.c.mode == "regular": - if request.form_in == "authority": # forward mode - self.hook_reconnect(request) - elif request.form_in == "absolute": - if request.scheme != "http": - raise http.HttpError(400, "Invalid Request") - if not self.c.config.forward_proxy: - request.form_out = "relative" + if self.expected_form_in == "absolute": + if not self.c.config.upstream_server: self.c.set_server_address((request.host, request.port), AddressPriority.FROM_PROTOCOL) flow.server_conn = self.c.server_conn # Update server_conn attribute on the flow - else: - raise http.HttpError(400, "Invalid request form (absolute-form or authority-form required)") + self.c.client_conn.send( + 'HTTP/1.1 200 Connection established\r\n' + + ('Proxy-agent: %s\r\n' % self.c.server_version) + + '\r\n' + ) + self.ssl_upgrade() + self.skip_authentication = True + return False + else: + self.hook_reconnect(request) + return True + elif request.form_in == self.expected_form_in: + if request.form_in == "absolute": + if request.scheme != "http": + raise http.HttpError(400, "Invalid request scheme: %s" % request.scheme) + + self.c.set_server_address((request.host, request.port), AddressPriority.FROM_PROTOCOL) + flow.server_conn = self.c.server_conn # Update server_conn attribute on the flow + + request.form_out = self.expected_form_out + return True + + raise http.HttpError(400, "Invalid HTTP request form (expected: %s, got: %s)" % (self.expected_form_in, + request.form_in)) def authenticate(self, request): if self.c.config.authenticator: diff --git a/libmproxy/proxy/config.py b/libmproxy/proxy/config.py index 38c6ce890..248808e96 100644 --- a/libmproxy/proxy/config.py +++ b/libmproxy/proxy/config.py @@ -10,16 +10,17 @@ CONF_DIR = "~/.mitmproxy" class ProxyConfig: def __init__(self, confdir=CONF_DIR, clientcerts=None, - no_upstream_cert=False, body_size_limit=None, reverse_proxy=None, - forward_proxy=None, transparent_proxy=None, authenticator=None, + no_upstream_cert=False, body_size_limit=None, upstream_server=None, + http_form_in="absolute", http_form_out="relative", transparent_proxy=None, authenticator=None, ciphers=None, certs=None ): self.ciphers = ciphers self.clientcerts = clientcerts self.no_upstream_cert = no_upstream_cert self.body_size_limit = body_size_limit - self.reverse_proxy = reverse_proxy - self.forward_proxy = forward_proxy + self.upstream_server = upstream_server + self.http_form_in = http_form_in + self.http_form_out = http_form_out self.transparent_proxy = transparent_proxy self.authenticator = authenticator self.confdir = os.path.expanduser(confdir) @@ -93,8 +94,9 @@ def process_proxy_options(parser, options): clientcerts=options.clientcerts, body_size_limit=body_size_limit, no_upstream_cert=options.no_upstream_cert, - reverse_proxy=rp, - forward_proxy=fp, + upstream_server=(rp or fp), + http_form_in=("relative" if (rp or trans) else "absolute"), + http_form_out=("absolute" if fp else "relative"), transparent_proxy=trans, authenticator=authenticator, ciphers=options.ciphers, diff --git a/libmproxy/proxy/primitives.py b/libmproxy/proxy/primitives.py index 8dd0e16a0..75ad54827 100644 --- a/libmproxy/proxy/primitives.py +++ b/libmproxy/proxy/primitives.py @@ -23,12 +23,10 @@ class AddressPriority(object): Enum that signifies the priority of the given address when choosing the destination host. Higher is better (None < i) """ - FORCE = 5 - """forward mode""" MANUALLY_CHANGED = 4 """user changed the target address in the ui""" FROM_SETTINGS = 3 - """reverse proxy mode""" + """upstream proxy from arguments (reverse proxy or forward proxy)""" FROM_CONNECTION = 2 """derived from transparent resolver""" FROM_PROTOCOL = 1 diff --git a/libmproxy/proxy/server.py b/libmproxy/proxy/server.py index 37ec7758b..5aaabf878 100644 --- a/libmproxy/proxy/server.py +++ b/libmproxy/proxy/server.py @@ -59,12 +59,6 @@ class ConnectionHandler: self.conntype = None self.sni = None - self.mode = "regular" - if self.config.reverse_proxy: - self.mode = "reverse" - if self.config.transparent_proxy: - self.mode = "transparent" - def handle(self): self.log("clientconnect") self.channel.ask("clientconnect", self) @@ -76,11 +70,8 @@ class ConnectionHandler: # Can we already identify the target server and connect to it? server_address = None address_priority = None - if self.config.forward_proxy: - server_address = self.config.forward_proxy[1:] - address_priority = AddressPriority.FORCE - elif self.config.reverse_proxy: - server_address = self.config.reverse_proxy[1:] + if self.config.upstream_server: + server_address = self.config.upstream_server[1:] address_priority = AddressPriority.FROM_SETTINGS elif self.config.transparent_proxy: server_address = self.config.transparent_proxy["resolver"].original_addr( @@ -125,8 +116,8 @@ class ConnectionHandler: if self.config.transparent_proxy: client_ssl = server_ssl = (self.server_conn.address.port in self.config.transparent_proxy["sslports"]) - elif self.config.reverse_proxy: - client_ssl = server_ssl = (self.config.reverse_proxy[0] == "https") + elif self.config.upstream_server: + client_ssl = server_ssl = (self.config.upstream_server[0] == "https") # TODO: Make protocol generic (as with transparent proxies) # TODO: Add SSL-terminating capatbility (SSL -> mitmproxy -> plain and vice versa) if client_ssl or server_ssl: @@ -152,7 +143,6 @@ class ConnectionHandler: """ Sets a new server address with the given priority. Does not re-establish either connection or SSL handshake. - @type priority: libmproxy.proxy.primitives.AddressPriority """ address = tcp.Address.wrap(address) diff --git a/test/test_protocol_http.py b/test/test_protocol_http.py index 6ff0cb65a..290d82a10 100644 --- a/test/test_protocol_http.py +++ b/test/test_protocol_http.py @@ -90,7 +90,7 @@ class TestInvalidRequests(tservers.HTTPProxTest): def test_double_connect(self): p = self.pathoc() r = p.request("connect:'%s:%s'" % ("127.0.0.1", self.server2.port)) - assert r.status_code == 502 + assert r.status_code == 400 assert "Must not CONNECT on already encrypted connection" in r.content def test_relative_request(self): @@ -98,7 +98,7 @@ class TestInvalidRequests(tservers.HTTPProxTest): p.connect() r = p.request("get:/p/200") assert r.status_code == 400 - assert "Invalid request form" in r.content + assert "Invalid HTTP request form" in r.content class TestProxyChaining(tservers.HTTPChainProxyTest): diff --git a/test/tservers.py b/test/tservers.py index bfafc8cd6..addc70110 100644 --- a/test/tservers.py +++ b/test/tservers.py @@ -197,6 +197,8 @@ class TransparentProxTest(ProxTestBase): resolver = cls.resolver(cls.server.port), sslports = ports ) + d["http_form_in"] = "relative" + d["http_form_out"] = "relative" return d def pathod(self, spec, sni=None): @@ -225,11 +227,13 @@ class ReverseProxTest(ProxTestBase): @classmethod def get_proxy_config(cls): d = ProxTestBase.get_proxy_config() - d["reverse_proxy"] = ( + d["upstream_server"] = ( "https" if cls.ssl else "http", "127.0.0.1", cls.server.port ) + d["http_form_in"] = "relative" + d["http_form_out"] = "relative" return d def pathoc(self, sni=None): @@ -258,18 +262,19 @@ class ChainProxTest(ProxTestBase): Chain n instances of mitmproxy in a row - because we can. """ n = 2 - chain_config = [lambda: ProxyConfig( - )] * n + chain_config = [lambda: ProxyConfig()] * n @classmethod def setupAll(cls): super(ChainProxTest, cls).setupAll() cls.chain = [] for i in range(cls.n): config = cls.chain_config[i]() - config.forward_proxy = ("http", "127.0.0.1", + config.upstream_server = ("http", "127.0.0.1", cls.proxy.port if i == 0 else cls.chain[-1].port ) + config.http_form_in = "absolute" + config.http_form_out = "absolute" tmaster = cls.masterclass(config) tmaster.start_app(APP_HOST, APP_PORT, cls.externalapp) cls.chain.append(ProxyThread(tmaster))