mitmproxy/examples/contrib/mitmproxywrapper.py
Sean Flanigan 5df439b7e8
Add enhancements for contrib/mitmproxywrapper.py (#5883)
* Add options for mitmweb and random port

* Clean up proxy settings on ^C

* [autofix.ci] apply automated fixes

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2023-01-18 09:02:04 +01:00

186 lines
6.6 KiB
Python

#!/usr/bin/env python
#
# Helper tool to enable/disable OS X proxy and wrap mitmproxy
#
# Get usage information with:
#
# mitmproxywrapper.py -h
#
import argparse
import contextlib
import os
import re
import signal
import socketserver
import subprocess
import sys
class Wrapper:
def __init__(self, port, use_mitmweb, extra_arguments=None):
self.port = port
self.use_mitmweb = use_mitmweb
self.extra_arguments = extra_arguments
def run_networksetup_command(self, *arguments):
return subprocess.check_output(
["sudo", "networksetup"] + list(arguments)
).decode()
def proxy_state_for_service(self, service):
state = self.run_networksetup_command("-getwebproxy", service).splitlines()
return dict([re.findall(r"([^:]+): (.*)", line)[0] for line in state])
def enable_proxy_for_service(self, service):
print(f"Enabling proxy on {service}...")
for subcommand in ["-setwebproxy", "-setsecurewebproxy"]:
self.run_networksetup_command(
subcommand, service, "127.0.0.1", str(self.port)
)
def disable_proxy_for_service(self, service):
print(f"Disabling proxy on {service}...")
for subcommand in ["-setwebproxystate", "-setsecurewebproxystate"]:
self.run_networksetup_command(subcommand, service, "Off")
def interface_name_to_service_name_map(self):
order = self.run_networksetup_command("-listnetworkserviceorder")
mapping = re.findall(
r"\(\d+\)\s(.*)$\n\(.*Device: (.+)\)$", order, re.MULTILINE
)
return {b: a for (a, b) in mapping}
def run_command_with_input(self, command, input):
popen = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
(stdout, stderr) = popen.communicate(input.encode())
return stdout.decode()
def primary_interace_name(self):
scutil_script = "get State:/Network/Global/IPv4\nd.show\n"
stdout = self.run_command_with_input("/usr/sbin/scutil", scutil_script)
(interface,) = re.findall(r"PrimaryInterface\s*:\s*(.+)", stdout)
return interface
def primary_service_name(self):
return self.interface_name_to_service_name_map()[self.primary_interace_name()]
def proxy_enabled_for_service(self, service):
return self.proxy_state_for_service(service)["Enabled"] == "Yes"
def toggle_proxy(self):
new_state = not self.proxy_enabled_for_service(self.primary_service_name())
for service_name in self.connected_service_names():
if self.proxy_enabled_for_service(service_name) and not new_state:
self.disable_proxy_for_service(service_name)
elif not self.proxy_enabled_for_service(service_name) and new_state:
self.enable_proxy_for_service(service_name)
def connected_service_names(self):
scutil_script = "list\n"
stdout = self.run_command_with_input("/usr/sbin/scutil", scutil_script)
service_ids = re.findall(r"State:/Network/Service/(.+)/IPv4", stdout)
service_names = []
for service_id in service_ids:
scutil_script = f"show Setup:/Network/Service/{service_id}\n"
stdout = self.run_command_with_input("/usr/sbin/scutil", scutil_script)
(service_name,) = re.findall(r"UserDefinedName\s*:\s*(.+)", stdout)
service_names.append(service_name)
return service_names
def wrap_mitmproxy(self):
with self.wrap_proxy():
cmd = ["mitmweb" if self.use_mitmweb else "mitmproxy", "-p", str(self.port)]
if self.extra_arguments:
cmd.extend(self.extra_arguments)
subprocess.check_call(cmd)
def wrap_honeyproxy(self):
with self.wrap_proxy():
popen = subprocess.Popen("honeyproxy.sh")
try:
popen.wait()
except KeyboardInterrupt:
popen.terminate()
@contextlib.contextmanager
def wrap_proxy(self):
connected_service_names = self.connected_service_names()
for service_name in connected_service_names:
if not self.proxy_enabled_for_service(service_name):
self.enable_proxy_for_service(service_name)
yield
for service_name in connected_service_names:
if self.proxy_enabled_for_service(service_name):
self.disable_proxy_for_service(service_name)
@classmethod
def ensure_superuser(cls):
if os.getuid() != 0:
print("Relaunching with sudo...")
os.execv("/usr/bin/sudo", ["/usr/bin/sudo"] + sys.argv)
@classmethod
def main(cls):
parser = argparse.ArgumentParser(
description="Helper tool for OS X proxy configuration and mitmproxy.",
epilog="Any additional arguments will be passed on unchanged to mitmproxy/mitmweb.",
)
parser.add_argument(
"-t",
"--toggle",
action="store_true",
help="just toggle the proxy configuration",
)
# parser.add_argument('--honeyproxy', action='store_true', help='run honeyproxy instead of mitmproxy')
parser.add_argument(
"-p",
"--port",
type=int,
help="override the default port of 8080",
default=8080,
)
parser.add_argument(
"-P",
"--port-random",
action="store_true",
help="choose a random unused port",
)
parser.add_argument(
"-w",
"--web",
action="store_true",
help="web interface: run mitmweb instead of mitmproxy",
)
args, extra_arguments = parser.parse_known_args()
port = args.port
# Allocate a random unused port, and hope no other process steals it before mitmproxy/mitmweb uses it.
# Passing the allocated socket to mitmproxy/mitmweb would be nicer of course.
if args.port_random:
with socketserver.TCPServer(("localhost", 0), None) as s:
port = s.server_address[1]
print(f"Using random port {port}...")
wrapper = cls(port=port, use_mitmweb=args.web, extra_arguments=extra_arguments)
def handler(signum, frame):
print("Cleaning up proxy settings...")
wrapper.toggle_proxy()
signal.signal(signal.SIGINT, handler)
if args.toggle:
wrapper.toggle_proxy()
# elif args.honeyproxy:
# wrapper.wrap_honeyproxy()
else:
wrapper.wrap_mitmproxy()
if __name__ == "__main__":
Wrapper.ensure_superuser()
Wrapper.main()