[docs]class Logger:
"""
The base class for loggers for use with Netcat objects. Each of these
methods will be called to indicate a given event.
"""
[docs] def connected(self, peer):
"""
Called with a tuple of the peer to indicate "connection established",
either as a client or a server
"""
[docs] def sending(self, data):
"""
Called to indicate that some data has been sent over the wire
"""
[docs] def buffering(self, data):
"""
Called to indicate that some data has been received and inserted into
the buffer
"""
[docs] def unbuffering(self, data):
"""
Called to indicate that some data is being extracted from the buffer
and returned to the user
"""
[docs] def interrupted(self):
"""
Called to indicate that a socket operation was interrupted via ctrl-c
"""
[docs] def eofed(self):
"""
Called to indicate that reading from the socket resulted in an EOF
condition
"""
[docs] def requesting_send(self, data):
"""
Called to indicate that the user has asked to send all of some data
"""
[docs] def requesting_recv(self, n, timeout):
"""
Called to indicate that the user has asked for a receive of at most n
bytes
"""
[docs] def requesting_recv_until(self, s, max_size, timeout):
"""
Called to indicate that the user has asked to receive until a given
string appears
"""
[docs] def requesting_recv_all(self, timeout):
"""
Called to indicate that the user has asked to receive all data until
close
"""
[docs] def requesting_recv_exactly(self, n, timeout):
"""
Called to indicate that the user has asked to receive exactly n bytes
"""
[docs] def interact_starting(self):
"""
Called to indicate that an interactive session is beginning
"""
[docs] def interact_ending(self):
"""
Called to indicate that an interactive session is ending
"""
[docs]class ManyLogger(Logger):
"""
A logger which dispatches all events to all its children, which are other
loggers. You shouldn't have to deal with this much; it's used automatically
by the Netcat.
:param children: A list of loggers to which to dispatch events
"""
def __init__(self, children):
self.children = children
def connected(self, peer):
for child in self.children:
child.connected(peer)
def sending(self, data):
for child in self.children:
child.sending(data)
def buffering(self, data):
for child in self.children:
child.buffering(data)
def unbuffering(self, data):
for child in self.children:
child.unbuffering(data)
def interrupted(self):
for child in self.children:
child.interrupted()
def eofed(self):
for child in self.children:
child.eofed()
def requesting_send(self, data):
for child in self.children:
child.requesting_send(data)
def requesting_recv(self, n, timeout):
for child in self.children:
child.requesting_recv(n, timeout)
def requesting_recv_until(self, s, max_size, timeout):
for child in self.children:
child.requesting_recv_until(s, max_size, timeout)
def requesting_recv_all(self, timeout):
for child in self.children:
child.requesting_recv_all(timeout)
def requesting_recv_exactly(self, n, timeout):
for child in self.children:
child.requesting_recv_exactly(n, timeout)
def interact_starting(self):
for child in self.children:
child.interact_starting()
def interact_ending(self):
for child in self.children:
child.interact_ending()
[docs]class TeeLogger(Logger):
"""
A logger which feeds a copy of the input and output streams to a given stream
:param log_send: A simplesock object to log all sends to, or None
:param log_recv: A simplesock object to log all recvs to, or None
:param log_yield: Whether recv logging should happen when data is
buffered or returned to the user
"""
def __init__(self, log_send=None, log_recv=None, log_yield=False):
self.log_send = log_send
self.log_recv = log_recv
self.log_yield = log_yield
def sending(self, data):
if self.log_send is not None:
self.log_send.send(data)
def buffering(self, data):
if not self.log_yield and self.log_recv is not None:
self.log_recv.send(data)
def unbuffering(self, data):
if self.log_yield and self.log_recv is not None:
self.log_recv.send(data)
[docs]class StandardLogger(Logger):
"""
A logger which produces a human-readable log of what's happening
:param log: A simplesock object to which the logs should
be sent
:param log_yield: Whether to log receives when they are recieved
and written to the buffer or when they are
unbuffered and returned to the user
:param show_headers: Whether to show metadata notices about user
actions and exceptional conditions
:param hex_dump: Whether to encode logged data as a canonical
hexdump
:param split_newlines: When in non-hex mode, whether logging should
treat newlines as record separators
"""
def __init__(self, log, log_yield=False, show_headers=True,
hex_dump=False, split_newlines=True,
send_prefix='>> ', recv_prefix='<< ', no_eol_indicator='\x1b[3m%\x1b[0m'):
self.log = log
self.log_yield = log_yield
self.show_headers = show_headers
self.hex_dump = hex_dump
self.split_newlines = split_newlines
self.send_prefix = send_prefix
self.recv_prefix = recv_prefix
self.no_eol_indicator = no_eol_indicator
self.suppressed = False
self.counter_send = 0
self.counter_recv = 0
def _log(self, s):
self.log.send(s.encode())
def _header(self, s):
if self.show_headers:
self._log('======= %s =======\n' % s)
def sending(self, data):
if self.suppressed:
return
self._log_data(data, self.counter_send, self.send_prefix)
self.counter_send += len(data)
def _recving(self, data):
if self.suppressed:
return
self._log_data(data, self.counter_recv, self.recv_prefix)
self.counter_recv += len(data)
def _log_data(self, data, counter, prefix):
if not data:
return
if self.hex_dump:
line_progress = counter % 16
first_line_size = 16 - line_progress
first_line = data[:first_line_size]
self._log(prefix + self._hex_line(counter, first_line))
for i in range(first_line_size, len(data), 16):
line = data[i:i+16]
self._log(prefix + self._hex_line(counter + i, line))
else:
noeol = False
if self.split_newlines:
records = data.split(b'\n')
if len(records) > 1 and records[-1] == b'':
records.pop()
else:
noeol = True
else:
records = [data]
for i, record in enumerate(records):
sep = (self.no_eol_indicator if noeol and i == len(records) - 1 else '') + '\n'
self._log(prefix + self._escape(record) + sep)
@staticmethod
def _hex_line(counter, line):
advance = counter % 16
tail = 16 - advance - len(line)
lhex = line.hex().upper()
lhex = ' '*advance + lhex + ' '*tail
lascii = ' '*advance + ''.join(chr(c) if 0x20 <= c <= 0x7e else '.' for c in line) + ' '*tail
fargs = (counter,) + tuple(lhex[i:i+2] for i in range(0, 0x20, 2)) + (lascii,)
return '%06X %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s |%s|\n' % fargs
@staticmethod
def _escape(bs):
return ''.join(StandardLogger._escchr(c) for c in bs)
@staticmethod
def _escchr(c):
if c == ord('\\'):
return '\\\\'
if c == ord('\n'):
return '\\n'
if c == ord('\t'):
return '\\t'
if c < 0x20 or c > 0x7e:
return '\\x%02x' % c
return chr(c)
def buffering(self, data):
if not self.log_yield:
self._recving(data)
def unbuffering(self, data):
if self.log_yield:
self._recving(data)
def connected(self, peer):
self._header("Connected to %s" % str(peer))
def interrupted(self):
self._header("Connection interrupted")
def eofed(self):
self._header("Received EOF")
def requesting_send(self, data):
if self.suppressed:
return
self._header("Sending %d byte%s" % (len(data), '' if len(data) == 1 else 's'))
@staticmethod
def _timeout_text(timeout):
if timeout is None:
return ''
if timeout == 0:
return ' (nonblocking)'
return ' (until %s second%s)' % (timeout, '' if timeout == 1 else 's')
def requesting_recv(self, n, timeout):
if self.suppressed:
return
self._header("Receiving at most %d byte%s%s" % (n, '' if n == 1 else 's', self._timeout_text(timeout)))
def requesting_recv_until(self, s, max_size, timeout):
if self.suppressed:
return
if max_size is not None:
max_size_text = ', max of %d byte%s' % (max_size, '' if max_size == 1 else 's')
else:
max_size_text = ''
self._header("Receiving until %s%s%s" % (repr(s).strip('b'), max_size_text, self._timeout_text(timeout)))
def requesting_recv_all(self, timeout):
if self.suppressed:
return
self._header("Receiving until close%s" % self._timeout_text(timeout))
def requesting_recv_exactly(self, n, timeout):
self._header("Receiving exactly %d byte%s%s" % (n, '' if n == 1 else 's', self._timeout_text(timeout)))
def interact_starting(self):
self._header("Beginning interactive session")
self.suppressed = True
def interact_ending(self):
self.suppressed = False