aboutsummaryrefslogtreecommitdiffstats
path: root/libmproxy/proxy
diff options
context:
space:
mode:
Diffstat (limited to 'libmproxy/proxy')
-rw-r--r--libmproxy/proxy/__init__.py2
-rw-r--r--libmproxy/proxy/config.py67
-rw-r--r--libmproxy/proxy/connection.py31
-rw-r--r--libmproxy/proxy/primitives.py81
-rw-r--r--libmproxy/proxy/server.py47
5 files changed, 164 insertions, 64 deletions
diff --git a/libmproxy/proxy/__init__.py b/libmproxy/proxy/__init__.py
index e4c20030..f33d323b 100644
--- a/libmproxy/proxy/__init__.py
+++ b/libmproxy/proxy/__init__.py
@@ -1,2 +1,2 @@
from .primitives import *
-from .config import ProxyConfig
+from .config import ProxyConfig, process_proxy_options
diff --git a/libmproxy/proxy/config.py b/libmproxy/proxy/config.py
index 441e05f1..fe2b45f4 100644
--- a/libmproxy/proxy/config.py
+++ b/libmproxy/proxy/config.py
@@ -1,25 +1,42 @@
from __future__ import absolute_import
import os
import re
-from netlib import http_auth, certutils
-from .. import utils, platform
-from .primitives import RegularProxyMode, TransparentProxyMode, UpstreamProxyMode, ReverseProxyMode
+from netlib import http_auth, certutils, tcp
+from .. import utils, platform, version
+from .primitives import RegularProxyMode, TransparentProxyMode, UpstreamProxyMode, ReverseProxyMode, Socks5ProxyMode
TRANSPARENT_SSL_PORTS = [443, 8443]
CONF_BASENAME = "mitmproxy"
CONF_DIR = "~/.mitmproxy"
-def parse_host_pattern(patterns):
- return [re.compile(p, re.IGNORECASE) for p in patterns]
+class HostMatcher(object):
+ def __init__(self, patterns=[]):
+ self.patterns = list(patterns)
+ self.regexes = [re.compile(p, re.IGNORECASE) for p in self.patterns]
+
+ def __call__(self, address):
+ address = tcp.Address.wrap(address)
+ host = "%s:%s" % (address.host, address.port)
+ if any(rex.search(host) for rex in self.regexes):
+ return True
+ else:
+ return False
+
+ def __nonzero__(self):
+ return bool(self.patterns)
class ProxyConfig:
- def __init__(self, confdir=CONF_DIR, ca_file=None, clientcerts=None,
+ def __init__(self, host='', port=8080, server_version=version.NAMEVERSION,
+ confdir=CONF_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=[],
+ authenticator=None, ignore_hosts=[], tcp_hosts=[],
ciphers=None, certs=[], certforward=False, ssl_ports=TRANSPARENT_SSL_PORTS):
+ self.host = host
+ self.port = port
+ self.server_version = server_version
self.ciphers = ciphers
self.clientcerts = clientcerts
self.no_upstream_cert = no_upstream_cert
@@ -27,6 +44,8 @@ class ProxyConfig:
if mode == "transparent":
self.mode = TransparentProxyMode(platform.resolver(), TRANSPARENT_SSL_PORTS)
+ elif mode == "socks5":
+ self.mode = Socks5ProxyMode(TRANSPARENT_SSL_PORTS)
elif mode == "reverse":
self.mode = ReverseProxyMode(upstream_server)
elif mode == "upstream":
@@ -34,13 +53,14 @@ class ProxyConfig:
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.ignore = parse_host_pattern(ignore)
+ self.check_ignore = HostMatcher(ignore_hosts)
+ self.check_tcp = HostMatcher(tcp_hosts)
self.authenticator = authenticator
self.confdir = os.path.expanduser(confdir)
- self.ca_file = ca_file or os.path.join(self.confdir, CONF_BASENAME + "-ca.pem")
self.certstore = certutils.CertStore.from_store(self.confdir, CONF_BASENAME)
for spec, cert in certs:
self.certstore.add_cert_file(spec, cert)
@@ -58,6 +78,9 @@ def process_proxy_options(parser, options):
if not platform.resolver:
return parser.error("Transparent mode not supported on this platform.")
mode = "transparent"
+ if options.socks_proxy:
+ c += 1
+ mode = "socks5"
if options.reverse_proxy:
c += 1
mode = "reverse"
@@ -67,7 +90,7 @@ def process_proxy_options(parser, options):
mode = "upstream"
upstream_server = options.upstream_proxy
if c > 1:
- return parser.error("Transparent mode, reverse mode and upstream proxy mode "
+ return parser.error("Transparent, SOCKS5, reverse and upstream proxy mode "
"are mutually exclusive.")
if options.clientcerts:
@@ -104,7 +127,15 @@ 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,
confdir=options.confdir,
clientcerts=options.clientcerts,
no_upstream_cert=options.no_upstream_cert,
@@ -113,11 +144,13 @@ def process_proxy_options(parser, options):
upstream_server=upstream_server,
http_form_in=options.http_form_in,
http_form_out=options.http_form_out,
- ignore=options.ignore,
+ ignore_hosts=options.ignore_hosts,
+ tcp_hosts=options.tcp_hosts,
authenticator=authenticator,
ciphers=options.ciphers,
certs=certs,
certforward=options.certforward,
+ ssl_ports=ssl_ports
)
@@ -126,10 +159,12 @@ def ssl_option_group(parser):
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. Can be passed multiple times.'
+ 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(
"--client-certs", action="store",
@@ -152,7 +187,7 @@ def ssl_option_group(parser):
help="Don't connect to upstream server to look up certificate details."
)
group.add_argument(
- "--ssl-port", action="append", type=int, dest="ssl_ports", default=TRANSPARENT_SSL_PORTS,
+ "--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)
diff --git a/libmproxy/proxy/connection.py b/libmproxy/proxy/connection.py
index de8e20d8..fd034e8b 100644
--- a/libmproxy/proxy/connection.py
+++ b/libmproxy/proxy/connection.py
@@ -5,7 +5,7 @@ from netlib import tcp, certutils
from .. import stateobject, utils
-class ClientConnection(tcp.BaseHandler, stateobject.SimpleStateObject):
+class ClientConnection(tcp.BaseHandler, stateobject.StateObject):
def __init__(self, client_connection, address, server):
if client_connection: # Eventually, this object is restored from state. We don't have a connection then.
tcp.BaseHandler.__init__(self, client_connection, address, server)
@@ -36,16 +36,16 @@ class ClientConnection(tcp.BaseHandler, stateobject.SimpleStateObject):
timestamp_ssl_setup=float
)
- def _get_state(self):
- d = super(ClientConnection, self)._get_state()
+ 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)
+ 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
@@ -57,9 +57,9 @@ class ClientConnection(tcp.BaseHandler, stateobject.SimpleStateObject):
self.wfile.flush()
@classmethod
- def _from_state(cls, state):
+ def from_state(cls, state):
f = cls(None, tuple(), None)
- f._load_state(state)
+ f.load_state(state)
return f
def convert_to_ssl(self, *args, **kwargs):
@@ -71,7 +71,7 @@ class ClientConnection(tcp.BaseHandler, stateobject.SimpleStateObject):
self.timestamp_end = utils.timestamp()
-class ServerConnection(tcp.TCPClient, stateobject.SimpleStateObject):
+class ServerConnection(tcp.TCPClient, stateobject.StateObject):
def __init__(self, address):
tcp.TCPClient.__init__(self, address)
@@ -106,9 +106,10 @@ class ServerConnection(tcp.TCPClient, stateobject.SimpleStateObject):
ssl_established=bool,
sni=str
)
+ _stateobject_long_attributes = {"cert"}
- def _get_state(self):
- d = super(ServerConnection, self)._get_state()
+ 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},
@@ -118,17 +119,17 @@ class ServerConnection(tcp.TCPClient, stateobject.SimpleStateObject):
)
return d
- def _load_state(self, state):
- super(ServerConnection, self)._load_state(state)
+ 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):
+ def from_state(cls, state):
f = cls(tuple())
- f._load_state(state)
+ f.load_state(state)
return f
def copy(self):
@@ -154,4 +155,4 @@ class ServerConnection(tcp.TCPClient, stateobject.SimpleStateObject):
def finish(self):
tcp.TCPClient.finish(self)
- self.timestamp_end = utils.timestamp() \ No newline at end of file
+ self.timestamp_end = utils.timestamp()
diff --git a/libmproxy/proxy/primitives.py b/libmproxy/proxy/primitives.py
index 23d089d3..c0ae424d 100644
--- a/libmproxy/proxy/primitives.py
+++ b/libmproxy/proxy/primitives.py
@@ -1,5 +1,5 @@
from __future__ import absolute_import
-
+from netlib import socks
class ProxyError(Exception):
def __init__(self, code, message, headers=None):
@@ -15,7 +15,7 @@ class ProxyMode(object):
http_form_in = None
http_form_out = None
- def get_upstream_server(self, conn):
+ 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)
@@ -46,7 +46,7 @@ class RegularProxyMode(ProxyMode):
http_form_in = "absolute"
http_form_out = "relative"
- def get_upstream_server(self, conn):
+ def get_upstream_server(self, client_conn):
return None
@@ -58,9 +58,9 @@ class TransparentProxyMode(ProxyMode):
self.resolver = resolver
self.sslports = sslports
- def get_upstream_server(self, conn):
+ def get_upstream_server(self, client_conn):
try:
- dst = self.resolver.original_addr(conn)
+ dst = self.resolver.original_addr(client_conn.connection)
except Exception, e:
raise ProxyError(502, "Transparent mode failure: %s" % str(e))
@@ -71,11 +71,80 @@ class TransparentProxyMode(ProxyMode):
return [ssl, ssl] + list(dst)
+class Socks5ProxyMode(ProxyMode):
+ http_form_in = "relative"
+ http_form_out = "relative"
+
+ def __init__(self, sslports):
+ self.sslports = sslports
+
+ @staticmethod
+ def _assert_socks5(msg):
+ if msg.ver != socks.VERSION.SOCKS5:
+ if msg.ver == ord("G") and len(msg.methods) == ord("E"):
+ guess = "Probably not a SOCKS request but a regular HTTP request. "
+ else:
+ guess = ""
+ raise socks.SocksError(
+ socks.REP.GENERAL_SOCKS_SERVER_FAILURE,
+ guess + "Invalid SOCKS version. Expected 0x05, got 0x%x" % msg.ver)
+
+ def get_upstream_server(self, client_conn):
+ try:
+ # Parse Client Greeting
+ client_greet = socks.ClientGreeting.from_file(client_conn.rfile)
+ self._assert_socks5(client_greet)
+ 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)
+ self._assert_socks5(connect_request)
+ 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,
+ socks.ATYP.DOMAINNAME,
+ client_conn.address # dummy value, we don't have an upstream connection yet.
+ )
+ 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 as e:
+ msg = socks.Message(5, e.code, socks.ATYP.DOMAINNAME, repr(e))
+ try:
+ msg.to_file(client_conn.wfile)
+ except:
+ pass
+ raise ProxyError(502, "SOCKS5 mode failure: %s" % str(e))
+
+
class _ConstDestinationProxyMode(ProxyMode):
def __init__(self, dst):
self.dst = dst
- def get_upstream_server(self, conn):
+ def get_upstream_server(self, client_conn):
return self.dst
diff --git a/libmproxy/proxy/server.py b/libmproxy/proxy/server.py
index f4a978ca..fdf6405a 100644
--- a/libmproxy/proxy/server.py
+++ b/libmproxy/proxy/server.py
@@ -27,14 +27,13 @@ class ProxyServer(tcp.TCPServer):
allow_reuse_address = True
bound = True
- def __init__(self, config, port, host='', server_version=version.NAMEVERSION):
+ def __init__(self, config):
"""
Raises ProxyServerError if there's a startup problem.
"""
self.config = config
- self.server_version = server_version
try:
- tcp.TCPServer.__init__(self, (host, port))
+ tcp.TCPServer.__init__(self, (config.host, config.port))
except socket.error, v:
raise ProxyServerError('Error starting proxy server: ' + repr(v))
self.channel = None
@@ -47,22 +46,20 @@ class ProxyServer(tcp.TCPServer):
self.channel = channel
def handle_client_connection(self, conn, client_address):
- h = ConnectionHandler(self.config, conn, client_address, self, self.channel,
- self.server_version)
+ h = ConnectionHandler(self.config, conn, client_address, self, self.channel)
h.handle()
h.finish()
class ConnectionHandler:
- def __init__(self, config, client_connection, client_address, server, channel,
- server_version):
+ 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, self.server_version = channel, server_version
+ self.channel = channel
self.conntype = "http"
self.sni = None
@@ -73,13 +70,15 @@ class ConnectionHandler:
# Can we already identify the target server and connect to it?
client_ssl, server_ssl = False, False
- upstream_info = self.config.mode.get_upstream_server(self.client_conn.connection)
+ 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.check_ignore_address(self.server_conn.address):
+ 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:
pass # No upstream info from the metadata: upstream info in the protocol (e.g. HTTP absolute-form)
@@ -93,15 +92,19 @@ class ConnectionHandler:
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"
+
# Delegate handling to the protocol handler
- protocol_handler(self.conntype)(self).handle_messages()
+ protocol_handler(self.conntype)(self, **conn_kwargs).handle_messages()
self.del_server_connection()
self.log("clientdisconnect", "info")
self.channel.tell("clientdisconnect", self)
except ProxyError as e:
- protocol_handler(self.conntype)(self).handle_error(e)
+ protocol_handler(self.conntype)(self, **conn_kwargs).handle_error(e)
except Exception:
import traceback, sys
@@ -122,14 +125,6 @@ class ConnectionHandler:
self.server_conn = None
self.sni = None
- def check_ignore_address(self, address):
- address = tcp.Address.wrap(address)
- host = "%s:%s" % (address.host, address.port)
- if host and any(rex.search(host) for rex in self.config.ignore):
- return True
- else:
- return False
-
def set_server_address(self, address):
"""
Sets a new server address with the given priority.
@@ -193,14 +188,14 @@ class ConnectionHandler:
if client:
if self.client_conn.ssl_established:
raise ProxyError(502, "SSL to Client already established.")
- cert, key = self.find_cert()
+ cert, key, chain_file = self.find_cert()
try:
self.client_conn.convert_to_ssl(
cert, key,
handle_sni=self.handle_sni,
cipher_list=self.config.ciphers,
dhparams=self.config.certstore.dhparams,
- ca_file=self.config.ca_file
+ chain_file=chain_file
)
except tcp.NetLibError as v:
raise ProxyError(400, repr(v))
@@ -237,7 +232,7 @@ class ConnectionHandler:
def find_cert(self):
if self.config.certforward and self.server_conn.ssl_established:
- return self.server_conn.cert, self.config.certstore.gen_pkey(self.server_conn.cert)
+ return self.server_conn.cert, self.config.certstore.gen_pkey(self.server_conn.cert), None
else:
host = self.server_conn.address.host
sans = []
@@ -267,17 +262,17 @@ class ConnectionHandler:
self.log("SNI received: %s" % self.sni, "debug")
self.server_reconnect() # reconnect to upstream server with SNI
# Now, change client context to reflect changed certificate:
- cert, key = self.find_cert()
+ cert, key, chain_file = self.find_cert()
new_context = self.client_conn._create_ssl_context(
cert, key,
method=SSL.TLSv1_METHOD,
cipher_list=self.config.ciphers,
dhparams=self.config.certstore.dhparams,
- ca_file=self.config.ca_file
+ 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 Exception: # pragma: no cover
+ except: # pragma: no cover
import traceback
self.log("Error in handle_sni:\r\n" + traceback.format_exc(), "error") \ No newline at end of file