diff options
-rw-r--r-- | libpathod/pathod.py | 21 | ||||
-rw-r--r-- | libpathod/templates/docs_pathod.html | 16 | ||||
-rw-r--r-- | libpathod/test.py | 1 | ||||
-rw-r--r-- | libpathod/utils.py | 13 | ||||
-rwxr-xr-x | pathod | 30 | ||||
-rw-r--r-- | test/test_test.py | 1 | ||||
-rw-r--r-- | test/test_utils.py | 10 |
7 files changed, 77 insertions, 15 deletions
diff --git a/libpathod/pathod.py b/libpathod/pathod.py index d52af15b..587e51bf 100644 --- a/libpathod/pathod.py +++ b/libpathod/pathod.py @@ -1,7 +1,7 @@ import urllib, threading, re, logging, socket, sys, base64 from netlib import tcp, http, odict, wsgi import netlib.utils -import version, app, language +import version, app, language, utils logger = logging.getLogger('pathod') @@ -54,13 +54,24 @@ class PathodHandler(tcp.BaseHandler): # Normal termination return False, None - parts = http.parse_init_http(line) - if not parts: + m = utils.MemBool() + if m(http.parse_init_connect(line)): + self.wfile.write( + 'HTTP/1.1 200 Connection established\r\n' + + ('Proxy-agent: %s\r\n'%version.NAMEVERSION) + + '\r\n' + ) + self.wfile.flush() + + if m(http.parse_init_proxy(line)): + method, _, _, _, path, httpversion = m.v + elif m(http.parse_init_http(line)): + method, path, httpversion = m.v + else: s = "Invalid first line: %s"%repr(line) self.info(s) return False, dict(type = "error", msg = s) - method, path, httpversion = parts headers = http.read_headers(self.rfile) if headers is None: s = "Invalid headers" @@ -133,7 +144,7 @@ class PathodHandler(tcp.BaseHandler): self.info("\n".join(s)) def handle(self): - if self.server.ssloptions: + if self.server.ssloptions and not self.server.ssloptions["ssl_after_connect"]: try: self.convert_to_ssl( self.server.ssloptions["certfile"], diff --git a/libpathod/templates/docs_pathod.html b/libpathod/templates/docs_pathod.html index 8b345d71..42459352 100644 --- a/libpathod/templates/docs_pathod.html +++ b/libpathod/templates/docs_pathod.html @@ -45,6 +45,22 @@ those, use the command-line help:</p> </section> +<section> + <div class="page-header"> + <h1>Acting as a proxy</h1> + </div> + +<p>Pathod automatically responds to both straight HTTP and proxy requests. For +proxy requests, the upstream host is ignored, and the path portion of the URL +is used to match anchors. This lets you test software that supports a proxy +configuration by spoofing responses from upstream servers.</p> + +<p>Proxy mode operates even when Pathod is run in SSL mode, but we do not +support nested SSL connections. This means that CONNECT requests will cause an +error response.</p> + +</section> + <section> <div class="page-header"> diff --git a/libpathod/test.py b/libpathod/test.py index ed0ee26a..22dc035d 100644 --- a/libpathod/test.py +++ b/libpathod/test.py @@ -75,6 +75,7 @@ class _PaThread(threading.Thread): ssloptions = dict( keyfile = utils.data.path("resources/server.key"), certfile = utils.data.path("resources/server.crt"), + ssl_after_connect = False ) else: ssloptions = self.ssl diff --git a/libpathod/utils.py b/libpathod/utils.py index 70a97cff..26e9427d 100644 --- a/libpathod/utils.py +++ b/libpathod/utils.py @@ -8,6 +8,19 @@ SIZE_UNITS = dict( t = 1024**4, ) + +class MemBool: + """ + Truth-checking with a memory, for use in chained if statements. + """ + def __init__(self): + self.v = None + + def __call__(self, v): + self.v = v + return bool(v) + + def parse_size(s): try: return int(s) @@ -36,12 +36,13 @@ def main(parser, args): parser.error("Both --certfile and --keyfile must be specified.") if args.ssl: - ssl = dict( + ssloptions = dict( keyfile = args.ssl_keyfile or utils.data.path("resources/server.key"), certfile = args.ssl_certfile or utils.data.path("resources/server.crt"), + ssl_after_connect = args.ssl_after_connect ) else: - ssl = None + ssloptions = None alst = [] for i in args.anchors: @@ -81,7 +82,7 @@ def main(parser, args): pd = pathod.Pathod( (args.address, args.port), craftanchor = args.craftanchor, - ssloptions = ssl, + ssloptions = ssloptions, staticdir = args.staticdir, anchors = alst, sizelimit = sizelimit, @@ -93,7 +94,7 @@ def main(parser, args): logreq = args.logreq, logresp = args.logresp, hexdump = args.hexdump, - explain = args.explain + explain = args.explain, ) except pathod.PathodError, v: parser.error(str(v)) @@ -128,10 +129,6 @@ if __name__ == "__main__": help='Daemonize.' ) parser.add_argument( - "-s", dest='ssl', default=False, action="store_true", - help='Serve with SSL.' - ) - parser.add_argument( "-t", dest="timeout", type=int, default=None, help="Connection timeout" ) @@ -155,11 +152,24 @@ if __name__ == "__main__": "--nocraft", dest='nocraft', default=False, action="store_true", help='Disable response crafting. If anchors are specified, they still work.' ) - parser.add_argument( + + + group = parser.add_argument_group( + 'SSL', + ) + group.add_argument( + "-C", dest='ssl_after_connect', default=False, action="store_true", + help='Expect SSL after a CONNECT request.' + ) + group.add_argument( + "-s", dest='ssl', default=False, action="store_true", + help='Serve with SSL.' + ) + group.add_argument( "--keyfile", dest='ssl_keyfile', default=None, type=str, help='SSL key file. If not specified, a default key is used.' ) - parser.add_argument( + group.add_argument( "--certfile", dest='ssl_certfile', default=None, type=str, help='SSL cert file. If not specified, a default cert is used.' ) diff --git a/test/test_test.py b/test/test_test.py index fc97d263..89889ba1 100644 --- a/test/test_test.py +++ b/test/test_test.py @@ -25,6 +25,7 @@ class TestDaemonManual: ssloptions = dict( keyfile = utils.data.path("resources/server.key"), certfile = utils.data.path("resources/server.crt"), + ssl_after_connect = False ) d = test.Daemon(ssl=ssloptions) rsp = requests.get("https://localhost:%s/p/202:da"%d.port, verify=False) diff --git a/test/test_utils.py b/test/test_utils.py index 0a48d87d..b8aa7f12 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -1,6 +1,16 @@ from libpathod import utils import tutils + +def test_membool(): + m = utils.MemBool() + assert not m.v + assert m(1) + assert m.v == 1 + assert m(2) + assert m.v == 2 + + def test_parse_size(): assert utils.parse_size("100") == 100 assert utils.parse_size("100k") == 100 * 1024 |