diff options
Diffstat (limited to 'libmproxy')
-rw-r--r-- | libmproxy/app.py | 4 | ||||
-rw-r--r-- | libmproxy/cmdline.py | 4 | ||||
-rw-r--r-- | libmproxy/flow.py | 40 | ||||
-rw-r--r-- | libmproxy/protocol/__init__.py | 2 | ||||
-rw-r--r-- | libmproxy/protocol/http.py | 6 | ||||
-rw-r--r-- | libmproxy/protocol/primitives.py | 2 | ||||
-rw-r--r-- | libmproxy/proxy/__init__.py (renamed from libmproxy/prxy/__init__.py) | 0 | ||||
-rw-r--r-- | libmproxy/proxy/config.py | 124 | ||||
-rw-r--r-- | libmproxy/proxy/connection.py (renamed from libmproxy/prxy/connection.py) | 3 | ||||
-rw-r--r-- | libmproxy/proxy/primitives.py | 40 | ||||
-rw-r--r-- | libmproxy/proxy/server.py (renamed from libmproxy/proxy.py) | 246 | ||||
-rw-r--r-- | libmproxy/prxy/exception.py | 14 | ||||
-rw-r--r-- | libmproxy/prxy/server.py | 18 |
13 files changed, 247 insertions, 256 deletions
diff --git a/libmproxy/app.py b/libmproxy/app.py index 24187704..69721031 100644 --- a/libmproxy/app.py +++ b/libmproxy/app.py @@ -17,12 +17,12 @@ def index(): @mapp.route("/cert/pem") def certs_pem(): - p = os.path.join(master().server.config.confdir, proxy.CONF_BASENAME + "-ca-cert.pem") + p = os.path.join(master().server.config.confdir, proxy.config.CONF_BASENAME + "-ca-cert.pem") return flask.Response(open(p, "rb").read(), mimetype='application/x-x509-ca-cert') @mapp.route("/cert/p12") def certs_p12(): - p = os.path.join(master().server.config.confdir, proxy.CONF_BASENAME + "-ca-cert.p12") + p = os.path.join(master().server.config.confdir, proxy.config.CONF_BASENAME + "-ca-cert.p12") return flask.Response(open(p, "rb").read(), mimetype='application/x-pkcs12') diff --git a/libmproxy/cmdline.py b/libmproxy/cmdline.py index 7950d40b..72c13769 100644 --- a/libmproxy/cmdline.py +++ b/libmproxy/cmdline.py @@ -1,4 +1,4 @@ -import proxy +from . import proxy import re, filt import argparse @@ -387,4 +387,4 @@ def common_options(parser): help="Allow access to users specified in an Apache htpasswd file." ) - proxy.ssl_option_group(parser) + proxy.config.ssl_option_group(parser) diff --git a/libmproxy/flow.py b/libmproxy/flow.py index c362465c..452fd783 100644 --- a/libmproxy/flow.py +++ b/libmproxy/flow.py @@ -7,13 +7,15 @@ import hashlib, Cookie, cookielib, re, threading import os import flask import requests +from . import controller, protocol +from .protocol import http +from .proxy.connection import ServerConnection +from .proxy.primitives import ProxyError import tnetstring, filt, script -from netlib import odict, wsgi -import controller, version, protocol +from netlib import odict, wsgi, tcp +import netlib.http +import version import app -from .protocol import KILL -from .protocol.http import HTTPResponse, CONTENT_MISSING -from .proxy import RequestReplayThread ODict = odict.ODict ODictCaseless = odict.ODictCaseless @@ -564,7 +566,7 @@ class FlowMaster(controller.Master): rflow = self.server_playback.next_flow(flow) if not rflow: return None - response = HTTPResponse._from_state(rflow.response._get_state()) + response = http.HTTPResponse._from_state(rflow.response._get_state()) response.is_replay = True if self.refresh_server_playback: response.refresh() @@ -641,7 +643,7 @@ class FlowMaster(controller.Master): """ if f.intercepting: return "Can't replay while intercepting..." - if f.request.content == CONTENT_MISSING: + if f.request.content == http.CONTENT_MISSING: return "Can't replay request with missing content..." if f.request: f.request.is_replay = True @@ -692,7 +694,7 @@ class FlowMaster(controller.Master): err = app.serve(r, r.flow.client_conn.wfile, **{"mitmproxy.master": self}) if err: self.add_event("Error in wsgi app. %s"%err, "error") - r.reply(KILL) + r.reply(protocol.KILL) return f = self.state.add_request(r) self.replacehooks.run(f) @@ -784,3 +786,25 @@ class FilteredFlowWriter: d = f._get_state() tnetstring.dump(d, self.fo) + +class RequestReplayThread(threading.Thread): + name="RequestReplayThread" + + def __init__(self, config, flow, masterq): + self.config, self.flow, self.channel = config, flow, controller.Channel(masterq) + threading.Thread.__init__(self) + + def run(self): + try: + r = self.flow.request + server = ServerConnection(self.flow.server_conn.address(), None) + server.connect() + if self.flow.server_conn.ssl_established: + server.establish_ssl(self.config.clientcerts, + self.flow.server_conn.sni) + server.send(r._assemble()) + self.flow.response = http.HTTPResponse.from_stream(server.rfile, r.method, body_size_limit=self.config.body_size_limit) + self.channel.ask("response", self.flow.response) + except (ProxyError, netlib.http.HttpError, tcp.NetLibError), v: + self.flow.error = protocol.primitives.Error(str(v)) + self.channel.ask("error", self.flow.error)
\ No newline at end of file diff --git a/libmproxy/protocol/__init__.py b/libmproxy/protocol/__init__.py index 392a8e3d..6200757f 100644 --- a/libmproxy/protocol/__init__.py +++ b/libmproxy/protocol/__init__.py @@ -1,4 +1,4 @@ -from ..prxy.server import AddressPriority +from libmproxy.proxy.primitives import AddressPriority KILL = 0 # const for killed requests diff --git a/libmproxy/protocol/http.py b/libmproxy/protocol/http.py index 3f9668ae..77a09e61 100644 --- a/libmproxy/protocol/http.py +++ b/libmproxy/protocol/http.py @@ -1,8 +1,8 @@ import Cookie, urllib, urlparse, time, copy from email.utils import parsedate_tz, formatdate, mktime_tz -from ..prxy.connection import ServerConnection -from ..prxy.exception import ProxyError, ConnectionTypeChange -from ..prxy.server import AddressPriority +from libmproxy.proxy.primitives import AddressPriority +from ..proxy.connection import ServerConnection +from ..proxy.primitives import ProxyError, ConnectionTypeChange import netlib.utils from netlib import http, tcp, http_status from netlib.odict import ODict, ODictCaseless diff --git a/libmproxy/protocol/primitives.py b/libmproxy/protocol/primitives.py index 7cee074d..f2701458 100644 --- a/libmproxy/protocol/primitives.py +++ b/libmproxy/protocol/primitives.py @@ -1,5 +1,5 @@ from .. import stateobject, utils, version -from ..prxy.connection import ClientConnection, ServerConnection +from ..proxy.connection import ClientConnection, ServerConnection import copy diff --git a/libmproxy/prxy/__init__.py b/libmproxy/proxy/__init__.py index e69de29b..e69de29b 100644 --- a/libmproxy/prxy/__init__.py +++ b/libmproxy/proxy/__init__.py diff --git a/libmproxy/proxy/config.py b/libmproxy/proxy/config.py new file mode 100644 index 00000000..38c6ce89 --- /dev/null +++ b/libmproxy/proxy/config.py @@ -0,0 +1,124 @@ +import os +from .. import utils, platform +from netlib import http_auth, certutils + + +TRANSPARENT_SSL_PORTS = [443, 8443] +CONF_BASENAME = "mitmproxy" +CONF_DIR = "~/.mitmproxy" + + +class ProxyConfig: + def __init__(self, confdir=CONF_DIR, clientcerts=None, + no_upstream_cert=False, body_size_limit=None, reverse_proxy=None, + forward_proxy=None, transparent_proxy=None, authenticator=None, + ciphers=None, certs=None + ): + self.ciphers = ciphers + self.clientcerts = clientcerts + self.no_upstream_cert = no_upstream_cert + self.body_size_limit = body_size_limit + self.reverse_proxy = reverse_proxy + self.forward_proxy = forward_proxy + self.transparent_proxy = transparent_proxy + self.authenticator = authenticator + self.confdir = os.path.expanduser(confdir) + self.certstore = certutils.CertStore.from_store(self.confdir, CONF_BASENAME) + + +def process_proxy_options(parser, options): + body_size_limit = utils.parse_size(options.body_size_limit) + if options.reverse_proxy and options.transparent_proxy: + return parser.error("Can't set both reverse proxy and transparent proxy.") + + if options.transparent_proxy: + if not platform.resolver: + return parser.error("Transparent mode not supported on this platform.") + trans = dict( + resolver=platform.resolver(), + sslports=TRANSPARENT_SSL_PORTS + ) + else: + trans = None + + if options.reverse_proxy: + rp = utils.parse_proxy_spec(options.reverse_proxy) + if not rp: + return parser.error("Invalid reverse proxy specification: %s" % options.reverse_proxy) + else: + rp = None + + if options.forward_proxy: + fp = utils.parse_proxy_spec(options.forward_proxy) + if not fp: + return parser.error("Invalid forward proxy specification: %s" % options.forward_proxy) + else: + fp = None + + if options.clientcerts: + options.clientcerts = os.path.expanduser(options.clientcerts) + if not os.path.exists(options.clientcerts) or not os.path.isdir(options.clientcerts): + return parser.error( + "Client certificate directory does not exist or is not a directory: %s" % options.clientcerts + ) + + if (options.auth_nonanonymous or options.auth_singleuser or options.auth_htpasswd): + if options.auth_singleuser: + if len(options.auth_singleuser.split(':')) != 2: + return parser.error("Invalid single-user specification. Please use the format username:password") + username, password = options.auth_singleuser.split(':') + password_manager = http_auth.PassManSingleUser(username, password) + elif options.auth_nonanonymous: + password_manager = http_auth.PassManNonAnon() + elif options.auth_htpasswd: + try: + password_manager = http_auth.PassManHtpasswd(options.auth_htpasswd) + except ValueError, v: + return parser.error(v.message) + authenticator = http_auth.BasicProxyAuth(password_manager, "mitmproxy") + else: + authenticator = http_auth.NullProxyAuth(None) + + certs = [] + for i in options.certs: + parts = i.split("=", 1) + if len(parts) == 1: + parts = ["*", parts[0]] + parts[1] = os.path.expanduser(parts[1]) + if not os.path.exists(parts[1]): + parser.error("Certificate file does not exist: %s"%parts[1]) + certs.append(parts) + + return ProxyConfig( + clientcerts=options.clientcerts, + body_size_limit=body_size_limit, + no_upstream_cert=options.no_upstream_cert, + reverse_proxy=rp, + forward_proxy=fp, + transparent_proxy=trans, + authenticator=authenticator, + ciphers=options.ciphers, + certs = certs, + ) + + +def ssl_option_group(parser): + group = parser.add_argument_group("SSL") + 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.' + ) + group.add_argument( + "--client-certs", action="store", + type=str, dest="clientcerts", default=None, + help="Client certificate directory." + ) + group.add_argument( + "--ciphers", action="store", + type=str, dest="ciphers", default=None, + help="SSL cipher specification." + )
\ No newline at end of file diff --git a/libmproxy/prxy/connection.py b/libmproxy/proxy/connection.py index b1040c1c..3a0273af 100644 --- a/libmproxy/prxy/connection.py +++ b/libmproxy/proxy/connection.py @@ -1,9 +1,10 @@ import copy import os from .. import stateobject, utils -from .exception import ProxyError +from .primitives import ProxyError from netlib import tcp, certutils + class ClientConnection(tcp.BaseHandler, stateobject.SimpleStateObject): def __init__(self, client_connection, address, server): if client_connection: # Eventually, this object is restored from state. We don't have a connection then. diff --git a/libmproxy/proxy/primitives.py b/libmproxy/proxy/primitives.py new file mode 100644 index 00000000..8dd0e16a --- /dev/null +++ b/libmproxy/proxy/primitives.py @@ -0,0 +1,40 @@ +class ProxyError(Exception): + def __init__(self, code, msg, headers=None): + self.code, self.msg, self.headers = code, msg, headers + + def __str__(self): + return "ProxyError(%s, %s)" % (self.code, self.msg) + + +class ConnectionTypeChange(Exception): + """ + Gets raised if the connection type has been changed (e.g. after HTTP/1.1 101 Switching Protocols). + It's up to the raising ProtocolHandler to specify the new conntype before raising the exception. + """ + pass + + +class ProxyServerError(Exception): + pass + + +class AddressPriority(object): + """ + Enum that signifies the priority of the given address when choosing the destination host. + Higher is better (None < i) + """ + FORCE = 5 + """forward mode""" + MANUALLY_CHANGED = 4 + """user changed the target address in the ui""" + FROM_SETTINGS = 3 + """reverse proxy mode""" + FROM_CONNECTION = 2 + """derived from transparent resolver""" + FROM_PROTOCOL = 1 + """derived from protocol (e.g. absolute-form http requests)""" + + +class Log: + def __init__(self, msg): + self.msg = msg
\ No newline at end of file diff --git a/libmproxy/proxy.py b/libmproxy/proxy/server.py index ccb47c26..37ec7758 100644 --- a/libmproxy/proxy.py +++ b/libmproxy/proxy/server.py @@ -1,73 +1,51 @@ -import os import socket -import threading +from .. import version, protocol +from libmproxy.proxy.primitives import Log +from .primitives import ProxyServerError +from .connection import ClientConnection, ServerConnection +from .primitives import ProxyError, ConnectionTypeChange, AddressPriority +from netlib import tcp -from OpenSSL import SSL -from .prxy.connection import ClientConnection, ServerConnection -from .prxy.exception import ProxyError, ConnectionTypeChange -from .prxy.server import AddressPriority -from netlib import tcp, http, certutils, http_auth -import utils -import version -import platform -import controller - - -TRANSPARENT_SSL_PORTS = [443, 8443] -CONF_BASENAME = "mitmproxy" -CONF_DIR = "~/.mitmproxy" -CA_CERT_NAME = "mitmproxy-ca.pem" - - -class Log: - def __init__(self, msg): - self.msg = msg +class DummyServer: + bound = False + def __init__(self, config): + self.config = config -class ProxyConfig: - def __init__(self, confdir=CONF_DIR, clientcerts=None, - no_upstream_cert=False, body_size_limit=None, reverse_proxy=None, - forward_proxy=None, transparent_proxy=None, authenticator=None, - ciphers=None, certs=None - ): - self.ciphers = ciphers - self.clientcerts = clientcerts - self.no_upstream_cert = no_upstream_cert - self.body_size_limit = body_size_limit - self.reverse_proxy = reverse_proxy - self.forward_proxy = forward_proxy - self.transparent_proxy = transparent_proxy - self.authenticator = authenticator - self.confdir = os.path.expanduser(confdir) - self.certstore = certutils.CertStore.from_store(self.confdir, CONF_BASENAME) + def start_slave(self, *args): + pass + def shutdown(self): + pass -from . import protocol -from .protocol.http import HTTPResponse +class ProxyServer(tcp.TCPServer): + allow_reuse_address = True + bound = True + def __init__(self, config, port, host='', server_version=version.NAMEVERSION): + """ + Raises ProxyServerError if there's a startup problem. + """ + self.config = config + self.server_version = server_version + try: + tcp.TCPServer.__init__(self, (host, port)) + except socket.error, v: + raise ProxyServerError('Error starting proxy server: ' + v.strerror) + self.channel = None -class RequestReplayThread(threading.Thread): - name="RequestReplayThread" + def start_slave(self, klass, channel): + slave = klass(channel, self) + slave.start() - def __init__(self, config, flow, masterq): - self.config, self.flow, self.channel = config, flow, controller.Channel(masterq) - threading.Thread.__init__(self) + def set_channel(self, channel): + self.channel = channel - def run(self): - try: - r = self.flow.request - server = ServerConnection(self.flow.server_conn.address(), None) - server.connect() - if self.flow.server_conn.ssl_established: - server.establish_ssl(self.config.clientcerts, - self.flow.server_conn.sni) - server.send(r._assemble()) - self.flow.response = HTTPResponse.from_stream(server.rfile, r.method, body_size_limit=self.config.body_size_limit) - self.channel.ask("response", self.flow.response) - except (ProxyError, http.HttpError, tcp.NetLibError), v: - self.flow.error = protocol.primitives.Error(str(v)) - self.channel.ask("error", self.flow.error) + def handle_client_connection(self, conn, client_address): + h = ConnectionHandler(self.config, conn, client_address, self, self.channel, self.server_version) + h.handle() + h.finish() class ConnectionHandler: @@ -174,7 +152,7 @@ class ConnectionHandler: """ Sets a new server address with the given priority. Does not re-establish either connection or SSL handshake. - @type priority: libmproxy.prxy.server.AddressPriority + @type priority: libmproxy.proxy.primitives.AddressPriority """ address = tcp.Address.wrap(address) @@ -241,7 +219,7 @@ class ConnectionHandler: raise ProxyError(502, "SSL to Client already established.") cert, key = self.find_cert() self.client_conn.convert_to_ssl( - cert, key, + cert, key, handle_sni = self.handle_sni, cipher_list = self.config.ciphers ) @@ -306,148 +284,4 @@ class ConnectionHandler: # An unhandled exception in this method will core dump PyOpenSSL, so # make dang sure it doesn't happen. except Exception, e: # pragma: no cover - pass - - -class ProxyServerError(Exception): - pass - - -class ProxyServer(tcp.TCPServer): - allow_reuse_address = True - bound = True - def __init__(self, config, port, host='', server_version=version.NAMEVERSION): - """ - Raises ProxyServerError if there's a startup problem. - """ - self.config = config - self.server_version = server_version - try: - tcp.TCPServer.__init__(self, (host, port)) - except socket.error, v: - raise ProxyServerError('Error starting proxy server: ' + v.strerror) - self.channel = None - - def start_slave(self, klass, channel): - slave = klass(channel, self) - slave.start() - - def set_channel(self, channel): - 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.handle() - h.finish() - - -class DummyServer: - bound = False - - def __init__(self, config): - self.config = config - - def start_slave(self, *args): - pass - - def shutdown(self): - pass - - -# Command-line utils -def ssl_option_group(parser): - group = parser.add_argument_group("SSL") - 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.' - ) - group.add_argument( - "--client-certs", action="store", - type=str, dest="clientcerts", default=None, - help="Client certificate directory." - ) - group.add_argument( - "--ciphers", action="store", - type=str, dest="ciphers", default=None, - help="SSL cipher specification." - ) - - -def process_proxy_options(parser, options): - body_size_limit = utils.parse_size(options.body_size_limit) - if options.reverse_proxy and options.transparent_proxy: - return parser.error("Can't set both reverse proxy and transparent proxy.") - - if options.transparent_proxy: - if not platform.resolver: - return parser.error("Transparent mode not supported on this platform.") - trans = dict( - resolver=platform.resolver(), - sslports=TRANSPARENT_SSL_PORTS - ) - else: - trans = None - - if options.reverse_proxy: - rp = utils.parse_proxy_spec(options.reverse_proxy) - if not rp: - return parser.error("Invalid reverse proxy specification: %s" % options.reverse_proxy) - else: - rp = None - - if options.forward_proxy: - fp = utils.parse_proxy_spec(options.forward_proxy) - if not fp: - return parser.error("Invalid forward proxy specification: %s" % options.forward_proxy) - else: - fp = None - - if options.clientcerts: - options.clientcerts = os.path.expanduser(options.clientcerts) - if not os.path.exists(options.clientcerts) or not os.path.isdir(options.clientcerts): - return parser.error( - "Client certificate directory does not exist or is not a directory: %s" % options.clientcerts - ) - - if (options.auth_nonanonymous or options.auth_singleuser or options.auth_htpasswd): - if options.auth_singleuser: - if len(options.auth_singleuser.split(':')) != 2: - return parser.error("Invalid single-user specification. Please use the format username:password") - username, password = options.auth_singleuser.split(':') - password_manager = http_auth.PassManSingleUser(username, password) - elif options.auth_nonanonymous: - password_manager = http_auth.PassManNonAnon() - elif options.auth_htpasswd: - try: - password_manager = http_auth.PassManHtpasswd(options.auth_htpasswd) - except ValueError, v: - return parser.error(v.message) - authenticator = http_auth.BasicProxyAuth(password_manager, "mitmproxy") - else: - authenticator = http_auth.NullProxyAuth(None) - - certs = [] - for i in options.certs: - parts = i.split("=", 1) - if len(parts) == 1: - parts = ["*", parts[0]] - parts[1] = os.path.expanduser(parts[1]) - if not os.path.exists(parts[1]): - parser.error("Certificate file does not exist: %s"%parts[1]) - certs.append(parts) - - return ProxyConfig( - clientcerts=options.clientcerts, - body_size_limit=body_size_limit, - no_upstream_cert=options.no_upstream_cert, - reverse_proxy=rp, - forward_proxy=fp, - transparent_proxy=trans, - authenticator=authenticator, - ciphers=options.ciphers, - certs = certs, - ) + pass
\ No newline at end of file diff --git a/libmproxy/prxy/exception.py b/libmproxy/prxy/exception.py deleted file mode 100644 index c43a5d75..00000000 --- a/libmproxy/prxy/exception.py +++ /dev/null @@ -1,14 +0,0 @@ -class ProxyError(Exception): - def __init__(self, code, msg, headers=None): - self.code, self.msg, self.headers = code, msg, headers - - def __str__(self): - return "ProxyError(%s, %s)" % (self.code, self.msg) - - -class ConnectionTypeChange(Exception): - """ - Gets raised if the connection type has been changed (e.g. after HTTP/1.1 101 Switching Protocols). - It's up to the raising ProtocolHandler to specify the new conntype before raising the exception. - """ - pass
\ No newline at end of file diff --git a/libmproxy/prxy/server.py b/libmproxy/prxy/server.py deleted file mode 100644 index 441b29b4..00000000 --- a/libmproxy/prxy/server.py +++ /dev/null @@ -1,18 +0,0 @@ -__author__ = 'user' - - -class AddressPriority(object): - """ - Enum that signifies the priority of the given address when choosing the destination host. - Higher is better (None < i) - """ - FORCE = 5 - """forward mode""" - MANUALLY_CHANGED = 4 - """user changed the target address in the ui""" - FROM_SETTINGS = 3 - """reverse proxy mode""" - FROM_CONNECTION = 2 - """derived from transparent resolver""" - FROM_PROTOCOL = 1 - """derived from protocol (e.g. absolute-form http requests)"""
\ No newline at end of file |