lay the foundations for --(in|out)(abs|rel) command line switches, as proposed in https://groups.google.com/forum/#!topic/mitmproxy/nApno2TXS0c

This commit is contained in:
Maximilian Hils 2014-03-10 02:32:27 +01:00
parent dd3aedca01
commit 78750a8b4d
6 changed files with 67 additions and 60 deletions

View File

@ -843,6 +843,12 @@ class HttpAuthenticationError(Exception):
class HTTPHandler(ProtocolHandler, TemporaryServerChangeMixin): 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): def handle_messages(self):
while self.handle_flow(): while self.handle_flow():
pass pass
@ -877,13 +883,15 @@ class HTTPHandler(ProtocolHandler, TemporaryServerChangeMixin):
flow = HTTPFlow(self.c.client_conn, self.c.server_conn, self.change_server) flow = HTTPFlow(self.c.client_conn, self.c.server_conn, self.change_server)
try: try:
req = HTTPRequest.from_stream(self.c.client_conn.rfile, 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.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 # Be careful NOT to assign the request to the flow before
# process_request completes. This is because the call can raise an # 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 # in an Error object that has an attached request that has not been
# sent through to the Master. # sent through to the Master.
flow.request = req flow.request = req
@ -1004,44 +1012,48 @@ class HTTPHandler(ProtocolHandler, TemporaryServerChangeMixin):
Upgrade the connection to SSL after an authority (CONNECT) request has been made. Upgrade the connection to SSL after an authority (CONNECT) request has been made.
""" """
self.c.log("Received CONNECT request. Upgrading to SSL...") self.c.log("Received CONNECT request. Upgrading to SSL...")
self.c.mode = "transparent" self.expected_form_in = "relative"
self.c.determine_conntype() self.expected_form_out = "relative"
self.c.establish_ssl(server=True, client=True) self.c.establish_ssl(server=True, client=True)
self.c.log("Upgrade to SSL completed.") self.c.log("Upgrade to SSL completed.")
raise ConnectionTypeChange
def process_request(self, flow, request): def process_request(self, flow, request):
if self.c.mode == "regular":
if not self.skip_authentication:
self.authenticate(request) 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": if request.form_in == "authority":
directly_addressed_at_mitmproxy = (self.c.mode == "regular" and not self.c.config.forward_proxy) if self.c.client_conn.ssl_established:
if directly_addressed_at_mitmproxy: raise http.HttpError(400, "Must not CONNECT on already encrypted connection")
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.mode == "regular": if self.expected_form_in == "absolute":
if request.form_in == "authority": # forward mode if not self.c.config.upstream_server:
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"
self.c.set_server_address((request.host, request.port), AddressPriority.FROM_PROTOCOL) 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 flow.server_conn = self.c.server_conn # Update server_conn attribute on the flow
else: self.c.client_conn.send(
raise http.HttpError(400, "Invalid request form (absolute-form or authority-form required)") '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): def authenticate(self, request):
if self.c.config.authenticator: if self.c.config.authenticator:

View File

@ -10,16 +10,17 @@ CONF_DIR = "~/.mitmproxy"
class ProxyConfig: class ProxyConfig:
def __init__(self, confdir=CONF_DIR, clientcerts=None, def __init__(self, confdir=CONF_DIR, clientcerts=None,
no_upstream_cert=False, body_size_limit=None, reverse_proxy=None, no_upstream_cert=False, body_size_limit=None, upstream_server=None,
forward_proxy=None, transparent_proxy=None, authenticator=None, http_form_in="absolute", http_form_out="relative", transparent_proxy=None, authenticator=None,
ciphers=None, certs=None ciphers=None, certs=None
): ):
self.ciphers = ciphers self.ciphers = ciphers
self.clientcerts = clientcerts self.clientcerts = clientcerts
self.no_upstream_cert = no_upstream_cert self.no_upstream_cert = no_upstream_cert
self.body_size_limit = body_size_limit self.body_size_limit = body_size_limit
self.reverse_proxy = reverse_proxy self.upstream_server = upstream_server
self.forward_proxy = forward_proxy self.http_form_in = http_form_in
self.http_form_out = http_form_out
self.transparent_proxy = transparent_proxy self.transparent_proxy = transparent_proxy
self.authenticator = authenticator self.authenticator = authenticator
self.confdir = os.path.expanduser(confdir) self.confdir = os.path.expanduser(confdir)
@ -93,8 +94,9 @@ def process_proxy_options(parser, options):
clientcerts=options.clientcerts, clientcerts=options.clientcerts,
body_size_limit=body_size_limit, body_size_limit=body_size_limit,
no_upstream_cert=options.no_upstream_cert, no_upstream_cert=options.no_upstream_cert,
reverse_proxy=rp, upstream_server=(rp or fp),
forward_proxy=fp, http_form_in=("relative" if (rp or trans) else "absolute"),
http_form_out=("absolute" if fp else "relative"),
transparent_proxy=trans, transparent_proxy=trans,
authenticator=authenticator, authenticator=authenticator,
ciphers=options.ciphers, ciphers=options.ciphers,

View File

@ -23,12 +23,10 @@ class AddressPriority(object):
Enum that signifies the priority of the given address when choosing the destination host. Enum that signifies the priority of the given address when choosing the destination host.
Higher is better (None < i) Higher is better (None < i)
""" """
FORCE = 5
"""forward mode"""
MANUALLY_CHANGED = 4 MANUALLY_CHANGED = 4
"""user changed the target address in the ui""" """user changed the target address in the ui"""
FROM_SETTINGS = 3 FROM_SETTINGS = 3
"""reverse proxy mode""" """upstream proxy from arguments (reverse proxy or forward proxy)"""
FROM_CONNECTION = 2 FROM_CONNECTION = 2
"""derived from transparent resolver""" """derived from transparent resolver"""
FROM_PROTOCOL = 1 FROM_PROTOCOL = 1

View File

@ -59,12 +59,6 @@ class ConnectionHandler:
self.conntype = None self.conntype = None
self.sni = 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): def handle(self):
self.log("clientconnect") self.log("clientconnect")
self.channel.ask("clientconnect", self) self.channel.ask("clientconnect", self)
@ -76,11 +70,8 @@ class ConnectionHandler:
# Can we already identify the target server and connect to it? # Can we already identify the target server and connect to it?
server_address = None server_address = None
address_priority = None address_priority = None
if self.config.forward_proxy: if self.config.upstream_server:
server_address = self.config.forward_proxy[1:] server_address = self.config.upstream_server[1:]
address_priority = AddressPriority.FORCE
elif self.config.reverse_proxy:
server_address = self.config.reverse_proxy[1:]
address_priority = AddressPriority.FROM_SETTINGS address_priority = AddressPriority.FROM_SETTINGS
elif self.config.transparent_proxy: elif self.config.transparent_proxy:
server_address = self.config.transparent_proxy["resolver"].original_addr( server_address = self.config.transparent_proxy["resolver"].original_addr(
@ -125,8 +116,8 @@ class ConnectionHandler:
if self.config.transparent_proxy: if self.config.transparent_proxy:
client_ssl = server_ssl = (self.server_conn.address.port in self.config.transparent_proxy["sslports"]) client_ssl = server_ssl = (self.server_conn.address.port in self.config.transparent_proxy["sslports"])
elif self.config.reverse_proxy: elif self.config.upstream_server:
client_ssl = server_ssl = (self.config.reverse_proxy[0] == "https") client_ssl = server_ssl = (self.config.upstream_server[0] == "https")
# TODO: Make protocol generic (as with transparent proxies) # TODO: Make protocol generic (as with transparent proxies)
# TODO: Add SSL-terminating capatbility (SSL -> mitmproxy -> plain and vice versa) # TODO: Add SSL-terminating capatbility (SSL -> mitmproxy -> plain and vice versa)
if client_ssl or server_ssl: if client_ssl or server_ssl:
@ -152,7 +143,6 @@ class ConnectionHandler:
""" """
Sets a new server address with the given priority. Sets a new server address with the given priority.
Does not re-establish either connection or SSL handshake. Does not re-establish either connection or SSL handshake.
@type priority: libmproxy.proxy.primitives.AddressPriority
""" """
address = tcp.Address.wrap(address) address = tcp.Address.wrap(address)

View File

@ -90,7 +90,7 @@ class TestInvalidRequests(tservers.HTTPProxTest):
def test_double_connect(self): def test_double_connect(self):
p = self.pathoc() p = self.pathoc()
r = p.request("connect:'%s:%s'" % ("127.0.0.1", self.server2.port)) 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 assert "Must not CONNECT on already encrypted connection" in r.content
def test_relative_request(self): def test_relative_request(self):
@ -98,7 +98,7 @@ class TestInvalidRequests(tservers.HTTPProxTest):
p.connect() p.connect()
r = p.request("get:/p/200") r = p.request("get:/p/200")
assert r.status_code == 400 assert r.status_code == 400
assert "Invalid request form" in r.content assert "Invalid HTTP request form" in r.content
class TestProxyChaining(tservers.HTTPChainProxyTest): class TestProxyChaining(tservers.HTTPChainProxyTest):

View File

@ -197,6 +197,8 @@ class TransparentProxTest(ProxTestBase):
resolver = cls.resolver(cls.server.port), resolver = cls.resolver(cls.server.port),
sslports = ports sslports = ports
) )
d["http_form_in"] = "relative"
d["http_form_out"] = "relative"
return d return d
def pathod(self, spec, sni=None): def pathod(self, spec, sni=None):
@ -225,11 +227,13 @@ class ReverseProxTest(ProxTestBase):
@classmethod @classmethod
def get_proxy_config(cls): def get_proxy_config(cls):
d = ProxTestBase.get_proxy_config() d = ProxTestBase.get_proxy_config()
d["reverse_proxy"] = ( d["upstream_server"] = (
"https" if cls.ssl else "http", "https" if cls.ssl else "http",
"127.0.0.1", "127.0.0.1",
cls.server.port cls.server.port
) )
d["http_form_in"] = "relative"
d["http_form_out"] = "relative"
return d return d
def pathoc(self, sni=None): def pathoc(self, sni=None):
@ -258,18 +262,19 @@ class ChainProxTest(ProxTestBase):
Chain n instances of mitmproxy in a row - because we can. Chain n instances of mitmproxy in a row - because we can.
""" """
n = 2 n = 2
chain_config = [lambda: ProxyConfig( chain_config = [lambda: ProxyConfig()] * n
)] * n
@classmethod @classmethod
def setupAll(cls): def setupAll(cls):
super(ChainProxTest, cls).setupAll() super(ChainProxTest, cls).setupAll()
cls.chain = [] cls.chain = []
for i in range(cls.n): for i in range(cls.n):
config = cls.chain_config[i]() 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.proxy.port if i == 0 else
cls.chain[-1].port cls.chain[-1].port
) )
config.http_form_in = "absolute"
config.http_form_out = "absolute"
tmaster = cls.masterclass(config) tmaster = cls.masterclass(config)
tmaster.start_app(APP_HOST, APP_PORT, cls.externalapp) tmaster.start_app(APP_HOST, APP_PORT, cls.externalapp)
cls.chain.append(ProxyThread(tmaster)) cls.chain.append(ProxyThread(tmaster))