aboutsummaryrefslogtreecommitdiffstats
path: root/libmproxy/proxy
diff options
context:
space:
mode:
Diffstat (limited to 'libmproxy/proxy')
-rw-r--r--libmproxy/proxy/__init__.py8
-rw-r--r--libmproxy/proxy/config.py2
-rw-r--r--libmproxy/proxy/connection.py193
-rw-r--r--libmproxy/proxy/modes/__init__.py12
-rw-r--r--libmproxy/proxy/modes/http_proxy.py26
-rw-r--r--libmproxy/proxy/modes/reverse_proxy.py17
-rw-r--r--libmproxy/proxy/modes/socks_proxy.py60
-rw-r--r--libmproxy/proxy/modes/transparent_proxy.py24
-rw-r--r--libmproxy/proxy/primitives.py15
-rw-r--r--libmproxy/proxy/root_context.py93
-rw-r--r--libmproxy/proxy/server.py23
11 files changed, 248 insertions, 225 deletions
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 "<ClientConnection: {ssl}{host}:{port}>".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 "<ServerConnection: {ssl}{host}:{port}>".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