diff options
author | Maximilian Hils <git@maximilianhils.com> | 2015-08-30 01:21:58 +0200 |
---|---|---|
committer | Maximilian Hils <git@maximilianhils.com> | 2015-08-30 01:21:58 +0200 |
commit | dd7f50d64bef38fa67b4cace91913d03691dde26 (patch) | |
tree | c183aa1f286ec191a094713d1f697f2fa54ab385 /libmproxy | |
parent | 100ea27c30d89b895a02a1b128edc5472ab84b3e (diff) | |
download | mitmproxy-dd7f50d64bef38fa67b4cace91913d03691dde26.tar.gz mitmproxy-dd7f50d64bef38fa67b4cace91913d03691dde26.tar.bz2 mitmproxy-dd7f50d64bef38fa67b4cace91913d03691dde26.zip |
restructure code, remove cruft
Diffstat (limited to 'libmproxy')
-rw-r--r-- | libmproxy/exceptions.py | 4 | ||||
-rw-r--r-- | libmproxy/flow.py | 2 | ||||
-rw-r--r-- | libmproxy/protocol/http.py | 83 | ||||
-rw-r--r-- | libmproxy/protocol2/http.py | 112 | ||||
-rw-r--r-- | libmproxy/protocol2/http_proxy.py | 3 | ||||
-rw-r--r-- | libmproxy/protocol2/http_replay.py | 95 | ||||
-rw-r--r-- | libmproxy/protocol2/layer.py | 19 | ||||
-rw-r--r-- | libmproxy/protocol2/messages.py | 46 | ||||
-rw-r--r-- | libmproxy/protocol2/rawtcp.py | 13 | ||||
-rw-r--r-- | libmproxy/protocol2/reverse_proxy.py | 5 | ||||
-rw-r--r-- | libmproxy/protocol2/root_context.py | 5 | ||||
-rw-r--r-- | libmproxy/protocol2/socks_proxy.py | 54 | ||||
-rw-r--r-- | libmproxy/protocol2/tls.py | 22 | ||||
-rw-r--r-- | libmproxy/protocol2/transparent_proxy.py | 3 | ||||
-rw-r--r-- | libmproxy/proxy/__init__.py | 13 | ||||
-rw-r--r-- | libmproxy/proxy/connection.py | 6 | ||||
-rw-r--r-- | libmproxy/proxy/primitives.py | 179 | ||||
-rw-r--r-- | libmproxy/proxy/server.py | 401 | ||||
-rw-r--r-- | libmproxy/utils.py | 4 |
19 files changed, 240 insertions, 829 deletions
diff --git a/libmproxy/exceptions.py b/libmproxy/exceptions.py index 3825c409..f34d9707 100644 --- a/libmproxy/exceptions.py +++ b/libmproxy/exceptions.py @@ -18,6 +18,10 @@ class ProtocolException(ProxyException): pass +class Socks5Exception(ProtocolException): + pass + + class HttpException(ProtocolException): pass diff --git a/libmproxy/flow.py b/libmproxy/flow.py index a2b807ba..dac607a0 100644 --- a/libmproxy/flow.py +++ b/libmproxy/flow.py @@ -8,7 +8,7 @@ import Cookie import cookielib import os import re -from libmproxy.protocol2.http import RequestReplayThread +from libmproxy.protocol2.http_replay import RequestReplayThread from netlib import odict, wsgi, tcp from netlib.http.semantics import CONTENT_MISSING diff --git a/libmproxy/protocol/http.py b/libmproxy/protocol/http.py index 56d7d57f..a30437d1 100644 --- a/libmproxy/protocol/http.py +++ b/libmproxy/protocol/http.py @@ -695,85 +695,4 @@ class HTTPHandler(ProtocolHandler): else: raise http.HttpAuthenticationError( self.c.config.authenticator.auth_challenge_headers()) - return request.headers - - -class RequestReplayThread(threading.Thread): - name = "RequestReplayThread" - - def __init__(self, config, flow, masterq, should_exit): - """ - masterqueue can be a queue or None, if no scripthooks should be - processed. - """ - self.config, self.flow = config, flow - if masterq: - self.channel = controller.Channel(masterq, should_exit) - else: - self.channel = None - super(RequestReplayThread, self).__init__() - - def run(self): - r = self.flow.request - form_out_backup = r.form_out - try: - self.flow.response = None - - # If we have a channel, run script hooks. - if self.channel: - request_reply = self.channel.ask("request", self.flow) - if request_reply is None or request_reply == KILL: - raise KillSignal() - elif isinstance(request_reply, HTTPResponse): - self.flow.response = request_reply - - if not self.flow.response: - # In all modes, we directly connect to the server displayed - if self.config.mode == "upstream": - # FIXME - server_address = self.config.mode.get_upstream_server( - self.flow.client_conn - )[2:] - server = ServerConnection(server_address) - server.connect() - if r.scheme == "https": - send_connect_request(server, r.host, r.port) - server.establish_ssl( - self.config.clientcerts, - sni=self.flow.server_conn.sni - ) - r.form_out = "relative" - else: - r.form_out = "absolute" - else: - server_address = (r.host, r.port) - server = ServerConnection(server_address) - server.connect() - if r.scheme == "https": - server.establish_ssl( - self.config.clientcerts, - sni=self.flow.server_conn.sni - ) - r.form_out = "relative" - - server.send(self.flow.server_conn.protocol.assemble(r)) - self.flow.server_conn = server - self.flow.response = HTTPResponse.from_protocol( - self.flow.server_conn.protocol, - r.method, - body_size_limit=self.config.body_size_limit, - ) - if self.channel: - response_reply = self.channel.ask("response", self.flow) - if response_reply is None or response_reply == KILL: - raise KillSignal() - except (proxy.ProxyError, http.HttpError, tcp.NetLibError) as v: - self.flow.error = Error(repr(v)) - if self.channel: - self.channel.ask("error", self.flow) - except KillSignal: - # KillSignal should only be raised if there's a channel in the - # first place. - self.channel.tell("log", proxy.Log("Connection killed", "info")) - finally: - r.form_out = form_out_backup + return request.headers
\ No newline at end of file diff --git a/libmproxy/protocol2/http.py b/libmproxy/protocol2/http.py index a3f32926..a508ae8b 100644 --- a/libmproxy/protocol2/http.py +++ b/libmproxy/protocol2/http.py @@ -1,28 +1,20 @@ from __future__ import (absolute_import, print_function, division) -from .. import version -import threading -from ..exceptions import InvalidCredentials, HttpException, ProtocolException -from .layer import Layer -from libmproxy import utils -from libmproxy.controller import Channel -from libmproxy.protocol2.layer import Kill -from libmproxy.protocol import KILL, Error - -from libmproxy.protocol.http import HTTPFlow -from libmproxy.protocol.http_wrappers import HTTPResponse, HTTPRequest -from libmproxy.proxy import Log -from libmproxy.proxy.connection import ServerConnection from netlib import tcp -from netlib.http import status_codes, http1, http2, HttpErrorConnClosed, HttpError +from netlib.http import status_codes, http1, HttpErrorConnClosed, HttpError from netlib.http.semantics import CONTENT_MISSING from netlib import odict 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. +from .. import version, utils +from ..exceptions import InvalidCredentials, HttpException, ProtocolException +from .layer import Layer +from ..proxy import Kill +from libmproxy.protocol import KILL, Error +from libmproxy.protocol.http import HTTPFlow +from libmproxy.protocol.http_wrappers import HTTPResponse, HTTPRequest class _HttpLayer(Layer): @@ -138,6 +130,7 @@ class Http1Layer(_StreamingHttpLayer): layer() +# TODO: The HTTP2 layer is missing multiplexing, which requires a major rewrite. class Http2Layer(_HttpLayer): def __init__(self, ctx, mode): super(Http2Layer, self).__init__(ctx) @@ -359,6 +352,9 @@ class HttpLayer(Layer): return except (HttpErrorConnClosed, NetLibError, HttpError, ProtocolException) as e: + if flow.request and not flow.response: + flow.error = Error(repr(e)) + self.channel.ask("error", flow) try: self.send_response(make_error_response( getattr(e, "code", 502), @@ -590,87 +586,3 @@ class HttpLayer(Layer): ]) )) raise InvalidCredentials("Proxy Authentication Required") - - -class RequestReplayThread(threading.Thread): - name = "RequestReplayThread" - - def __init__(self, config, flow, masterq, should_exit): - """ - masterqueue can be a queue or None, if no scripthooks should be - processed. - """ - self.config, self.flow = config, flow - if masterq: - self.channel = Channel(masterq, should_exit) - else: - self.channel = None - super(RequestReplayThread, self).__init__() - - def run(self): - r = self.flow.request - form_out_backup = r.form_out - try: - self.flow.response = None - - # If we have a channel, run script hooks. - if self.channel: - request_reply = self.channel.ask("request", self.flow) - if request_reply is None or request_reply == KILL: - raise Kill() - elif isinstance(request_reply, HTTPResponse): - self.flow.response = request_reply - - if not self.flow.response: - # In all modes, we directly connect to the server displayed - if self.config.mode == "upstream": - server_address = self.config.upstream_server.address - server = ServerConnection(server_address) - server.connect() - protocol = HTTP1Protocol(server) - if r.scheme == "https": - connect_request = make_connect_request((r.host, r.port)) - server.send(protocol.assemble(connect_request)) - resp = protocol.read_response("CONNECT") - if resp.code != 200: - raise HttpError(502, "Upstream server refuses CONNECT request") - server.establish_ssl( - self.config.clientcerts, - sni=self.flow.server_conn.sni - ) - r.form_out = "relative" - else: - r.form_out = "absolute" - else: - server_address = (r.host, r.port) - server = ServerConnection(server_address) - server.connect() - protocol = HTTP1Protocol(server) - if r.scheme == "https": - server.establish_ssl( - self.config.clientcerts, - sni=self.flow.server_conn.sni - ) - r.form_out = "relative" - - server.send(protocol.assemble(r)) - self.flow.server_conn = server - self.flow.response = HTTPResponse.from_protocol( - protocol, - r.method, - body_size_limit=self.config.body_size_limit, - ) - if self.channel: - response_reply = self.channel.ask("response", self.flow) - if response_reply is None or response_reply == KILL: - raise Kill() - except (HttpError, tcp.NetLibError) as v: - self.flow.error = Error(repr(v)) - if self.channel: - self.channel.ask("error", self.flow) - except Kill: - # KillSignal should only be raised if there's a channel in the - # first place. - self.channel.tell("log", Log("Connection killed", "info")) - finally: - r.form_out = form_out_backup diff --git a/libmproxy/protocol2/http_proxy.py b/libmproxy/protocol2/http_proxy.py index c24af6cf..b3389eb7 100644 --- a/libmproxy/protocol2/http_proxy.py +++ b/libmproxy/protocol2/http_proxy.py @@ -13,6 +13,7 @@ class HttpProxy(Layer, ServerConnectionMixin): 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) @@ -23,4 +24,4 @@ class HttpUpstreamProxy(Layer, ServerConnectionMixin): layer() finally: if self.server_conn: - self._disconnect()
\ No newline at end of file + self._disconnect() diff --git a/libmproxy/protocol2/http_replay.py b/libmproxy/protocol2/http_replay.py new file mode 100644 index 00000000..872ef9cd --- /dev/null +++ b/libmproxy/protocol2/http_replay.py @@ -0,0 +1,95 @@ +import threading +from netlib.http import HttpError +from netlib.http.http1 import HTTP1Protocol +from netlib.tcp import NetLibError + +from ..controller import Channel +from ..protocol import KILL, Error +from ..protocol.http_wrappers import HTTPResponse +from ..proxy import Log, Kill +from ..proxy.connection import ServerConnection +from .http import make_connect_request + + +class RequestReplayThread(threading.Thread): + name = "RequestReplayThread" + + def __init__(self, config, flow, masterq, should_exit): + """ + masterqueue can be a queue or None, if no scripthooks should be + processed. + """ + self.config, self.flow = config, flow + if masterq: + self.channel = Channel(masterq, should_exit) + else: + self.channel = None + super(RequestReplayThread, self).__init__() + + def run(self): + r = self.flow.request + form_out_backup = r.form_out + try: + self.flow.response = None + + # If we have a channel, run script hooks. + if self.channel: + request_reply = self.channel.ask("request", self.flow) + if request_reply is None or request_reply == KILL: + raise Kill() + elif isinstance(request_reply, HTTPResponse): + self.flow.response = request_reply + + if not self.flow.response: + # In all modes, we directly connect to the server displayed + if self.config.mode == "upstream": + server_address = self.config.upstream_server.address + server = ServerConnection(server_address) + server.connect() + protocol = HTTP1Protocol(server) + if r.scheme == "https": + connect_request = make_connect_request((r.host, r.port)) + server.send(protocol.assemble(connect_request)) + resp = protocol.read_response("CONNECT") + if resp.code != 200: + raise HttpError(502, "Upstream server refuses CONNECT request") + server.establish_ssl( + self.config.clientcerts, + sni=self.flow.server_conn.sni + ) + r.form_out = "relative" + else: + r.form_out = "absolute" + else: + server_address = (r.host, r.port) + server = ServerConnection(server_address) + server.connect() + protocol = HTTP1Protocol(server) + if r.scheme == "https": + server.establish_ssl( + self.config.clientcerts, + sni=self.flow.server_conn.sni + ) + r.form_out = "relative" + + server.send(protocol.assemble(r)) + self.flow.server_conn = server + self.flow.response = HTTPResponse.from_protocol( + protocol, + r.method, + body_size_limit=self.config.body_size_limit, + ) + if self.channel: + response_reply = self.channel.ask("response", self.flow) + if response_reply is None or response_reply == KILL: + raise Kill() + except (HttpError, NetLibError) as v: + self.flow.error = Error(repr(v)) + if self.channel: + self.channel.ask("error", self.flow) + except Kill: + # KillSignal should only be raised if there's a channel in the + # first place. + self.channel.tell("log", Log("Connection killed", "info")) + finally: + r.form_out = form_out_backup diff --git a/libmproxy/protocol2/layer.py b/libmproxy/protocol2/layer.py index f72320ff..2b47cc26 100644 --- a/libmproxy/protocol2/layer.py +++ b/libmproxy/protocol2/layer.py @@ -30,8 +30,6 @@ Further goals: inline scripts shall have a chance to handle everything locally. """ from __future__ import (absolute_import, print_function, division) -import Queue -import threading from netlib import tcp from ..proxy import Log from ..proxy.connection import ServerConnection @@ -80,10 +78,8 @@ class Layer(_LayerCodeCompletion): def log(self, msg, level, subs=()): full_msg = [ - "%s:%s: %s" % - (self.client_conn.address.host, - self.client_conn.address.port, - msg)] + "{}: {}".format(repr(self.client_conn.address), msg) + ] for i in subs: full_msg.append(" -> " + i) full_msg = "\n".join(full_msg) @@ -119,7 +115,7 @@ class ServerConnectionMixin(object): self.log("Set new server address: " + repr(address), "debug") self.server_conn.address = address else: - self.ctx.set_server(address, server_tls, sni, depth-1) + self.ctx.set_server(address, server_tls, sni, depth - 1) def _disconnect(self): """ @@ -138,10 +134,5 @@ class ServerConnectionMixin(object): try: self.server_conn.connect() except tcp.NetLibError as e: - raise ProtocolException("Server connection to '%s' failed: %s" % (self.server_conn.address, e), e) - - -class Kill(Exception): - """ - Kill a connection. - """
\ No newline at end of file + raise ProtocolException( + "Server connection to '%s' failed: %s" % (self.server_conn.address, e), e) diff --git a/libmproxy/protocol2/messages.py b/libmproxy/protocol2/messages.py deleted file mode 100644 index de049486..00000000 --- a/libmproxy/protocol2/messages.py +++ /dev/null @@ -1,46 +0,0 @@ -""" -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): - def __eq__(self, other): - # Allow message == Connect checks. - if isinstance(self, other): - return True - return self is other - - def __ne__(self, other): - return not self.__eq__(other) - - -class Connect(_Message): - """ - Connect to the server - """ - - -class Reconnect(_Message): - """ - Re-establish the server connection - """ - - -class SetServer(_Message): - """ - Change the upstream server. - """ - - def __init__(self, address, server_tls, sni, depth=1): - self.address = Address.wrap(address) - self.server_tls = server_tls - self.sni = sni - - # upstream proxy scenario: you may want to change either the final target or the upstream proxy. - # We can express this neatly as the "nth-server-providing-layer" - # ServerConnection could get a `via` attribute. - self.depth = depth - - diff --git a/libmproxy/protocol2/rawtcp.py b/libmproxy/protocol2/rawtcp.py index e8e3cf65..b10217f1 100644 --- a/libmproxy/protocol2/rawtcp.py +++ b/libmproxy/protocol2/rawtcp.py @@ -4,10 +4,9 @@ import select from OpenSSL import SSL -from ..exceptions import ProtocolException from netlib.tcp import NetLibError from netlib.utils import cleanBin -from ..protocol.tcp import TCPHandler +from ..exceptions import ProtocolException from .layer import Layer @@ -31,6 +30,7 @@ class RawTcpLayer(Layer): while True: r, _, _ = select.select(conns, [], [], 10) for conn in r: + dst = server if conn == client else client size = conn.recv_into(buf, self.chunk_size) if not size: @@ -41,22 +41,21 @@ class RawTcpLayer(Layer): # Sockets will be cleaned up on a higher level. return else: - conn.shutdown(socket.SHUT_WR) + dst.shutdown(socket.SHUT_WR) if len(conns) == 0: return continue - dst = server if conn == client else client dst.sendall(buf[:size]) if self.logging: # log messages are prepended with the client address, # hence the "weird" direction string. if dst == server: - direction = "-> tcp -> {!r}".format(self.server_conn.address) + direction = "-> tcp -> {}".format(repr(self.server_conn.address)) else: - direction = "<- tcp <- {!r}".format(self.server_conn.address) + direction = "<- tcp <- {}".format(repr(self.server_conn.address)) data = cleanBin(buf[:size].tobytes()) self.log( "{}\r\n{}".format(direction, data), @@ -64,4 +63,4 @@ class RawTcpLayer(Layer): ) except (socket.error, NetLibError, SSL.Error) as e: - raise ProtocolException("TCP connection closed unexpectedly: {}".format(repr(e)), e)
\ No newline at end of file + raise ProtocolException("TCP connection closed unexpectedly: {}".format(repr(e)), e) diff --git a/libmproxy/protocol2/reverse_proxy.py b/libmproxy/protocol2/reverse_proxy.py index 76163c71..e959db86 100644 --- a/libmproxy/protocol2/reverse_proxy.py +++ b/libmproxy/protocol2/reverse_proxy.py @@ -5,17 +5,18 @@ from .tls import TlsLayer class ReverseProxy(Layer, ServerConnectionMixin): - def __init__(self, ctx, server_address, client_tls, server_tls): super(ReverseProxy, self).__init__(ctx, server_address=server_address) self._client_tls = client_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) try: layer() finally: if self.server_conn: - self._disconnect()
\ No newline at end of file + self._disconnect() diff --git a/libmproxy/protocol2/root_context.py b/libmproxy/protocol2/root_context.py index af0e7a37..4d69204f 100644 --- a/libmproxy/protocol2/root_context.py +++ b/libmproxy/protocol2/root_context.py @@ -32,7 +32,7 @@ class RootContext(object): # 1. Check for --ignore. if self.config.check_ignore(top_layer.server_conn.address): - return RawTcpLayer(top_layer) + return RawTcpLayer(top_layer, logging=False) # 2. Check for TLS # TLS ClientHello magic, works for SSLv3, TLSv1.0, TLSv1.1, TLSv1.2 @@ -62,7 +62,8 @@ class RootContext(object): # d = top_layer.client_conn.rfile.peek(3) # is_ascii = ( # len(d) == 3 and - # all(x in string.ascii_letters for x in d) # better be safe here and don't expect uppercase... + # # 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)) diff --git a/libmproxy/protocol2/socks_proxy.py b/libmproxy/protocol2/socks_proxy.py index 91935d24..525520e8 100644 --- a/libmproxy/protocol2/socks_proxy.py +++ b/libmproxy/protocol2/socks_proxy.py @@ -1,27 +1,59 @@ from __future__ import (absolute_import, print_function, division) -from ..exceptions import ProtocolException -from ..proxy import ProxyError, Socks5ProxyMode +from netlib import socks +from netlib.tcp import NetLibError +from ..exceptions import Socks5Exception from .layer import Layer, ServerConnectionMixin class Socks5Proxy(Layer, ServerConnectionMixin): def __call__(self): try: - s5mode = Socks5ProxyMode([]) - address = s5mode.get_upstream_server(self.client_conn)[2:] - except ProxyError as e: - # TODO: Unmonkeypatch - raise ProtocolException(str(e), e) + # 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" + ) - self.server_conn.address = address + # 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() - # TODO: Kill event + # 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." + ) - layer = self.ctx.next_layer(self) + # 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()
\ No newline at end of file + self._disconnect() diff --git a/libmproxy/protocol2/tls.py b/libmproxy/protocol2/tls.py index 850bf5dc..0c02b0ea 100644 --- a/libmproxy/protocol2/tls.py +++ b/libmproxy/protocol2/tls.py @@ -1,11 +1,11 @@ from __future__ import (absolute_import, print_function, division) import struct -from construct import ConstructError -from netlib import tcp -import netlib.http.http2 +from construct import ConstructError +from netlib.tcp import NetLibError, NetLibInvalidCertificateError +from netlib.http.http1 import HTTP1Protocol from ..contrib.tls._constructs import ClientHello from ..exceptions import ProtocolException from .layer import Layer @@ -161,7 +161,7 @@ class TlsLayer(Layer): """ # This gets triggered if we haven't established an upstream connection yet. - default_alpn = netlib.http.http1.HTTP1Protocol.ALPN_PROTO_HTTP1 + default_alpn = HTTP1Protocol.ALPN_PROTO_HTTP1 # alpn_preference = netlib.http.http2.HTTP2Protocol.ALPN_PROTO_H2 if self.alpn_for_client_connection in options: @@ -203,7 +203,7 @@ class TlsLayer(Layer): chain_file=chain_file, alpn_select_callback=self.__alpn_select_callback, ) - except tcp.NetLibError as e: + except NetLibError as e: raise ProtocolException("Cannot establish TLS with client: %s" % repr(e), e) def _establish_tls_with_server(self): @@ -236,7 +236,7 @@ class TlsLayer(Layer): (tls_cert_err['depth'], tls_cert_err['errno']), "error") self.log("Ignoring server verification error, continuing with connection", "error") - except tcp.NetLibInvalidCertificateError as e: + except NetLibInvalidCertificateError as e: tls_cert_err = self.server_conn.ssl_verification_error self.log( "TLS verification failed for upstream server at depth %s with error: %s" % @@ -244,7 +244,7 @@ class TlsLayer(Layer): "error") self.log("Aborting connection attempt", "error") raise ProtocolException("Cannot establish TLS with server: %s" % repr(e), e) - except tcp.NetLibError as e: + except NetLibError as e: raise ProtocolException("Cannot establish TLS with server: %s" % repr(e), e) self.log("ALPN selected by server: %s" % self.alpn_for_client_connection, "debug") @@ -253,8 +253,12 @@ class TlsLayer(Layer): host = self.server_conn.address.host sans = set() # Incorporate upstream certificate - if self.server_conn and self.server_conn.tls_established and ( - not self.config.no_upstream_cert): + use_upstream_cert = ( + self.server_conn and + self.server_conn.tls_established and + (not self.config.no_upstream_cert) + ) + if use_upstream_cert: upstream_cert = self.server_conn.cert sans.update(upstream_cert.altnames) if upstream_cert.cn: diff --git a/libmproxy/protocol2/transparent_proxy.py b/libmproxy/protocol2/transparent_proxy.py index 9263dbde..e6ebf115 100644 --- a/libmproxy/protocol2/transparent_proxy.py +++ b/libmproxy/protocol2/transparent_proxy.py @@ -6,7 +6,6 @@ from .layer import Layer, ServerConnectionMixin class TransparentProxy(Layer, ServerConnectionMixin): - def __init__(self, ctx): super(TransparentProxy, self).__init__(ctx) self.resolver = platform.resolver() @@ -22,4 +21,4 @@ class TransparentProxy(Layer, ServerConnectionMixin): layer() finally: if self.server_conn: - self._disconnect()
\ No newline at end of file + self._disconnect() diff --git a/libmproxy/proxy/__init__.py b/libmproxy/proxy/__init__.py index 7d664707..709654cb 100644 --- a/libmproxy/proxy/__init__.py +++ b/libmproxy/proxy/__init__.py @@ -1,2 +1,11 @@ -from .primitives import * -from .config import ProxyConfig, process_proxy_options
\ No newline at end of file +from __future__ import (absolute_import, print_function, division) + +from .primitives import Log, Kill +from .config import ProxyConfig +from .connection import ClientConnection, ServerConnection + +__all__ = [ + "Log", "Kill", + "ProxyConfig", + "ClientConnection", "ServerConnection" +]
\ No newline at end of file diff --git a/libmproxy/proxy/connection.py b/libmproxy/proxy/connection.py index c329ed64..94f318f6 100644 --- a/libmproxy/proxy/connection.py +++ b/libmproxy/proxy/connection.py @@ -12,7 +12,7 @@ class ClientConnection(tcp.BaseHandler, stateobject.StateObject): # 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) + super(ClientConnection, self).__init__(client_connection, address, server) else: self.connection = None self.server = None @@ -80,11 +80,11 @@ class ClientConnection(tcp.BaseHandler, stateobject.StateObject): return f def convert_to_ssl(self, *args, **kwargs): - tcp.BaseHandler.convert_to_ssl(self, *args, **kwargs) + super(ClientConnection, self).convert_to_ssl(*args, **kwargs) self.timestamp_ssl_setup = utils.timestamp() def finish(self): - tcp.BaseHandler.finish(self) + super(ClientConnection, self).finish() self.timestamp_end = utils.timestamp() diff --git a/libmproxy/proxy/primitives.py b/libmproxy/proxy/primitives.py index a9f31181..2e440fe8 100644 --- a/libmproxy/proxy/primitives.py +++ b/libmproxy/proxy/primitives.py @@ -1,178 +1,15 @@ from __future__ import absolute_import +import collections 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" % repr(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: +class Log(object): def __init__(self, msg, level="info"): self.msg = msg self.level = level + + +class Kill(Exception): + """ + Kill a connection. + """
\ No newline at end of file diff --git a/libmproxy/proxy/server.py b/libmproxy/proxy/server.py index 1fc4cbda..69784014 100644 --- a/libmproxy/proxy/server.py +++ b/libmproxy/proxy/server.py @@ -3,16 +3,14 @@ from __future__ import absolute_import, print_function import traceback import sys import socket -from libmproxy.protocol2.layer import Kill from netlib import tcp from netlib.http.http1 import HTTP1Protocol from netlib.tcp import NetLibError -from ..protocol.handle import protocol_handler from .. import protocol2 -from ..exceptions import ProtocolException -from .primitives import ProxyServerError, Log, ProxyError -from .connection import ClientConnection, ServerConnection +from ..exceptions import ProtocolException, ServerException +from .primitives import Log, Kill +from .connection import ClientConnection class DummyServer: @@ -38,9 +36,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): @@ -51,33 +49,28 @@ class ProxyServer(tcp.TCPServer): self.channel = channel def handle_client_connection(self, conn, client_address): - h = ConnectionHandler2( - self.config, + h = ConnectionHandler( conn, client_address, - self, - self.channel) + self.config, + self.channel + ) h.handle() - h.finish() -class ConnectionHandler2: - # FIXME: parameter ordering - # FIXME: remove server attribute - def __init__(self, config, client_conn, 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_conn, client_address, - server) + None) """@type: libmproxy.proxy.connection.ClientConnection""" self.channel = channel """@type: libmproxy.controller.Channel""" - def handle(self): - self.log("clientconnect", "info") - + def _create_root_layer(self): root_context = protocol2.RootContext( self.client_conn, self.config, @@ -86,33 +79,38 @@ class ConnectionHandler2: mode = self.config.mode if mode == "upstream": - root_layer = protocol2.HttpUpstreamProxy( + return protocol2.HttpUpstreamProxy( root_context, self.config.upstream_server.address ) elif mode == "transparent": - root_layer = protocol2.TransparentProxy(root_context) + 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") - root_layer = protocol2.ReverseProxy( + return protocol2.ReverseProxy( root_context, self.config.upstream_server.address, client_tls, server_tls ) elif mode == "socks5": - root_layer = protocol2.Socks5Proxy(root_context) + return protocol2.Socks5Proxy(root_context) elif mode == "regular": - root_layer = protocol2.HttpProxy(root_context) + return protocol2.HttpProxy(root_context) elif callable(mode): # pragma: nocover - root_layer = mode(root_context) + return mode(root_context) else: # pragma: nocover raise ValueError("Unknown proxy mode: %s" % mode) + def handle(self): + self.log("clientconnect", "info") + + root_layer = self._create_root_layer() + try: root_layer() - except Kill as e: + except Kill: self.log("Connection killed", "info") except ProtocolException as e: self.log(e, "info") @@ -131,349 +129,8 @@ class ConnectionHandler2: print("Please lodge a bug report at: https://github.com/mitmproxy/mitmproxy", file=sys.stderr) self.log("clientdisconnect", "info") - - def finish(self): - self.client_conn.finish() - - def log(self, msg, level, subs=()): - # FIXME: Duplicate code - 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)) - - -class ConnectionHandler: - def __init__( - self, - config, - client_connection, - client_address, - server, - channel): - self.config = config - """@type: libmproxy.proxy.config.ProxyConfig""" - self.client_conn = ClientConnection( - client_connection, - client_address, - server) - """@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 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(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) - 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 - - if self.server_conn: - self.del_server_connection() - - 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.") - 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) - - def finish(self): 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 diff --git a/libmproxy/utils.py b/libmproxy/utils.py index 3ac3cc01..a6ca55f7 100644 --- a/libmproxy/utils.py +++ b/libmproxy/utils.py @@ -1,14 +1,10 @@ from __future__ import absolute_import import os import datetime -import urllib import re import time -import functools -import cgi import json -import netlib.utils def timestamp(): """ |