aboutsummaryrefslogtreecommitdiffstats
path: root/libmproxy
diff options
context:
space:
mode:
Diffstat (limited to 'libmproxy')
-rw-r--r--libmproxy/cmdline.py5
-rw-r--r--libmproxy/console/__init__.py19
-rw-r--r--libmproxy/console/flowdetailview.py4
-rw-r--r--libmproxy/flow.py12
-rw-r--r--libmproxy/main.py1
-rw-r--r--libmproxy/models/connections.py4
-rw-r--r--libmproxy/protocol/__init__.py4
-rw-r--r--libmproxy/protocol/base.py5
-rw-r--r--libmproxy/protocol/http_replay.py4
-rw-r--r--libmproxy/protocol/rawtcp.py19
-rw-r--r--libmproxy/protocol/tls.py126
-rw-r--r--libmproxy/proxy/modes/socks_proxy.py3
-rw-r--r--libmproxy/proxy/root_context.py22
-rw-r--r--libmproxy/script/reloader.py26
14 files changed, 167 insertions, 87 deletions
diff --git a/libmproxy/cmdline.py b/libmproxy/cmdline.py
index 99b76e68..cd1a8bba 100644
--- a/libmproxy/cmdline.py
+++ b/libmproxy/cmdline.py
@@ -669,6 +669,11 @@ def mitmproxy():
help="Show event log."
)
parser.add_argument(
+ "-f", "--follow",
+ action="store_true", dest="follow",
+ help="Follow flow list."
+ )
+ parser.add_argument(
"--no-mouse",
action="store_true", dest="no_mouse",
help="Disable mouse interaction."
diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py
index 31edca81..0df10256 100644
--- a/libmproxy/console/__init__.py
+++ b/libmproxy/console/__init__.py
@@ -105,25 +105,25 @@ class ConsoleState(flow.State):
for f in self.flows:
if self.flow_marked(f):
marked_flows.append(f)
-
+
super(ConsoleState, self).clear()
-
+
for f in marked_flows:
self.add_flow(f)
self.set_flow_marked(f, True)
-
+
if len(self.flows.views) == 0:
self.focus = None
else:
self.focus = 0
self.set_focus(self.focus)
-
+
def flow_marked(self, flow):
return self.get_flow_setting(flow, "marked", False)
-
+
def set_flow_marked(self, flow, marked):
self.add_flow_setting(flow, "marked", marked)
-
+
class Options(object):
attributes = [
@@ -134,6 +134,7 @@ class Options(object):
"anticomp",
"client_replay",
"eventlog",
+ "follow",
"keepserving",
"kill",
"intercept",
@@ -212,6 +213,7 @@ class ConsoleMaster(flow.FlowMaster):
self.eventlog = options.eventlog
self.eventlist = urwid.SimpleListWalker([])
+ self.follow = options.follow
if options.client_replay:
self.client_playback_path(options.client_replay)
@@ -562,6 +564,9 @@ class ConsoleMaster(flow.FlowMaster):
else:
body = flowlist.FlowListBox(self)
+ if self.follow:
+ self.toggle_follow_flows()
+
signals.push_view_state.send(
self,
window = window.Window(
@@ -604,7 +609,7 @@ class ConsoleMaster(flow.FlowMaster):
def save_flows(self, path):
return self._write_flows(path, self.state.view)
-
+
def save_marked_flows(self, path):
marked_flows = []
for f in self.state.view:
diff --git a/libmproxy/console/flowdetailview.py b/libmproxy/console/flowdetailview.py
index 40769c95..394ad217 100644
--- a/libmproxy/console/flowdetailview.py
+++ b/libmproxy/console/flowdetailview.py
@@ -20,7 +20,7 @@ def flowdetails(state, flow):
req = flow.request
resp = flow.response
- if sc:
+ if sc is not None:
text.append(urwid.Text([("head", "Server Connection:")]))
parts = [
["Address", "%s:%s" % sc.address()],
@@ -76,7 +76,7 @@ def flowdetails(state, flow):
common.format_keyvals(parts, key="key", val="text", indent=4)
)
- if cc:
+ if cc is not None:
text.append(urwid.Text([("head", "Client Connection:")]))
parts = [
diff --git a/libmproxy/flow.py b/libmproxy/flow.py
index f02b5767..1f28166f 100644
--- a/libmproxy/flow.py
+++ b/libmproxy/flow.py
@@ -666,7 +666,7 @@ class FlowMaster(controller.Master):
script.reloader.unwatch(script_obj)
self.scripts.remove(script_obj)
- def load_script(self, command, use_reloader=False):
+ def load_script(self, command, use_reloader=True):
"""
Loads a script. Returns an error description if something went
wrong.
@@ -1040,16 +1040,20 @@ class FlowMaster(controller.Master):
s.unload()
except script.ScriptException as e:
ok = False
- self.add_event('Error reloading "{}": {}'.format(s.filename, str(e)))
+ self.add_event('Error reloading "{}": {}'.format(s.filename, str(e)), 'error')
try:
s.load()
except script.ScriptException as e:
ok = False
- self.add_event('Error reloading "{}": {}'.format(s.filename, str(e)))
+ self.add_event('Error reloading "{}": {}'.format(s.filename, str(e)), 'error')
else:
- self.add_event('"{}" reloaded.'.format(s.filename))
+ self.add_event('"{}" reloaded.'.format(s.filename), 'info')
return ok
+ def handle_tcp_message(self, m):
+ self.run_script_hook("tcp_message", m)
+ m.reply()
+
def shutdown(self):
self.unload_scripts()
controller.Master.shutdown(self)
diff --git a/libmproxy/main.py b/libmproxy/main.py
index 3c908ed9..655d573d 100644
--- a/libmproxy/main.py
+++ b/libmproxy/main.py
@@ -54,6 +54,7 @@ def mitmproxy(args=None): # pragma: nocover
console_options.palette = options.palette
console_options.palette_transparent = options.palette_transparent
console_options.eventlog = options.eventlog
+ console_options.follow = options.follow
console_options.intercept = options.intercept
console_options.limit = options.limit
console_options.no_mouse = options.no_mouse
diff --git a/libmproxy/models/connections.py b/libmproxy/models/connections.py
index 0991955d..f5dabe4e 100644
--- a/libmproxy/models/connections.py
+++ b/libmproxy/models/connections.py
@@ -88,8 +88,8 @@ class ClientConnection(tcp.BaseHandler, stateobject.StateObject):
class ServerConnection(tcp.TCPClient, stateobject.StateObject):
- def __init__(self, address):
- tcp.TCPClient.__init__(self, address)
+ def __init__(self, address, source_address=None):
+ tcp.TCPClient.__init__(self, address, source_address)
self.via = None
self.timestamp_start = None
diff --git a/libmproxy/protocol/__init__.py b/libmproxy/protocol/__init__.py
index 0d624fd7..d8ebd4f0 100644
--- a/libmproxy/protocol/__init__.py
+++ b/libmproxy/protocol/__init__.py
@@ -28,12 +28,12 @@ as late as possible; this makes server replay without any outgoing connections p
from __future__ import (absolute_import, print_function, division)
from .base import Layer, ServerConnectionMixin, Kill
from .http import Http1Layer, UpstreamConnectLayer, Http2Layer
-from .tls import TlsLayer, is_tls_record_magic
+from .tls import TlsLayer, is_tls_record_magic, TlsClientHello
from .rawtcp import RawTCPLayer
__all__ = [
"Layer", "ServerConnectionMixin", "Kill",
"Http1Layer", "UpstreamConnectLayer", "Http2Layer",
- "TlsLayer", "is_tls_record_magic",
+ "TlsLayer", "is_tls_record_magic", "TlsClientHello"
"RawTCPLayer"
]
diff --git a/libmproxy/protocol/base.py b/libmproxy/protocol/base.py
index af6b1c3b..d984cadb 100644
--- a/libmproxy/protocol/base.py
+++ b/libmproxy/protocol/base.py
@@ -111,7 +111,7 @@ class ServerConnectionMixin(object):
def __init__(self, server_address=None):
super(ServerConnectionMixin, self).__init__()
- self.server_conn = ServerConnection(server_address)
+ self.server_conn = ServerConnection(server_address, (self.config.host, 0))
self.__check_self_connect()
def __check_self_connect(self):
@@ -157,10 +157,11 @@ class ServerConnectionMixin(object):
"""
self.log("serverdisconnect", "debug", [repr(self.server_conn.address)])
address = self.server_conn.address
+ source_address = self.server_conn.source_address
self.server_conn.finish()
self.server_conn.close()
self.channel.tell("serverdisconnect", self.server_conn)
- self.server_conn = ServerConnection(address)
+ self.server_conn = ServerConnection(address, source_address)
def connect(self):
"""
diff --git a/libmproxy/protocol/http_replay.py b/libmproxy/protocol/http_replay.py
index b7faad07..63870dfb 100644
--- a/libmproxy/protocol/http_replay.py
+++ b/libmproxy/protocol/http_replay.py
@@ -46,7 +46,7 @@ class RequestReplayThread(threading.Thread):
# In all modes, we directly connect to the server displayed
if self.config.mode == "upstream":
server_address = self.config.upstream_server.address
- server = ServerConnection(server_address)
+ server = ServerConnection(server_address, (self.config.host, 0))
server.connect()
if r.scheme == "https":
connect_request = make_connect_request((r.host, r.port))
@@ -68,7 +68,7 @@ class RequestReplayThread(threading.Thread):
r.form_out = "absolute"
else:
server_address = (r.host, r.port)
- server = ServerConnection(server_address)
+ server = ServerConnection(server_address, (self.config.host, 0))
server.connect()
if r.scheme == "https":
server.establish_ssl(
diff --git a/libmproxy/protocol/rawtcp.py b/libmproxy/protocol/rawtcp.py
index 5f08fd17..ccd3c7ec 100644
--- a/libmproxy/protocol/rawtcp.py
+++ b/libmproxy/protocol/rawtcp.py
@@ -13,6 +13,15 @@ from ..exceptions import ProtocolException
from .base import Layer
+class TcpMessage(object):
+ def __init__(self, client_conn, server_conn, sender, receiver, message):
+ self.client_conn = client_conn
+ self.server_conn = server_conn
+ self.sender = sender
+ self.receiver = receiver
+ self.message = message
+
+
class RawTCPLayer(Layer):
chunk_size = 4096
@@ -50,7 +59,13 @@ class RawTCPLayer(Layer):
return
continue
- dst.sendall(buf[:size])
+ tcp_message = TcpMessage(
+ self.client_conn, self.server_conn,
+ self.client_conn if dst == server else self.server_conn,
+ self.server_conn if dst == server else self.client_conn,
+ buf[:size].tobytes())
+ self.channel.ask("tcp_message", tcp_message)
+ dst.sendall(tcp_message.message)
if self.logging:
# log messages are prepended with the client address,
@@ -59,7 +74,7 @@ class RawTCPLayer(Layer):
direction = "-> tcp -> {}".format(repr(self.server_conn.address))
else:
direction = "<- tcp <- {}".format(repr(self.server_conn.address))
- data = clean_bin(buf[:size].tobytes())
+ data = clean_bin(tcp_message.message)
self.log(
"{}\r\n{}".format(direction, data),
"info"
diff --git a/libmproxy/protocol/tls.py b/libmproxy/protocol/tls.py
index ed747643..6d4cac85 100644
--- a/libmproxy/protocol/tls.py
+++ b/libmproxy/protocol/tls.py
@@ -221,6 +221,80 @@ def is_tls_record_magic(d):
d[2] in ('\x00', '\x01', '\x02', '\x03')
)
+def get_client_hello(client_conn):
+ """
+ Peek into the socket and read all records that contain the initial client hello message.
+
+ client_conn:
+ The :py:class:`client connection <libmproxy.models.ClientConnection>`.
+
+ Returns:
+ The raw handshake packet bytes, without TLS record header(s).
+ """
+ client_hello = ""
+ client_hello_size = 1
+ offset = 0
+ while len(client_hello) < client_hello_size:
+ record_header = client_conn.rfile.peek(offset + 5)[offset:]
+ if not is_tls_record_magic(record_header) or len(record_header) != 5:
+ raise TlsProtocolException('Expected TLS record, got "%s" instead.' % record_header)
+ record_size = struct.unpack("!H", record_header[3:])[0] + 5
+ record_body = client_conn.rfile.peek(offset + record_size)[offset + 5:]
+ if len(record_body) != record_size - 5:
+ raise TlsProtocolException("Unexpected EOF in TLS handshake: %s" % record_body)
+ client_hello += record_body
+ offset += record_size
+ client_hello_size = struct.unpack("!I", '\x00' + client_hello[1:4])[0] + 4
+ return client_hello
+
+class TlsClientHello(object):
+ def __init__(self, raw_client_hello):
+ self._client_hello = ClientHello.parse(raw_client_hello)
+
+ def raw(self):
+ return self._client_hello
+
+ @property
+ def client_cipher_suites(self):
+ return self._client_hello.cipher_suites.cipher_suites
+
+ @property
+ def client_sni(self):
+ for extension in self._client_hello.extensions:
+ if (extension.type == 0x00 and len(extension.server_names) == 1
+ and extension.server_names[0].type == 0):
+ return extension.server_names[0].name
+
+ @property
+ def client_alpn_protocols(self):
+ for extension in self._client_hello.extensions:
+ if extension.type == 0x10:
+ return list(extension.alpn_protocols)
+
+ @classmethod
+ def from_client_conn(cls, client_conn):
+ """
+ Peek into the connection, read the initial client hello and parse it to obtain ALPN values.
+ client_conn:
+ The :py:class:`client connection <libmproxy.models.ClientConnection>`.
+ Returns:
+ :py:class:`client hello <libmproxy.protocol.tls.TlsClientHello>`.
+ """
+ try:
+ raw_client_hello = get_client_hello(client_conn)[4:] # exclude handshake header.
+ except ProtocolException as e:
+ raise TlsProtocolException('Cannot read raw Client Hello: %s' % repr(e))
+
+ try:
+ return cls(raw_client_hello)
+ except ConstructError as e:
+ raise TlsProtocolException('Cannot parse Client Hello: %s, Raw Client Hello: %s' % \
+ (repr(e), raw_client_hello.encode("hex")))
+
+ def __repr__(self):
+ return "TlsClientHello( sni: %s alpn_protocols: %s, cipher_suites: %s)" % \
+ (self.client_sni, self.client_alpn_protocols, self.client_cipher_suites)
+
class TlsLayer(Layer):
def __init__(self, ctx, client_tls, server_tls):
@@ -281,60 +355,18 @@ class TlsLayer(Layer):
else:
return "TlsLayer(inactive)"
- def _get_client_hello(self):
- """
- Peek into the socket and read all records that contain the initial client hello message.
-
- Returns:
- The raw handshake packet bytes, without TLS record header(s).
- """
- client_hello = ""
- client_hello_size = 1
- offset = 0
- while len(client_hello) < client_hello_size:
- record_header = self.client_conn.rfile.peek(offset + 5)[offset:]
- if not is_tls_record_magic(record_header) or len(record_header) != 5:
- raise TlsProtocolException('Expected TLS record, got "%s" instead.' % record_header)
- record_size = struct.unpack("!H", record_header[3:])[0] + 5
- record_body = self.client_conn.rfile.peek(offset + record_size)[offset + 5:]
- if len(record_body) != record_size - 5:
- raise TlsProtocolException("Unexpected EOF in TLS handshake: %s" % record_body)
- client_hello += record_body
- offset += record_size
- client_hello_size = struct.unpack("!I", '\x00' + client_hello[1:4])[0] + 4
- return client_hello
def _parse_client_hello(self):
"""
Peek into the connection, read the initial client hello and parse it to obtain ALPN values.
"""
try:
- raw_client_hello = self._get_client_hello()[4:] # exclude handshake header.
- except ProtocolException as e:
- self.log("Cannot parse Client Hello: %s" % repr(e), "error")
- return
-
- try:
- client_hello = ClientHello.parse(raw_client_hello)
- except ConstructError as e:
+ parsed = TlsClientHello.from_client_conn(self.client_conn)
+ self.client_sni = parsed.client_sni
+ self.client_alpn_protocols = parsed.client_alpn_protocols
+ self.client_ciphers = parsed.client_cipher_suites
+ except TlsProtocolException as e:
self.log("Cannot parse Client Hello: %s" % repr(e), "error")
- self.log("Raw Client Hello: %s" % raw_client_hello.encode("hex"), "debug")
- return
-
- self.client_ciphers = client_hello.cipher_suites.cipher_suites
-
- for extension in client_hello.extensions:
- if extension.type == 0x00:
- if len(extension.server_names) != 1 or extension.server_names[0].type != 0:
- self.log("Unknown Server Name Indication: %s" % extension.server_names, "error")
- self.client_sni = extension.server_names[0].name
- elif extension.type == 0x10:
- self.client_alpn_protocols = list(extension.alpn_protocols)
-
- self.log(
- "Parsed Client Hello: sni=%s, alpn=%s" % (self.client_sni, self.client_alpn_protocols),
- "debug"
- )
def connect(self):
if not self.server_conn:
diff --git a/libmproxy/proxy/modes/socks_proxy.py b/libmproxy/proxy/modes/socks_proxy.py
index 264c734a..90788e37 100644
--- a/libmproxy/proxy/modes/socks_proxy.py
+++ b/libmproxy/proxy/modes/socks_proxy.py
@@ -8,6 +8,9 @@ from ...protocol import Layer, ServerConnectionMixin
class Socks5Proxy(Layer, ServerConnectionMixin):
+ def __init__(self, ctx):
+ super(Socks5Proxy, self).__init__(ctx)
+
def __call__(self):
try:
# Parse Client Greeting
diff --git a/libmproxy/proxy/root_context.py b/libmproxy/proxy/root_context.py
index f62b0c8e..d70fc299 100644
--- a/libmproxy/proxy/root_context.py
+++ b/libmproxy/proxy/root_context.py
@@ -4,15 +4,14 @@ import sys
import six
-from libmproxy.exceptions import ProtocolException
+from libmproxy.exceptions import ProtocolException, TlsProtocolException
from netlib.exceptions import TcpException
from ..protocol import (
RawTCPLayer, TlsLayer, Http1Layer, Http2Layer, is_tls_record_magic, ServerConnectionMixin,
- UpstreamConnectLayer
+ UpstreamConnectLayer, TlsClientHello
)
from .modes import HttpProxy, HttpUpstreamProxy, ReverseProxy
-
class RootContext(object):
"""
The outermost context provided to the root layer.
@@ -48,16 +47,25 @@ class RootContext(object):
return self.channel.ask("next_layer", layer)
def _next_layer(self, top_layer):
- # 1. Check for --ignore.
- if self.config.check_ignore(top_layer.server_conn.address):
- return RawTCPLayer(top_layer, logging=False)
-
try:
d = top_layer.client_conn.rfile.peek(3)
except TcpException as e:
six.reraise(ProtocolException, ProtocolException(str(e)), sys.exc_info()[2])
client_tls = is_tls_record_magic(d)
+ # 1. check for --ignore
+ if self.config.check_ignore:
+ ignore = self.config.check_ignore(top_layer.server_conn.address)
+ if not ignore and client_tls:
+ try:
+ client_hello = TlsClientHello.from_client_conn(self.client_conn)
+ except TlsProtocolException as e:
+ self.log("Cannot parse Client Hello: %s" % repr(e), "error")
+ else:
+ ignore = self.config.check_ignore((client_hello.client_sni, 443))
+ if ignore:
+ return RawTCPLayer(top_layer, logging=False)
+
# 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.
diff --git a/libmproxy/script/reloader.py b/libmproxy/script/reloader.py
index 26691fa3..e81bdef6 100644
--- a/libmproxy/script/reloader.py
+++ b/libmproxy/script/reloader.py
@@ -1,6 +1,12 @@
import os
-from watchdog.events import PatternMatchingEventHandler
-from watchdog.observers import Observer
+import sys
+from watchdog.events import RegexMatchingEventHandler
+if sys.platform == 'darwin':
+ from watchdog.observers.polling import PollingObserver as Observer
+else:
+ from watchdog.observers import Observer
+# The OSX reloader in watchdog 0.8.3 breaks when unobserving paths.
+# We use the PollingObserver instead.
_observers = {}
@@ -9,7 +15,8 @@ def watch(script, callback):
if script in _observers:
raise RuntimeError("Script already observed")
script_dir = os.path.dirname(os.path.abspath(script.args[0]))
- event_handler = _ScriptModificationHandler(callback)
+ script_name = os.path.basename(script.args[0])
+ event_handler = _ScriptModificationHandler(callback, filename=script_name)
observer = Observer()
observer.schedule(event_handler, script_dir)
observer.start()
@@ -23,18 +30,17 @@ def unwatch(script):
observer.join()
-class _ScriptModificationHandler(PatternMatchingEventHandler):
- def __init__(self, callback):
- # We could enumerate all relevant *.py files (as werkzeug does it),
- # but our case looks like it isn't as simple as enumerating sys.modules.
- # This should be good enough for now.
+class _ScriptModificationHandler(RegexMatchingEventHandler):
+ def __init__(self, callback, filename='.*'):
+
super(_ScriptModificationHandler, self).__init__(
ignore_directories=True,
- patterns=["*.py"]
+ regexes=['.*'+filename]
)
self.callback = callback
def on_modified(self, event):
self.callback()
-__all__ = ["watch", "unwatch"] \ No newline at end of file
+__all__ = ["watch", "unwatch"]
+