Add -q and -r flags to pathod, logging request and respnose bytes.
- These flags also mean that a bytes log is included in the internal log buffer. - There's an -x flag to turn on hex dump output in the text logs (does not affect the log buffer).
This commit is contained in:
parent
915bcfbd30
commit
41f1c66772
|
@ -1,10 +1,10 @@
|
||||||
import urllib, threading, re, logging, socket, sys
|
import urllib, threading, re, logging, socket, sys
|
||||||
from netlib import tcp, http, odict, wsgi
|
from netlib import tcp, http, odict, wsgi
|
||||||
|
import netlib.utils
|
||||||
import version, app, rparse
|
import version, app, rparse
|
||||||
|
|
||||||
logger = logging.getLogger('pathod')
|
logger = logging.getLogger('pathod')
|
||||||
|
|
||||||
|
|
||||||
class PathodError(Exception): pass
|
class PathodError(Exception): pass
|
||||||
|
|
||||||
|
|
||||||
|
@ -19,51 +19,41 @@ class PathodHandler(tcp.BaseHandler):
|
||||||
|
|
||||||
def serve_crafted(self, crafted, request_log):
|
def serve_crafted(self, crafted, request_log):
|
||||||
response_log = crafted.serve(self.wfile, self.server.check_policy)
|
response_log = crafted.serve(self.wfile, self.server.check_policy)
|
||||||
self.server.add_log(
|
log = dict(
|
||||||
dict(
|
|
||||||
type = "crafted",
|
type = "crafted",
|
||||||
request=request_log,
|
request=request_log,
|
||||||
response=response_log
|
response=response_log
|
||||||
)
|
)
|
||||||
)
|
|
||||||
if response_log["disconnect"]:
|
if response_log["disconnect"]:
|
||||||
return False
|
return False, log
|
||||||
return True
|
return True, log
|
||||||
|
|
||||||
def handle_request(self):
|
def handle_request(self):
|
||||||
"""
|
"""
|
||||||
Returns True if handling should continue.
|
Returns a (again, log) tuple.
|
||||||
|
|
||||||
|
again: True if request handling should continue.
|
||||||
|
log: A dictionary, or None
|
||||||
"""
|
"""
|
||||||
line = self.rfile.readline()
|
line = self.rfile.readline()
|
||||||
if line == "\r\n" or line == "\n": # Possible leftover from previous message
|
if line == "\r\n" or line == "\n": # Possible leftover from previous message
|
||||||
line = self.rfile.readline()
|
line = self.rfile.readline()
|
||||||
if line == "":
|
if line == "":
|
||||||
return
|
# Normal termination
|
||||||
|
return False, None
|
||||||
|
|
||||||
parts = http.parse_init_http(line)
|
parts = http.parse_init_http(line)
|
||||||
if not parts:
|
if not parts:
|
||||||
s = "Invalid first line: %s"%repr(line)
|
s = "Invalid first line: %s"%repr(line)
|
||||||
self.info(s)
|
self.info(s)
|
||||||
self.server.add_log(
|
return False, dict(type = "error", msg = s)
|
||||||
dict(
|
|
||||||
type = "error",
|
|
||||||
msg = s
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
method, path, httpversion = parts
|
method, path, httpversion = parts
|
||||||
headers = http.read_headers(self.rfile)
|
headers = http.read_headers(self.rfile)
|
||||||
if headers is None:
|
if headers is None:
|
||||||
s = "Invalid headers"
|
s = "Invalid headers"
|
||||||
self.info(s)
|
self.info(s)
|
||||||
self.server.add_log(
|
return False, dict(type = "error", msg = s)
|
||||||
dict(
|
|
||||||
type = "error",
|
|
||||||
msg = s
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
request_log = dict(
|
request_log = dict(
|
||||||
path = path,
|
path = path,
|
||||||
|
@ -81,13 +71,7 @@ class PathodHandler(tcp.BaseHandler):
|
||||||
except http.HttpError, s:
|
except http.HttpError, s:
|
||||||
s = str(s)
|
s = str(s)
|
||||||
self.info(s)
|
self.info(s)
|
||||||
self.server.add_log(
|
return False, dict(type = "error", msg = s)
|
||||||
dict(
|
|
||||||
type = "error",
|
|
||||||
msg = s
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
for i in self.server.anchors:
|
for i in self.server.anchors:
|
||||||
if i[0].match(path):
|
if i[0].match(path):
|
||||||
|
@ -113,7 +97,7 @@ class PathodHandler(tcp.BaseHandler):
|
||||||
elif self.server.noweb:
|
elif self.server.noweb:
|
||||||
crafted = rparse.PathodErrorResponse("Access Denied")
|
crafted = rparse.PathodErrorResponse("Access Denied")
|
||||||
crafted.serve(self.wfile, self.server.check_policy)
|
crafted.serve(self.wfile, self.server.check_policy)
|
||||||
return False
|
return False, dict(type = "error", msg="Access denied: web interface disabled")
|
||||||
else:
|
else:
|
||||||
self.info("app: %s %s"%(method, path))
|
self.info("app: %s %s"%(method, path))
|
||||||
cc = wsgi.ClientConn(self.client_address)
|
cc = wsgi.ClientConn(self.client_address)
|
||||||
|
@ -126,7 +110,18 @@ class PathodHandler(tcp.BaseHandler):
|
||||||
version.NAMEVERSION
|
version.NAMEVERSION
|
||||||
)
|
)
|
||||||
app.serve(req, self.wfile)
|
app.serve(req, self.wfile)
|
||||||
return True
|
return True, None
|
||||||
|
|
||||||
|
def _log_bytes(self, header, data, hexdump):
|
||||||
|
s = []
|
||||||
|
if hexdump:
|
||||||
|
s.append("%s (hex dump):"%header)
|
||||||
|
for line in netlib.utils.hexdump(data):
|
||||||
|
s.append("\t%s %s %s"%line)
|
||||||
|
else:
|
||||||
|
s.append("%s (unprintables escaped):"%header)
|
||||||
|
s.append(netlib.utils.cleanBin(data))
|
||||||
|
self.info("\n".join(s))
|
||||||
|
|
||||||
def handle(self):
|
def handle(self):
|
||||||
if self.server.ssloptions:
|
if self.server.ssloptions:
|
||||||
|
@ -147,7 +142,20 @@ class PathodHandler(tcp.BaseHandler):
|
||||||
return
|
return
|
||||||
self.settimeout(self.server.timeout)
|
self.settimeout(self.server.timeout)
|
||||||
while not self.finished:
|
while not self.finished:
|
||||||
if not self.handle_request():
|
if self.server.logreq:
|
||||||
|
self.rfile.start_log()
|
||||||
|
if self.server.logresp:
|
||||||
|
self.wfile.start_log()
|
||||||
|
again, log = self.handle_request()
|
||||||
|
if log:
|
||||||
|
if self.server.logreq:
|
||||||
|
log["request_bytes"] = self.rfile.get_log()
|
||||||
|
self._log_bytes("Request", log["request_bytes"], self.server.hexdump)
|
||||||
|
if self.server.logresp:
|
||||||
|
log["response_bytes"] = self.wfile.get_log()
|
||||||
|
self._log_bytes("Response", log["response_bytes"], self.server.hexdump)
|
||||||
|
self.server.add_log(log)
|
||||||
|
if not again:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
|
@ -156,7 +164,7 @@ class Pathod(tcp.TCPServer):
|
||||||
def __init__( self,
|
def __init__( self,
|
||||||
addr, ssloptions=None, craftanchor="/p/", staticdir=None, anchors=None,
|
addr, ssloptions=None, craftanchor="/p/", staticdir=None, anchors=None,
|
||||||
sizelimit=None, noweb=False, nocraft=False, noapi=False, nohang=False,
|
sizelimit=None, noweb=False, nocraft=False, noapi=False, nohang=False,
|
||||||
timeout=None
|
timeout=None, logreq=False, logresp=False, hexdump=False
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
addr: (address, port) tuple. If port is 0, a free port will be
|
addr: (address, port) tuple. If port is 0, a free port will be
|
||||||
|
@ -176,7 +184,8 @@ class Pathod(tcp.TCPServer):
|
||||||
self.craftanchor = craftanchor
|
self.craftanchor = craftanchor
|
||||||
self.sizelimit = sizelimit
|
self.sizelimit = sizelimit
|
||||||
self.noweb, self.nocraft, self.noapi, self.nohang = noweb, nocraft, noapi, nohang
|
self.noweb, self.nocraft, self.noapi, self.nohang = noweb, nocraft, noapi, nohang
|
||||||
self.timeout = timeout
|
self.timeout, self.logreq, self.logresp, self.hexdump = timeout, logreq, logresp, hexdump
|
||||||
|
|
||||||
if not noapi:
|
if not noapi:
|
||||||
app.api()
|
app.api()
|
||||||
self.app = app.app
|
self.app = app.app
|
||||||
|
|
33
pathod
33
pathod
|
@ -89,7 +89,10 @@ def main(parser, args):
|
||||||
nocraft = args.nocraft,
|
nocraft = args.nocraft,
|
||||||
noapi = args.noapi,
|
noapi = args.noapi,
|
||||||
nohang = args.nohang,
|
nohang = args.nohang,
|
||||||
timeout = args.timeout
|
timeout = args.timeout,
|
||||||
|
logreq = args.logreq,
|
||||||
|
logresp = args.logresp,
|
||||||
|
hexdump = args.hexdump
|
||||||
)
|
)
|
||||||
except pathod.PathodError, v:
|
except pathod.PathodError, v:
|
||||||
parser.error(str(v))
|
parser.error(str(v))
|
||||||
|
@ -123,14 +126,6 @@ if __name__ == "__main__":
|
||||||
"-D", dest='daemonize', default=False, action="store_true",
|
"-D", dest='daemonize', default=False, action="store_true",
|
||||||
help='Daemonize.'
|
help='Daemonize.'
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
|
||||||
"-f", dest='logfile', default=None, type=str,
|
|
||||||
help='Log file.'
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--debug", dest='debug', default=False, action="store_true",
|
|
||||||
help='Enable debug output.'
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-s", dest='ssl', default=False, action="store_true",
|
"-s", dest='ssl', default=False, action="store_true",
|
||||||
help='Serve with SSL.'
|
help='Serve with SSL.'
|
||||||
|
@ -167,6 +162,26 @@ if __name__ == "__main__":
|
||||||
"--certfile", dest='ssl_certfile', default=None, type=str,
|
"--certfile", dest='ssl_certfile', default=None, type=str,
|
||||||
help='SSL cert file. If not specified, a default cert is used.'
|
help='SSL cert file. If not specified, a default cert is used.'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
group = parser.add_argument_group('Controlling Output')
|
||||||
|
group.add_argument(
|
||||||
|
"-f", dest='logfile', default=None, type=str,
|
||||||
|
help='Log to file.'
|
||||||
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"-q", dest="logreq", action="store_true", default=False,
|
||||||
|
help="Log full request"
|
||||||
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"-r", dest="logresp", action="store_true", default=False,
|
||||||
|
help="Log full response"
|
||||||
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"-x", dest="hexdump", action="store_true", default=False,
|
||||||
|
help="Log request/response in hexdump format"
|
||||||
|
)
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
if args.daemonize:
|
if args.daemonize:
|
||||||
daemonize()
|
daemonize()
|
||||||
|
|
|
@ -20,6 +20,8 @@ class DaemonTests:
|
||||||
noapi = self.noapi,
|
noapi = self.noapi,
|
||||||
nohang = self.nohang,
|
nohang = self.nohang,
|
||||||
timeout = self.timeout,
|
timeout = self.timeout,
|
||||||
|
logreq = True,
|
||||||
|
logresp = True
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
Loading…
Reference in New Issue