From 863113f989ee2a089c86b06a88a22e92d840348b Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Fri, 24 Jul 2015 13:31:55 +0200 Subject: first initial proof-of-concept --- libmproxy/proxy/layer.py | 181 +++++++++++++++++++++++++++++++++++++++++++++ libmproxy/proxy/message.py | 40 ++++++++++ libmproxy/proxy/server.py | 69 +++++++++++++++-- 3 files changed, 282 insertions(+), 8 deletions(-) create mode 100644 libmproxy/proxy/layer.py create mode 100644 libmproxy/proxy/message.py (limited to 'libmproxy/proxy') diff --git a/libmproxy/proxy/layer.py b/libmproxy/proxy/layer.py new file mode 100644 index 00000000..500fb6ba --- /dev/null +++ b/libmproxy/proxy/layer.py @@ -0,0 +1,181 @@ +from __future__ import (absolute_import, print_function, division, unicode_literals) +from libmproxy.protocol.tcp import TCPHandler +from libmproxy.proxy.connection import ServerConnection +from netlib import tcp +from .primitives import Socks5ProxyMode, ProxyError, Log +from .message import Connect, Reconnect, ChangeServer + + +""" +mitmproxy protocol architecture + +In mitmproxy, protocols are implemented as a set of layers, which are composed on top each other. +For example, the following scenarios depict possible scenarios (lowest layer first): + +Transparent HTTP proxy, no SSL: + TransparentModeLayer + HttpLayer + +Regular proxy, CONNECT request with WebSockets over SSL: + RegularModeLayer + HttpLayer + SslLayer + WebsocketLayer (or TcpLayer) + +Automated protocol detection by peeking into the buffer: + TransparentModeLayer + AutoLayer + SslLayer + AutoLayer + Http2Layer + +Communication between layers is done as follows: + - lower layers provide context information to higher layers + - higher layers can "yield" commands to lower layers, + which are propagated until they reach a suitable layer. + +Further goals: + - Connections should always be peekable to make automatic protocol detection work. + - Upstream connections should be established as late as possible; + inline scripts shall have a chance to handle everything locally. +""" + + +class ProxyError2(Exception): + def __init__(self, message, cause=None): + super(ProxyError2, self).__init__(message) + self.cause = cause + + +class RootContext(object): + """ + The outmost context provided to the root layer. + As a consequence, every layer has .client_conn, .channel and .config. + """ + + def __init__(self, client_conn, config, channel): + self.client_conn = client_conn # Client Connection + self.channel = channel # provides .ask() method to communicate with FlowMaster + self.config = config # Proxy Configuration + + def __getattr__(self, name): + """ + Accessing a nonexisting attribute does not throw an error but returns None instead. + """ + return None + + +class LayerCodeCompletion(object): + """ + Dummy class that provides type hinting in PyCharm, which simplifies development a lot. + """ + def __init__(self): + if True: + return + self.config = None + """@type: libmproxy.proxy.config.ProxyConfig""" + self.client_conn = None + """@type: libmproxy.proxy.connection.ClientConnection""" + self.channel = None + """@type: libmproxy.controller.Channel""" + + +class Layer(LayerCodeCompletion): + def __init__(self, ctx): + """ + Args: + ctx: The (read-only) higher layer. + """ + super(Layer, self).__init__() + self.ctx = ctx + + def __call__(self): + """ + Logic of the layer. + Raises: + ProxyError2 in case of protocol exceptions. + """ + raise NotImplementedError + + def __getattr__(self, name): + """ + Attributes not present on the current layer may exist on a higher layer. + """ + return getattr(self.ctx, name) + + 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)) + + +class _ServerConnectionMixin(object): + def __init__(self): + self._server_address = None + self.server_conn = None + + def _handle_message(self, message): + if message == Reconnect: + self._disconnect() + self._connect() + return True + elif message == Connect: + self._connect() + return True + elif message == ChangeServer: + raise NotImplementedError + return False + + def _disconnect(self): + """ + Deletes (and closes) an existing server connection. + """ + 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 + + def _connect(self): + self.log("serverconnect", "debug", [repr(self.server_conn.address)]) + try: + self.server_conn.connect() + except tcp.NetLibError as e: + raise ProxyError2("Server connection to '%s' failed: %s" % (self._server_address, repr(e)), e) + + def _set_address(self, address): + a = tcp.Address.wrap(address) + self.log("Set new server address: " + repr(a), "debug") + self.server_conn = ServerConnection(a) + + +class Socks5IncomingLayer(Layer, _ServerConnectionMixin): + def __call__(self): + try: + s5mode = Socks5ProxyMode(self.config.ssl_ports) + address = s5mode.get_upstream_server(self.client_conn)[2:] + except ProxyError as e: + raise ProxyError2(str(e), e) + + self._set_address(address) + + layer = TcpLayer(self) + for message in layer(): + if not self._handle_message(message): + yield message + + +class TcpLayer(Layer): + def __call__(self): + yield Connect() + tcp_handler = TCPHandler(self) + tcp_handler.handle_messages() + + def establish_server_connection(self): + print("establish server conn called") \ No newline at end of file diff --git a/libmproxy/proxy/message.py b/libmproxy/proxy/message.py new file mode 100644 index 00000000..a667123c --- /dev/null +++ b/libmproxy/proxy/message.py @@ -0,0 +1,40 @@ +""" +This module contains all valid messages layers can send to the underlying layers. +""" + + +class _Message(object): + def __eq__(self, other): + # Allow message == Connect checks. + # FIXME: make Connect == message work. + if isinstance(self, other): + return True + return self is other + + +class Connect(_Message): + """ + Connect to the server + """ + + +class Reconnect(_Message): + """ + Re-establish the server connection + """ + + +class ChangeServer(_Message): + """ + Change the upstream server. + """ + + def __init__(self, address, server_ssl, sni, depth=1): + self.address = address + self.server_ssl = server_ssl + 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/proxy/server.py b/libmproxy/proxy/server.py index 2f6ee061..a45276d4 100644 --- a/libmproxy/proxy/server.py +++ b/libmproxy/proxy/server.py @@ -1,13 +1,14 @@ -from __future__ import absolute_import +from __future__ import absolute_import, print_function +import traceback +import sys import socket -from OpenSSL import SSL from netlib import tcp +from . import layer from .primitives import ProxyServerError, Log, ProxyError from .connection import ClientConnection, ServerConnection from ..protocol.handle import protocol_handler -from .. import version class DummyServer: @@ -46,7 +47,7 @@ class ProxyServer(tcp.TCPServer): self.channel = channel def handle_client_connection(self, conn, client_address): - h = ConnectionHandler( + h = ConnectionHandler2( self.config, conn, client_address, @@ -56,6 +57,57 @@ class ProxyServer(tcp.TCPServer): h.finish() +class ConnectionHandler2: + # FIXME: parameter ordering + # FIXME: remove server attribute + def __init__(self, config, client_conn, client_address, server, channel): + self.config = config + """@type: libmproxy.proxy.config.ProxyConfig""" + self.client_conn = ClientConnection( + client_conn, + client_address, + server) + """@type: libmproxy.proxy.connection.ClientConnection""" + self.channel = channel + """@type: libmproxy.controller.Channel""" + + def handle(self): + self.log("clientconnect", "info") + + root_context = layer.RootContext( + self.client_conn, + self.config, + self.channel + ) + root_layer = layer.Socks5IncomingLayer(root_context) + + try: + for message in root_layer(): + print("Root layer receveived: %s" % message) + except layer.ProxyError2 as e: + self.log(e, "info") + except Exception: + 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) + + 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, @@ -74,6 +126,7 @@ class ConnectionHandler: self.server_conn = None """@type: libmproxy.proxy.connection.ServerConnection""" self.channel = channel + """@type: libmproxy.controller.Channel""" self.conntype = "http" @@ -144,9 +197,9 @@ class ConnectionHandler: import sys self.log(traceback.format_exc(), "error") - print >> sys.stderr, traceback.format_exc() - print >> sys.stderr, "mitmproxy has crashed!" - print >> sys.stderr, "Please lodge a bug report at: https://github.com/mitmproxy/mitmproxy" + 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 @@ -201,7 +254,7 @@ class ConnectionHandler: "serverconnect", "debug", [ "%s:%s" % self.server_conn.address()[ - :2]]) + :2]]) if ask: self.channel.ask("serverconnect", self) try: -- cgit v1.2.3 From be995ddbd62579621ed06ed7197c8f22939c16d1 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Fri, 24 Jul 2015 17:48:27 +0200 Subject: add ssl layer --- libmproxy/proxy/layer.py | 255 ++++++++++++++++++++++++++++++++++++++++-- libmproxy/proxy/message.py | 4 +- libmproxy/proxy/primitives.py | 2 +- 3 files changed, 247 insertions(+), 14 deletions(-) (limited to 'libmproxy/proxy') diff --git a/libmproxy/proxy/layer.py b/libmproxy/proxy/layer.py index 500fb6ba..e4878bdf 100644 --- a/libmproxy/proxy/layer.py +++ b/libmproxy/proxy/layer.py @@ -1,11 +1,13 @@ from __future__ import (absolute_import, print_function, division, unicode_literals) +import Queue +import threading +import traceback from libmproxy.protocol.tcp import TCPHandler from libmproxy.proxy.connection import ServerConnection from netlib import tcp from .primitives import Socks5ProxyMode, ProxyError, Log from .message import Connect, Reconnect, ChangeServer - """ mitmproxy protocol architecture @@ -65,10 +67,11 @@ class RootContext(object): return None -class LayerCodeCompletion(object): +class _LayerCodeCompletion(object): """ Dummy class that provides type hinting in PyCharm, which simplifies development a lot. """ + def __init__(self): if True: return @@ -80,7 +83,7 @@ class LayerCodeCompletion(object): """@type: libmproxy.controller.Channel""" -class Layer(LayerCodeCompletion): +class Layer(_LayerCodeCompletion): def __init__(self, ctx): """ Args: @@ -116,11 +119,15 @@ class Layer(LayerCodeCompletion): class _ServerConnectionMixin(object): + """ + Mixin that provides a layer with the capabilities to manage a server connection. + """ + def __init__(self): - self._server_address = None + self.server_address = None self.server_conn = None - def _handle_message(self, message): + def _handle_server_message(self, message): if message == Reconnect: self._disconnect() self._connect() @@ -136,23 +143,24 @@ class _ServerConnectionMixin(object): """ Deletes (and closes) an existing server connection. """ - self.log("serverdisconnect", "debug", [repr(self.server_conn.address)]) + self.log("serverdisconnect", "debug", [repr(self.server_address)]) self.server_conn.finish() self.server_conn.close() # self.channel.tell("serverdisconnect", self) self.server_conn = None def _connect(self): - self.log("serverconnect", "debug", [repr(self.server_conn.address)]) + self.log("serverconnect", "debug", [repr(self.server_address)]) + self.server_conn = ServerConnection(self.server_address) try: self.server_conn.connect() except tcp.NetLibError as e: - raise ProxyError2("Server connection to '%s' failed: %s" % (self._server_address, repr(e)), e) + raise ProxyError2("Server connection to '%s' failed: %s" % (self.server_address, e), e) def _set_address(self, address): a = tcp.Address.wrap(address) self.log("Set new server address: " + repr(a), "debug") - self.server_conn = ServerConnection(a) + self.server_address = address class Socks5IncomingLayer(Layer, _ServerConnectionMixin): @@ -161,13 +169,17 @@ class Socks5IncomingLayer(Layer, _ServerConnectionMixin): s5mode = Socks5ProxyMode(self.config.ssl_ports) address = s5mode.get_upstream_server(self.client_conn)[2:] except ProxyError as e: + # TODO: Unmonkeypatch raise ProxyError2(str(e), e) self._set_address(address) - layer = TcpLayer(self) + if address[1] == 443: + layer = SslLayer(self, True, True) + else: + layer = TcpLayer(self) for message in layer(): - if not self._handle_message(message): + if not self._handle_server_message(message): yield message @@ -178,4 +190,223 @@ class TcpLayer(Layer): tcp_handler.handle_messages() def establish_server_connection(self): - print("establish server conn called") \ No newline at end of file + pass + # FIXME: Remove method, currently just here to mock TCPHandler's call to it. + + +class ReconnectRequest(object): + def __init__(self): + self.done = threading.Event() + + +class SslLayer(Layer): + def __init__(self, ctx, client_ssl, server_ssl): + super(SslLayer, self).__init__(ctx) + self._client_ssl = client_ssl + self._server_ssl = server_ssl + self._connected = False + self._sni_from_handshake = None + self._sni_from_server_change = None + + def __call__(self): + """ + The strategy for establishing SSL is as follows: + First, we determine whether we need the server cert to establish ssl with the client. + If so, we first connect to the server and then to the client. + If not, we only connect to the client and do the server_ssl lazily on a Connect message. + + An additional complexity is that establish ssl with the server may require a SNI value from the client. + In an ideal world, we'd do the following: + 1. Start the SSL handshake with the client + 2. Check if the client sends a SNI. + 3. Pause the client handshake, establish SSL with the server. + 4. Finish the client handshake with the certificate from the server. + There's just one issue: We cannot get a callback from OpenSSL if the client doesn't send a SNI. :( + Thus, we resort to the following workaround when establishing SSL with the server: + 1. Try to establish SSL with the server without SNI. If this fails, we ignore it. + 2. Establish SSL with client. + - If there's a SNI callback, reconnect to the server with SNI. + - If not and the server connect failed, raise the original exception. + Further notes: + - OpenSSL 1.0.2 introduces a callback that would help here: + https://www.openssl.org/docs/ssl/SSL_CTX_set_cert_cb.html + - The original mitmproxy issue is https://github.com/mitmproxy/mitmproxy/issues/427 + """ + client_ssl_requires_server_cert = ( + self._client_ssl and self._server_ssl and not self.config.no_upstream_cert + ) + lazy_server_ssl = ( + self._server_ssl and not client_ssl_requires_server_cert + ) + + if client_ssl_requires_server_cert: + for m in self._establish_ssl_with_client_and_server(): + yield m + elif self.client_ssl: + self._establish_ssl_with_client() + + layer = TcpLayer(self) + for message in layer(): + if message != Connect or not self._connected: + yield message + if message == Connect: + if lazy_server_ssl: + self._establish_ssl_with_server() + if message == ChangeServer and message.depth == 1: + self.server_ssl = message.server_ssl + self._sni_from_server_change = message.sni + if message == Reconnect or message == ChangeServer: + if self.server_ssl: + self._establish_ssl_with_server() + + @property + def sni(self): + if self._sni_from_server_change is False: + return None + else: + return self._sni_from_server_change or self._sni_from_handshake + + def _establish_ssl_with_client_and_server(self): + """ + This function deals with the problem that the server may require a SNI value from the client. + """ + + # First, try to connect to the server. + yield Connect() + self._connected = True + server_err = None + try: + self._establish_ssl_with_server() + except ProxyError2 as e: + server_err = e + + # The part below is a bit ugly as we cannot yield from the handle_sni callback. + # The workaround is to do that in a separate thread and yield from the main thread. + + # client_ssl_queue may contain the following elements + # - True, if ssl was successfully established + # - An Exception thrown by self._establish_ssl_with_client() + # - A threading.Event, which singnifies a request for a reconnect from the sni callback + self.__client_ssl_queue = Queue.Queue() + + def establish_client_ssl(): + try: + self._establish_ssl_with_client() + self.__client_ssl_queue.put(True) + except Exception as client_err: + self.__client_ssl_queue.put(client_err) + + threading.Thread(target=establish_client_ssl, name="ClientSSLThread").start() + e = self.__client_ssl_queue.get() + if isinstance(e, ReconnectRequest): + yield Reconnect() + self._establish_ssl_with_server() + e.done.set() + e = self.__client_ssl_queue.get() + if e is not True: + raise ProxyError2("Error when establish client SSL: " + repr(e), e) + self.__client_ssl_queue = None + + if server_err and not self._sni_from_handshake: + raise server_err + + 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). + """ + try: + sn = connection.get_servername() + if not sn: + return + sni = sn.decode("utf8").encode("idna") + + if sni != self.sni: + self._sni_from_handshake = sni + + # Perform reconnect + if self.server_ssl: + reconnect = ReconnectRequest() + self.__client_ssl_queue.put() + reconnect.done.wait() + + # 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 + self.log("Error in handle_sni:\r\n" + traceback.format_exc(), "error") + + def _establish_ssl_with_client(self): + self.log("Establish SSL with client", "debug") + 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 e: + raise ProxyError2(repr(e), e) + + def _establish_ssl_with_server(self): + self.log("Establish SSL with server", "debug") + try: + self.server_conn.establish_ssl( + self.config.clientcerts, + self.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.NetLibInvalidCertificateError as e: + ssl_cert_err = self.server_conn.ssl_verification_error + 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 ProxyError2(repr(e), e) + except Exception as e: + raise ProxyError2(repr(e), e) + + def find_cert(self): + host = self.server_conn.address.host + sans = [] + # Incorporate upstream certificate + 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") + # Also add SNI values. + if self._sni_from_handshake: + sans.append(self._sni_from_handshake) + if self._sni_from_server_change: + sans.append(self._sni_from_server_change) + + return self.config.certstore.get_cert(host, sans) diff --git a/libmproxy/proxy/message.py b/libmproxy/proxy/message.py index a667123c..7eb59344 100644 --- a/libmproxy/proxy/message.py +++ b/libmproxy/proxy/message.py @@ -6,11 +6,13 @@ This module contains all valid messages layers can send to the underlying layers class _Message(object): def __eq__(self, other): # Allow message == Connect checks. - # FIXME: make Connect == message work. if isinstance(self, other): return True return self is other + def __ne__(self, other): + return not self.__eq__(other) + class Connect(_Message): """ diff --git a/libmproxy/proxy/primitives.py b/libmproxy/proxy/primitives.py index 923f84ca..a9f31181 100644 --- a/libmproxy/proxy/primitives.py +++ b/libmproxy/proxy/primitives.py @@ -151,7 +151,7 @@ class Socks5ProxyMode(ProxyMode): 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" % str(e)) + raise ProxyError(502, "SOCKS5 mode failure: %s" % repr(e)) class _ConstDestinationProxyMode(ProxyMode): -- cgit v1.2.3 From c1d016823c67fc834a2fdb6c77181d14b5fd8008 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Fri, 24 Jul 2015 18:29:13 +0200 Subject: move files around --- libmproxy/proxy/__init__.py | 2 +- libmproxy/proxy/layer.py | 412 ------------------------------------------ libmproxy/proxy/message.py | 42 ----- libmproxy/proxy/primitives.py | 6 + libmproxy/proxy/server.py | 14 +- 5 files changed, 14 insertions(+), 462 deletions(-) delete mode 100644 libmproxy/proxy/layer.py delete mode 100644 libmproxy/proxy/message.py (limited to 'libmproxy/proxy') diff --git a/libmproxy/proxy/__init__.py b/libmproxy/proxy/__init__.py index f33d323b..7d664707 100644 --- a/libmproxy/proxy/__init__.py +++ b/libmproxy/proxy/__init__.py @@ -1,2 +1,2 @@ from .primitives import * -from .config import ProxyConfig, process_proxy_options +from .config import ProxyConfig, process_proxy_options \ No newline at end of file diff --git a/libmproxy/proxy/layer.py b/libmproxy/proxy/layer.py deleted file mode 100644 index e4878bdf..00000000 --- a/libmproxy/proxy/layer.py +++ /dev/null @@ -1,412 +0,0 @@ -from __future__ import (absolute_import, print_function, division, unicode_literals) -import Queue -import threading -import traceback -from libmproxy.protocol.tcp import TCPHandler -from libmproxy.proxy.connection import ServerConnection -from netlib import tcp -from .primitives import Socks5ProxyMode, ProxyError, Log -from .message import Connect, Reconnect, ChangeServer - -""" -mitmproxy protocol architecture - -In mitmproxy, protocols are implemented as a set of layers, which are composed on top each other. -For example, the following scenarios depict possible scenarios (lowest layer first): - -Transparent HTTP proxy, no SSL: - TransparentModeLayer - HttpLayer - -Regular proxy, CONNECT request with WebSockets over SSL: - RegularModeLayer - HttpLayer - SslLayer - WebsocketLayer (or TcpLayer) - -Automated protocol detection by peeking into the buffer: - TransparentModeLayer - AutoLayer - SslLayer - AutoLayer - Http2Layer - -Communication between layers is done as follows: - - lower layers provide context information to higher layers - - higher layers can "yield" commands to lower layers, - which are propagated until they reach a suitable layer. - -Further goals: - - Connections should always be peekable to make automatic protocol detection work. - - Upstream connections should be established as late as possible; - inline scripts shall have a chance to handle everything locally. -""" - - -class ProxyError2(Exception): - def __init__(self, message, cause=None): - super(ProxyError2, self).__init__(message) - self.cause = cause - - -class RootContext(object): - """ - The outmost context provided to the root layer. - As a consequence, every layer has .client_conn, .channel and .config. - """ - - def __init__(self, client_conn, config, channel): - self.client_conn = client_conn # Client Connection - self.channel = channel # provides .ask() method to communicate with FlowMaster - self.config = config # Proxy Configuration - - def __getattr__(self, name): - """ - Accessing a nonexisting attribute does not throw an error but returns None instead. - """ - return None - - -class _LayerCodeCompletion(object): - """ - Dummy class that provides type hinting in PyCharm, which simplifies development a lot. - """ - - def __init__(self): - if True: - return - self.config = None - """@type: libmproxy.proxy.config.ProxyConfig""" - self.client_conn = None - """@type: libmproxy.proxy.connection.ClientConnection""" - self.channel = None - """@type: libmproxy.controller.Channel""" - - -class Layer(_LayerCodeCompletion): - def __init__(self, ctx): - """ - Args: - ctx: The (read-only) higher layer. - """ - super(Layer, self).__init__() - self.ctx = ctx - - def __call__(self): - """ - Logic of the layer. - Raises: - ProxyError2 in case of protocol exceptions. - """ - raise NotImplementedError - - def __getattr__(self, name): - """ - Attributes not present on the current layer may exist on a higher layer. - """ - return getattr(self.ctx, name) - - 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)) - - -class _ServerConnectionMixin(object): - """ - Mixin that provides a layer with the capabilities to manage a server connection. - """ - - def __init__(self): - self.server_address = None - self.server_conn = None - - def _handle_server_message(self, message): - if message == Reconnect: - self._disconnect() - self._connect() - return True - elif message == Connect: - self._connect() - return True - elif message == ChangeServer: - raise NotImplementedError - return False - - def _disconnect(self): - """ - Deletes (and closes) an existing server connection. - """ - self.log("serverdisconnect", "debug", [repr(self.server_address)]) - self.server_conn.finish() - self.server_conn.close() - # self.channel.tell("serverdisconnect", self) - self.server_conn = None - - def _connect(self): - self.log("serverconnect", "debug", [repr(self.server_address)]) - self.server_conn = ServerConnection(self.server_address) - try: - self.server_conn.connect() - except tcp.NetLibError as e: - raise ProxyError2("Server connection to '%s' failed: %s" % (self.server_address, e), e) - - def _set_address(self, address): - a = tcp.Address.wrap(address) - self.log("Set new server address: " + repr(a), "debug") - self.server_address = address - - -class Socks5IncomingLayer(Layer, _ServerConnectionMixin): - def __call__(self): - try: - s5mode = Socks5ProxyMode(self.config.ssl_ports) - address = s5mode.get_upstream_server(self.client_conn)[2:] - except ProxyError as e: - # TODO: Unmonkeypatch - raise ProxyError2(str(e), e) - - self._set_address(address) - - if address[1] == 443: - layer = SslLayer(self, True, True) - else: - layer = TcpLayer(self) - for message in layer(): - if not self._handle_server_message(message): - yield message - - -class TcpLayer(Layer): - def __call__(self): - yield Connect() - tcp_handler = TCPHandler(self) - tcp_handler.handle_messages() - - def establish_server_connection(self): - pass - # FIXME: Remove method, currently just here to mock TCPHandler's call to it. - - -class ReconnectRequest(object): - def __init__(self): - self.done = threading.Event() - - -class SslLayer(Layer): - def __init__(self, ctx, client_ssl, server_ssl): - super(SslLayer, self).__init__(ctx) - self._client_ssl = client_ssl - self._server_ssl = server_ssl - self._connected = False - self._sni_from_handshake = None - self._sni_from_server_change = None - - def __call__(self): - """ - The strategy for establishing SSL is as follows: - First, we determine whether we need the server cert to establish ssl with the client. - If so, we first connect to the server and then to the client. - If not, we only connect to the client and do the server_ssl lazily on a Connect message. - - An additional complexity is that establish ssl with the server may require a SNI value from the client. - In an ideal world, we'd do the following: - 1. Start the SSL handshake with the client - 2. Check if the client sends a SNI. - 3. Pause the client handshake, establish SSL with the server. - 4. Finish the client handshake with the certificate from the server. - There's just one issue: We cannot get a callback from OpenSSL if the client doesn't send a SNI. :( - Thus, we resort to the following workaround when establishing SSL with the server: - 1. Try to establish SSL with the server without SNI. If this fails, we ignore it. - 2. Establish SSL with client. - - If there's a SNI callback, reconnect to the server with SNI. - - If not and the server connect failed, raise the original exception. - Further notes: - - OpenSSL 1.0.2 introduces a callback that would help here: - https://www.openssl.org/docs/ssl/SSL_CTX_set_cert_cb.html - - The original mitmproxy issue is https://github.com/mitmproxy/mitmproxy/issues/427 - """ - client_ssl_requires_server_cert = ( - self._client_ssl and self._server_ssl and not self.config.no_upstream_cert - ) - lazy_server_ssl = ( - self._server_ssl and not client_ssl_requires_server_cert - ) - - if client_ssl_requires_server_cert: - for m in self._establish_ssl_with_client_and_server(): - yield m - elif self.client_ssl: - self._establish_ssl_with_client() - - layer = TcpLayer(self) - for message in layer(): - if message != Connect or not self._connected: - yield message - if message == Connect: - if lazy_server_ssl: - self._establish_ssl_with_server() - if message == ChangeServer and message.depth == 1: - self.server_ssl = message.server_ssl - self._sni_from_server_change = message.sni - if message == Reconnect or message == ChangeServer: - if self.server_ssl: - self._establish_ssl_with_server() - - @property - def sni(self): - if self._sni_from_server_change is False: - return None - else: - return self._sni_from_server_change or self._sni_from_handshake - - def _establish_ssl_with_client_and_server(self): - """ - This function deals with the problem that the server may require a SNI value from the client. - """ - - # First, try to connect to the server. - yield Connect() - self._connected = True - server_err = None - try: - self._establish_ssl_with_server() - except ProxyError2 as e: - server_err = e - - # The part below is a bit ugly as we cannot yield from the handle_sni callback. - # The workaround is to do that in a separate thread and yield from the main thread. - - # client_ssl_queue may contain the following elements - # - True, if ssl was successfully established - # - An Exception thrown by self._establish_ssl_with_client() - # - A threading.Event, which singnifies a request for a reconnect from the sni callback - self.__client_ssl_queue = Queue.Queue() - - def establish_client_ssl(): - try: - self._establish_ssl_with_client() - self.__client_ssl_queue.put(True) - except Exception as client_err: - self.__client_ssl_queue.put(client_err) - - threading.Thread(target=establish_client_ssl, name="ClientSSLThread").start() - e = self.__client_ssl_queue.get() - if isinstance(e, ReconnectRequest): - yield Reconnect() - self._establish_ssl_with_server() - e.done.set() - e = self.__client_ssl_queue.get() - if e is not True: - raise ProxyError2("Error when establish client SSL: " + repr(e), e) - self.__client_ssl_queue = None - - if server_err and not self._sni_from_handshake: - raise server_err - - 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). - """ - try: - sn = connection.get_servername() - if not sn: - return - sni = sn.decode("utf8").encode("idna") - - if sni != self.sni: - self._sni_from_handshake = sni - - # Perform reconnect - if self.server_ssl: - reconnect = ReconnectRequest() - self.__client_ssl_queue.put() - reconnect.done.wait() - - # 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 - self.log("Error in handle_sni:\r\n" + traceback.format_exc(), "error") - - def _establish_ssl_with_client(self): - self.log("Establish SSL with client", "debug") - 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 e: - raise ProxyError2(repr(e), e) - - def _establish_ssl_with_server(self): - self.log("Establish SSL with server", "debug") - try: - self.server_conn.establish_ssl( - self.config.clientcerts, - self.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.NetLibInvalidCertificateError as e: - ssl_cert_err = self.server_conn.ssl_verification_error - 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 ProxyError2(repr(e), e) - except Exception as e: - raise ProxyError2(repr(e), e) - - def find_cert(self): - host = self.server_conn.address.host - sans = [] - # Incorporate upstream certificate - 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") - # Also add SNI values. - if self._sni_from_handshake: - sans.append(self._sni_from_handshake) - if self._sni_from_server_change: - sans.append(self._sni_from_server_change) - - return self.config.certstore.get_cert(host, sans) diff --git a/libmproxy/proxy/message.py b/libmproxy/proxy/message.py deleted file mode 100644 index 7eb59344..00000000 --- a/libmproxy/proxy/message.py +++ /dev/null @@ -1,42 +0,0 @@ -""" -This module contains all valid messages layers can send to the underlying layers. -""" - - -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 ChangeServer(_Message): - """ - Change the upstream server. - """ - - def __init__(self, address, server_ssl, sni, depth=1): - self.address = address - self.server_ssl = server_ssl - 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/proxy/primitives.py b/libmproxy/proxy/primitives.py index a9f31181..fd4eb882 100644 --- a/libmproxy/proxy/primitives.py +++ b/libmproxy/proxy/primitives.py @@ -2,6 +2,12 @@ from __future__ import absolute_import from netlib import socks, tcp +class ProxyError2(Exception): + def __init__(self, message, cause=None): + super(ProxyError2, self).__init__(message) + self.cause = cause + + class ProxyError(Exception): def __init__(self, code, message, headers=None): super(ProxyError, self).__init__(message) diff --git a/libmproxy/proxy/server.py b/libmproxy/proxy/server.py index a45276d4..c8990a9a 100644 --- a/libmproxy/proxy/server.py +++ b/libmproxy/proxy/server.py @@ -3,12 +3,12 @@ from __future__ import absolute_import, print_function import traceback import sys import socket - from netlib import tcp -from . import layer -from .primitives import ProxyServerError, Log, ProxyError -from .connection import ClientConnection, ServerConnection + from ..protocol.handle import protocol_handler +from .. import protocol2 +from .primitives import ProxyServerError, Log, ProxyError, ProxyError2 +from .connection import ClientConnection, ServerConnection class DummyServer: @@ -74,17 +74,17 @@ class ConnectionHandler2: def handle(self): self.log("clientconnect", "info") - root_context = layer.RootContext( + root_context = protocol2.RootContext( self.client_conn, self.config, self.channel ) - root_layer = layer.Socks5IncomingLayer(root_context) + root_layer = protocol2.Socks5IncomingLayer(root_context) try: for message in root_layer(): print("Root layer receveived: %s" % message) - except layer.ProxyError2 as e: + except ProxyError2 as e: self.log(e, "info") except Exception: self.log(traceback.format_exc(), "error") -- cgit v1.2.3 From 314e0f5839fcd4a1c35323f61938b207232de287 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Thu, 6 Aug 2015 12:32:33 +0200 Subject: add reverseproxy mode, fix bugs --- libmproxy/proxy/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'libmproxy/proxy') diff --git a/libmproxy/proxy/server.py b/libmproxy/proxy/server.py index c8990a9a..32d596ad 100644 --- a/libmproxy/proxy/server.py +++ b/libmproxy/proxy/server.py @@ -79,7 +79,7 @@ class ConnectionHandler2: self.config, self.channel ) - root_layer = protocol2.Socks5IncomingLayer(root_context) + root_layer = protocol2.ReverseProxy(root_context, ("localhost", 5000), True, True) try: for message in root_layer(): -- cgit v1.2.3 From 026330a3b014f24f095b839b29186036854de3bc Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Sat, 8 Aug 2015 16:08:57 +0200 Subject: cleaner Exceptions, ssl -> tls, upstream proxy mode --- libmproxy/proxy/connection.py | 8 ++++++++ libmproxy/proxy/primitives.py | 6 ------ libmproxy/proxy/server.py | 6 +++--- 3 files changed, 11 insertions(+), 9 deletions(-) (limited to 'libmproxy/proxy') diff --git a/libmproxy/proxy/connection.py b/libmproxy/proxy/connection.py index 9e03157a..49210e47 100644 --- a/libmproxy/proxy/connection.py +++ b/libmproxy/proxy/connection.py @@ -32,6 +32,10 @@ class ClientConnection(tcp.BaseHandler, stateobject.StateObject): port=self.address.port ) + @property + def tls_established(self): + return self.ssl_established + _stateobject_attributes = dict( ssl_established=bool, timestamp_start=float, @@ -112,6 +116,10 @@ class ServerConnection(tcp.TCPClient, stateobject.StateObject): port=self.address.port ) + @property + def tls_established(self): + return self.ssl_established + _stateobject_attributes = dict( state=list, timestamp_start=float, diff --git a/libmproxy/proxy/primitives.py b/libmproxy/proxy/primitives.py index fd4eb882..a9f31181 100644 --- a/libmproxy/proxy/primitives.py +++ b/libmproxy/proxy/primitives.py @@ -2,12 +2,6 @@ from __future__ import absolute_import from netlib import socks, tcp -class ProxyError2(Exception): - def __init__(self, message, cause=None): - super(ProxyError2, self).__init__(message) - self.cause = cause - - class ProxyError(Exception): def __init__(self, code, message, headers=None): super(ProxyError, self).__init__(message) diff --git a/libmproxy/proxy/server.py b/libmproxy/proxy/server.py index 32d596ad..c107cbed 100644 --- a/libmproxy/proxy/server.py +++ b/libmproxy/proxy/server.py @@ -7,7 +7,7 @@ from netlib import tcp from ..protocol.handle import protocol_handler from .. import protocol2 -from .primitives import ProxyServerError, Log, ProxyError, ProxyError2 +from .primitives import ProxyServerError, Log, ProxyError from .connection import ClientConnection, ServerConnection @@ -79,12 +79,12 @@ class ConnectionHandler2: self.config, self.channel ) - root_layer = protocol2.ReverseProxy(root_context, ("localhost", 5000), True, True) + root_layer = protocol2.Socks5IncomingLayer(root_context) try: for message in root_layer(): print("Root layer receveived: %s" % message) - except ProxyError2 as e: + except protocol2.ProtocolException as e: self.log(e, "info") except Exception: self.log(traceback.format_exc(), "error") -- cgit v1.2.3 From aef3b626a70de5f385c8f5496c2e49575b5c3e1c Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Tue, 11 Aug 2015 20:27:34 +0200 Subject: wip commit --- libmproxy/proxy/server.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'libmproxy/proxy') diff --git a/libmproxy/proxy/server.py b/libmproxy/proxy/server.py index c107cbed..6a7048e0 100644 --- a/libmproxy/proxy/server.py +++ b/libmproxy/proxy/server.py @@ -7,6 +7,7 @@ from netlib import tcp from ..protocol.handle import protocol_handler from .. import protocol2 +from ..exceptions import ProtocolException from .primitives import ProxyServerError, Log, ProxyError from .connection import ClientConnection, ServerConnection @@ -79,12 +80,12 @@ class ConnectionHandler2: self.config, self.channel ) - root_layer = protocol2.Socks5IncomingLayer(root_context) + root_layer = protocol2.Socks5Proxy(root_context) try: for message in root_layer(): print("Root layer receveived: %s" % message) - except protocol2.ProtocolException as e: + except ProtocolException as e: self.log(e, "info") except Exception: self.log(traceback.format_exc(), "error") -- cgit v1.2.3 From 747699b126ab5788aca4541c9c9b4608611e7efa Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Fri, 14 Aug 2015 16:49:52 +0200 Subject: more work on http protocol --- libmproxy/proxy/server.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'libmproxy/proxy') diff --git a/libmproxy/proxy/server.py b/libmproxy/proxy/server.py index 6a7048e0..defcd464 100644 --- a/libmproxy/proxy/server.py +++ b/libmproxy/proxy/server.py @@ -84,6 +84,10 @@ class ConnectionHandler2: try: for message in root_layer(): + if message == protocol2.messages.Kill: + self.log("Connection killed", "info") + break + print("Root layer receveived: %s" % message) except ProtocolException as e: self.log(e, "info") -- cgit v1.2.3 From 0dd243c5e42950de9c8b1193ba9dbdd2d0414a45 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Sat, 15 Aug 2015 16:26:12 +0200 Subject: various fixes --- libmproxy/proxy/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'libmproxy/proxy') diff --git a/libmproxy/proxy/server.py b/libmproxy/proxy/server.py index defcd464..ffca55ee 100644 --- a/libmproxy/proxy/server.py +++ b/libmproxy/proxy/server.py @@ -80,7 +80,7 @@ class ConnectionHandler2: self.config, self.channel ) - root_layer = protocol2.Socks5Proxy(root_context) + root_layer = protocol2.HttpProxy(root_context) try: for message in root_layer(): -- cgit v1.2.3 From 1e40d34e942382bbb11234e0e9232794b3bf6acf Mon Sep 17 00:00:00 2001 From: Thomas Kriechbaumer Date: Sat, 15 Aug 2015 17:43:46 +0200 Subject: add ALPN to proxy connections --- libmproxy/proxy/connection.py | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) (limited to 'libmproxy/proxy') diff --git a/libmproxy/proxy/connection.py b/libmproxy/proxy/connection.py index 49210e47..f33e84cd 100644 --- a/libmproxy/proxy/connection.py +++ b/libmproxy/proxy/connection.py @@ -1,6 +1,8 @@ from __future__ import absolute_import + import copy import os + from netlib import tcp, certutils from .. import stateobject, utils @@ -75,15 +77,6 @@ class ClientConnection(tcp.BaseHandler, stateobject.StateObject): return f def convert_to_ssl(self, *args, **kwargs): - # TODO: read ALPN from server and select same proto for client conn - # alpn_select = 'h2' - # def alpn_select_callback(conn_, options): - # if alpn_select in options: - # return bytes(alpn_select) - # else: # pragma no cover - # return options[0] - # tcp.BaseHandler.convert_to_ssl(self, alpn_select=alpn_select_callback, *args, **kwargs) - tcp.BaseHandler.convert_to_ssl(self, *args, **kwargs) self.timestamp_ssl_setup = utils.timestamp() @@ -184,9 +177,6 @@ class ServerConnection(tcp.TCPClient, stateobject.StateObject): if os.path.exists(path): clientcert = path - # TODO: read ALPN from client and use same list for server conn - # self.convert_to_ssl(cert=clientcert, sni=sni, alpn_protos=[netlib.http.http2.HTTP2Protocol.ALPN_PROTO_H2], **kwargs) - self.convert_to_ssl(cert=clientcert, sni=sni, **kwargs) self.sni = sni self.timestamp_ssl_setup = utils.timestamp() -- cgit v1.2.3 From a2b85048892626e6834df06e9022498814724636 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Sun, 16 Aug 2015 23:25:02 +0200 Subject: improve protocol handling --- libmproxy/proxy/connection.py | 7 +++++-- libmproxy/proxy/server.py | 11 ++++++++--- 2 files changed, 13 insertions(+), 5 deletions(-) (limited to 'libmproxy/proxy') 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 -- cgit v1.2.3 From 96de7ad562da9b5110059988b851c66b51874510 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Tue, 18 Aug 2015 14:15:08 +0200 Subject: various fixes --- libmproxy/proxy/connection.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'libmproxy/proxy') diff --git a/libmproxy/proxy/connection.py b/libmproxy/proxy/connection.py index f92b53aa..c9b57998 100644 --- a/libmproxy/proxy/connection.py +++ b/libmproxy/proxy/connection.py @@ -27,6 +27,9 @@ class ClientConnection(tcp.BaseHandler, stateobject.StateObject): self.timestamp_ssl_setup = None self.protocol = None + def __nonzero__(self): + return bool(self.connection) and not self.finished + def __repr__(self): return "".format( ssl="[ssl] " if self.ssl_established else "", @@ -89,7 +92,7 @@ class ServerConnection(tcp.TCPClient, stateobject.StateObject): def __init__(self, address): tcp.TCPClient.__init__(self, address) - self.state = [] # a list containing (conntype, state) tuples + self.via = None self.timestamp_start = None self.timestamp_end = None self.timestamp_tcp_setup = None @@ -97,7 +100,7 @@ class ServerConnection(tcp.TCPClient, stateobject.StateObject): self.protocol = None def __nonzero__(self): - return bool(self.connection) + return bool(self.connection) and not self.finished def __repr__(self): if self.ssl_established and self.sni: @@ -117,7 +120,6 @@ class ServerConnection(tcp.TCPClient, stateobject.StateObject): return self.ssl_established _stateobject_attributes = dict( - state=list, timestamp_start=float, timestamp_end=float, timestamp_tcp_setup=float, @@ -187,3 +189,5 @@ class ServerConnection(tcp.TCPClient, stateobject.StateObject): def finish(self): tcp.TCPClient.finish(self) self.timestamp_end = utils.timestamp() + +ServerConnection._stateobject_attributes["via"] = ServerConnection \ No newline at end of file -- cgit v1.2.3 From ab1549e0eff98588211346aada44549311f04938 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Tue, 18 Aug 2015 15:59:44 +0200 Subject: yield -> callbacks --- libmproxy/proxy/server.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'libmproxy/proxy') diff --git a/libmproxy/proxy/server.py b/libmproxy/proxy/server.py index e23a7d72..9957caa0 100644 --- a/libmproxy/proxy/server.py +++ b/libmproxy/proxy/server.py @@ -3,6 +3,7 @@ from __future__ import absolute_import, print_function import traceback import sys import socket +from libmproxy.protocol2.layer import Kill from netlib import tcp from ..protocol.handle import protocol_handler @@ -88,12 +89,9 @@ class ConnectionHandler2: root_layer = protocol2.HttpProxy(root_context) try: - for message in root_layer(): - if message == protocol2.messages.Kill: - self.log("Connection killed", "info") - break - - print("Root layer receveived: %s" % message) + root_layer() + except Kill as e: + self.log("Connection killed", "info") except ProtocolException as e: self.log(e, "info") except Exception: -- cgit v1.2.3 From c9fa8491ccc015ddff09ce15a5d718d6b58b515c Mon Sep 17 00:00:00 2001 From: Thomas Kriechbaumer Date: Wed, 19 Aug 2015 15:23:52 +0200 Subject: improve next_layer detection --- libmproxy/proxy/connection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'libmproxy/proxy') diff --git a/libmproxy/proxy/connection.py b/libmproxy/proxy/connection.py index c9b57998..c329ed64 100644 --- a/libmproxy/proxy/connection.py +++ b/libmproxy/proxy/connection.py @@ -190,4 +190,4 @@ class ServerConnection(tcp.TCPClient, stateobject.StateObject): tcp.TCPClient.finish(self) self.timestamp_end = utils.timestamp() -ServerConnection._stateobject_attributes["via"] = ServerConnection \ No newline at end of file +ServerConnection._stateobject_attributes["via"] = ServerConnection -- cgit v1.2.3 From 8ce0de8bed5fbd2c42e7b43ee553e065e1c08a4c Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Tue, 25 Aug 2015 18:24:17 +0200 Subject: minor fixes --- libmproxy/proxy/server.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'libmproxy/proxy') diff --git a/libmproxy/proxy/server.py b/libmproxy/proxy/server.py index 9957caa0..19ddb930 100644 --- a/libmproxy/proxy/server.py +++ b/libmproxy/proxy/server.py @@ -100,6 +100,8 @@ class ConnectionHandler2: print("mitmproxy has crashed!", file=sys.stderr) 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() -- cgit v1.2.3 From 778644d4b810e87ce20cf9da1dca55913c2ffd07 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Wed, 26 Aug 2015 15:12:04 +0200 Subject: http2: fix bugs, chrome works :tada: --- libmproxy/proxy/config.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'libmproxy/proxy') diff --git a/libmproxy/proxy/config.py b/libmproxy/proxy/config.py index ec91a6e0..4ca15747 100644 --- a/libmproxy/proxy/config.py +++ b/libmproxy/proxy/config.py @@ -14,6 +14,9 @@ TRANSPARENT_SSL_PORTS = [443, 8443] CONF_BASENAME = "mitmproxy" CA_DIR = "~/.mitmproxy" +# We manually need to specify this, otherwise OpenSSL may select a non-HTTP2 cipher by default. +# https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=apache-2.2.15&openssl=1.0.2&hsts=yes&profile=old +DEFAULT_CLIENT_CIPHERS = "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA" class HostMatcher(object): def __init__(self, patterns=[]): @@ -241,7 +244,7 @@ def ssl_option_group(parser): 'Can be passed multiple times.') group.add_argument( "--ciphers-client", action="store", - type=str, dest="ciphers_client", default=None, + type=str, dest="ciphers_client", default=DEFAULT_CLIENT_CIPHERS, help="Set supported ciphers for client connections. (OpenSSL Syntax)" ) group.add_argument( -- cgit v1.2.3 From 5b17496c7e5ea3c40a910c4973eeb7bfbcf065bd Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Thu, 27 Aug 2015 18:31:15 +0200 Subject: start fixing proxy config --- libmproxy/proxy/config.py | 48 ++++++----------------------------------------- 1 file changed, 6 insertions(+), 42 deletions(-) (limited to 'libmproxy/proxy') diff --git a/libmproxy/proxy/config.py b/libmproxy/proxy/config.py index 4ca15747..83030235 100644 --- a/libmproxy/proxy/config.py +++ b/libmproxy/proxy/config.py @@ -3,14 +3,11 @@ import os import re from OpenSSL import SSL -import netlib -from netlib import http, certutils, tcp +from netlib import certutils, tcp from netlib.http import authentication -from .. import utils, platform, version -from .primitives import RegularProxyMode, SpoofMode, SSLSpoofMode, TransparentProxyMode, UpstreamProxyMode, ReverseProxyMode, Socks5ProxyMode +from .. import utils, platform -TRANSPARENT_SSL_PORTS = [443, 8443] CONF_BASENAME = "mitmproxy" CA_DIR = "~/.mitmproxy" @@ -40,15 +37,12 @@ class ProxyConfig: self, host='', port=8080, - server_version=version.NAMEVERSION, cadir=CA_DIR, clientcerts=None, no_upstream_cert=False, body_size_limit=None, mode=None, upstream_server=None, - http_form_in=None, - http_form_out=None, authenticator=None, ignore_hosts=[], tcp_hosts=[], @@ -57,39 +51,19 @@ class ProxyConfig: certs=[], ssl_version_client=tcp.SSL_DEFAULT_METHOD, ssl_version_server=tcp.SSL_DEFAULT_METHOD, - ssl_ports=TRANSPARENT_SSL_PORTS, - spoofed_ssl_port=None, ssl_verify_upstream_cert=False, ssl_upstream_trusted_cadir=None, ssl_upstream_trusted_ca=None ): self.host = host self.port = port - self.server_version = server_version self.ciphers_client = ciphers_client self.ciphers_server = ciphers_server self.clientcerts = clientcerts self.no_upstream_cert = no_upstream_cert self.body_size_limit = body_size_limit - - if mode == "transparent": - self.mode = TransparentProxyMode(platform.resolver(), ssl_ports) - elif mode == "socks5": - self.mode = Socks5ProxyMode(ssl_ports) - elif mode == "reverse": - self.mode = ReverseProxyMode(upstream_server) - elif mode == "upstream": - self.mode = UpstreamProxyMode(upstream_server) - elif mode == "spoof": - self.mode = SpoofMode() - elif mode == "sslspoof": - self.mode = SSLSpoofMode(spoofed_ssl_port) - else: - self.mode = RegularProxyMode() - - # Handle manual overrides of the http forms - self.mode.http_form_in = http_form_in or self.mode.http_form_in - self.mode.http_form_out = http_form_out or self.mode.http_form_out + self.mode = mode + self.upstream_server = upstream_server self.check_ignore = HostMatcher(ignore_hosts) self.check_tcp = HostMatcher(tcp_hosts) @@ -97,10 +71,10 @@ class ProxyConfig: self.cadir = os.path.expanduser(cadir) self.certstore = certutils.CertStore.from_store( self.cadir, - CONF_BASENAME) + CONF_BASENAME + ) for spec, cert in certs: self.certstore.add_cert_file(spec, cert) - self.ssl_ports = ssl_ports if isinstance(ssl_version_client, int): self.openssl_method_client = ssl_version_client @@ -279,16 +253,6 @@ def ssl_option_group(parser): dest="ssl_upstream_trusted_ca", help="Path to a PEM formatted trusted CA certificate." ) - group.add_argument( - "--ssl-port", - action="append", - type=int, - dest="ssl_ports", - default=list(TRANSPARENT_SSL_PORTS), - metavar="PORT", - help="Can be passed multiple times. Specify destination ports which are assumed to be SSL. " - "Defaults to %s." % - str(TRANSPARENT_SSL_PORTS)) group.add_argument( "--ssl-version-client", dest="ssl_version_client", type=str, default=tcp.SSL_DEFAULT_VERSION, choices=tcp.SSL_VERSIONS.keys(), -- cgit v1.2.3 From a86491eeed13c7889356e5102312f52bd86c3c66 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Thu, 27 Aug 2015 18:37:16 +0200 Subject: Revert "unify SSL version/method handling" This reverts commit 14e49f4fc7a38b63099ab0d42afd213b0d567c0f. --- libmproxy/proxy/config.py | 69 ++++++++++++++++++++++++++++++----------------- 1 file changed, 44 insertions(+), 25 deletions(-) (limited to 'libmproxy/proxy') diff --git a/libmproxy/proxy/config.py b/libmproxy/proxy/config.py index 83030235..f438e9c2 100644 --- a/libmproxy/proxy/config.py +++ b/libmproxy/proxy/config.py @@ -49,11 +49,11 @@ class ProxyConfig: ciphers_client=None, ciphers_server=None, certs=[], - ssl_version_client=tcp.SSL_DEFAULT_METHOD, - ssl_version_server=tcp.SSL_DEFAULT_METHOD, + ssl_version_client="secure", + ssl_version_server="secure", ssl_verify_upstream_cert=False, ssl_upstream_trusted_cadir=None, - ssl_upstream_trusted_ca=None + ssl_upstream_trusted_ca=None, ): self.host = host self.port = port @@ -76,14 +76,10 @@ class ProxyConfig: for spec, cert in certs: self.certstore.add_cert_file(spec, cert) - if isinstance(ssl_version_client, int): - self.openssl_method_client = ssl_version_client - else: - self.openssl_method_client = tcp.SSL_VERSIONS[ssl_version_client] - if isinstance(ssl_version_server, int): - self.openssl_method_server = ssl_version_server - else: - self.openssl_method_server = tcp.SSL_VERSIONS[ssl_version_server] + self.openssl_method_client, self.openssl_options_client = version_to_openssl( + ssl_version_client) + self.openssl_method_server, self.openssl_options_server = version_to_openssl( + ssl_version_server) if ssl_verify_upstream_cert: self.openssl_verification_mode_server = SSL.VERIFY_PEER @@ -92,8 +88,33 @@ class ProxyConfig: self.openssl_trusted_cadir_server = ssl_upstream_trusted_cadir self.openssl_trusted_ca_server = ssl_upstream_trusted_ca - self.openssl_options_client = tcp.SSL_DEFAULT_OPTIONS - self.openssl_options_server = tcp.SSL_DEFAULT_OPTIONS + +sslversion_choices = ( + "all", + "secure", + "SSLv2", + "SSLv3", + "TLSv1", + "TLSv1_1", + "TLSv1_2") + + +def version_to_openssl(version): + """ + Convert a reasonable SSL version specification into the format OpenSSL expects. + Don't ask... + https://bugs.launchpad.net/pyopenssl/+bug/1020632/comments/3 + """ + if version == "all": + return SSL.SSLv23_METHOD, None + elif version == "secure": + # SSLv23_METHOD + NO_SSLv2 + NO_SSLv3 == TLS 1.0+ + # TLSv1_METHOD would be TLS 1.0 only + return SSL.SSLv23_METHOD, (SSL.OP_NO_SSLv2 | SSL.OP_NO_SSLv3) + elif version in sslversion_choices: + return getattr(SSL, "%s_METHOD" % version), None + else: + raise ValueError("Invalid SSL version: %s" % version) def process_proxy_options(parser, options): @@ -254,18 +275,16 @@ def ssl_option_group(parser): help="Path to a PEM formatted trusted CA certificate." ) group.add_argument( - "--ssl-version-client", dest="ssl_version_client", type=str, default=tcp.SSL_DEFAULT_VERSION, - choices=tcp.SSL_VERSIONS.keys(), - help="""" - Use a specified protocol for client connections: - TLSv1.2, TLSv1.1, TLSv1, SSLv3, SSLv2, SSLv23. - Default to SSLv23.""" + "--ssl-version-client", dest="ssl_version_client", + default="secure", action="store", + choices=sslversion_choices, + help="Set supported SSL/TLS version for client connections. " + "SSLv2, SSLv3 and 'all' are INSECURE. Defaults to secure." ) group.add_argument( - "--ssl-version-server", dest="ssl_version_server", type=str, default=tcp.SSL_DEFAULT_VERSION, - choices=tcp.SSL_VERSIONS.keys(), - help="""" - Use a specified protocol for server connections: - TLSv1.2, TLSv1.1, TLSv1, SSLv3, SSLv2, SSLv23. - Default to SSLv23.""" + "--ssl-version-server", dest="ssl_version_server", + default="secure", action="store", + choices=sslversion_choices, + help="Set supported SSL/TLS version for server connections. " + "SSLv2, SSLv3 and 'all' are INSECURE. Defaults to secure." ) -- cgit v1.2.3 From 1cc48345e13917aadc1e0fd93d6011139e78e3d9 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Fri, 28 Aug 2015 01:51:13 +0200 Subject: clean up config/cmdline, fix bugs, remove cruft --- libmproxy/proxy/config.py | 196 +++++++++++++--------------------------------- libmproxy/proxy/server.py | 38 ++++++++- 2 files changed, 89 insertions(+), 145 deletions(-) (limited to 'libmproxy/proxy') diff --git a/libmproxy/proxy/config.py b/libmproxy/proxy/config.py index f438e9c2..8ab5a216 100644 --- a/libmproxy/proxy/config.py +++ b/libmproxy/proxy/config.py @@ -1,4 +1,5 @@ from __future__ import absolute_import +import collections import os import re from OpenSSL import SSL @@ -7,6 +8,7 @@ from netlib import certutils, tcp from netlib.http import authentication from .. import utils, platform +from netlib.tcp import Address CONF_BASENAME = "mitmproxy" CA_DIR = "~/.mitmproxy" @@ -15,8 +17,9 @@ CA_DIR = "~/.mitmproxy" # https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=apache-2.2.15&openssl=1.0.2&hsts=yes&profile=old DEFAULT_CLIENT_CIPHERS = "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA" + class HostMatcher(object): - def __init__(self, patterns=[]): + def __init__(self, patterns=tuple()): self.patterns = list(patterns) self.regexes = [re.compile(p, re.IGNORECASE) for p in self.patterns] @@ -32,6 +35,9 @@ class HostMatcher(object): return bool(self.patterns) +ServerSpec = collections.namedtuple("ServerSpec", "scheme address") + + class ProxyConfig: def __init__( self, @@ -41,19 +47,19 @@ class ProxyConfig: clientcerts=None, no_upstream_cert=False, body_size_limit=None, - mode=None, + mode="regular", upstream_server=None, authenticator=None, - ignore_hosts=[], - tcp_hosts=[], + ignore_hosts=tuple(), + tcp_hosts=tuple(), ciphers_client=None, ciphers_server=None, - certs=[], + certs=tuple(), ssl_version_client="secure", ssl_version_server="secure", ssl_verify_upstream_cert=False, - ssl_upstream_trusted_cadir=None, - ssl_upstream_trusted_ca=None, + ssl_verify_upstream_trusted_cadir=None, + ssl_verify_upstream_trusted_ca=None, ): self.host = host self.port = port @@ -63,7 +69,10 @@ class ProxyConfig: self.no_upstream_cert = no_upstream_cert self.body_size_limit = body_size_limit self.mode = mode - self.upstream_server = upstream_server + if upstream_server: + self.upstream_server = ServerSpec(upstream_server[0], Address.wrap(upstream_server[1])) + else: + self.upstream_server = None self.check_ignore = HostMatcher(ignore_hosts) self.check_tcp = HostMatcher(tcp_hosts) @@ -76,57 +85,46 @@ class ProxyConfig: for spec, cert in certs: self.certstore.add_cert_file(spec, cert) - self.openssl_method_client, self.openssl_options_client = version_to_openssl( - ssl_version_client) - self.openssl_method_server, self.openssl_options_server = version_to_openssl( - ssl_version_server) + self.openssl_method_client, self.openssl_options_client = \ + sslversion_choices[ssl_version_client] + self.openssl_method_server, self.openssl_options_server = \ + sslversion_choices[ssl_version_server] if ssl_verify_upstream_cert: self.openssl_verification_mode_server = SSL.VERIFY_PEER else: self.openssl_verification_mode_server = SSL.VERIFY_NONE - self.openssl_trusted_cadir_server = ssl_upstream_trusted_cadir - self.openssl_trusted_ca_server = ssl_upstream_trusted_ca - - -sslversion_choices = ( - "all", - "secure", - "SSLv2", - "SSLv3", - "TLSv1", - "TLSv1_1", - "TLSv1_2") - - -def version_to_openssl(version): - """ - Convert a reasonable SSL version specification into the format OpenSSL expects. - Don't ask... - https://bugs.launchpad.net/pyopenssl/+bug/1020632/comments/3 - """ - if version == "all": - return SSL.SSLv23_METHOD, None - elif version == "secure": - # SSLv23_METHOD + NO_SSLv2 + NO_SSLv3 == TLS 1.0+ - # TLSv1_METHOD would be TLS 1.0 only - return SSL.SSLv23_METHOD, (SSL.OP_NO_SSLv2 | SSL.OP_NO_SSLv3) - elif version in sslversion_choices: - return getattr(SSL, "%s_METHOD" % version), None - else: - raise ValueError("Invalid SSL version: %s" % version) + self.openssl_trusted_cadir_server = ssl_verify_upstream_trusted_cadir + self.openssl_trusted_ca_server = ssl_verify_upstream_trusted_ca + + +""" +Map a reasonable SSL version specification into the format OpenSSL expects. +Don't ask... +https://bugs.launchpad.net/pyopenssl/+bug/1020632/comments/3 +""" +sslversion_choices = { + "all": (SSL.SSLv23_METHOD, 0), + # SSLv23_METHOD + NO_SSLv2 + NO_SSLv3 == TLS 1.0+ + # TLSv1_METHOD would be TLS 1.0 only + "secure": (SSL.SSLv23_METHOD, (SSL.OP_NO_SSLv2 | SSL.OP_NO_SSLv3)), + "SSLv2": (SSL.SSLv2_METHOD, 0), + "SSLv3": (SSL.SSLv3_METHOD, 0), + "TLSv1": (SSL.TLSv1_METHOD, 0), + "TLSv1_1": (SSL.TLSv1_1_METHOD, 0), + "TLSv1_2": (SSL.TLSv1_2_METHOD, 0), +} def process_proxy_options(parser, options): body_size_limit = utils.parse_size(options.body_size_limit) c = 0 - mode, upstream_server, spoofed_ssl_port = None, None, None + mode, upstream_server = "regular", None if options.transparent_proxy: c += 1 if not platform.resolver: - return parser.error( - "Transparent mode not supported on this platform.") + return parser.error("Transparent mode not supported on this platform.") mode = "transparent" if options.socks_proxy: c += 1 @@ -139,32 +137,26 @@ def process_proxy_options(parser, options): c += 1 mode = "upstream" upstream_server = options.upstream_proxy - if options.spoof_mode: - c += 1 - mode = "spoof" - if options.ssl_spoof_mode: - c += 1 - mode = "sslspoof" - spoofed_ssl_port = options.spoofed_ssl_port if c > 1: return parser.error( "Transparent, SOCKS5, reverse and upstream proxy mode " - "are mutually exclusive.") + "are mutually exclusive. Read the docs on proxy modes to understand why." + ) if options.clientcerts: options.clientcerts = os.path.expanduser(options.clientcerts) - if not os.path.exists( - options.clientcerts) or not os.path.isdir( - options.clientcerts): + if not os.path.exists(options.clientcerts) or not os.path.isdir(options.clientcerts): return parser.error( "Client certificate directory does not exist or is not a directory: %s" % - options.clientcerts) + options.clientcerts + ) - if (options.auth_nonanonymous or options.auth_singleuser or options.auth_htpasswd): + if options.auth_nonanonymous or options.auth_singleuser or options.auth_htpasswd: if options.auth_singleuser: if len(options.auth_singleuser.split(':')) != 2: return parser.error( - "Invalid single-user specification. Please use the format username:password") + "Invalid single-user specification. Please use the format username:password" + ) username, password = options.auth_singleuser.split(':') password_manager = authentication.PassManSingleUser(username, password) elif options.auth_nonanonymous: @@ -189,12 +181,6 @@ def process_proxy_options(parser, options): parser.error("Certificate file does not exist: %s" % parts[1]) certs.append(parts) - ssl_ports = options.ssl_ports - if options.ssl_ports != TRANSPARENT_SSL_PORTS: - # arparse appends to default value by default, strip that off. - # see http://bugs.python.org/issue16399 - ssl_ports = ssl_ports[len(TRANSPARENT_SSL_PORTS):] - return ProxyConfig( host=options.addr, port=options.port, @@ -204,87 +190,15 @@ def process_proxy_options(parser, options): body_size_limit=body_size_limit, mode=mode, upstream_server=upstream_server, - http_form_in=options.http_form_in, - http_form_out=options.http_form_out, ignore_hosts=options.ignore_hosts, tcp_hosts=options.tcp_hosts, authenticator=authenticator, ciphers_client=options.ciphers_client, ciphers_server=options.ciphers_server, - certs=certs, + certs=tuple(certs), ssl_version_client=options.ssl_version_client, ssl_version_server=options.ssl_version_server, - ssl_ports=ssl_ports, - spoofed_ssl_port=spoofed_ssl_port, ssl_verify_upstream_cert=options.ssl_verify_upstream_cert, - ssl_upstream_trusted_cadir=options.ssl_upstream_trusted_cadir, - ssl_upstream_trusted_ca=options.ssl_upstream_trusted_ca - ) - - -def ssl_option_group(parser): - group = parser.add_argument_group("SSL") - group.add_argument( - "--cert", - dest='certs', - default=[], - type=str, - metavar="SPEC", - action="append", - help='Add an SSL certificate. SPEC is of the form "[domain=]path". ' - 'The domain may include a wildcard, and is equal to "*" if not specified. ' - 'The file at path is a certificate in PEM format. If a private key is included in the PEM, ' - 'it is used, else the default key in the conf dir is used. ' - 'The PEM file should contain the full certificate chain, with the leaf certificate as the first entry. ' - 'Can be passed multiple times.') - group.add_argument( - "--ciphers-client", action="store", - type=str, dest="ciphers_client", default=DEFAULT_CLIENT_CIPHERS, - help="Set supported ciphers for client connections. (OpenSSL Syntax)" - ) - group.add_argument( - "--ciphers-server", action="store", - type=str, dest="ciphers_server", default=None, - help="Set supported ciphers for server connections. (OpenSSL Syntax)" - ) - group.add_argument( - "--client-certs", action="store", - type=str, dest="clientcerts", default=None, - help="Client certificate directory." - ) - group.add_argument( - "--no-upstream-cert", default=False, - action="store_true", dest="no_upstream_cert", - help="Don't connect to upstream server to look up certificate details." - ) - group.add_argument( - "--verify-upstream-cert", default=False, - action="store_true", dest="ssl_verify_upstream_cert", - help="Verify upstream server SSL/TLS certificates and fail if invalid " - "or not present." - ) - group.add_argument( - "--upstream-trusted-cadir", default=None, action="store", - dest="ssl_upstream_trusted_cadir", - help="Path to a directory of trusted CA certificates for upstream " - "server verification prepared using the c_rehash tool." - ) - group.add_argument( - "--upstream-trusted-ca", default=None, action="store", - dest="ssl_upstream_trusted_ca", - help="Path to a PEM formatted trusted CA certificate." - ) - group.add_argument( - "--ssl-version-client", dest="ssl_version_client", - default="secure", action="store", - choices=sslversion_choices, - help="Set supported SSL/TLS version for client connections. " - "SSLv2, SSLv3 and 'all' are INSECURE. Defaults to secure." - ) - group.add_argument( - "--ssl-version-server", dest="ssl_version_server", - default="secure", action="store", - choices=sslversion_choices, - help="Set supported SSL/TLS version for server connections. " - "SSLv2, SSLv3 and 'all' are INSECURE. Defaults to secure." - ) + ssl_verify_upstream_trusted_cadir=options.ssl_verify_upstream_trusted_cadir, + ssl_verify_upstream_trusted_ca=options.ssl_verify_upstream_trusted_ca + ) \ No newline at end of file diff --git a/libmproxy/proxy/server.py b/libmproxy/proxy/server.py index 19ddb930..1fc4cbda 100644 --- a/libmproxy/proxy/server.py +++ b/libmproxy/proxy/server.py @@ -5,6 +5,8 @@ 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 @@ -82,11 +84,31 @@ class ConnectionHandler2: self.channel ) - # FIXME: properly parse config - if self.config.mode == "upstream": - root_layer = protocol2.HttpUpstreamProxy(root_context, ("localhost", 8081)) - else: + mode = self.config.mode + if mode == "upstream": + root_layer = protocol2.HttpUpstreamProxy( + root_context, + self.config.upstream_server.address + ) + elif mode == "transparent": + root_layer = 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( + root_context, + self.config.upstream_server.address, + client_tls, + server_tls + ) + elif mode == "socks5": + root_layer = protocol2.Socks5Proxy(root_context) + elif mode == "regular": root_layer = protocol2.HttpProxy(root_context) + elif callable(mode): # pragma: nocover + root_layer = mode(root_context) + else: # pragma: nocover + raise ValueError("Unknown proxy mode: %s" % mode) try: root_layer() @@ -94,6 +116,14 @@ class ConnectionHandler2: self.log("Connection killed", "info") except ProtocolException as e: self.log(e, "info") + # If an error propagates to the topmost level, + # we send an HTTP error response, which is both + # understandable by HTTP clients and humans. + try: + error_response = protocol2.make_error_response(502, repr(e)) + self.client_conn.send(HTTP1Protocol().assemble(error_response)) + except NetLibError: + pass except Exception: self.log(traceback.format_exc(), "error") print(traceback.format_exc(), file=sys.stderr) -- cgit v1.2.3 From 2dfba2105b4b5ad094ee364124c0552d2e4a4947 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Sat, 29 Aug 2015 12:34:01 +0200 Subject: move sslversion mapping to netlib --- libmproxy/proxy/config.py | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) (limited to 'libmproxy/proxy') diff --git a/libmproxy/proxy/config.py b/libmproxy/proxy/config.py index 8ab5a216..415ee215 100644 --- a/libmproxy/proxy/config.py +++ b/libmproxy/proxy/config.py @@ -8,7 +8,7 @@ from netlib import certutils, tcp from netlib.http import authentication from .. import utils, platform -from netlib.tcp import Address +from netlib.tcp import Address, sslversion_choices CONF_BASENAME = "mitmproxy" CA_DIR = "~/.mitmproxy" @@ -98,24 +98,6 @@ class ProxyConfig: self.openssl_trusted_ca_server = ssl_verify_upstream_trusted_ca -""" -Map a reasonable SSL version specification into the format OpenSSL expects. -Don't ask... -https://bugs.launchpad.net/pyopenssl/+bug/1020632/comments/3 -""" -sslversion_choices = { - "all": (SSL.SSLv23_METHOD, 0), - # SSLv23_METHOD + NO_SSLv2 + NO_SSLv3 == TLS 1.0+ - # TLSv1_METHOD would be TLS 1.0 only - "secure": (SSL.SSLv23_METHOD, (SSL.OP_NO_SSLv2 | SSL.OP_NO_SSLv3)), - "SSLv2": (SSL.SSLv2_METHOD, 0), - "SSLv3": (SSL.SSLv3_METHOD, 0), - "TLSv1": (SSL.TLSv1_METHOD, 0), - "TLSv1_1": (SSL.TLSv1_1_METHOD, 0), - "TLSv1_2": (SSL.TLSv1_2_METHOD, 0), -} - - def process_proxy_options(parser, options): body_size_limit = utils.parse_size(options.body_size_limit) -- cgit v1.2.3 From dd7f50d64bef38fa67b4cace91913d03691dde26 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Sun, 30 Aug 2015 01:21:58 +0200 Subject: restructure code, remove cruft --- libmproxy/proxy/__init__.py | 13 +- libmproxy/proxy/connection.py | 6 +- libmproxy/proxy/primitives.py | 179 +------------------ libmproxy/proxy/server.py | 401 +++--------------------------------------- 4 files changed, 51 insertions(+), 548 deletions(-) (limited to 'libmproxy/proxy') 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 -- cgit v1.2.3 From 1dd09a5509219e7390abbb8c0b6818c7e792daa1 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Sun, 30 Aug 2015 02:27:38 +0200 Subject: always insert tls layer for inline script upgrades --- libmproxy/proxy/config.py | 2 ++ libmproxy/proxy/server.py | 4 +--- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'libmproxy/proxy') diff --git a/libmproxy/proxy/config.py b/libmproxy/proxy/config.py index 415ee215..b360abbd 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 69784014..5abd0877 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": -- cgit v1.2.3 From a86ec56012136664688fa4a8efcd866b5e3e17a8 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Sun, 30 Aug 2015 15:27:29 +0200 Subject: move files around --- libmproxy/proxy/__init__.py | 8 +- libmproxy/proxy/config.py | 2 +- libmproxy/proxy/connection.py | 193 ----------------------------- libmproxy/proxy/modes/__init__.py | 12 ++ libmproxy/proxy/modes/http_proxy.py | 26 ++++ libmproxy/proxy/modes/reverse_proxy.py | 17 +++ libmproxy/proxy/modes/socks_proxy.py | 60 +++++++++ libmproxy/proxy/modes/transparent_proxy.py | 24 ++++ libmproxy/proxy/primitives.py | 15 --- libmproxy/proxy/root_context.py | 93 ++++++++++++++ libmproxy/proxy/server.py | 23 ++-- 11 files changed, 248 insertions(+), 225 deletions(-) delete mode 100644 libmproxy/proxy/connection.py create mode 100644 libmproxy/proxy/modes/__init__.py create mode 100644 libmproxy/proxy/modes/http_proxy.py create mode 100644 libmproxy/proxy/modes/reverse_proxy.py create mode 100644 libmproxy/proxy/modes/socks_proxy.py create mode 100644 libmproxy/proxy/modes/transparent_proxy.py delete mode 100644 libmproxy/proxy/primitives.py create mode 100644 libmproxy/proxy/root_context.py (limited to 'libmproxy/proxy') diff --git a/libmproxy/proxy/__init__.py b/libmproxy/proxy/__init__.py index 709654cb..d5297cb1 100644 --- a/libmproxy/proxy/__init__.py +++ b/libmproxy/proxy/__init__.py @@ -1,11 +1,9 @@ from __future__ import (absolute_import, print_function, division) -from .primitives import Log, Kill +from .server import ProxyServer, DummyServer from .config import ProxyConfig -from .connection import ClientConnection, ServerConnection __all__ = [ - "Log", "Kill", + "ProxyServer", "DummyServer", "ProxyConfig", - "ClientConnection", "ServerConnection" -] \ No newline at end of file +] diff --git a/libmproxy/proxy/config.py b/libmproxy/proxy/config.py index b360abbd..65029087 100644 --- a/libmproxy/proxy/config.py +++ b/libmproxy/proxy/config.py @@ -6,9 +6,9 @@ from OpenSSL import SSL from netlib import certutils, tcp from netlib.http import authentication +from netlib.tcp import Address, sslversion_choices from .. import utils, platform -from netlib.tcp import Address, sslversion_choices CONF_BASENAME = "mitmproxy" CA_DIR = "~/.mitmproxy" diff --git a/libmproxy/proxy/connection.py b/libmproxy/proxy/connection.py deleted file mode 100644 index 94f318f6..00000000 --- a/libmproxy/proxy/connection.py +++ /dev/null @@ -1,193 +0,0 @@ -from __future__ import absolute_import - -import copy -import os - -from netlib import tcp, certutils -from .. import stateobject, utils - - -class ClientConnection(tcp.BaseHandler, stateobject.StateObject): - def __init__(self, client_connection, address, server): - # Eventually, this object is restored from state. We don't have a - # connection then. - if client_connection: - super(ClientConnection, self).__init__(client_connection, address, server) - else: - self.connection = None - self.server = None - self.wfile = None - self.rfile = None - self.address = None - self.clientcert = None - self.ssl_established = None - - self.timestamp_start = utils.timestamp() - self.timestamp_end = None - self.timestamp_ssl_setup = None - self.protocol = None - - def __nonzero__(self): - return bool(self.connection) and not self.finished - - def __repr__(self): - return "".format( - ssl="[ssl] " if self.ssl_established else "", - host=self.address.host, - port=self.address.port - ) - - @property - def tls_established(self): - return self.ssl_established - - _stateobject_attributes = dict( - ssl_established=bool, - timestamp_start=float, - timestamp_end=float, - timestamp_ssl_setup=float - ) - - def get_state(self, short=False): - d = super(ClientConnection, self).get_state(short) - d.update( - address={ - "address": self.address(), - "use_ipv6": self.address.use_ipv6}, - clientcert=self.cert.to_pem() if self.clientcert else None) - return d - - def load_state(self, state): - super(ClientConnection, self).load_state(state) - self.address = tcp.Address( - **state["address"]) if state["address"] else None - self.clientcert = certutils.SSLCert.from_pem( - state["clientcert"]) if state["clientcert"] else None - - def copy(self): - return copy.copy(self) - - def send(self, message): - if isinstance(message, list): - message = b''.join(message) - self.wfile.write(message) - self.wfile.flush() - - @classmethod - def from_state(cls, state): - f = cls(None, tuple(), None) - f.load_state(state) - return f - - def convert_to_ssl(self, *args, **kwargs): - super(ClientConnection, self).convert_to_ssl(*args, **kwargs) - self.timestamp_ssl_setup = utils.timestamp() - - def finish(self): - super(ClientConnection, self).finish() - self.timestamp_end = utils.timestamp() - - -class ServerConnection(tcp.TCPClient, stateobject.StateObject): - def __init__(self, address): - tcp.TCPClient.__init__(self, address) - - self.via = None - self.timestamp_start = None - self.timestamp_end = None - self.timestamp_tcp_setup = None - self.timestamp_ssl_setup = None - self.protocol = None - - def __nonzero__(self): - return bool(self.connection) and not self.finished - - def __repr__(self): - if self.ssl_established and self.sni: - ssl = "[ssl: {0}] ".format(self.sni) - elif self.ssl_established: - ssl = "[ssl] " - else: - ssl = "" - return "".format( - ssl=ssl, - host=self.address.host, - port=self.address.port - ) - - @property - def tls_established(self): - return self.ssl_established - - _stateobject_attributes = dict( - timestamp_start=float, - timestamp_end=float, - timestamp_tcp_setup=float, - timestamp_ssl_setup=float, - address=tcp.Address, - source_address=tcp.Address, - cert=certutils.SSLCert, - ssl_established=bool, - sni=str - ) - _stateobject_long_attributes = {"cert"} - - def get_state(self, short=False): - d = super(ServerConnection, self).get_state(short) - 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), - cert=self.cert.to_pem() if self.cert else None - ) - return d - - def load_state(self, state): - super(ServerConnection, self).load_state(state) - - self.address = tcp.Address( - **state["address"]) if state["address"] else None - self.source_address = tcp.Address( - **state["source_address"]) if state["source_address"] else None - self.cert = certutils.SSLCert.from_pem( - state["cert"]) if state["cert"] else None - - @classmethod - def from_state(cls, state): - f = cls(tuple()) - f.load_state(state) - return f - - def copy(self): - return copy.copy(self) - - def connect(self): - self.timestamp_start = utils.timestamp() - tcp.TCPClient.connect(self) - self.timestamp_tcp_setup = utils.timestamp() - - def send(self, message): - if isinstance(message, list): - message = b''.join(message) - self.wfile.write(message) - self.wfile.flush() - - def establish_ssl(self, clientcerts, sni, **kwargs): - clientcert = None - if clientcerts: - path = os.path.join( - clientcerts, - self.address.host.encode("idna")) + ".pem" - if os.path.exists(path): - clientcert = path - - self.convert_to_ssl(cert=clientcert, sni=sni, **kwargs) - self.sni = sni - self.timestamp_ssl_setup = utils.timestamp() - - def finish(self): - tcp.TCPClient.finish(self) - self.timestamp_end = utils.timestamp() - -ServerConnection._stateobject_attributes["via"] = ServerConnection diff --git a/libmproxy/proxy/modes/__init__.py b/libmproxy/proxy/modes/__init__.py new file mode 100644 index 00000000..f014ed98 --- /dev/null +++ b/libmproxy/proxy/modes/__init__.py @@ -0,0 +1,12 @@ +from __future__ import (absolute_import, print_function, division) +from .http_proxy import HttpProxy, HttpUpstreamProxy +from .reverse_proxy import ReverseProxy +from .socks_proxy import Socks5Proxy +from .transparent_proxy import TransparentProxy + +__all__ = [ + "HttpProxy", "HttpUpstreamProxy", + "ReverseProxy", + "Socks5Proxy", + "TransparentProxy" +] diff --git a/libmproxy/proxy/modes/http_proxy.py b/libmproxy/proxy/modes/http_proxy.py new file mode 100644 index 00000000..90c54cc6 --- /dev/null +++ b/libmproxy/proxy/modes/http_proxy.py @@ -0,0 +1,26 @@ +from __future__ import (absolute_import, print_function, division) + +from ...protocol import Layer, ServerConnectionMixin + + +class HttpProxy(Layer, ServerConnectionMixin): + def __call__(self): + layer = self.ctx.next_layer(self) + try: + layer() + finally: + 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) + + def __call__(self): + layer = self.ctx.next_layer(self) + try: + layer() + finally: + if self.server_conn: + self._disconnect() diff --git a/libmproxy/proxy/modes/reverse_proxy.py b/libmproxy/proxy/modes/reverse_proxy.py new file mode 100644 index 00000000..b57ac5eb --- /dev/null +++ b/libmproxy/proxy/modes/reverse_proxy.py @@ -0,0 +1,17 @@ +from __future__ import (absolute_import, print_function, division) + +from ...protocol import Layer, ServerConnectionMixin + + +class ReverseProxy(Layer, ServerConnectionMixin): + def __init__(self, ctx, server_address, server_tls): + super(ReverseProxy, self).__init__(ctx, server_address=server_address) + self.server_tls = server_tls + + def __call__(self): + layer = self.ctx.next_layer(self) + try: + layer() + finally: + if self.server_conn: + self._disconnect() diff --git a/libmproxy/proxy/modes/socks_proxy.py b/libmproxy/proxy/modes/socks_proxy.py new file mode 100644 index 00000000..ebaf939e --- /dev/null +++ b/libmproxy/proxy/modes/socks_proxy.py @@ -0,0 +1,60 @@ +from __future__ import (absolute_import, print_function, division) + +from netlib import socks +from netlib.tcp import NetLibError + +from ...exceptions import Socks5Exception +from ...protocol import Layer, ServerConnectionMixin + + +class Socks5Proxy(Layer, ServerConnectionMixin): + def __call__(self): + try: + # 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" + ) + + # 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() + + # 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." + ) + + # 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() diff --git a/libmproxy/proxy/modes/transparent_proxy.py b/libmproxy/proxy/modes/transparent_proxy.py new file mode 100644 index 00000000..96ad86c4 --- /dev/null +++ b/libmproxy/proxy/modes/transparent_proxy.py @@ -0,0 +1,24 @@ +from __future__ import (absolute_import, print_function, division) + +from ... import platform +from ...exceptions import ProtocolException +from ...protocol import Layer, ServerConnectionMixin + + +class TransparentProxy(Layer, ServerConnectionMixin): + def __init__(self, ctx): + super(TransparentProxy, self).__init__(ctx) + self.resolver = platform.resolver() + + def __call__(self): + try: + 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) + + layer = self.ctx.next_layer(self) + try: + layer() + finally: + if self.server_conn: + self._disconnect() diff --git a/libmproxy/proxy/primitives.py b/libmproxy/proxy/primitives.py deleted file mode 100644 index 2e440fe8..00000000 --- a/libmproxy/proxy/primitives.py +++ /dev/null @@ -1,15 +0,0 @@ -from __future__ import absolute_import -import collections -from netlib import socks, tcp - - -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/root_context.py b/libmproxy/proxy/root_context.py new file mode 100644 index 00000000..35909612 --- /dev/null +++ b/libmproxy/proxy/root_context.py @@ -0,0 +1,93 @@ +from __future__ import (absolute_import, print_function, division) + +from netlib.http.http1 import HTTP1Protocol +from netlib.http.http2 import HTTP2Protocol + +from ..protocol import ( + RawTCPLayer, TlsLayer, Http1Layer, Http2Layer, is_tls_record_magic, ServerConnectionMixin +) +from .modes import HttpProxy, HttpUpstreamProxy, ReverseProxy + + +class RootContext(object): + """ + The outmost context provided to the root layer. + As a consequence, every layer has .client_conn, .channel, .next_layer() and .config. + """ + + def __init__(self, client_conn, config, channel): + self.client_conn = client_conn # Client Connection + self.channel = channel # provides .ask() method to communicate with FlowMaster + self.config = config # Proxy Configuration + + def next_layer(self, top_layer): + """ + This function determines the next layer in the protocol stack. + + Arguments: + top_layer: the current top layer. + + Returns: + The next layer + """ + + # 1. Check for --ignore. + if self.config.check_ignore(top_layer.server_conn.address): + return RawTCPLayer(top_layer, logging=False) + + d = top_layer.client_conn.rfile.peek(3) + 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) + + # 4. Check for --tcp + if self.config.check_tcp(top_layer.server_conn.address): + return RawTCPLayer(top_layer) + + # 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: + return Http2Layer(top_layer, 'transparent') + if alpn == HTTP1Protocol.ALPN_PROTO_HTTP1: + return Http1Layer(top_layer, 'transparent') + + # 6. Assume HTTP1 by default + return Http1Layer(top_layer, 'transparent') + + # In a future version, we want to implement TCP passthrough as the last fallback, + # but we don't have the UI part ready for that. + # + # d = top_layer.client_conn.rfile.peek(3) + # is_ascii = ( + # len(d) == 3 and + # # 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)) + # is_http2_magic = (d == HTTP2Protocol.CLIENT_CONNECTION_PREFACE) + + @property + def layers(self): + return [] + + def __repr__(self): + return "RootContext" diff --git a/libmproxy/proxy/server.py b/libmproxy/proxy/server.py index 5abd0877..2a451ba1 100644 --- a/libmproxy/proxy/server.py +++ b/libmproxy/proxy/server.py @@ -3,14 +3,15 @@ from __future__ import absolute_import, print_function import traceback import sys import socket + from netlib import tcp from netlib.http.http1 import HTTP1Protocol from netlib.tcp import NetLibError - -from .. import protocol2 from ..exceptions import ProtocolException, ServerException -from .primitives import Log, Kill -from .connection import ClientConnection +from ..protocol import Log, Kill +from ..models import ClientConnection, make_error_response +from .modes import HttpUpstreamProxy, HttpProxy, ReverseProxy, TransparentProxy, Socks5Proxy +from .root_context import RootContext class DummyServer: @@ -71,7 +72,7 @@ class ConnectionHandler(object): """@type: libmproxy.controller.Channel""" def _create_root_layer(self): - root_context = protocol2.RootContext( + root_context = RootContext( self.client_conn, self.config, self.channel @@ -79,23 +80,23 @@ class ConnectionHandler(object): mode = self.config.mode if mode == "upstream": - return protocol2.HttpUpstreamProxy( + return HttpUpstreamProxy( root_context, self.config.upstream_server.address ) elif mode == "transparent": - return protocol2.TransparentProxy(root_context) + return TransparentProxy(root_context) elif mode == "reverse": server_tls = self.config.upstream_server.scheme == "https" - return protocol2.ReverseProxy( + return ReverseProxy( root_context, self.config.upstream_server.address, server_tls ) elif mode == "socks5": - return protocol2.Socks5Proxy(root_context) + return Socks5Proxy(root_context) elif mode == "regular": - return protocol2.HttpProxy(root_context) + return HttpProxy(root_context) elif callable(mode): # pragma: nocover return mode(root_context) else: # pragma: nocover @@ -116,7 +117,7 @@ class ConnectionHandler(object): # we send an HTTP error response, which is both # understandable by HTTP clients and humans. try: - error_response = protocol2.make_error_response(502, repr(e)) + error_response = make_error_response(502, repr(e)) self.client_conn.send(HTTP1Protocol().assemble(error_response)) except NetLibError: pass -- cgit v1.2.3 From 7450bef615436d39bcd2a0d2a8892b8f42beea6f Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Mon, 31 Aug 2015 13:43:30 +0200 Subject: fix dns_spoofing example, avoid connecting to itself --- libmproxy/proxy/config.py | 2 +- libmproxy/proxy/server.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'libmproxy/proxy') diff --git a/libmproxy/proxy/config.py b/libmproxy/proxy/config.py index 65029087..8d2a286d 100644 --- a/libmproxy/proxy/config.py +++ b/libmproxy/proxy/config.py @@ -1,4 +1,4 @@ -from __future__ import absolute_import +from __future__ import (absolute_import, print_function, division) import collections import os import re diff --git a/libmproxy/proxy/server.py b/libmproxy/proxy/server.py index 2a451ba1..b565ef86 100644 --- a/libmproxy/proxy/server.py +++ b/libmproxy/proxy/server.py @@ -1,4 +1,4 @@ -from __future__ import absolute_import, print_function +from __future__ import (absolute_import, print_function, division) import traceback import sys -- cgit v1.2.3 From b04e6e56ab1e69853abebfb950539e3a3aefbdf2 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Mon, 31 Aug 2015 17:05:52 +0200 Subject: update inline script hooks --- libmproxy/proxy/server.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'libmproxy/proxy') diff --git a/libmproxy/proxy/server.py b/libmproxy/proxy/server.py index b565ef86..e9e8df09 100644 --- a/libmproxy/proxy/server.py +++ b/libmproxy/proxy/server.py @@ -106,6 +106,10 @@ class ConnectionHandler(object): self.log("clientconnect", "info") root_layer = self._create_root_layer() + root_layer = self.channel.ask("clientconnect", root_layer) + if root_layer == Kill: + def root_layer(): + raise Kill() try: root_layer() @@ -128,6 +132,7 @@ class ConnectionHandler(object): print("Please lodge a bug report at: https://github.com/mitmproxy/mitmproxy", file=sys.stderr) self.log("clientdisconnect", "info") + self.channel.tell("clientdisconnect", root_layer) self.client_conn.finish() def log(self, msg, level): -- cgit v1.2.3 From 481cc6ea842dc3c531c45a4bd228bdd6ebcc4229 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Mon, 31 Aug 2015 17:29:14 +0200 Subject: we don't support socks auth, refs #738 --- libmproxy/proxy/config.py | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'libmproxy/proxy') diff --git a/libmproxy/proxy/config.py b/libmproxy/proxy/config.py index 8d2a286d..2a1b84cb 100644 --- a/libmproxy/proxy/config.py +++ b/libmproxy/proxy/config.py @@ -136,6 +136,13 @@ def process_proxy_options(parser, options): ) if options.auth_nonanonymous or options.auth_singleuser or options.auth_htpasswd: + + if options.socks_proxy: + return parser.error( + "Proxy Authentication not supported in SOCKS mode. " + "https://github.com/mitmproxy/mitmproxy/issues/738" + ) + if options.auth_singleuser: if len(options.auth_singleuser.split(':')) != 2: return parser.error( -- cgit v1.2.3