Home | History | Annotate | Download | only in scapy
      1 ## This file is part of Scapy
      2 ## See http://www.secdev.org/projects/scapy for more informations
      3 ## Copyright (C) Philippe Biondi <phil (at] secdev.org>
      4 ## This program is published under a GPLv2 license
      5 
      6 """
      7 General utility functions.
      8 """
      9 
     10 from __future__ import absolute_import
     11 from __future__ import print_function
     12 import os, sys, socket, types
     13 import random, time
     14 import gzip, zlib
     15 import re, struct, array
     16 import subprocess
     17 import tempfile
     18 
     19 import warnings
     20 import scapy.modules.six as six
     21 from scapy.modules.six.moves import range
     22 warnings.filterwarnings("ignore","tempnam",RuntimeWarning, __name__)
     23 
     24 from scapy.config import conf
     25 from scapy.consts import DARWIN, WINDOWS
     26 from scapy.data import MTU
     27 from scapy.compat import *
     28 from scapy.error import log_runtime, log_loading, log_interactive, Scapy_Exception, warning
     29 from scapy.base_classes import BasePacketList
     30 
     31 ###########
     32 ## Tools ##
     33 ###########
     34 
     35 def get_temp_file(keep=False, autoext=""):
     36     """Create a temporary file and return its name. When keep is False,
     37 the file is deleted when scapy exits.
     38 
     39     """
     40     fname = tempfile.NamedTemporaryFile(prefix="scapy", suffix=autoext,
     41                                         delete=False).name
     42     if not keep:
     43         conf.temp_files.append(fname)
     44     return fname
     45 
     46 def sane_color(x):
     47     r=""
     48     for i in x:
     49         j = orb(i)
     50         if (j < 32) or (j >= 127):
     51             r=r+conf.color_theme.not_printable(".")
     52         else:
     53             r=r+chr(j)
     54     return r
     55 
     56 def sane(x):
     57     r=""
     58     for i in x:
     59         j = orb(i)
     60         if (j < 32) or (j >= 127):
     61             r=r+"."
     62         else:
     63             r=r+chr(j)
     64     return r
     65 
     66 @conf.commands.register
     67 def restart():
     68     """Restarts scapy"""
     69     if not conf.interactive or not os.path.isfile(sys.argv[0]):
     70         raise OSError("Scapy was not started from console")
     71     if WINDOWS:
     72         os._exit(subprocess.call([sys.executable] + sys.argv))
     73     os.execv(sys.executable, [sys.executable] + sys.argv)
     74 
     75 def lhex(x):
     76     if type(x) in six.integer_types:
     77         return hex(x)
     78     elif isinstance(x, tuple):
     79         return "(%s)" % ", ".join(map(lhex, x))
     80     elif isinstance(x, list):
     81         return "[%s]" % ", ".join(map(lhex, x))
     82     else:
     83         return x
     84 
     85 @conf.commands.register
     86 def hexdump(x, dump=False):
     87     """ Build a tcpdump like hexadecimal view
     88 
     89     :param x: a Packet
     90     :param dump: define if the result must be printed or returned in a variable
     91     :returns: a String only when dump=True
     92     """
     93     s = ""
     94     x = raw(x)
     95     l = len(x)
     96     i = 0
     97     while i < l:
     98         s += "%04x  " % i
     99         for j in range(16):
    100             if i+j < l:
    101                 s += "%02X" % orb(x[i+j])
    102             else:
    103                 s += "  "
    104             if j%16 == 7:
    105                 s += ""
    106         s += " "
    107         s += sane_color(x[i:i+16])
    108         i += 16
    109         s += "\n"
    110     # remove trailing \n
    111     if s.endswith("\n"):
    112         s = s[:-1]
    113     if dump:
    114         return s
    115     else:
    116         print(s)
    117 
    118 
    119 @conf.commands.register
    120 def linehexdump(x, onlyasc=0, onlyhex=0, dump=False):
    121     """ Build an equivalent view of hexdump() on a single line
    122 
    123     Note that setting both onlyasc and onlyhex to 1 results in a empty output
    124 
    125     :param x: a Packet
    126     :param onlyasc: 1 to display only the ascii view
    127     :param onlyhex: 1 to display only the hexadecimal view
    128     :param dump: print the view if False
    129     :returns: a String only when dump=True
    130     """
    131     s = ""
    132     x = raw(x)
    133     l = len(x)
    134     if not onlyasc:
    135         for i in range(l):
    136             s += "%02X" % orb(x[i])
    137         if not onlyhex:  # separate asc & hex if both are displayed
    138             s += " "
    139     if not onlyhex:
    140         s += sane_color(x)
    141     if dump:
    142         return s
    143     else:
    144         print(s)
    145 
    146 @conf.commands.register
    147 def chexdump(x, dump=False):
    148     """ Build a per byte hexadecimal representation
    149     
    150     Example:
    151         >>> chexdump(IP())
    152         0x45, 0x00, 0x00, 0x14, 0x00, 0x01, 0x00, 0x00, 0x40, 0x00, 0x7c, 0xe7, 0x7f, 0x00, 0x00, 0x01, 0x7f, 0x00, 0x00, 0x01
    153     
    154     :param x: a Packet
    155     :param dump: print the view if False
    156     :returns: a String only if dump=True
    157     """
    158     x = raw(x)
    159     s = ", ".join("%#04x" % orb(x) for x in x)
    160     if dump:
    161         return s
    162     else:
    163         print(s)
    164 
    165 @conf.commands.register
    166 def hexstr(x, onlyasc=0, onlyhex=0):
    167     s = []
    168     if not onlyasc:
    169         s.append(" ".join("%02x" % orb(b) for b in x))
    170     if not onlyhex:
    171         s.append(sane(x)) 
    172     return "  ".join(s)
    173 
    174 def repr_hex(s):
    175     """ Convert provided bitstring to a simple string of hex digits """
    176     return "".join("%02x" % orb(x) for x in s)
    177 
    178 @conf.commands.register
    179 def hexdiff(x,y):
    180     """Show differences between 2 binary strings"""
    181     x=raw(x)[::-1]
    182     y=raw(y)[::-1]
    183     SUBST=1
    184     INSERT=1
    185     d = {(-1, -1): (0, (-1, -1))}
    186     for j in range(len(y)):
    187         d[-1,j] = d[-1,j-1][0]+INSERT, (-1,j-1)
    188     for i in range(len(x)):
    189         d[i,-1] = d[i-1,-1][0]+INSERT, (i-1,-1)
    190 
    191     for j in range(len(y)):
    192         for i in range(len(x)):
    193             d[i,j] = min( ( d[i-1,j-1][0]+SUBST*(x[i] != y[j]), (i-1,j-1) ),
    194                           ( d[i-1,j][0]+INSERT, (i-1,j) ),
    195                           ( d[i,j-1][0]+INSERT, (i,j-1) ) )
    196                           
    197 
    198     backtrackx = []
    199     backtracky = []
    200     i=len(x)-1
    201     j=len(y)-1
    202     while not (i == j == -1):
    203         i2,j2 = d[i,j][1]
    204         backtrackx.append(x[i2+1:i+1])
    205         backtracky.append(y[j2+1:j+1])
    206         i,j = i2,j2
    207 
    208         
    209 
    210     x = y = i = 0
    211     colorize = { 0: lambda x:x,
    212                 -1: conf.color_theme.left,
    213                  1: conf.color_theme.right }
    214     
    215     dox=1
    216     doy=0
    217     l = len(backtrackx)
    218     while i < l:
    219         separate=0
    220         linex = backtrackx[i:i+16]
    221         liney = backtracky[i:i+16]
    222         xx = sum(len(k) for k in linex)
    223         yy = sum(len(k) for k in liney)
    224         if dox and not xx:
    225             dox = 0
    226             doy = 1
    227         if dox and linex == liney:
    228             doy=1
    229             
    230         if dox:
    231             xd = y
    232             j = 0
    233             while not linex[j]:
    234                 j += 1
    235                 xd -= 1
    236             print(colorize[doy-dox]("%04x" % xd), end=' ')
    237             x += xx
    238             line=linex
    239         else:
    240             print("    ", end=' ')
    241         if doy:
    242             yd = y
    243             j = 0
    244             while not liney[j]:
    245                 j += 1
    246                 yd -= 1
    247             print(colorize[doy-dox]("%04x" % yd), end=' ')
    248             y += yy
    249             line=liney
    250         else:
    251             print("    ", end=' ')
    252             
    253         print(" ", end=' ')
    254         
    255         cl = ""
    256         for j in range(16):
    257             if i+j < l:
    258                 if line[j]:
    259                     col = colorize[(linex[j]!=liney[j])*(doy-dox)]
    260                     print(col("%02X" % orb(line[j])), end=' ')
    261                     if linex[j]==liney[j]:
    262                         cl += sane_color(line[j])
    263                     else:
    264                         cl += col(sane(line[j]))
    265                 else:
    266                     print("  ", end=' ')
    267                     cl += " "
    268             else:
    269                 print("  ", end=' ')
    270             if j == 7:
    271                 print("", end=' ')
    272 
    273 
    274         print(" ",cl)
    275 
    276         if doy or not yy:
    277             doy=0
    278             dox=1
    279             i += 16
    280         else:
    281             if yy:
    282                 dox=0
    283                 doy=1
    284             else:
    285                 i += 16
    286 
    287 if struct.pack("H",1) == b"\x00\x01": # big endian
    288     def checksum(pkt):
    289         if len(pkt) % 2 == 1:
    290             pkt += b"\0"
    291         s = sum(array.array("H", pkt))
    292         s = (s >> 16) + (s & 0xffff)
    293         s += s >> 16
    294         s = ~s
    295         return s & 0xffff
    296 else:
    297     def checksum(pkt):
    298         if len(pkt) % 2 == 1:
    299             pkt += b"\0"
    300         s = sum(array.array("H", pkt))
    301         s = (s >> 16) + (s & 0xffff)
    302         s += s >> 16
    303         s = ~s
    304         return (((s>>8)&0xff)|s<<8) & 0xffff
    305 
    306 
    307 def _fletcher16(charbuf):
    308     # This is based on the GPLed C implementation in Zebra <http://www.zebra.org/>
    309     c0 = c1 = 0
    310     for char in charbuf:
    311         c0 += orb(char)
    312         c1 += c0
    313 
    314     c0 %= 255
    315     c1 %= 255
    316     return (c0,c1)
    317 
    318 @conf.commands.register
    319 def fletcher16_checksum(binbuf):
    320     """ Calculates Fletcher-16 checksum of the given buffer.
    321         
    322         Note:
    323         If the buffer contains the two checkbytes derived from the Fletcher-16 checksum
    324         the result of this function has to be 0. Otherwise the buffer has been corrupted.
    325     """
    326     (c0,c1)= _fletcher16(binbuf)
    327     return (c1 << 8) | c0
    328 
    329 
    330 @conf.commands.register
    331 def fletcher16_checkbytes(binbuf, offset):
    332     """ Calculates the Fletcher-16 checkbytes returned as 2 byte binary-string.
    333     
    334         Including the bytes into the buffer (at the position marked by offset) the
    335         global Fletcher-16 checksum of the buffer will be 0. Thus it is easy to verify
    336         the integrity of the buffer on the receiver side.
    337         
    338         For details on the algorithm, see RFC 2328 chapter 12.1.7 and RFC 905 Annex B.
    339     """
    340     
    341     # This is based on the GPLed C implementation in Zebra <http://www.zebra.org/>
    342     if len(binbuf) < offset:
    343         raise Exception("Packet too short for checkbytes %d" % len(binbuf))
    344 
    345     binbuf = binbuf[:offset] + b"\x00\x00" + binbuf[offset + 2:]
    346     (c0,c1)= _fletcher16(binbuf)
    347 
    348     x = ((len(binbuf) - offset - 1) * c0 - c1) % 255
    349 
    350     if (x <= 0):
    351         x += 255
    352 
    353     y = 510 - c0 - x
    354 
    355     if (y > 255):
    356         y -= 255
    357     return chb(x) + chb(y)
    358 
    359 
    360 def mac2str(mac):
    361     return b"".join(chb(int(x, 16)) for x in mac.split(':'))
    362 
    363 def str2mac(s):
    364     if isinstance(s, str):
    365         return ("%02x:"*6)[:-1] % tuple(map(ord, s))
    366     return ("%02x:"*6)[:-1] % tuple(s)
    367 
    368 def randstring(l):
    369     """
    370     Returns a random string of length l (l >= 0)
    371     """
    372     return b"".join(struct.pack('B', random.randint(0, 255)) for _ in range(l))
    373 
    374 def zerofree_randstring(l):
    375     """
    376     Returns a random string of length l (l >= 0) without zero in it.
    377     """
    378     return b"".join(struct.pack('B', random.randint(1, 255)) for _ in range(l))
    379 
    380 def strxor(s1, s2):
    381     """
    382     Returns the binary XOR of the 2 provided strings s1 and s2. s1 and s2
    383     must be of same length.
    384     """
    385     return b"".join(map(lambda x,y:chb(orb(x)^orb(y)), s1, s2))
    386 
    387 def strand(s1, s2):
    388     """
    389     Returns the binary AND of the 2 provided strings s1 and s2. s1 and s2
    390     must be of same length.
    391     """
    392     return b"".join(map(lambda x,y:chb(orb(x)&orb(y)), s1, s2))
    393 
    394 
    395 # Workaround bug 643005 : https://sourceforge.net/tracker/?func=detail&atid=105470&aid=643005&group_id=5470
    396 try:
    397     socket.inet_aton("255.255.255.255")
    398 except socket.error:
    399     def inet_aton(x):
    400         if x == "255.255.255.255":
    401             return b"\xff"*4
    402         else:
    403             return socket.inet_aton(x)
    404 else:
    405     inet_aton = socket.inet_aton
    406 
    407 inet_ntoa = socket.inet_ntoa
    408 from scapy.pton_ntop import *
    409 
    410 
    411 def atol(x):
    412     try:
    413         ip = inet_aton(x)
    414     except socket.error:
    415         ip = inet_aton(socket.gethostbyname(x))
    416     return struct.unpack("!I", ip)[0]
    417 def ltoa(x):
    418     return inet_ntoa(struct.pack("!I", x&0xffffffff))
    419 
    420 def itom(x):
    421     return (0xffffffff00000000>>x)&0xffffffff
    422 
    423 class ContextManagerSubprocess(object):
    424     """
    425     Context manager that eases checking for unknown command.
    426 
    427     Example:
    428     >>> with ContextManagerSubprocess("my custom message"):
    429     >>>     subprocess.Popen(["unknown_command"])
    430 
    431     """
    432     def __init__(self, name, prog):
    433         self.name = name
    434         self.prog = prog
    435 
    436     def __enter__(self):
    437         pass
    438 
    439     def __exit__(self, exc_type, exc_value, traceback):
    440         if isinstance(exc_value, (OSError, TypeError)):
    441             msg = "%s: executing %r failed" % (self.name, self.prog) if self.prog else "Could not execute %s, is it installed ?" % self.name
    442             if not conf.interactive:
    443                 raise OSError(msg)
    444             else:
    445                 log_runtime.error(msg, exc_info=True)
    446                 return True  # Suppress the exception
    447 
    448 class ContextManagerCaptureOutput(object):
    449     """
    450     Context manager that intercept the console's output.
    451 
    452     Example:
    453     >>> with ContextManagerCaptureOutput() as cmco:
    454     ...     print("hey")
    455     ...     assert cmco.get_output() == "hey"
    456     """
    457     def __init__(self):
    458         self.result_export_object = ""
    459         try:
    460             import mock
    461         except:
    462             raise ImportError("The mock module needs to be installed !")
    463     def __enter__(self):
    464         import mock
    465         def write(s, decorator=self):
    466             decorator.result_export_object += s
    467         mock_stdout = mock.Mock()
    468         mock_stdout.write = write
    469         self.bck_stdout = sys.stdout
    470         sys.stdout = mock_stdout
    471         return self
    472     def __exit__(self, *exc):
    473         sys.stdout = self.bck_stdout
    474         return False
    475     def get_output(self, eval_bytes=False):
    476         if self.result_export_object.startswith("b'") and eval_bytes:
    477             return plain_str(eval(self.result_export_object))
    478         return self.result_export_object
    479 
    480 def do_graph(graph,prog=None,format=None,target=None,type=None,string=None,options=None):
    481     """do_graph(graph, prog=conf.prog.dot, format="svg",
    482          target="| conf.prog.display", options=None, [string=1]):
    483     string: if not None, simply return the graph string
    484     graph: GraphViz graph description
    485     format: output type (svg, ps, gif, jpg, etc.), passed to dot's "-T" option
    486     target: filename or redirect. Defaults pipe to Imagemagick's display program
    487     prog: which graphviz program to use
    488     options: options to be passed to prog"""
    489         
    490     if format is None:
    491         if WINDOWS:
    492             format = "png" # use common format to make sure a viewer is installed
    493         else:
    494             format = "svg"
    495     if string:
    496         return graph
    497     if type is not None:
    498         format=type
    499     if prog is None:
    500         prog = conf.prog.dot
    501     start_viewer=False
    502     if target is None:
    503         if WINDOWS:
    504             target = get_temp_file(autoext="."+format)
    505             start_viewer = True
    506         else:
    507             with ContextManagerSubprocess("do_graph()", conf.prog.display):
    508                 target = subprocess.Popen([conf.prog.display],
    509                                           stdin=subprocess.PIPE).stdin
    510     if format is not None:
    511         format = "-T%s" % format
    512     if isinstance(target, str):
    513         if target.startswith('|'):
    514             target = subprocess.Popen(target[1:].lstrip(), shell=True,
    515                                       stdin=subprocess.PIPE).stdin
    516         elif target.startswith('>'):
    517             target = open(target[1:].lstrip(), "wb")
    518         else:
    519             target = open(os.path.abspath(target), "wb")
    520     proc = subprocess.Popen("\"%s\" %s %s" % (prog, options or "", format or ""),
    521                             shell=True, stdin=subprocess.PIPE, stdout=target)
    522     proc.stdin.write(raw(graph))
    523     try:
    524         target.close()
    525     except:
    526         pass
    527     if start_viewer:
    528         # Workaround for file not found error: We wait until tempfile is written.
    529         waiting_start = time.time()
    530         while not os.path.exists(target.name):
    531             time.sleep(0.1)
    532             if time.time() - waiting_start > 3:
    533                 warning("Temporary file '%s' could not be written. Graphic will not be displayed.", tempfile)
    534                 break
    535         else:  
    536             if conf.prog.display == conf.prog._default:
    537                 os.startfile(target.name)
    538             else:
    539                 with ContextManagerSubprocess("do_graph()", conf.prog.display):
    540                     subprocess.Popen([conf.prog.display, target.name])
    541 
    542 _TEX_TR = {
    543     "{":"{\\tt\\char123}",
    544     "}":"{\\tt\\char125}",
    545     "\\":"{\\tt\\char92}",
    546     "^":"\\^{}",
    547     "$":"\\$",
    548     "#":"\\#",
    549     "~":"\\~",
    550     "_":"\\_",
    551     "&":"\\&",
    552     "%":"\\%",
    553     "|":"{\\tt\\char124}",
    554     "~":"{\\tt\\char126}",
    555     "<":"{\\tt\\char60}",
    556     ">":"{\\tt\\char62}",
    557     }
    558     
    559 def tex_escape(x):
    560     s = ""
    561     for c in x:
    562         s += _TEX_TR.get(c,c)
    563     return s
    564 
    565 def colgen(*lstcol,**kargs):
    566     """Returns a generator that mixes provided quantities forever
    567     trans: a function to convert the three arguments into a color. lambda x,y,z:(x,y,z) by default"""
    568     if len(lstcol) < 2:
    569         lstcol *= 2
    570     trans = kargs.get("trans", lambda x,y,z: (x,y,z))
    571     while True:
    572         for i in range(len(lstcol)):
    573             for j in range(len(lstcol)):
    574                 for k in range(len(lstcol)):
    575                     if i != j or j != k or k != i:
    576                         yield trans(lstcol[(i+j)%len(lstcol)],lstcol[(j+k)%len(lstcol)],lstcol[(k+i)%len(lstcol)])
    577 
    578 def incremental_label(label="tag%05i", start=0):
    579     while True:
    580         yield label % start
    581         start += 1
    582 
    583 def binrepr(val):
    584     return bin(val)[2:]
    585 
    586 def long_converter(s):
    587     return int(s.replace('\n', '').replace(' ', ''), 16)
    588 
    589 #########################
    590 #### Enum management ####
    591 #########################
    592 
    593 class EnumElement:
    594     _value=None
    595     def __init__(self, key, value):
    596         self._key = key
    597         self._value = value
    598     def __repr__(self):
    599         return "<%s %s[%r]>" % (self.__dict__.get("_name", self.__class__.__name__), self._key, self._value)
    600     def __getattr__(self, attr):
    601         return getattr(self._value, attr)
    602     def __str__(self):
    603         return self._key
    604     def __bytes__(self):
    605         return raw(self.__str__())
    606     def __hash__(self):
    607         return self._value
    608     def __int__(self):
    609         return int(self._value)
    610     def __eq__(self, other):
    611         return self._value == int(other)
    612     def __neq__(self, other):
    613         return not self.__eq__(other)
    614 
    615 
    616 class Enum_metaclass(type):
    617     element_class = EnumElement
    618     def __new__(cls, name, bases, dct):
    619         rdict={}
    620         for k,v in six.iteritems(dct):
    621             if isinstance(v, int):
    622                 v = cls.element_class(k,v)
    623                 dct[k] = v
    624                 rdict[v] = k
    625         dct["__rdict__"] = rdict
    626         return super(Enum_metaclass, cls).__new__(cls, name, bases, dct)
    627     def __getitem__(self, attr):
    628         return self.__rdict__[attr]
    629     def __contains__(self, val):
    630         return val in self.__rdict__
    631     def get(self, attr, val=None):
    632         return self.__rdict__.get(attr, val)
    633     def __repr__(self):
    634         return "<%s>" % self.__dict__.get("name", self.__name__)
    635 
    636 
    637 
    638 ###################
    639 ## Object saving ##
    640 ###################
    641 
    642 
    643 def export_object(obj):
    644     print(bytes_base64(gzip.zlib.compress(six.moves.cPickle.dumps(obj, 2), 9)))
    645 
    646 def import_object(obj=None):
    647     if obj is None:
    648         obj = sys.stdin.read()
    649     return six.moves.cPickle.loads(gzip.zlib.decompress(base64_bytes(obj.strip())))
    650 
    651 
    652 def save_object(fname, obj):
    653     """Pickle a Python object"""
    654 
    655     fd = gzip.open(fname, "wb")
    656     six.moves.cPickle.dump(obj, fd)
    657     fd.close()
    658 
    659 def load_object(fname):
    660     """unpickle a Python object"""
    661     return six.moves.cPickle.load(gzip.open(fname,"rb"))
    662 
    663 @conf.commands.register
    664 def corrupt_bytes(s, p=0.01, n=None):
    665     """Corrupt a given percentage or number of bytes from a string"""
    666     s = array.array("B",raw(s))
    667     l = len(s)
    668     if n is None:
    669         n = max(1,int(l*p))
    670     for i in random.sample(range(l), n):
    671         s[i] = (s[i]+random.randint(1,255))%256
    672     return s.tostring()
    673 
    674 @conf.commands.register
    675 def corrupt_bits(s, p=0.01, n=None):
    676     """Flip a given percentage or number of bits from a string"""
    677     s = array.array("B",raw(s))
    678     l = len(s)*8
    679     if n is None:
    680         n = max(1,int(l*p))
    681     for i in random.sample(range(l), n):
    682         s[i // 8] ^= 1 << (i % 8)
    683     return s.tostring()
    684 
    685 
    686 
    687 
    688 #############################
    689 ## pcap capture file stuff ##
    690 #############################
    691 
    692 @conf.commands.register
    693 def wrpcap(filename, pkt, *args, **kargs):
    694     """Write a list of packets to a pcap file
    695 
    696 filename: the name of the file to write packets to, or an open,
    697           writable file-like object. The file descriptor will be
    698           closed at the end of the call, so do not use an object you
    699           do not want to close (e.g., running wrpcap(sys.stdout, [])
    700           in interactive mode will crash Scapy).
    701 gz: set to 1 to save a gzipped capture
    702 linktype: force linktype value
    703 endianness: "<" or ">", force endianness
    704 sync: do not bufferize writes to the capture file
    705 
    706     """
    707     with PcapWriter(filename, *args, **kargs) as fdesc:
    708         fdesc.write(pkt)
    709 
    710 @conf.commands.register
    711 def rdpcap(filename, count=-1):
    712     """Read a pcap or pcapng file and return a packet list
    713 
    714 count: read only <count> packets
    715 
    716     """
    717     with PcapReader(filename) as fdesc:
    718         return fdesc.read_all(count=count)
    719 
    720 
    721 class PcapReader_metaclass(type):
    722     """Metaclass for (Raw)Pcap(Ng)Readers"""
    723 
    724     def __new__(cls, name, bases, dct):
    725         """The `alternative` class attribute is declared in the PcapNg
    726         variant, and set here to the Pcap variant.
    727 
    728         """
    729         newcls = super(PcapReader_metaclass, cls).__new__(cls, name, bases, dct)
    730         if 'alternative' in dct:
    731             dct['alternative'].alternative = newcls
    732         return newcls
    733 
    734     def __call__(cls, filename):
    735         """Creates a cls instance, use the `alternative` if that
    736         fails.
    737 
    738         """
    739         i = cls.__new__(cls, cls.__name__, cls.__bases__, cls.__dict__)
    740         filename, fdesc, magic = cls.open(filename)
    741         try:
    742             i.__init__(filename, fdesc, magic)
    743         except Scapy_Exception:
    744             if "alternative" in cls.__dict__:
    745                 cls = cls.__dict__["alternative"]
    746                 i = cls.__new__(cls, cls.__name__, cls.__bases__, cls.__dict__)
    747                 try:
    748                     i.__init__(filename, fdesc, magic)
    749                 except Scapy_Exception:
    750                     raise
    751                     try:
    752                         i.f.seek(-4, 1)
    753                     except:
    754                         pass
    755                     raise Scapy_Exception("Not a supported capture file")
    756 
    757         return i
    758 
    759     @staticmethod
    760     def open(filename):
    761         """Open (if necessary) filename, and read the magic."""
    762         if isinstance(filename, six.string_types):
    763             try:
    764                 fdesc = gzip.open(filename,"rb")
    765                 magic = fdesc.read(4)
    766             except IOError:
    767                 fdesc = open(filename, "rb")
    768                 magic = fdesc.read(4)
    769         else:
    770             fdesc = filename
    771             filename = (fdesc.name
    772                         if hasattr(fdesc, "name") else
    773                         "No name")
    774             magic = fdesc.read(4)
    775         return filename, fdesc, magic
    776 
    777 
    778 class RawPcapReader(six.with_metaclass(PcapReader_metaclass)):
    779     """A stateful pcap reader. Each packet is returned as a string"""
    780     def __init__(self, filename, fdesc, magic):
    781         self.filename = filename
    782         self.f = fdesc
    783         if magic == b"\xa1\xb2\xc3\xd4": # big endian
    784             self.endian = ">"
    785             self.nano = False
    786         elif magic == b"\xd4\xc3\xb2\xa1": # little endian
    787             self.endian = "<"
    788             self.nano = False
    789         elif magic == b"\xa1\xb2\x3c\x4d":  # big endian, nanosecond-precision
    790             self.endian = ">"
    791             self.nano = True
    792         elif magic == b"\x4d\x3c\xb2\xa1":  # little endian, nanosecond-precision
    793             self.endian = "<"
    794             self.nano = True
    795         else:
    796             raise Scapy_Exception(
    797                 "Not a pcap capture file (bad magic: %r)" % magic
    798             )
    799         hdr = self.f.read(20)
    800         if len(hdr)<20:
    801             raise Scapy_Exception("Invalid pcap file (too short)")
    802         vermaj, vermin, tz, sig, snaplen, linktype = struct.unpack(
    803             self.endian + "HHIIII", hdr
    804         )
    805         self.linktype = linktype
    806 
    807     def __iter__(self):
    808         return self
    809 
    810     def next(self):
    811         """implement the iterator protocol on a set of packets in a pcap file"""
    812         pkt = self.read_packet()
    813         if pkt == None:
    814             raise StopIteration
    815         return pkt
    816     __next__ = next
    817 
    818 
    819     def read_packet(self, size=MTU):
    820         """return a single packet read from the file
    821         
    822         returns None when no more packets are available
    823         """
    824         hdr = self.f.read(16)
    825         if len(hdr) < 16:
    826             return None
    827         sec,usec,caplen,wirelen = struct.unpack(self.endian+"IIII", hdr)
    828         s = self.f.read(caplen)[:size]
    829         return s,(sec,usec,wirelen) # caplen = len(s)
    830 
    831 
    832     def dispatch(self, callback):
    833         """call the specified callback routine for each packet read
    834         
    835         This is just a convenience function for the main loop
    836         that allows for easy launching of packet processing in a 
    837         thread.
    838         """
    839         for p in self:
    840             callback(p)
    841 
    842     def read_all(self,count=-1):
    843         """return a list of all packets in the pcap file
    844         """
    845         res=[]
    846         while count != 0:
    847             count -= 1
    848             p = self.read_packet()
    849             if p is None:
    850                 break
    851             res.append(p)
    852         return res
    853 
    854     def recv(self, size=MTU):
    855         """ Emulate a socket
    856         """
    857         return self.read_packet(size=size)[0]
    858 
    859     def fileno(self):
    860         return self.f.fileno()
    861 
    862     def close(self):
    863         return self.f.close()
    864 
    865     def __enter__(self):
    866         return self
    867 
    868     def __exit__(self, exc_type, exc_value, tracback):
    869         self.close()
    870 
    871 
    872 class PcapReader(RawPcapReader):
    873     def __init__(self, filename, fdesc, magic):
    874         RawPcapReader.__init__(self, filename, fdesc, magic)
    875         try:
    876             self.LLcls = conf.l2types[self.linktype]
    877         except KeyError:
    878             warning("PcapReader: unknown LL type [%i]/[%#x]. Using Raw packets" % (self.linktype,self.linktype))
    879             self.LLcls = conf.raw_layer
    880     def read_packet(self, size=MTU):
    881         rp = RawPcapReader.read_packet(self, size=size)
    882         if rp is None:
    883             return None
    884         s,(sec,usec,wirelen) = rp
    885         
    886         try:
    887             p = self.LLcls(s)
    888         except KeyboardInterrupt:
    889             raise
    890         except:
    891             if conf.debug_dissector:
    892                 raise
    893             p = conf.raw_layer(s)
    894         p.time = sec + (0.000000001 if self.nano else 0.000001) * usec
    895         return p
    896     def read_all(self,count=-1):
    897         res = RawPcapReader.read_all(self, count)
    898         from scapy import plist
    899         return plist.PacketList(res,name = os.path.basename(self.filename))
    900     def recv(self, size=MTU):
    901         return self.read_packet(size=size)
    902 
    903 
    904 class RawPcapNgReader(RawPcapReader):
    905     """A stateful pcapng reader. Each packet is returned as a
    906     string.
    907 
    908     """
    909 
    910     alternative = RawPcapReader
    911 
    912     def __init__(self, filename, fdesc, magic):
    913         self.filename = filename
    914         self.f = fdesc
    915         # A list of (linktype, snaplen, tsresol); will be populated by IDBs.
    916         self.interfaces = []
    917         self.blocktypes = {
    918             1: self.read_block_idb,
    919             2: self.read_block_pkt,
    920             3: self.read_block_spb,
    921             6: self.read_block_epb,
    922         }
    923         if magic != b"\x0a\x0d\x0d\x0a": # PcapNg:
    924             raise Scapy_Exception(
    925                 "Not a pcapng capture file (bad magic: %r)" % magic
    926             )
    927         # see https://github.com/pcapng/pcapng
    928         blocklen, magic = self.f.read(4), self.f.read(4)
    929         if magic == b"\x1a\x2b\x3c\x4d":
    930             self.endian = ">"
    931         elif magic == b"\x4d\x3c\x2b\x1a":
    932             self.endian = "<"
    933         else:
    934             raise Scapy_Exception("Not a pcapng capture file (bad magic)")
    935         try:
    936             self.f.seek(0)
    937         except:
    938             pass
    939 
    940     def read_packet(self, size=MTU):
    941         """Read blocks until it reaches either EOF or a packet, and
    942         returns None or (packet, (linktype, sec, usec, wirelen)),
    943         where packet is a string.
    944 
    945         """
    946         while True:
    947             try:
    948                 blocktype, blocklen = struct.unpack(self.endian + "2I",
    949                                                     self.f.read(8))
    950             except struct.error:
    951                 return None
    952             block = self.f.read(blocklen - 12)
    953             if blocklen % 4:
    954                 pad = self.f.read(4 - (blocklen % 4))
    955                 warning("PcapNg: bad blocklen %d (MUST be a multiple of 4. "
    956                         "Ignored padding %r" % (blocklen, pad))
    957             try:
    958                 if (blocklen,) != struct.unpack(self.endian + 'I',
    959                                                 self.f.read(4)):
    960                     warning("PcapNg: Invalid pcapng block (bad blocklen)")
    961             except struct.error:
    962                 return None
    963             res = self.blocktypes.get(blocktype,
    964                                       lambda block, size: None)(block, size)
    965             if res is not None:
    966                 return res
    967 
    968     def read_block_idb(self, block, _):
    969         """Interface Description Block"""
    970         options = block[16:]
    971         tsresol = 1000000
    972         while len(options) >= 4:
    973             code, length = struct.unpack(self.endian + "HH", options[:4])
    974             # PCAP Next Generation (pcapng) Capture File Format
    975             # 4.2. - Interface Description Block
    976             # http://xml2rfc.tools.ietf.org/cgi-bin/xml2rfc.cgi?url=https://raw.githubusercontent.com/pcapng/pcapng/master/draft-tuexen-opsawg-pcapng.xml&modeAsFormat=html/ascii&type=ascii#rfc.section.4.2
    977             if code == 9 and length == 1 and len(options) >= 5:
    978                 tsresol = orb(options[4])
    979                 tsresol = (2 if tsresol & 128 else 10) ** (tsresol & 127)
    980             if code == 0:
    981                 if length != 0:
    982                     warning("PcapNg: invalid option length %d for end-of-option" % length)
    983                 break
    984             if length % 4:
    985                 length += (4 - (length % 4))
    986             options = options[4 + length:]
    987         self.interfaces.append(struct.unpack(self.endian + "HxxI", block[:8])
    988                                + (tsresol,))
    989 
    990     def read_block_epb(self, block, size):
    991         """Enhanced Packet Block"""
    992         intid, tshigh, tslow, caplen, wirelen = struct.unpack(
    993             self.endian + "5I",
    994             block[:20],
    995         )
    996         return (block[20:20 + caplen][:size],
    997                 (self.interfaces[intid][0], self.interfaces[intid][2],
    998                  tshigh, tslow, wirelen))
    999 
   1000     def read_block_spb(self, block, size):
   1001         """Simple Packet Block"""
   1002         # "it MUST be assumed that all the Simple Packet Blocks have
   1003         # been captured on the interface previously specified in the
   1004         # first Interface Description Block."
   1005         intid = 0
   1006         wirelen, = struct.unpack(self.endian + "I", block[:4])
   1007         caplen = min(wirelen, self.interfaces[intid][1])
   1008         return (block[4:4 + caplen][:size],
   1009                 (self.interfaces[intid][0], self.interfaces[intid][2],
   1010                  None, None, wirelen))
   1011 
   1012     def read_block_pkt(self, block, size):
   1013         """(Obsolete) Packet Block"""
   1014         intid, drops, tshigh, tslow, caplen, wirelen = struct.unpack(
   1015             self.endian + "HH4I",
   1016             block[:20],
   1017         )
   1018         return (block[20:20 + caplen][:size],
   1019                 (self.interfaces[intid][0], self.interfaces[intid][2],
   1020                  tshigh, tslow, wirelen))
   1021 
   1022 
   1023 class PcapNgReader(RawPcapNgReader):
   1024 
   1025     alternative = PcapReader
   1026 
   1027     def __init__(self, filename, fdesc, magic):
   1028         RawPcapNgReader.__init__(self, filename, fdesc, magic)
   1029 
   1030     def read_packet(self, size=MTU):
   1031         rp = RawPcapNgReader.read_packet(self, size=size)
   1032         if rp is None:
   1033             return None
   1034         s, (linktype, tsresol, tshigh, tslow, wirelen) = rp
   1035         try:
   1036             p = conf.l2types[linktype](s)
   1037         except KeyboardInterrupt:
   1038             raise
   1039         except:
   1040             if conf.debug_dissector:
   1041                 raise
   1042             p = conf.raw_layer(s)
   1043         if tshigh is not None:
   1044             p.time = float((tshigh << 32) + tslow) / tsresol
   1045         return p
   1046     def read_all(self,count=-1):
   1047         res = RawPcapNgReader.read_all(self, count)
   1048         from scapy import plist
   1049         return plist.PacketList(res, name=os.path.basename(self.filename))
   1050     def recv(self, size=MTU):
   1051         return self.read_packet()
   1052 
   1053 
   1054 class RawPcapWriter:
   1055     """A stream PCAP writer with more control than wrpcap()"""
   1056     def __init__(self, filename, linktype=None, gz=False, endianness="",
   1057                  append=False, sync=False, nano=False):
   1058         """
   1059 filename:   the name of the file to write packets to, or an open,
   1060             writable file-like object.
   1061 linktype:   force linktype to a given value. If None, linktype is taken
   1062             from the first writer packet
   1063 gz:         compress the capture on the fly
   1064 endianness: force an endianness (little:"<", big:">"). Default is native
   1065 append:     append packets to the capture file instead of truncating it
   1066 sync:       do not bufferize writes to the capture file
   1067 nano:       use nanosecond-precision (requires libpcap >= 1.5.0)
   1068 
   1069         """
   1070         
   1071         self.linktype = linktype
   1072         self.header_present = 0
   1073         self.append = append
   1074         self.gz = gz
   1075         self.endian = endianness
   1076         self.sync = sync
   1077         self.nano = nano
   1078         bufsz=4096
   1079         if sync:
   1080             bufsz = 0
   1081 
   1082         if isinstance(filename, six.string_types):
   1083             self.filename = filename
   1084             self.f = [open,gzip.open][gz](filename,append and "ab" or "wb", gz and 9 or bufsz)
   1085         else:
   1086             self.f = filename
   1087             self.filename = (filename.name
   1088                              if hasattr(filename, "name") else
   1089                              "No name")
   1090 
   1091     def fileno(self):
   1092         return self.f.fileno()
   1093 
   1094     def _write_header(self, pkt):
   1095         self.header_present=1
   1096 
   1097         if self.append:
   1098             # Even if prone to race conditions, this seems to be
   1099             # safest way to tell whether the header is already present
   1100             # because we have to handle compressed streams that
   1101             # are not as flexible as basic files
   1102             g = [open,gzip.open][self.gz](self.filename,"rb")
   1103             if g.read(16):
   1104                 return
   1105             
   1106         self.f.write(struct.pack(self.endian+"IHHIIII", 0xa1b23c4d if self.nano else 0xa1b2c3d4,
   1107                                  2, 4, 0, 0, MTU, self.linktype))
   1108         self.f.flush()
   1109     
   1110 
   1111     def write(self, pkt):
   1112         """accepts either a single packet or a list of packets to be
   1113         written to the dumpfile
   1114 
   1115         """
   1116         if isinstance(pkt, str):
   1117             if not self.header_present:
   1118                 self._write_header(pkt)
   1119             self._write_packet(pkt)
   1120         else:
   1121             pkt = pkt.__iter__()
   1122             if not self.header_present:
   1123                 try:
   1124                     p = next(pkt)
   1125                 except StopIteration:
   1126                     self._write_header(b"")
   1127                     return
   1128                 self._write_header(p)
   1129                 self._write_packet(p)
   1130             for p in pkt:
   1131                 self._write_packet(p)
   1132 
   1133     def _write_packet(self, packet, sec=None, usec=None, caplen=None, wirelen=None):
   1134         """writes a single packet to the pcap file
   1135         """
   1136         if isinstance(packet, tuple):
   1137             for pkt in packet:
   1138                 self._write_packet(pkt, sec=sec, usec=usec, caplen=caplen,
   1139                                    wirelen=wirelen)
   1140             return
   1141         if caplen is None:
   1142             caplen = len(packet)
   1143         if wirelen is None:
   1144             wirelen = caplen
   1145         if sec is None or usec is None:
   1146             t=time.time()
   1147             it = int(t)
   1148             if sec is None:
   1149                 sec = it
   1150             if usec is None:
   1151                 usec = int(round((t - it) * (1000000000 if self.nano else 1000000)))
   1152         self.f.write(struct.pack(self.endian+"IIII", sec, usec, caplen, wirelen))
   1153         self.f.write(packet)
   1154         if self.sync:
   1155             self.f.flush()
   1156 
   1157     def flush(self):
   1158         return self.f.flush()
   1159 
   1160     def close(self):
   1161         return self.f.close()
   1162 
   1163     def __enter__(self):
   1164         return self
   1165     def __exit__(self, exc_type, exc_value, tracback):
   1166         self.flush()
   1167         self.close()
   1168 
   1169 
   1170 class PcapWriter(RawPcapWriter):
   1171     """A stream PCAP writer with more control than wrpcap()"""
   1172     def _write_header(self, pkt):
   1173         if isinstance(pkt, tuple) and pkt:
   1174             pkt = pkt[0]
   1175         if self.linktype == None:
   1176             try:
   1177                 self.linktype = conf.l2types[pkt.__class__]
   1178             except KeyError:
   1179                 warning("PcapWriter: unknown LL type for %s. Using type 1 (Ethernet)", pkt.__class__.__name__)
   1180                 self.linktype = 1
   1181         RawPcapWriter._write_header(self, pkt)
   1182 
   1183     def _write_packet(self, packet):
   1184         if isinstance(packet, tuple):
   1185             for pkt in packet:
   1186                 self._write_packet(pkt)
   1187             return
   1188         sec = int(packet.time)
   1189         usec = int(round((packet.time - sec) * (1000000000 if self.nano else 1000000)))
   1190         s = raw(packet)
   1191         caplen = len(s)
   1192         RawPcapWriter._write_packet(self, s, sec, usec, caplen, caplen)
   1193 
   1194 
   1195 re_extract_hexcap = re.compile("^((0x)?[0-9a-fA-F]{2,}[ :\t]{,3}|) *(([0-9a-fA-F]{2} {,2}){,16})")
   1196 
   1197 @conf.commands.register
   1198 def import_hexcap():
   1199     p = ""
   1200     try:
   1201         while True:
   1202             l = input().strip()
   1203             try:
   1204                 p += re_extract_hexcap.match(l).groups()[2]
   1205             except:
   1206                 warning("Parsing error during hexcap")
   1207                 continue
   1208     except EOFError:
   1209         pass
   1210     
   1211     p = p.replace(" ","")
   1212     return p.decode("hex")
   1213         
   1214 
   1215 
   1216 @conf.commands.register
   1217 def wireshark(pktlist):
   1218     """Run wireshark on a list of packets"""
   1219     f = get_temp_file()
   1220     wrpcap(f, pktlist)
   1221     with ContextManagerSubprocess("wireshark()", conf.prog.wireshark):
   1222         subprocess.Popen([conf.prog.wireshark, "-r", f])
   1223 
   1224 @conf.commands.register
   1225 def tcpdump(pktlist, dump=False, getfd=False, args=None,
   1226             prog=None, getproc=False, quiet=False):
   1227     """Run tcpdump or tshark on a list of packets
   1228 
   1229 pktlist: a Packet instance, a PacketList instance or a list of Packet
   1230          instances. Can also be a filename (as a string) or an open
   1231          file-like object that must be a file format readable by
   1232          tshark (Pcap, PcapNg, etc.)
   1233 
   1234 dump:    when set to True, returns a string instead of displaying it.
   1235 getfd:   when set to True, returns a file-like object to read data
   1236          from tcpdump or tshark from.
   1237 getproc: when set to True, the subprocess.Popen object is returned
   1238 args:    arguments (as a list) to pass to tshark (example for tshark:
   1239          args=["-T", "json"]). Defaults to ["-n"].
   1240 prog:    program to use (defaults to tcpdump, will work with tshark)
   1241 quiet:   when set to True, the process stderr is discarded
   1242 
   1243 Examples:
   1244 
   1245 >>> tcpdump([IP()/TCP(), IP()/UDP()])
   1246 reading from file -, link-type RAW (Raw IP)
   1247 16:46:00.474515 IP 127.0.0.1.20 > 127.0.0.1.80: Flags [S], seq 0, win 8192, length 0
   1248 16:46:00.475019 IP 127.0.0.1.53 > 127.0.0.1.53: [|domain]
   1249 
   1250 >>> tcpdump([IP()/TCP(), IP()/UDP()], prog=conf.prog.tshark)
   1251   1   0.000000    127.0.0.1 -> 127.0.0.1    TCP 40 20->80 [SYN] Seq=0 Win=8192 Len=0
   1252   2   0.000459    127.0.0.1 -> 127.0.0.1    UDP 28 53->53 Len=0
   1253 
   1254 To get a JSON representation of a tshark-parsed PacketList(), one can:
   1255 >>> import json, pprint
   1256 >>> json_data = json.load(tcpdump(IP(src="217.25.178.5", dst="45.33.32.156"),
   1257 ...                               prog=conf.prog.tshark, args=["-T", "json"],
   1258 ...                               getfd=True))
   1259 >>> pprint.pprint(json_data)
   1260 [{u'_index': u'packets-2016-12-23',
   1261   u'_score': None,
   1262   u'_source': {u'layers': {u'frame': {u'frame.cap_len': u'20',
   1263                                       u'frame.encap_type': u'7',
   1264 [...]
   1265                                       u'frame.time_relative': u'0.000000000'},
   1266                            u'ip': {u'ip.addr': u'45.33.32.156',
   1267                                    u'ip.checksum': u'0x0000a20d',
   1268 [...]
   1269                                    u'ip.ttl': u'64',
   1270                                    u'ip.version': u'4'},
   1271                            u'raw': u'Raw packet data'}},
   1272   u'_type': u'pcap_file'}]
   1273 >>> json_data[0]['_source']['layers']['ip']['ip.ttl']
   1274 u'64'
   1275 
   1276     """
   1277     getfd = getfd or getproc
   1278     if prog is None:
   1279         prog = [conf.prog.tcpdump]
   1280     elif isinstance(prog, six.string_types):
   1281         prog = [prog]
   1282     _prog_name = "windump()" if WINDOWS else "tcpdump()"
   1283     if pktlist is None:
   1284         with ContextManagerSubprocess(_prog_name, prog[0]):
   1285             proc = subprocess.Popen(
   1286                 prog + (args if args is not None else []),
   1287                 stdout=subprocess.PIPE if dump or getfd else None,
   1288                 stderr=open(os.devnull) if quiet else None,
   1289             )
   1290     elif isinstance(pktlist, six.string_types):
   1291         with ContextManagerSubprocess(_prog_name, prog[0]):
   1292             proc = subprocess.Popen(
   1293                 prog + ["-r", pktlist] + (args if args is not None else []),
   1294                 stdout=subprocess.PIPE if dump or getfd else None,
   1295                 stderr=open(os.devnull) if quiet else None,
   1296             )
   1297     elif DARWIN:
   1298         # Tcpdump cannot read from stdin, see
   1299         # <http://apple.stackexchange.com/questions/152682/>
   1300         tmpfile = tempfile.NamedTemporaryFile(delete=False)
   1301         try:
   1302             tmpfile.writelines(iter(lambda: pktlist.read(1048576), b""))
   1303         except AttributeError:
   1304             wrpcap(tmpfile, pktlist)
   1305         else:
   1306             tmpfile.close()
   1307         with ContextManagerSubprocess(_prog_name, prog[0]):
   1308             proc = subprocess.Popen(
   1309                 prog + ["-r", tmpfile.name] + (args if args is not None else []),
   1310                 stdout=subprocess.PIPE if dump or getfd else None,
   1311                 stderr=open(os.devnull) if quiet else None,
   1312             )
   1313         conf.temp_files.append(tmpfile.name)
   1314     else:
   1315         with ContextManagerSubprocess(_prog_name, prog[0]):
   1316             proc = subprocess.Popen(
   1317                 prog + ["-r", "-"] + (args if args is not None else []),
   1318                 stdin=subprocess.PIPE,
   1319                 stdout=subprocess.PIPE if dump or getfd else None,
   1320                 stderr=open(os.devnull) if quiet else None,
   1321             )
   1322         try:
   1323             proc.stdin.writelines(iter(lambda: pktlist.read(1048576), b""))
   1324         except AttributeError:
   1325             wrpcap(proc.stdin, pktlist)
   1326         else:
   1327             proc.stdin.close()
   1328     if dump:
   1329         return b"".join(iter(lambda: proc.stdout.read(1048576), b""))
   1330     if getproc:
   1331         return proc
   1332     if getfd:
   1333         return proc.stdout
   1334     proc.wait()
   1335 
   1336 @conf.commands.register
   1337 def hexedit(x):
   1338     x = str(x)
   1339     f = get_temp_file()
   1340     open(f,"wb").write(x)
   1341     with ContextManagerSubprocess("hexedit()", conf.prog.hexedit):
   1342         subprocess.call([conf.prog.hexedit, f])
   1343     x = open(f).read()
   1344     os.unlink(f)
   1345     return x
   1346 
   1347 def get_terminal_width():
   1348     """Get terminal width if in a window"""
   1349     if WINDOWS:
   1350         from ctypes import windll, create_string_buffer
   1351         # http://code.activestate.com/recipes/440694-determine-size-of-console-window-on-windows/
   1352         h = windll.kernel32.GetStdHandle(-12)
   1353         csbi = create_string_buffer(22)
   1354         res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi)
   1355         if res:
   1356             import struct
   1357             (bufx, bufy, curx, cury, wattr,
   1358              left, top, right, bottom, maxx, maxy) = struct.unpack("hhhhHhhhhhh", csbi.raw)
   1359             sizex = right - left + 1
   1360             #sizey = bottom - top + 1
   1361             return sizex
   1362         else:
   1363             return None
   1364     else:
   1365         sizex = 0
   1366         try:
   1367             import struct, fcntl, termios
   1368             s = struct.pack('HHHH', 0, 0, 0, 0)
   1369             x = fcntl.ioctl(1, termios.TIOCGWINSZ, s)
   1370             sizex = struct.unpack('HHHH', x)[1]
   1371         except IOError:
   1372             pass
   1373         if not sizex:
   1374             try:
   1375                 sizex = int(os.environ['COLUMNS'])
   1376             except:
   1377                 pass
   1378         if sizex:
   1379             return sizex
   1380         else:
   1381             return None
   1382 
   1383 def pretty_list(rtlst, header, sortBy=0):
   1384     """Pretty list to fit the terminal, and add header"""
   1385     _l_header = len(header[0])
   1386     _space = "  "
   1387     # Sort correctly
   1388     rtlst.sort(key=lambda x: x[sortBy])
   1389     # Append tag
   1390     rtlst = header + rtlst
   1391     # Detect column's width
   1392     colwidth = [max([len(y) for y in x]) for x in zip(*rtlst)]
   1393     # Make text fit in box (if exist)
   1394     # TODO: find a better and more precise way of doing this. That's currently working but very complicated
   1395     width = get_terminal_width()
   1396     if width:
   1397         if sum(colwidth) > width:
   1398             # Needs to be cropped
   1399             _med = (width // _l_header) - (1 if WINDOWS else 0) # Windows has a fat window border
   1400             # Crop biggest until size is correct
   1401             for i in range(1, len(colwidth)): # Should use while, but this is safer
   1402                 if (sum(colwidth)+6) <= width:
   1403                     break
   1404                 _max = max(colwidth)
   1405                 colwidth = [_med if x == _max else x for x in colwidth]
   1406             def _crop(x, width):
   1407                 _r = x[:width]
   1408                 if _r != x:
   1409                     _r = x[:width-3]
   1410                     return _r + "..."
   1411                 return _r
   1412             rtlst = [tuple([_crop(rtlst[j][i], colwidth[i]) for i in range(0, len(rtlst[j]))]) for j in range(0, len(rtlst))]
   1413             # Recalculate column's width
   1414             colwidth = [max([len(y) for y in x]) for x in zip(*rtlst)]
   1415     fmt = _space.join(["%%-%ds"%x for x in colwidth])
   1416     rt = "\n".join([fmt % x for x in rtlst])
   1417     return rt
   1418 
   1419 def __make_table(yfmtfunc, fmtfunc, endline, data, fxyz, sortx=None, sorty=None, seplinefunc=None):
   1420     vx = {} 
   1421     vy = {} 
   1422     vz = {}
   1423     vxf = {}
   1424     vyf = {}
   1425     l = 0
   1426     for e in data:
   1427         xx, yy, zz = [str(s) for s in fxyz(e)]
   1428         l = max(len(yy),l)
   1429         vx[xx] = max(vx.get(xx,0), len(xx), len(zz))
   1430         vy[yy] = None
   1431         vz[(xx,yy)] = zz
   1432 
   1433     vxk = list(vx)
   1434     vyk = list(vy)
   1435     if sortx:
   1436         vxk.sort(key=sortx)
   1437     else:
   1438         try:
   1439             vxk.sort(key=int)
   1440         except:
   1441             try:
   1442                 vxk.sort(key=atol)
   1443             except:
   1444                 vxk.sort()
   1445     if sorty:
   1446         vyk.sort(key=sorty)
   1447     else:
   1448         try:
   1449             vyk.sort(key=int)
   1450         except:
   1451             try:
   1452                 vyk.sort(key=atol)
   1453             except:
   1454                 vyk.sort()
   1455 
   1456 
   1457     if seplinefunc:
   1458         sepline = seplinefunc(l, [vx[x] for x in vxk])
   1459         print(sepline)
   1460 
   1461     fmt = yfmtfunc(l)
   1462     print(fmt % "", end=' ')
   1463     for x in vxk:
   1464         vxf[x] = fmtfunc(vx[x])
   1465         print(vxf[x] % x, end=' ')
   1466     print(endline)
   1467     if seplinefunc:
   1468         print(sepline)
   1469     for y in vyk:
   1470         print(fmt % y, end=' ')
   1471         for x in vxk:
   1472             print(vxf[x] % vz.get((x,y), "-"), end=' ')
   1473         print(endline)
   1474     if seplinefunc:
   1475         print(sepline)
   1476 
   1477 def make_table(*args, **kargs):
   1478     __make_table(lambda l:"%%-%is" % l, lambda l:"%%-%is" % l, "", *args, **kargs)
   1479     
   1480 def make_lined_table(*args, **kargs):
   1481     __make_table(lambda l:"%%-%is |" % l, lambda l:"%%-%is |" % l, "",
   1482                  seplinefunc=lambda a,x:"+".join('-'*(y+2) for y in [a-1]+x+[-2]),
   1483                  *args, **kargs)
   1484 
   1485 def make_tex_table(*args, **kargs):
   1486     __make_table(lambda l: "%s", lambda l: "& %s", "\\\\", seplinefunc=lambda a,x:"\\hline", *args, **kargs)
   1487 
   1488 ###############################################
   1489 ### WHOIS CLIENT (not available on windows) ###
   1490 ###############################################
   1491 
   1492 def whois(ip_address):
   1493     """Whois client for Python"""
   1494     whois_ip = str(ip_address)
   1495     try:
   1496         query = socket.gethostbyname(whois_ip)
   1497     except:
   1498         query = whois_ip
   1499     s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
   1500     s.connect(("whois.ripe.net", 43))
   1501     s.send(query.encode("utf8") + b"\r\n")
   1502     answer = b""
   1503     while True:
   1504         d = s.recv(4096)
   1505         answer += d
   1506         if not d:
   1507             break
   1508     s.close()
   1509     ignore_tag = b"remarks:"
   1510     # ignore all lines starting with the ignore_tag
   1511     lines = [ line for line in answer.split(b"\n") if not line or (line and not line.startswith(ignore_tag))]
   1512     # remove empty lines at the bottom
   1513     for i in range(1, len(lines)):
   1514         if not lines[-i].strip():
   1515             del lines[-i]
   1516         else:
   1517             break
   1518     return b"\n".join(lines[3:])
   1519