aboutsummaryrefslogtreecommitdiffstats
path: root/libmproxy
diff options
context:
space:
mode:
Diffstat (limited to 'libmproxy')
-rw-r--r--libmproxy/app.py7
-rw-r--r--libmproxy/authentication.py122
-rw-r--r--libmproxy/cmdline.py15
-rw-r--r--libmproxy/console/__init__.py24
-rw-r--r--libmproxy/console/common.py8
-rw-r--r--libmproxy/console/flowlist.py2
-rw-r--r--libmproxy/console/flowview.py8
-rw-r--r--libmproxy/console/help.py4
-rw-r--r--libmproxy/contrib/md5crypt.py94
-rw-r--r--libmproxy/controller.py87
-rw-r--r--libmproxy/dump.py42
-rw-r--r--libmproxy/flow.py128
-rw-r--r--libmproxy/proxy.py417
13 files changed, 447 insertions, 511 deletions
diff --git a/libmproxy/app.py b/libmproxy/app.py
new file mode 100644
index 00000000..18d78b3e
--- /dev/null
+++ b/libmproxy/app.py
@@ -0,0 +1,7 @@
+import flask
+
+mapp = flask.Flask(__name__)
+
+@mapp.route("/")
+def hello():
+ return "mitmproxy"
diff --git a/libmproxy/authentication.py b/libmproxy/authentication.py
deleted file mode 100644
index 500ead6b..00000000
--- a/libmproxy/authentication.py
+++ /dev/null
@@ -1,122 +0,0 @@
-import binascii
-import contrib.md5crypt as md5crypt
-
-class NullProxyAuth():
- """
- No proxy auth at all (returns empty challange headers)
- """
- def __init__(self, password_manager):
- self.password_manager = password_manager
- self.username = ""
-
- def clean(self, headers):
- """
- Clean up authentication headers, so they're not passed upstream.
- """
- pass
-
- def authenticate(self, headers):
- """
- Tests that the user is allowed to use the proxy
- """
- return True
-
- def auth_challenge_headers(self):
- """
- Returns a dictionary containing the headers require to challenge the user
- """
- return {}
-
-
-class BasicProxyAuth(NullProxyAuth):
- CHALLENGE_HEADER = 'Proxy-Authenticate'
- AUTH_HEADER = 'Proxy-Authorization'
- def __init__(self, password_manager, realm):
- NullProxyAuth.__init__(self, password_manager)
- self.realm = realm
-
- def clean(self, headers):
- del headers[self.AUTH_HEADER]
-
- def authenticate(self, headers):
- auth_value = headers.get(self.AUTH_HEADER, [])
- if not auth_value:
- return False
- try:
- scheme, username, password = self.parse_auth_value(auth_value[0])
- except ValueError:
- return False
- if scheme.lower()!='basic':
- return False
- if not self.password_manager.test(username, password):
- return False
- self.username = username
- return True
-
- def auth_challenge_headers(self):
- return {self.CHALLENGE_HEADER:'Basic realm="%s"'%self.realm}
-
- def unparse_auth_value(self, scheme, username, password):
- v = binascii.b2a_base64(username + ":" + password)
- return scheme + " " + v
-
- def parse_auth_value(self, auth_value):
- words = auth_value.split()
- if len(words) != 2:
- raise ValueError("Invalid basic auth credential.")
- scheme = words[0]
- try:
- user = binascii.a2b_base64(words[1])
- except binascii.Error:
- raise ValueError("Invalid basic auth credential: user:password pair not valid base64: %s"%words[1])
- parts = user.split(':')
- if len(parts) != 2:
- raise ValueError("Invalid basic auth credential: decoded user:password pair not valid: %s"%user)
- return scheme, parts[0], parts[1]
-
-
-class PasswordManager():
- def __init__(self):
- pass
-
- def test(self, username, password_token):
- return False
-
-
-class PermissivePasswordManager(PasswordManager):
- def __init__(self):
- PasswordManager.__init__(self)
-
- def test(self, username, password_token):
- if username:
- return True
- return False
-
-
-class HtpasswdPasswordManager(PasswordManager):
- """
- Read usernames and passwords from a file created by Apache htpasswd
- """
- def __init__(self, filehandle):
- PasswordManager.__init__(self)
- entries = (line.strip().split(':') for line in filehandle)
- valid_entries = (entry for entry in entries if len(entry)==2)
- self.usernames = {username:token for username,token in valid_entries}
-
- def test(self, username, password_token):
- if username not in self.usernames:
- return False
- full_token = self.usernames[username]
- dummy, magic, salt, hashed_password = full_token.split('$')
- expected = md5crypt.md5crypt(password_token, salt, '$'+magic+'$')
- return expected==full_token
-
-
-class SingleUserPasswordManager(PasswordManager):
- def __init__(self, username, password):
- PasswordManager.__init__(self)
- self.username = username
- self.password = password
-
- def test(self, username, password_token):
- return self.username==username and self.password==password_token
diff --git a/libmproxy/cmdline.py b/libmproxy/cmdline.py
index de70bea8..b76792cf 100644
--- a/libmproxy/cmdline.py
+++ b/libmproxy/cmdline.py
@@ -154,6 +154,7 @@ def get_common_options(options):
script = options.script,
stickycookie = stickycookie,
stickyauth = stickyauth,
+ showhost = options.showhost,
wfile = options.wfile,
verbosity = options.verbose,
nopop = options.nopop,
@@ -162,7 +163,7 @@ def get_common_options(options):
def common_options(parser):
parser.add_argument(
- "-a",
+ "-b",
action="store", type = str, dest="addr", default='',
help = "Address to bind proxy to (defaults to all interfaces)"
)
@@ -248,6 +249,11 @@ def common_options(parser):
help="Byte size limit of HTTP request and response bodies."\
" Understands k/m/g suffixes, i.e. 3m for 3 megabytes."
)
+ parser.add_argument(
+ "--host",
+ action="store_true", dest="showhost", default=False,
+ help="Use the Host header to construct URLs for display."
+ )
parser.add_argument(
"--no-upstream-cert", default=False,
@@ -255,6 +261,13 @@ def common_options(parser):
help="Don't connect to upstream server to look up certificate details."
)
+ group = parser.add_argument_group("Web App")
+ group.add_argument(
+ "-a",
+ action="store_true", dest="app", default=False,
+ help="Enable the mitmproxy web app."
+ )
+
group = parser.add_argument_group("Client Replay")
group.add_argument(
"-c",
diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py
index d6c7f5a2..fe75a047 100644
--- a/libmproxy/console/__init__.py
+++ b/libmproxy/console/__init__.py
@@ -174,6 +174,8 @@ class StatusBar(common.WWrap):
opts.append("anticache")
if self.master.anticomp:
opts.append("anticomp")
+ if self.master.showhost:
+ opts.append("showhost")
if not self.master.refresh_server_playback:
opts.append("norefresh")
if self.master.killextra:
@@ -195,9 +197,6 @@ class StatusBar(common.WWrap):
if self.master.stream:
r.append("[W:%s]"%self.master.stream_path)
- if self.master.state.last_saveload:
- r.append("[%s]"%self.master.state.last_saveload)
-
return r
def redraw(self):
@@ -328,7 +327,7 @@ class ConsoleState(flow.State):
class Options(object):
- __slots__ = [
+ attributes = [
"anticache",
"anticomp",
"client_replay",
@@ -341,6 +340,7 @@ class Options(object):
"refresh_server_playback",
"rfile",
"script",
+ "showhost",
"replacements",
"rheaders",
"setheaders",
@@ -355,7 +355,7 @@ class Options(object):
def __init__(self, **kwargs):
for k, v in kwargs.items():
setattr(self, k, v)
- for i in self.__slots__:
+ for i in self.attributes:
if not hasattr(self, i):
setattr(self, i, None)
@@ -401,6 +401,7 @@ class ConsoleMaster(flow.FlowMaster):
self.killextra = options.kill
self.rheaders = options.rheaders
self.nopop = options.nopop
+ self.showhost = options.showhost
self.eventlog = options.eventlog
self.eventlist = urwid.SimpleListWalker([])
@@ -429,7 +430,7 @@ class ConsoleMaster(flow.FlowMaster):
path = os.path.expanduser(path)
try:
f = file(path, "wb")
- flow.FlowMaster.start_stream(self, f)
+ flow.FlowMaster.start_stream(self, f, None)
except IOError, v:
return str(v)
self.stream_path = path
@@ -580,7 +581,7 @@ class ConsoleMaster(flow.FlowMaster):
self.view_flowlist()
- self.server.start_slave(controller.Slave, self.masterq)
+ self.server.start_slave(controller.Slave, controller.Channel(self.masterq))
if self.options.rfile:
ret = self.load_flows(self.options.rfile)
@@ -921,6 +922,7 @@ class ConsoleMaster(flow.FlowMaster):
(
("anticache", "a"),
("anticomp", "c"),
+ ("showhost", "h"),
("killextra", "k"),
("norefresh", "n"),
("no-upstream-certs", "u"),
@@ -960,6 +962,10 @@ class ConsoleMaster(flow.FlowMaster):
self.anticache = not self.anticache
if a == "c":
self.anticomp = not self.anticomp
+ if a == "h":
+ self.showhost = not self.showhost
+ self.sync_list_view()
+ self.refresh_flow(self.currentflow)
elif a == "k":
self.killextra = not self.killextra
elif a == "n":
@@ -1002,7 +1008,7 @@ class ConsoleMaster(flow.FlowMaster):
if self.state.intercept and f.match(self.state.intercept) and not f.request.is_replay():
f.intercept()
else:
- r._ack()
+ r.reply()
self.sync_list_view()
self.refresh_flow(f)
@@ -1023,7 +1029,7 @@ class ConsoleMaster(flow.FlowMaster):
# Handlers
def handle_log(self, l):
self.add_event(l.msg)
- l._ack()
+ l.reply()
def handle_error(self, r):
f = flow.FlowMaster.handle_error(self, r)
diff --git a/libmproxy/console/common.py b/libmproxy/console/common.py
index 2da7f802..d68aba3d 100644
--- a/libmproxy/console/common.py
+++ b/libmproxy/console/common.py
@@ -177,15 +177,15 @@ class FlowCache:
flowcache = FlowCache()
-def format_flow(f, focus, extended=False, padding=2):
+def format_flow(f, focus, extended=False, hostheader=False, padding=2):
d = dict(
intercepting = f.intercepting,
req_timestamp = f.request.timestamp_start,
req_is_replay = f.request.is_replay(),
req_method = f.request.method,
- req_acked = f.request.acked,
- req_url = f.request.get_url(),
+ req_acked = f.request.reply.acked,
+ req_url = f.request.get_url(hostheader=hostheader),
err_msg = f.error.msg if f.error else None,
resp_code = f.response.code if f.response else None,
@@ -200,7 +200,7 @@ def format_flow(f, focus, extended=False, padding=2):
d.update(dict(
resp_code = f.response.code,
resp_is_replay = f.response.is_replay(),
- resp_acked = f.response.acked,
+ resp_acked = f.response.reply.acked,
resp_clen = contentdesc
))
t = f.response.headers["content-type"]
diff --git a/libmproxy/console/flowlist.py b/libmproxy/console/flowlist.py
index c70393a1..8fd4efce 100644
--- a/libmproxy/console/flowlist.py
+++ b/libmproxy/console/flowlist.py
@@ -105,7 +105,7 @@ class ConnectionItem(common.WWrap):
common.WWrap.__init__(self, w)
def get_text(self):
- return common.format_flow(self.flow, self.f)
+ return common.format_flow(self.flow, self.f, hostheader=self.master.showhost)
def selectable(self):
return True
diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py
index 4215f170..5f1d261f 100644
--- a/libmproxy/console/flowview.py
+++ b/libmproxy/console/flowview.py
@@ -88,11 +88,11 @@ footer = [
class FlowViewHeader(common.WWrap):
def __init__(self, master, f):
self.master, self.flow = master, f
- self.w = common.format_flow(f, False, extended=True, padding=0)
+ self.w = common.format_flow(f, False, extended=True, padding=0, hostheader=self.master.showhost)
def refresh_flow(self, f):
if f == self.flow:
- self.w = common.format_flow(f, False, extended=True, padding=0)
+ self.w = common.format_flow(f, False, extended=True, padding=0, hostheader=self.master.showhost)
class CallbackCache:
@@ -201,7 +201,7 @@ class FlowView(common.WWrap):
def wrap_body(self, active, body):
parts = []
- if self.flow.intercepting and not self.flow.request.acked:
+ if self.flow.intercepting and not self.flow.request.reply.acked:
qt = "Request intercepted"
else:
qt = "Request"
@@ -210,7 +210,7 @@ class FlowView(common.WWrap):
else:
parts.append(self._tab(qt, "heading_inactive"))
- if self.flow.intercepting and self.flow.response and not self.flow.response.acked:
+ if self.flow.intercepting and self.flow.response and not self.flow.response.reply.acked:
st = "Response intercepted"
else:
st = "Response"
diff --git a/libmproxy/console/help.py b/libmproxy/console/help.py
index 178b36f7..40f81955 100644
--- a/libmproxy/console/help.py
+++ b/libmproxy/console/help.py
@@ -98,6 +98,10 @@ class HelpView(urwid.ListBox):
[("text", ": prevent compressed responses")]
),
(None,
+ common.highlight_key("showhost", "h") +
+ [("text", ": use Host header for URL display")]
+ ),
+ (None,
common.highlight_key("killextra", "k") +
[("text", ": kill requests not part of server replay")]
),
diff --git a/libmproxy/contrib/md5crypt.py b/libmproxy/contrib/md5crypt.py
deleted file mode 100644
index d64ea8ac..00000000
--- a/libmproxy/contrib/md5crypt.py
+++ /dev/null
@@ -1,94 +0,0 @@
-# Based on FreeBSD src/lib/libcrypt/crypt.c 1.2
-# http://www.freebsd.org/cgi/cvsweb.cgi/~checkout~/src/lib/libcrypt/crypt.c?rev=1.2&content-type=text/plain
-
-# Original license:
-# * "THE BEER-WARE LICENSE" (Revision 42):
-# * <phk@login.dknet.dk> wrote this file. As long as you retain this notice you
-# * can do whatever you want with this stuff. If we meet some day, and you think
-# * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp
-
-# This port adds no further stipulations. I forfeit any copyright interest.
-
-import md5
-
-def md5crypt(password, salt, magic='$1$'):
- # /* The password first, since that is what is most unknown */ /* Then our magic string */ /* Then the raw salt */
- m = md5.new()
- m.update(password + magic + salt)
-
- # /* Then just as many characters of the MD5(pw,salt,pw) */
- mixin = md5.md5(password + salt + password).digest()
- for i in range(0, len(password)):
- m.update(mixin[i % 16])
-
- # /* Then something really weird... */
- # Also really broken, as far as I can tell. -m
- i = len(password)
- while i:
- if i & 1:
- m.update('\x00')
- else:
- m.update(password[0])
- i >>= 1
-
- final = m.digest()
-
- # /* and now, just to make sure things don't run too fast */
- for i in range(1000):
- m2 = md5.md5()
- if i & 1:
- m2.update(password)
- else:
- m2.update(final)
-
- if i % 3:
- m2.update(salt)
-
- if i % 7:
- m2.update(password)
-
- if i & 1:
- m2.update(final)
- else:
- m2.update(password)
-
- final = m2.digest()
-
- # This is the bit that uses to64() in the original code.
-
- itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
-
- rearranged = ''
- for a, b, c in ((0, 6, 12), (1, 7, 13), (2, 8, 14), (3, 9, 15), (4, 10, 5)):
- v = ord(final[a]) << 16 | ord(final[b]) << 8 | ord(final[c])
- for i in range(4):
- rearranged += itoa64[v & 0x3f]; v >>= 6
-
- v = ord(final[11])
- for i in range(2):
- rearranged += itoa64[v & 0x3f]; v >>= 6
-
- return magic + salt + '$' + rearranged
-
-if __name__ == '__main__':
-
- def test(clear_password, the_hash):
- magic, salt = the_hash[1:].split('$')[:2]
- magic = '$' + magic + '$'
- return md5crypt(clear_password, salt, magic) == the_hash
-
- test_cases = (
- (' ', '$1$yiiZbNIH$YiCsHZjcTkYd31wkgW8JF.'),
- ('pass', '$1$YeNsbWdH$wvOF8JdqsoiLix754LTW90'),
- ('____fifteen____', '$1$s9lUWACI$Kk1jtIVVdmT01p0z3b/hw1'),
- ('____sixteen_____', '$1$dL3xbVZI$kkgqhCanLdxODGq14g/tW1'),
- ('____seventeen____', '$1$NaH5na7J$j7y8Iss0hcRbu3kzoJs5V.'),
- ('__________thirty-three___________', '$1$HO7Q6vzJ$yGwp2wbL5D7eOVzOmxpsy.'),
- ('apache', '$apr1$J.w5a/..$IW9y6DR0oO/ADuhlMF5/X1')
- )
-
- for clearpw, hashpw in test_cases:
- if test(clearpw, hashpw):
- print '%s: pass' % clearpw
- else:
- print '%s: FAIL' % clearpw
diff --git a/libmproxy/controller.py b/libmproxy/controller.py
index f38d1edb..bb22597d 100644
--- a/libmproxy/controller.py
+++ b/libmproxy/controller.py
@@ -17,37 +17,75 @@ import Queue, threading
should_exit = False
-class Msg:
+
+class DummyReply:
+ """
+ A reply object that does nothing. Useful when we need an object to seem
+ like it has a channel, and during testing.
+ """
def __init__(self):
+ self.acked = False
+
+ def __call__(self, msg=False):
+ self.acked = True
+
+
+class Reply:
+ """
+ Messages sent through a channel are decorated with a "reply" attribute.
+ This object is used to respond to the message through the return
+ channel.
+ """
+ def __init__(self, obj):
+ self.obj = obj
self.q = Queue.Queue()
self.acked = False
- def _ack(self, data=False):
+ def __call__(self, msg=None):
if not self.acked:
self.acked = True
- if data is None:
- self.q.put(data)
+ if msg is None:
+ self.q.put(self.obj)
else:
- self.q.put(data or self)
+ self.q.put(msg)
- def _send(self, masterq):
- self.acked = False
- try:
- masterq.put(self, timeout=3)
- while not should_exit: # pragma: no cover
- try:
- g = self.q.get(timeout=0.5)
- except Queue.Empty:
- continue
- return g
- except (Queue.Empty, Queue.Full): # pragma: no cover
- return None
+
+class Channel:
+ def __init__(self, q):
+ self.q = q
+
+ def ask(self, m):
+ """
+ Decorate a message with a reply attribute, and send it to the
+ master. then wait for a response.
+ """
+ m.reply = Reply(m)
+ self.q.put(m)
+ while not should_exit:
+ try:
+ # The timeout is here so we can handle a should_exit event.
+ g = m.reply.q.get(timeout=0.5)
+ except Queue.Empty: # pragma: nocover
+ continue
+ return g
+
+ def tell(self, m):
+ """
+ Decorate a message with a dummy reply attribute, send it to the
+ master, then return immediately.
+ """
+ m.reply = DummyReply()
+ self.q.put(m)
class Slave(threading.Thread):
- def __init__(self, masterq, server):
- self.masterq, self.server = masterq, server
- self.server.set_mqueue(masterq)
+ """
+ Slaves get a channel end-point through which they can send messages to
+ the master.
+ """
+ def __init__(self, channel, server):
+ self.channel, self.server = channel, server
+ self.server.set_channel(channel)
threading.Thread.__init__(self)
def run(self):
@@ -55,6 +93,9 @@ class Slave(threading.Thread):
class Master:
+ """
+ Masters get and respond to messages from slaves.
+ """
def __init__(self, server):
"""
server may be None if no server is needed.
@@ -81,18 +122,18 @@ class Master:
def run(self):
global should_exit
should_exit = False
- self.server.start_slave(Slave, self.masterq)
+ self.server.start_slave(Slave, Channel(self.masterq))
while not should_exit:
self.tick(self.masterq)
self.shutdown()
- def handle(self, msg): # pragma: no cover
+ def handle(self, msg):
c = "handle_" + msg.__class__.__name__.lower()
m = getattr(self, c, None)
if m:
m(msg)
else:
- msg._ack()
+ msg.reply()
def shutdown(self):
global should_exit
diff --git a/libmproxy/dump.py b/libmproxy/dump.py
index 170c701d..b1022ef5 100644
--- a/libmproxy/dump.py
+++ b/libmproxy/dump.py
@@ -21,7 +21,7 @@ class DumpError(Exception): pass
class Options(object):
- __slots__ = [
+ attributes = [
"anticache",
"anticomp",
"client_replay",
@@ -37,6 +37,7 @@ class Options(object):
"setheaders",
"server_replay",
"script",
+ "showhost",
"stickycookie",
"stickyauth",
"verbosity",
@@ -45,7 +46,7 @@ class Options(object):
def __init__(self, **kwargs):
for k, v in kwargs.items():
setattr(self, k, v)
- for i in self.__slots__:
+ for i in self.attributes:
if not hasattr(self, i):
setattr(self, i, None)
@@ -57,12 +58,12 @@ def str_response(resp):
return r
-def str_request(req):
+def str_request(req, showhost):
if req.client_conn:
c = req.client_conn.address[0]
else:
c = "[replay]"
- r = "%s %s %s"%(c, req.method, req.get_url())
+ r = "%s %s %s"%(c, req.method, req.get_url(showhost))
if req.stickycookie:
r = "[stickycookie] " + r
return r
@@ -76,6 +77,7 @@ class DumpMaster(flow.FlowMaster):
self.anticache = options.anticache
self.anticomp = options.anticomp
self.eventlog = options.eventlog
+ self.showhost = options.showhost
self.refresh_server_playback = options.refresh_server_playback
if filtstr:
@@ -93,7 +95,7 @@ class DumpMaster(flow.FlowMaster):
path = os.path.expanduser(options.wfile)
try:
f = file(path, "wb")
- self.start_stream(f)
+ self.start_stream(f, self.filt)
except IOError, v:
raise DumpError(v.strerror)
@@ -150,21 +152,12 @@ class DumpMaster(flow.FlowMaster):
print >> self.outfile, e
self.outfile.flush()
- def handle_log(self, l):
- self.add_event(l.msg)
- l._ack()
-
- def handle_request(self, r):
- f = flow.FlowMaster.handle_request(self, r)
- if f:
- r._ack()
- return f
-
def indent(self, n, t):
l = str(t).strip().split("\n")
return "\n".join(" "*n + i for i in l)
def _process_flow(self, f):
+ self.state.delete_flow(f)
if self.filt and not f.match(self.filt):
return
@@ -188,16 +181,16 @@ class DumpMaster(flow.FlowMaster):
result = " << %s"%f.error.msg
if self.o.verbosity == 1:
- print >> self.outfile, str_request(f.request)
+ print >> self.outfile, str_request(f.request, self.showhost)
print >> self.outfile, result
elif self.o.verbosity == 2:
- print >> self.outfile, str_request(f.request)
+ print >> self.outfile, str_request(f.request, self.showhost)
print >> self.outfile, self.indent(4, f.request.headers)
print >> self.outfile
print >> self.outfile, result
print >> self.outfile, "\n"
elif self.o.verbosity >= 3:
- print >> self.outfile, str_request(f.request)
+ print >> self.outfile, str_request(f.request, self.showhost)
print >> self.outfile, self.indent(4, f.request.headers)
if utils.isBin(f.request.content):
print >> self.outfile, self.indent(4, netlib.utils.hexdump(f.request.content))
@@ -208,12 +201,21 @@ class DumpMaster(flow.FlowMaster):
print >> self.outfile, "\n"
if self.o.verbosity:
self.outfile.flush()
- self.state.delete_flow(f)
+
+ def handle_log(self, l):
+ self.add_event(l.msg)
+ l.reply()
+
+ def handle_request(self, r):
+ f = flow.FlowMaster.handle_request(self, r)
+ if f:
+ r.reply()
+ return f
def handle_response(self, msg):
f = flow.FlowMaster.handle_response(self, msg)
if f:
- msg._ack()
+ msg.reply()
self._process_flow(f)
return f
diff --git a/libmproxy/flow.py b/libmproxy/flow.py
index 9238cfbf..6d77fd88 100644
--- a/libmproxy/flow.py
+++ b/libmproxy/flow.py
@@ -23,6 +23,7 @@ import tnetstring, filt, script, utils, encoding, proxy
from email.utils import parsedate_tz, formatdate, mktime_tz
from netlib import odict, http, certutils
import controller, version
+import app
HDR_FORM_URLENCODED = "application/x-www-form-urlencoded"
CONTENT_MISSING = 0
@@ -42,13 +43,13 @@ class ReplaceHooks:
def add(self, fpatt, rex, s):
"""
- Add a replacement hook.
+ add a replacement hook.
- fpatt: A string specifying a filter pattern.
- rex: A regular expression.
- s: The replacement string
+ fpatt: a string specifying a filter pattern.
+ rex: a regular expression.
+ s: the replacement string
- Returns True if hook was added, False if the pattern could not be
+ returns true if hook was added, false if the pattern could not be
parsed.
"""
cpatt = filt.parse(fpatt)
@@ -196,7 +197,15 @@ class decoded(object):
self.o.encode(self.ce)
-class HTTPMsg(controller.Msg):
+class StateObject:
+ def __eq__(self, other):
+ try:
+ return self._get_state() == other._get_state()
+ except AttributeError:
+ return False
+
+
+class HTTPMsg(StateObject):
def get_decoded_content(self):
"""
Returns the decoded content based on the current Content-Encoding header.
@@ -252,6 +261,7 @@ class HTTPMsg(controller.Msg):
return 0
return len(self.content)
+
class Request(HTTPMsg):
"""
An HTTP request.
@@ -289,7 +299,6 @@ class Request(HTTPMsg):
self.timestamp_start = timestamp_start or utils.timestamp()
self.timestamp_end = max(timestamp_end or utils.timestamp(), timestamp_start)
self.close = False
- controller.Msg.__init__(self)
# Have this request's cookies been modified by sticky cookies or auth?
self.stickycookie = False
@@ -388,15 +397,8 @@ class Request(HTTPMsg):
def __hash__(self):
return id(self)
- def __eq__(self, other):
- return self._get_state() == other._get_state()
-
def copy(self):
- """
- Returns a copy of this object.
- """
c = copy.copy(self)
- c.acked = True
c.headers = self.headers.copy()
return c
@@ -458,11 +460,19 @@ class Request(HTTPMsg):
query = utils.urlencode(odict.lst)
self.set_url(urlparse.urlunparse([scheme, netloc, path, params, query, fragment]))
- def get_url(self):
+ def get_url(self, hostheader=False):
"""
Returns a URL string, constructed from the Request's URL compnents.
+
+ If hostheader is True, we use the value specified in the request
+ Host header to construct the URL.
"""
- return utils.unparse_url(self.scheme, self.host.decode("idna"), self.port, self.path).encode('ascii')
+ if hostheader:
+ host = self.headers.get_first("host") or self.host
+ else:
+ host = self.host
+ host = host.encode("idna")
+ return utils.unparse_url(self.scheme, host, self.port, self.path).encode('ascii')
def set_url(self, url):
"""
@@ -603,7 +613,6 @@ class Response(HTTPMsg):
self.cert = cert
self.timestamp_start = timestamp_start or utils.timestamp()
self.timestamp_end = max(timestamp_end or utils.timestamp(), timestamp_start)
- controller.Msg.__init__(self)
self.replay = False
def _refresh_cookie(self, c, delta):
@@ -700,15 +709,8 @@ class Response(HTTPMsg):
state["timestamp_end"],
)
- def __eq__(self, other):
- return self._get_state() == other._get_state()
-
def copy(self):
- """
- Returns a copy of this object.
- """
c = copy.copy(self)
- c.acked = True
c.headers = self.headers.copy()
return c
@@ -773,7 +775,7 @@ class Response(HTTPMsg):
cookies.append((cookie_name, (cookie_value, cookie_parameters)))
return dict(cookies)
-class ClientDisconnect(controller.Msg):
+class ClientDisconnect:
"""
A client disconnection event.
@@ -782,11 +784,10 @@ class ClientDisconnect(controller.Msg):
client_conn: ClientConnect object.
"""
def __init__(self, client_conn):
- controller.Msg.__init__(self)
self.client_conn = client_conn
-class ClientConnect(controller.Msg):
+class ClientConnect(StateObject):
"""
A single client connection. Each connection can result in multiple HTTP
Requests.
@@ -807,10 +808,6 @@ class ClientConnect(controller.Msg):
self.close = False
self.requestcount = 0
self.error = None
- controller.Msg.__init__(self)
-
- def __eq__(self, other):
- return self._get_state() == other._get_state()
def _load_state(self, state):
self.close = True
@@ -834,15 +831,10 @@ class ClientConnect(controller.Msg):
return None
def copy(self):
- """
- Returns a copy of this object.
- """
- c = copy.copy(self)
- c.acked = True
- return c
+ return copy.copy(self)
-class Error(controller.Msg):
+class Error(StateObject):
"""
An Error.
@@ -860,18 +852,13 @@ class Error(controller.Msg):
def __init__(self, request, msg, timestamp=None):
self.request, self.msg = request, msg
self.timestamp = timestamp or utils.timestamp()
- controller.Msg.__init__(self)
def _load_state(self, state):
self.msg = state["msg"]
self.timestamp = state["timestamp"]
def copy(self):
- """
- Returns a copy of this object.
- """
c = copy.copy(self)
- c.acked = True
return c
def _get_state(self):
@@ -888,9 +875,6 @@ class Error(controller.Msg):
state["timestamp"],
)
- def __eq__(self, other):
- return self._get_state() == other._get_state()
-
def replace(self, pattern, repl, *args, **kwargs):
"""
Replaces a regular expression pattern with repl in both the headers
@@ -1180,10 +1164,11 @@ class Flow:
Kill this request.
"""
self.error = Error(self.request, "Connection killed")
- if self.request and not self.request.acked:
- self.request._ack(None)
- elif self.response and not self.response.acked:
- self.response._ack(None)
+ self.error.reply = controller.DummyReply()
+ if self.request and not self.request.reply.acked:
+ self.request.reply(proxy.KILL)
+ elif self.response and not self.response.reply.acked:
+ self.response.reply(proxy.KILL)
master.handle_error(self.error)
self.intercepting = False
@@ -1199,10 +1184,10 @@ class Flow:
Continue with the flow - called after an intercept().
"""
if self.request:
- if not self.request.acked:
- self.request._ack()
- elif self.response and not self.response.acked:
- self.response._ack()
+ if not self.request.reply.acked:
+ self.request.reply()
+ elif self.response and not self.response.reply.acked:
+ self.response.reply()
self.intercepting = False
def replace(self, pattern, repl, *args, **kwargs):
@@ -1325,7 +1310,7 @@ class State(object):
if f.request in self._flow_map:
del self._flow_map[f.request]
self._flow_list.remove(f)
- if f.match(self._limit):
+ if f in self.view:
self.view.remove(f)
return True
@@ -1368,6 +1353,7 @@ class FlowMaster(controller.Master):
self.setheaders = SetHeaders()
self.stream = None
+ app.mapp.config["PMASTER"] = self
def add_event(self, e, level="info"):
"""
@@ -1464,7 +1450,7 @@ class FlowMaster(controller.Master):
flow.response = response
if self.refresh_server_playback:
response.refresh()
- flow.request._ack(response)
+ flow.request.reply(response)
if self.server_playback.count() == 0:
self.stop_server_playback()
return True
@@ -1491,10 +1477,13 @@ class FlowMaster(controller.Master):
Loads a flow, and returns a new flow object.
"""
if f.request:
+ f.request.reply = controller.DummyReply()
fr = self.handle_request(f.request)
if f.response:
+ f.response.reply = controller.DummyReply()
self.handle_response(f.response)
if f.error:
+ f.error.reply = controller.DummyReply()
self.handle_error(f.error)
return fr
@@ -1522,7 +1511,7 @@ class FlowMaster(controller.Master):
if self.kill_nonreplay:
f.kill(self)
else:
- f.request._ack()
+ f.request.reply()
def process_new_response(self, f):
if self.stickycookie_state:
@@ -1561,11 +1550,11 @@ class FlowMaster(controller.Master):
def handle_clientconnect(self, cc):
self.run_script_hook("clientconnect", cc)
- cc._ack()
+ cc.reply()
def handle_clientdisconnect(self, r):
self.run_script_hook("clientdisconnect", r)
- r._ack()
+ r.reply()
def handle_error(self, r):
f = self.state.add_error(r)
@@ -1573,7 +1562,7 @@ class FlowMaster(controller.Master):
self.run_script_hook("error", f)
if self.client_playback:
self.client_playback.clear(f)
- r._ack()
+ r.reply()
return f
def handle_request(self, r):
@@ -1596,7 +1585,7 @@ class FlowMaster(controller.Master):
if self.stream:
self.stream.add(f)
else:
- r._ack()
+ r.reply()
return f
def shutdown(self):
@@ -1609,8 +1598,8 @@ class FlowMaster(controller.Master):
self.stream.add(i)
self.stop_stream()
- def start_stream(self, fp):
- self.stream = FlowWriter(fp)
+ def start_stream(self, fp, filt):
+ self.stream = FilteredFlowWriter(fp, filt)
def stop_stream(self):
self.stream.fo.close()
@@ -1656,3 +1645,16 @@ class FlowReader:
return
raise FlowReadError("Invalid data format.")
+
+class FilteredFlowWriter:
+ def __init__(self, fo, filt):
+ self.fo = fo
+ self.filt = filt
+
+ def add(self, f):
+ if self.filt and not f.match(self.filt):
+ return
+ d = f._get_state()
+ tnetstring.dump(d, self.fo)
+
+
diff --git a/libmproxy/proxy.py b/libmproxy/proxy.py
index f14e4e3e..3fac17b8 100644
--- a/libmproxy/proxy.py
+++ b/libmproxy/proxy.py
@@ -16,9 +16,13 @@ import sys, os, string, socket, time
import shutil, tempfile, threading
import SocketServer
from OpenSSL import SSL
-from netlib import odict, tcp, http, wsgi, certutils, http_status
-import utils, flow, version, platform, controller
-import authentication
+from netlib import odict, tcp, http, wsgi, certutils, http_status, http_auth
+import utils, flow, version, platform, controller, app
+
+
+APP_DOMAIN = "mitm"
+APP_IP = "1.1.1.1"
+KILL = 0
class ProxyError(Exception):
@@ -29,15 +33,14 @@ class ProxyError(Exception):
return "ProxyError(%s, %s)"%(self.code, self.msg)
-class Log(controller.Msg):
+class Log:
def __init__(self, msg):
- controller.Msg.__init__(self)
self.msg = msg
class ProxyConfig:
- def __init__(self, certfile = None, cacert = None, clientcerts = None, no_upstream_cert=False, body_size_limit = None, reverse_proxy=None, transparent_proxy=None, certdir = None, authenticator=None):
- assert not (reverse_proxy and transparent_proxy)
+ def __init__(self, app=False, certfile = None, cacert = None, clientcerts = None, no_upstream_cert=False, body_size_limit = None, reverse_proxy=None, transparent_proxy=None, certdir = None, authenticator=None):
+ self.app = app
self.certfile = certfile
self.cacert = cacert
self.clientcerts = clientcerts
@@ -49,45 +52,23 @@ class ProxyConfig:
self.certstore = certutils.CertStore(certdir)
-class RequestReplayThread(threading.Thread):
- def __init__(self, config, flow, masterq):
- self.config, self.flow, self.masterq = config, flow, masterq
- threading.Thread.__init__(self)
-
- def run(self):
- try:
- r = self.flow.request
- server = ServerConnection(self.config, r.host, r.port)
- server.connect(r.scheme)
- server.send(r)
- httpversion, code, msg, headers, content = http.read_response(
- server.rfile, r.method, self.config.body_size_limit
- )
- response = flow.Response(
- self.flow.request, httpversion, code, msg, headers, content, server.cert
- )
- response._send(self.masterq)
- except (ProxyError, http.HttpError, tcp.NetLibError), v:
- err = flow.Error(self.flow.request, str(v))
- err._send(self.masterq)
-
-
class ServerConnection(tcp.TCPClient):
- def __init__(self, config, host, port):
+ def __init__(self, config, scheme, host, port, sni):
tcp.TCPClient.__init__(self, host, port)
self.config = config
+ self.scheme, self.sni = scheme, sni
self.requestcount = 0
- def connect(self, scheme):
+ def connect(self):
tcp.TCPClient.connect(self)
- if scheme == "https":
+ if self.scheme == "https":
clientcert = None
if self.config.clientcerts:
path = os.path.join(self.config.clientcerts, self.host.encode("idna")) + ".pem"
if os.path.exists(path):
clientcert = path
try:
- self.convert_to_ssl(clientcert=clientcert, sni=self.host)
+ self.convert_to_ssl(cert=clientcert, sni=self.sni)
except tcp.NetLibError, v:
raise ProxyError(400, str(v))
@@ -101,49 +82,115 @@ class ServerConnection(tcp.TCPClient):
def terminate(self):
try:
- if not self.wfile.closed:
- self.wfile.flush()
+ self.wfile.flush()
self.connection.close()
except IOError:
pass
+
+class RequestReplayThread(threading.Thread):
+ 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.config, r.scheme, r.host, r.port, r.host)
+ server.connect()
+ server.send(r)
+ httpversion, code, msg, headers, content = http.read_response(
+ server.rfile, r.method, self.config.body_size_limit
+ )
+ response = flow.Response(
+ self.flow.request, httpversion, code, msg, headers, content, server.cert
+ )
+ self.channel.ask(response)
+ except (ProxyError, http.HttpError, tcp.NetLibError), v:
+ err = flow.Error(self.flow.request, str(v))
+ self.channel.ask(err)
+
+
+class HandleSNI:
+ def __init__(self, handler, client_conn, host, port, cert, key):
+ self.handler, self.client_conn, self.host, self.port = handler, client_conn, host, port
+ self.cert, self.key = cert, key
+
+ def __call__(self, connection):
+ try:
+ sn = connection.get_servername()
+ if sn:
+ self.handler.get_server_connection(self.client_conn, "https", self.host, self.port, sn)
+ new_context = SSL.Context(SSL.TLSv1_METHOD)
+ new_context.use_privatekey_file(self.key)
+ new_context.use_certificate_file(self.cert)
+ connection.set_context(new_context)
+ self.handler.sni = sn.decode("utf8").encode("idna")
+ # 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 ProxyHandler(tcp.BaseHandler):
- def __init__(self, config, connection, client_address, server, mqueue, server_version):
- self.mqueue, self.server_version = mqueue, server_version
+ def __init__(self, config, connection, client_address, server, channel, server_version):
+ self.channel, self.server_version = channel, server_version
self.config = config
- self.server_conn = None
self.proxy_connect_state = None
self.sni = None
+ self.server_conn = None
tcp.BaseHandler.__init__(self, connection, client_address, server)
+ def get_server_connection(self, cc, scheme, host, port, sni):
+ """
+ When SNI is in play, this means we have an SSL-encrypted
+ connection, which means that the entire handler is dedicated to a
+ single server connection - no multiplexing. If this assumption ever
+ breaks, we'll have to do something different with the SNI host
+ variable on the handler object.
+ """
+ sc = self.server_conn
+ if not sni:
+ sni = host
+ if sc and (scheme, host, port, sni) != (sc.scheme, sc.host, sc.port, sc.sni):
+ sc.terminate()
+ self.server_conn = None
+ self.log(
+ cc,
+ "switching connection", [
+ "%s://%s:%s (sni=%s) -> %s://%s:%s (sni=%s)"%(
+ scheme, host, port, sni,
+ sc.scheme, sc.host, sc.port, sc.sni
+ )
+ ]
+ )
+ if not self.server_conn:
+ try:
+ self.server_conn = ServerConnection(self.config, scheme, host, port, sni)
+ self.server_conn.connect()
+ except tcp.NetLibError, v:
+ raise ProxyError(502, v)
+ return self.server_conn
+
+ def del_server_connection(self):
+ self.server_conn = None
+
def handle(self):
cc = flow.ClientConnect(self.client_address)
self.log(cc, "connect")
- cc._send(self.mqueue)
+ self.channel.ask(cc)
while self.handle_request(cc) and not cc.close:
pass
cc.close = True
- cd = flow.ClientDisconnect(cc)
+ cd = flow.ClientDisconnect(cc)
self.log(
cc, "disconnect",
[
"handled %s requests"%cc.requestcount]
)
- cd._send(self.mqueue)
-
- def server_connect(self, scheme, host, port):
- sc = self.server_conn
- if sc and (host, port) != (sc.host, sc.port):
- sc.terminate()
- self.server_conn = None
- if not self.server_conn:
- try:
- self.server_conn = ServerConnection(self.config, host, port)
- self.server_conn.connect(scheme)
- except tcp.NetLibError, v:
- raise ProxyError(502, v)
+ self.channel.tell(cd)
def handle_request(self, cc):
try:
@@ -160,45 +207,68 @@ class ProxyHandler(tcp.BaseHandler):
self.log(cc, "Error in wsgi app.", err.split("\n"))
return
else:
- request = request._send(self.mqueue)
- if request is None:
+ request_reply = self.channel.ask(request)
+ if request_reply == KILL:
return
-
- if isinstance(request, flow.Response):
- response = request
+ elif isinstance(request_reply, flow.Response):
request = False
- response = response._send(self.mqueue)
+ response = request_reply
+ response_reply = self.channel.ask(response)
else:
+ request = request_reply
if self.config.reverse_proxy:
scheme, host, port = self.config.reverse_proxy
else:
scheme, host, port = request.scheme, request.host, request.port
- self.server_connect(scheme, host, port)
- self.server_conn.send(request)
- self.server_conn.rfile.reset_timestamps()
- httpversion, code, msg, headers, content = http.read_response(
- self.server_conn.rfile,
- request.method,
- self.config.body_size_limit
- )
+
+ # If we've already pumped a request over this connection,
+ # it's possible that the server has timed out. If this is
+ # the case, we want to reconnect without sending an error
+ # to the client.
+ while 1:
+ sc = self.get_server_connection(cc, scheme, host, port, self.sni)
+ sc.send(request)
+ sc.rfile.reset_timestamps()
+ try:
+ httpversion, code, msg, headers, content = http.read_response(
+ sc.rfile,
+ request.method,
+ self.config.body_size_limit
+ )
+ except http.HttpErrorConnClosed, v:
+ self.del_server_connection()
+ if sc.requestcount > 1:
+ continue
+ else:
+ raise
+ except http.HttpError, v:
+ raise ProxyError(502, "Invalid server response.")
+ else:
+ break
+
response = flow.Response(
- request, httpversion, code, msg, headers, content, self.server_conn.cert, self.server_conn.rfile.first_byte_timestamp, utils.timestamp()
+ request, httpversion, code, msg, headers, content, sc.cert,
+ sc.rfile.first_byte_timestamp, utils.timestamp()
)
+ response_reply = self.channel.ask(response)
+ # Not replying to the server invalidates the server
+ # connection, so we terminate.
+ if response_reply == KILL:
+ sc.terminate()
- response = response._send(self.mqueue)
- if response is None:
- self.server_conn.terminate()
- if response is None:
- return
- self.send_response(response)
- if request and http.request_connection_close(request.httpversion, request.headers):
- return
- # We could keep the client connection when the server
- # connection needs to go away. However, we want to mimic
- # behaviour as closely as possible to the client, so we
- # disconnect.
- if http.response_connection_close(response.httpversion, response.headers):
+ if response_reply == KILL:
return
+ else:
+ response = response_reply
+ self.send_response(response)
+ if request and http.request_connection_close(request.httpversion, request.headers):
+ return
+ # We could keep the client connection when the server
+ # connection needs to go away. However, we want to mimic
+ # behaviour as closely as possible to the client, so we
+ # disconnect.
+ if http.response_connection_close(response.httpversion, response.headers):
+ return
except (IOError, ProxyError, http.HttpError, tcp.NetLibDisconnect), e:
if hasattr(e, "code"):
cc.error = "%s: %s"%(e.code, e.msg)
@@ -207,14 +277,13 @@ class ProxyHandler(tcp.BaseHandler):
if request:
err = flow.Error(request, cc.error)
- err._send(self.mqueue)
+ self.channel.ask(err)
self.log(
cc, cc.error,
["url: %s"%request.get_url()]
)
else:
self.log(cc, cc.error)
-
if isinstance(e, ProxyError):
self.send_error(e.code, e.msg, e.headers)
else:
@@ -228,23 +297,20 @@ class ProxyHandler(tcp.BaseHandler):
msg.append(" -> "+i)
msg = "\n".join(msg)
l = Log(msg)
- l._send(self.mqueue)
+ self.channel.tell(l)
- def find_cert(self, host, port, sni):
+ def find_cert(self, cc, host, port, sni):
if self.config.certfile:
return self.config.certfile
else:
sans = []
if not self.config.no_upstream_cert:
- try:
- cert = certutils.get_remote_cert(host, port, sni)
- except tcp.NetLibError, v:
- raise ProxyError(502, "Unable to get remote cert: %s"%str(v))
- sans = cert.altnames
- host = cert.cn.decode("utf8").encode("idna")
+ conn = self.get_server_connection(cc, "https", host, port, sni)
+ sans = conn.cert.altnames
+ host = conn.cert.cn.decode("utf8").encode("idna")
ret = self.config.certstore.get_cert(host, sans, self.config.cacert)
if not ret:
- raise ProxyError(502, "mitmproxy: Unable to generate dummy cert.")
+ raise ProxyError(502, "Unable to generate dummy cert.")
return ret
def get_line(self, fp):
@@ -256,26 +322,27 @@ class ProxyHandler(tcp.BaseHandler):
line = fp.readline()
return line
- def handle_sni(self, conn):
- sn = conn.get_servername()
- if sn:
- self.sni = sn.decode("utf8").encode("idna")
-
def read_request_transparent(self, client_conn):
orig = self.config.transparent_proxy["resolver"].original_addr(self.connection)
if not orig:
raise ProxyError(502, "Transparent mode failure: could not resolve original destination.")
+ self.log(client_conn, "transparent to %s:%s"%orig)
+
host, port = orig
- if not self.ssl_established and (port in self.config.transparent_proxy["sslports"]):
+ if port in self.config.transparent_proxy["sslports"]:
scheme = "https"
- certfile = self.find_cert(host, port, None)
- try:
- self.convert_to_ssl(certfile, self.config.certfile or self.config.cacert)
- except tcp.NetLibError, v:
- raise ProxyError(400, str(v))
+ if not self.ssl_established:
+ dummycert = self.find_cert(client_conn, host, port, host)
+ sni = HandleSNI(
+ self, client_conn, host, port,
+ dummycert, self.config.certfile or self.config.cacert
+ )
+ try:
+ self.convert_to_ssl(dummycert, self.config.certfile or self.config.cacert, handle_sni=sni)
+ except tcp.NetLibError, v:
+ raise ProxyError(400, str(v))
else:
scheme = "http"
- host = self.sni or host
line = self.get_line(self.rfile)
if line == "":
return None
@@ -292,50 +359,33 @@ class ProxyHandler(tcp.BaseHandler):
self.rfile.first_byte_timestamp, utils.timestamp()
)
- def read_request_reverse(self, client_conn):
- line = self.get_line(self.rfile)
- if line == "":
- return None
- scheme, host, port = self.config.reverse_proxy
- r = http.parse_init_http(line)
- if not r:
- raise ProxyError(400, "Bad HTTP request line: %s"%repr(line))
- method, path, httpversion = r
- headers = self.read_headers(authenticate=False)
- content = http.read_http_body_request(
- self.rfile, self.wfile, headers, httpversion, self.config.body_size_limit
- )
- return flow.Request(
- client_conn, httpversion, host, port, "http", method, path, headers, content,
- self.rfile.first_byte_timestamp, utils.timestamp()
- )
-
-
def read_request_proxy(self, client_conn):
line = self.get_line(self.rfile)
if line == "":
return None
- if http.parse_init_connect(line):
- r = http.parse_init_connect(line)
- if not r:
- raise ProxyError(400, "Bad HTTP request line: %s"%repr(line))
- host, port, httpversion = r
- headers = self.read_headers(authenticate=True)
-
- self.wfile.write(
- 'HTTP/1.1 200 Connection established\r\n' +
- ('Proxy-agent: %s\r\n'%self.server_version) +
- '\r\n'
- )
- self.wfile.flush()
- certfile = self.find_cert(host, port, None)
- try:
- self.convert_to_ssl(certfile, self.config.certfile or self.config.cacert)
- except tcp.NetLibError, v:
- raise ProxyError(400, str(v))
- self.proxy_connect_state = (host, port, httpversion)
- line = self.rfile.readline(line)
+ if not self.proxy_connect_state:
+ connparts = http.parse_init_connect(line)
+ if connparts:
+ host, port, httpversion = connparts
+ headers = self.read_headers(authenticate=True)
+ self.wfile.write(
+ 'HTTP/1.1 200 Connection established\r\n' +
+ ('Proxy-agent: %s\r\n'%self.server_version) +
+ '\r\n'
+ )
+ self.wfile.flush()
+ dummycert = self.find_cert(client_conn, host, port, host)
+ sni = HandleSNI(
+ self, client_conn, host, port,
+ dummycert, self.config.certfile or self.config.cacert
+ )
+ try:
+ self.convert_to_ssl(dummycert, self.config.certfile or self.config.cacert, handle_sni=sni)
+ except tcp.NetLibError, v:
+ raise ProxyError(400, str(v))
+ self.proxy_connect_state = (host, port, httpversion)
+ line = self.rfile.readline(line)
if self.proxy_connect_state:
r = http.parse_init_http(line)
@@ -366,6 +416,24 @@ class ProxyHandler(tcp.BaseHandler):
self.rfile.first_byte_timestamp, utils.timestamp()
)
+ def read_request_reverse(self, client_conn):
+ line = self.get_line(self.rfile)
+ if line == "":
+ return None
+ scheme, host, port = self.config.reverse_proxy
+ r = http.parse_init_http(line)
+ if not r:
+ raise ProxyError(400, "Bad HTTP request line: %s"%repr(line))
+ method, path, httpversion = r
+ headers = self.read_headers(authenticate=False)
+ content = http.read_http_body_request(
+ self.rfile, self.wfile, headers, httpversion, self.config.body_size_limit
+ )
+ return flow.Request(
+ client_conn, httpversion, host, port, "http", method, path, headers, content,
+ self.rfile.first_byte_timestamp, utils.timestamp()
+ )
+
def read_request(self, client_conn):
self.rfile.reset_timestamps()
if self.config.transparent_proxy:
@@ -431,23 +499,31 @@ class ProxyServer(tcp.TCPServer):
tcp.TCPServer.__init__(self, (address, port))
except socket.error, v:
raise ProxyServerError('Error starting proxy server: ' + v.strerror)
- self.masterq = None
+ self.channel = None
self.apps = AppRegistry()
+ if config.app:
+ self.apps.add(
+ app.mapp,
+ APP_DOMAIN,
+ 80
+ )
+ self.apps.add(
+ app.mapp,
+ APP_IP,
+ 80
+ )
- def start_slave(self, klass, masterq):
- slave = klass(masterq, self)
+ def start_slave(self, klass, channel):
+ slave = klass(channel, self)
slave.start()
- def set_mqueue(self, q):
- self.masterq = q
+ def set_channel(self, channel):
+ self.channel = channel
def handle_connection(self, request, client_address):
- h = ProxyHandler(self.config, request, client_address, self, self.masterq, self.server_version)
+ h = ProxyHandler(self.config, request, client_address, self, self.channel, self.server_version)
h.handle()
- try:
- h.finish()
- except tcp.NetLibDisconnect, e:
- pass
+ h.finish()
def handle_shutdown(self):
self.config.certstore.cleanup()
@@ -480,7 +556,7 @@ class DummyServer:
def __init__(self, config):
self.config = config
- def start_slave(self, klass, masterq):
+ def start_slave(self, *args):
pass
def shutdown(self):
@@ -513,22 +589,19 @@ def process_proxy_options(parser, options):
if options.cert:
options.cert = os.path.expanduser(options.cert)
if not os.path.exists(options.cert):
- parser.error("Manually created certificate does not exist: %s"%options.cert)
+ return parser.error("Manually created certificate does not exist: %s"%options.cert)
cacert = os.path.join(options.confdir, "mitmproxy-ca.pem")
cacert = os.path.expanduser(cacert)
if not os.path.exists(cacert):
certutils.dummy_ca(cacert)
- if getattr(options, "cache", None) is not None:
- options.cache = os.path.expanduser(options.cache)
body_size_limit = utils.parse_size(options.body_size_limit)
-
if options.reverse_proxy and options.transparent_proxy:
- parser.errror("Can't set both reverse proxy and transparent proxy.")
+ return parser.error("Can't set both reverse proxy and transparent proxy.")
if options.transparent_proxy:
if not platform.resolver:
- parser.error("Transparent mode not supported on this platform.")
+ return parser.error("Transparent mode not supported on this platform.")
trans = dict(
resolver = platform.resolver(),
sslports = TRANSPARENT_SSL_PORTS
@@ -539,35 +612,39 @@ def process_proxy_options(parser, options):
if options.reverse_proxy:
rp = utils.parse_proxy_spec(options.reverse_proxy)
if not rp:
- parser.error("Invalid reverse proxy specification: %s"%options.reverse_proxy)
+ return parser.error("Invalid reverse proxy specification: %s"%options.reverse_proxy)
else:
rp = 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):
- parser.error("Client certificate directory does not exist or is not a directory: %s"%options.clientcerts)
+ return parser.error("Client certificate directory does not exist or is not a directory: %s"%options.clientcerts)
if options.certdir:
options.certdir = os.path.expanduser(options.certdir)
if not os.path.exists(options.certdir) or not os.path.isdir(options.certdir):
- parser.error("Dummy cert directory does not exist or is not a directory: %s"%options.certdir)
+ return parser.error("Dummy cert directory does not exist or is not a directory: %s"%options.certdir)
if (options.auth_nonanonymous or options.auth_singleuser or options.auth_htpasswd):
if options.auth_singleuser:
if len(options.auth_singleuser.split(':')) != 2:
- parser.error("Please specify user in the format username:password")
+ return parser.error("Invalid single-user specification. Please use the format username:password")
username, password = options.auth_singleuser.split(':')
- password_manager = authentication.SingleUserPasswordManager(username, password)
+ password_manager = http_auth.PassManSingleUser(username, password)
elif options.auth_nonanonymous:
- password_manager = authentication.PermissivePasswordManager()
+ password_manager = http_auth.PassManNonAnon()
elif options.auth_htpasswd:
- password_manager = authentication.HtpasswdPasswordManager(options.auth_htpasswd)
- authenticator = authentication.BasicProxyAuth(password_manager, "mitmproxy")
+ 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 = authentication.NullProxyAuth(None)
+ authenticator = http_auth.NullProxyAuth(None)
return ProxyConfig(
+ app = options.app,
certfile = options.cert,
cacert = cacert,
clientcerts = options.clientcerts,