aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--issue_template.md8
-rw-r--r--mitmproxy/addons/__init__.py2
-rw-r--r--mitmproxy/addons/allowremote.py29
-rw-r--r--mitmproxy/addons/core.py4
-rw-r--r--mitmproxy/addons/script.py17
-rw-r--r--mitmproxy/eventsequence.py97
-rw-r--r--mitmproxy/options.py23
-rw-r--r--mitmproxy/tools/cmdline.py8
-rw-r--r--mitmproxy/utils/human.py2
-rw-r--r--setup.py2
-rw-r--r--test/mitmproxy/addons/test_allowremote.py38
-rw-r--r--test/mitmproxy/addons/test_script.py24
-rw-r--r--test/mitmproxy/data/addonscripts/same_filename/addon.py1
13 files changed, 181 insertions, 74 deletions
diff --git a/issue_template.md b/issue_template.md
index 5c5f95ed..0ac2afe9 100644
--- a/issue_template.md
+++ b/issue_template.md
@@ -11,10 +11,4 @@
##### System information
-
-<!--
- Cut and paste the output of "mitmproxy --version".
-
- If you're using an older version if mitmproxy, please specify the version
- and OS.
--->
+<!-- Paste the output of "mitmproxy --version" here. -->
diff --git a/mitmproxy/addons/__init__.py b/mitmproxy/addons/__init__.py
index 783a2c94..24cf2270 100644
--- a/mitmproxy/addons/__init__.py
+++ b/mitmproxy/addons/__init__.py
@@ -1,3 +1,4 @@
+from mitmproxy.addons import allowremote
from mitmproxy.addons import anticache
from mitmproxy.addons import anticomp
from mitmproxy.addons import check_alpn
@@ -25,6 +26,7 @@ def default_addons():
return [
core.Core(),
core_option_validation.CoreOptionValidation(),
+ allowremote.AllowRemote(),
anticache.AntiCache(),
anticomp.AntiComp(),
check_alpn.CheckALPN(),
diff --git a/mitmproxy/addons/allowremote.py b/mitmproxy/addons/allowremote.py
new file mode 100644
index 00000000..f1d3d8fb
--- /dev/null
+++ b/mitmproxy/addons/allowremote.py
@@ -0,0 +1,29 @@
+import ipaddress
+from mitmproxy import ctx
+
+
+class AllowRemote:
+ def load(self, loader):
+ loader.add_option(
+ "allow_remote", bool, False,
+ """
+ Allow remote clients to connect to proxy. If set to false,
+ client will not be able to connect to proxy unless it is on the same network
+ or the proxyauth option is set
+ """
+ )
+
+ def clientconnect(self, layer):
+ address = layer.client_conn.address
+
+ accept_connection = (
+ ctx.options.allow_remote or
+ ipaddress.ip_address(address[0]).is_private or
+ ctx.options.proxyauth is not None
+ )
+
+ if not accept_connection:
+ layer.reply.kill()
+ ctx.log.warn("Client connection was killed because allow_remote option is set to false, "
+ "client IP was not a private IP and proxyauth was not set.\n"
+ "To allow remote connections set allow_remote option to true or set proxyauth option.")
diff --git a/mitmproxy/addons/core.py b/mitmproxy/addons/core.py
index b0d8ee27..33d67279 100644
--- a/mitmproxy/addons/core.py
+++ b/mitmproxy/addons/core.py
@@ -112,7 +112,7 @@ class Core:
val = sval # type: typing.Union[int, str]
if spec == "status_code":
try:
- val = int(val)
+ val = int(val) # type: ignore
except ValueError as v:
raise exceptions.CommandError(
"Status code is not an integer: %s" % val
@@ -145,7 +145,7 @@ class Core:
if spec == "status_code":
resp.status_code = val
if val in status_codes.RESPONSES:
- resp.reason = status_codes.RESPONSES[int(val)]
+ resp.reason = status_codes.RESPONSES[val] # type: ignore
elif spec == "reason":
resp.reason = val
else:
diff --git a/mitmproxy/addons/script.py b/mitmproxy/addons/script.py
index b4274f8c..3e60fe67 100644
--- a/mitmproxy/addons/script.py
+++ b/mitmproxy/addons/script.py
@@ -1,5 +1,6 @@
import os
-import importlib
+import importlib.util
+import importlib.machinery
import time
import sys
import typing
@@ -16,12 +17,21 @@ def load_script(actx, path):
if not os.path.exists(path):
ctx.log.info("No such file: %s" % path)
return
- loader = importlib.machinery.SourceFileLoader(os.path.basename(path), path)
+
+ fullname = "__mitmproxy_script__.{}".format(
+ os.path.splitext(os.path.basename(path))[0]
+ )
+ # the fullname is not unique among scripts, so if there already is an existing script with said
+ # fullname, remove it.
+ sys.modules.pop(fullname, None)
try:
oldpath = sys.path
sys.path.insert(0, os.path.dirname(path))
with addonmanager.safecall():
- m = loader.load_module()
+ loader = importlib.machinery.SourceFileLoader(fullname, path)
+ spec = importlib.util.spec_from_loader(fullname, loader=loader)
+ m = importlib.util.module_from_spec(spec)
+ loader.exec_module(m)
if not getattr(m, "name", None):
m.name = path
return m
@@ -64,7 +74,6 @@ class Script:
ctx.log.info("Loading script: %s" % self.path)
if self.ns:
ctx.master.addons.remove(self.ns)
- del sys.modules[self.ns.__name__]
self.ns = load_script(ctx, self.fullpath)
if self.ns:
# We're already running, so we have to explicitly register and
diff --git a/mitmproxy/eventsequence.py b/mitmproxy/eventsequence.py
index 4e199972..d263727b 100644
--- a/mitmproxy/eventsequence.py
+++ b/mitmproxy/eventsequence.py
@@ -1,4 +1,7 @@
+import typing
+
from mitmproxy import controller
+from mitmproxy import flow
from mitmproxy import http
from mitmproxy import tcp
from mitmproxy import websocket
@@ -8,27 +11,26 @@ Events = frozenset([
"clientdisconnect",
"serverconnect",
"serverdisconnect",
-
+ # TCP
"tcp_start",
"tcp_message",
"tcp_error",
"tcp_end",
-
+ # HTTP
"http_connect",
"request",
"requestheaders",
"response",
"responseheaders",
"error",
-
+ # WebSocket
"websocket_handshake",
"websocket_start",
"websocket_message",
"websocket_error",
"websocket_end",
-
+ # misc
"next_layer",
-
"configure",
"done",
"log",
@@ -38,38 +40,57 @@ Events = frozenset([
"update",
])
+TEventGenerator = typing.Iterator[typing.Tuple[str, typing.Any]]
+
+
+def _iterate_http(f: http.HTTPFlow) -> TEventGenerator:
+ if f.request:
+ yield "requestheaders", f
+ yield "request", f
+ if f.response:
+ yield "responseheaders", f
+ yield "response", f
+ if f.error:
+ yield "error", f
+
+
+def _iterate_websocket(f: websocket.WebSocketFlow) -> TEventGenerator:
+ messages = f.messages
+ f.messages = []
+ f.reply = controller.DummyReply()
+ yield "websocket_start", f
+ while messages:
+ f.messages.append(messages.pop(0))
+ yield "websocket_message", f
+ if f.error:
+ yield "websocket_error", f
+ yield "websocket_end", f
+
+
+def _iterate_tcp(f: tcp.TCPFlow) -> TEventGenerator:
+ messages = f.messages
+ f.messages = []
+ f.reply = controller.DummyReply()
+ yield "tcp_start", f
+ while messages:
+ f.messages.append(messages.pop(0))
+ yield "tcp_message", f
+ if f.error:
+ yield "tcp_error", f
+ yield "tcp_end", f
+
+
+_iterate_map = {
+ http.HTTPFlow: _iterate_http,
+ websocket.WebSocketFlow: _iterate_websocket,
+ tcp.TCPFlow: _iterate_tcp,
+} # type: typing.Dict[typing.Type[flow.Flow], typing.Callable[[typing.Any], TEventGenerator]]
+
-def iterate(f):
- if isinstance(f, http.HTTPFlow):
- if f.request:
- yield "requestheaders", f
- yield "request", f
- if f.response:
- yield "responseheaders", f
- yield "response", f
- if f.error:
- yield "error", f
- elif isinstance(f, websocket.WebSocketFlow):
- messages = f.messages
- f.messages = []
- f.reply = controller.DummyReply()
- yield "websocket_start", f
- while messages:
- f.messages.append(messages.pop(0))
- yield "websocket_message", f
- if f.error:
- yield "websocket_error", f
- yield "websocket_end", f
- elif isinstance(f, tcp.TCPFlow):
- messages = f.messages
- f.messages = []
- f.reply = controller.DummyReply()
- yield "tcp_start", f
- while messages:
- f.messages.append(messages.pop(0))
- yield "tcp_message", f
- if f.error:
- yield "tcp_error", f
- yield "tcp_end", f
+def iterate(f: flow.Flow) -> TEventGenerator:
+ try:
+ e = _iterate_map[type(f)]
+ except KeyError as err:
+ raise TypeError("Unknown flow type: {}".format(f)) from err
else:
- raise TypeError()
+ yield from e(f)
diff --git a/mitmproxy/options.py b/mitmproxy/options.py
index e6c2fed6..954db7e8 100644
--- a/mitmproxy/options.py
+++ b/mitmproxy/options.py
@@ -209,15 +209,11 @@ class Options(optmanager.OptManager):
self.add_option(
"proxyauth", Optional[str], None,
"""
- Require proxy authentication. Value may be "any" to require
- authenticaiton but accept any credentials, start with "@" to specify
- a path to an Apache htpasswd file, be of the form
- "username:password", or be of the form
- "ldap[s]:url_server_ldap:dn_auth:password:dn_subtree",
- the dn_auth & password is the dn/pass used to authenticate
- the dn subtree is the subtree that we will search to find the username
- an example would be
- "ldap:localhost:cn=default,dc=example,dc=com:password:ou=application,dc=example,dc=com".
+ Require proxy authentication. Format:
+ "username:pass",
+ "any" to accept any user/pass combination,
+ "@path" to use an Apache htpasswd file,
+ or "ldap[s]:url_server_ldap:dn_auth:password:dn_subtree" for LDAP authentication.
"""
)
self.add_option(
@@ -288,7 +284,7 @@ class Options(optmanager.OptManager):
"""
Mode can be "regular", "transparent", "socks5", "reverse:SPEC",
or "upstream:SPEC". For reverse and upstream proxy modes, SPEC
- is proxy specification in the form of "http[s]://host[:port]".
+ is host specification in the form of "http[s]://host[:port]".
"""
)
self.add_option(
@@ -311,9 +307,8 @@ class Options(optmanager.OptManager):
self.add_option(
"http2_priority", bool, False,
"""
- PRIORITY forwarding for HTTP/2 connections. PRIORITY forwarding is
- disabled by default, because some webservers fail to implement the
- RFC properly.
+ PRIORITY forwarding for HTTP/2 connections. Disabled by default to ensure compatibility
+ with misbehaving servers.
"""
)
self.add_option(
@@ -337,7 +332,7 @@ class Options(optmanager.OptManager):
self.add_option(
"upstream_auth", Optional[str], None,
"""
- Add HTTP Basic authentcation to upstream proxy and reverse proxy
+ Add HTTP Basic authentication to upstream proxy and reverse proxy
requests. Format: username:password.
"""
)
diff --git a/mitmproxy/tools/cmdline.py b/mitmproxy/tools/cmdline.py
index 97b04e74..68ddc2c8 100644
--- a/mitmproxy/tools/cmdline.py
+++ b/mitmproxy/tools/cmdline.py
@@ -2,7 +2,6 @@ import argparse
import os
from mitmproxy import options
-from mitmproxy import version
CONFIG_PATH = os.path.join(options.CA_DIR, "config.yaml")
@@ -12,15 +11,10 @@ def common_options(parser, opts):
parser.add_argument(
'--version',
action='store_true',
+ help="show version number and exit",
dest='version',
)
parser.add_argument(
- '--shortversion',
- action='version',
- help="show program's short version number and exit",
- version=version.VERSION
- )
- parser.add_argument(
'--options',
action='store_true',
help="Show all options and their default values",
diff --git a/mitmproxy/utils/human.py b/mitmproxy/utils/human.py
index b3934846..d67fb310 100644
--- a/mitmproxy/utils/human.py
+++ b/mitmproxy/utils/human.py
@@ -71,7 +71,7 @@ def format_address(address: tuple) -> str:
"""
try:
host = ipaddress.ip_address(address[0])
- if host.version == 4:
+ if isinstance(host, ipaddress.IPv4Address):
return "{}:{}".format(str(host), address[1])
# If IPv6 is mapped to IPv4
elif host.ipv4_mapped:
diff --git a/setup.py b/setup.py
index 212ad95e..441a1d8f 100644
--- a/setup.py
+++ b/setup.py
@@ -90,7 +90,7 @@ setup(
'dev': [
"flake8>=3.2.1, <3.4",
"Flask>=0.10.1, <0.13",
- "mypy>=0.501, <0.512",
+ "mypy>=0.501, <0.521",
"pytest-cov>=2.2.1, <3",
"pytest-faulthandler>=1.3.0, <2",
"pytest-timeout>=1.0.0, <2",
diff --git a/test/mitmproxy/addons/test_allowremote.py b/test/mitmproxy/addons/test_allowremote.py
new file mode 100644
index 00000000..9fc71525
--- /dev/null
+++ b/test/mitmproxy/addons/test_allowremote.py
@@ -0,0 +1,38 @@
+from unittest import mock
+import pytest
+
+from mitmproxy.addons import allowremote
+from mitmproxy.test import taddons
+
+
+@pytest.mark.parametrize("allow_remote, ip, should_be_killed", [
+ (True, "192.168.1.3", False),
+ (True, "122.176.243.101", False),
+ (False, "192.168.1.3", False),
+ (False, "122.176.243.101", True),
+ (True, "::ffff:1:2", False),
+ (True, "fe80::", False),
+ (True, "2001:4860:4860::8888", False),
+ (False, "::ffff:1:2", False),
+ (False, "fe80::", False),
+ (False, "2001:4860:4860::8888", True),
+])
+def test_allowremote(allow_remote, ip, should_be_killed):
+ ar = allowremote.AllowRemote()
+ with taddons.context() as tctx:
+ tctx.master.addons.register(ar)
+ tctx.options.allow_remote = allow_remote
+
+ with mock.patch('mitmproxy.proxy.protocol.base.Layer') as layer:
+ layer.client_conn.address = (ip, 12345)
+
+ ar.clientconnect(layer)
+ if should_be_killed:
+ assert tctx.master.has_log("Client connection was killed", "warn")
+ else:
+ assert tctx.master.logs == []
+ tctx.master.clear()
+
+ tctx.options.proxyauth = "any"
+ ar.clientconnect(layer)
+ assert tctx.master.logs == []
diff --git a/test/mitmproxy/addons/test_script.py b/test/mitmproxy/addons/test_script.py
index 03b1f620..b7e6c82a 100644
--- a/test/mitmproxy/addons/test_script.py
+++ b/test/mitmproxy/addons/test_script.py
@@ -30,6 +30,30 @@ def test_load_script():
assert not ns
+def test_load_fullname():
+ """
+ Test that loading two scripts at locations a/foo.py and b/foo.py works.
+ This only succeeds if they get assigned different basenames.
+
+ """
+ with taddons.context() as tctx:
+ ns = script.load_script(
+ tctx.ctx(),
+ tutils.test_data.path(
+ "mitmproxy/data/addonscripts/addon.py"
+ )
+ )
+ assert ns.addons
+ ns2 = script.load_script(
+ tctx.ctx(),
+ tutils.test_data.path(
+ "mitmproxy/data/addonscripts/same_filename/addon.py"
+ )
+ )
+ assert ns.name != ns2.name
+ assert not hasattr(ns2, "addons")
+
+
def test_script_print_stdout():
with taddons.context() as tctx:
with mock.patch('mitmproxy.ctx.log.warn') as mock_warn:
diff --git a/test/mitmproxy/data/addonscripts/same_filename/addon.py b/test/mitmproxy/data/addonscripts/same_filename/addon.py
new file mode 100644
index 00000000..c84a9b13
--- /dev/null
+++ b/test/mitmproxy/data/addonscripts/same_filename/addon.py
@@ -0,0 +1 @@
+foo = 42