1## This file is part of Scapy
2## See http://www.secdev.org/projects/scapy for more informations
3## Copyright (C) Philippe Biondi <phil@secdev.org>
4## This program is published under a GPLv2 license
5
6"""
7Implementation of the configuration object.
8"""
9
10from __future__ import absolute_import
11from __future__ import print_function
12import os,time,socket,sys
13
14from scapy import VERSION
15from scapy.data import *
16from scapy import base_classes
17from scapy.themes import NoTheme, apply_ipython_style
18from scapy.error import log_scapy
19import scapy.modules.six as six
20
21############
22## Config ##
23############
24
25class ConfClass(object):
26    def configure(self, cnf):
27        self.__dict__ = cnf.__dict__.copy()
28    def __repr__(self):
29        return str(self)
30    def __str__(self):
31        s = ""
32        keys = self.__class__.__dict__.copy()
33        keys.update(self.__dict__)
34        keys = sorted(keys)
35        for i in keys:
36            if i[0] != "_":
37                r = repr(getattr(self, i))
38                r = " ".join(r.split())
39                wlen = 76-max(len(i),10)
40                if len(r) > wlen:
41                    r = r[:wlen-3]+"..."
42                s += "%-10s = %s\n" % (i, r)
43        return s[:-1]
44
45class Interceptor(object):
46    def __init__(self, name, default, hook, args=None, kargs=None):
47        self.name = name
48        self.intname = "_intercepted_%s" % name
49        self.default=default
50        self.hook = hook
51        self.args = args if args is not None else []
52        self.kargs = kargs if kargs is not None else {}
53    def __get__(self, obj, typ=None):
54        if not hasattr(obj, self.intname):
55            setattr(obj, self.intname, self.default)
56        return getattr(obj, self.intname)
57    def __set__(self, obj, val):
58        setattr(obj, self.intname, val)
59        self.hook(self.name, val, *self.args, **self.kargs)
60
61
62class ProgPath(ConfClass):
63    pdfreader = "acroread"
64    psreader = "gv"
65    dot = "dot"
66    display = "display"
67    tcpdump = "tcpdump"
68    tcpreplay = "tcpreplay"
69    hexedit = "hexer"
70    tshark = "tshark"
71    wireshark = "wireshark"
72    ifconfig = "ifconfig"
73
74
75class ConfigFieldList:
76    def __init__(self):
77        self.fields = set()
78        self.layers = set()
79    @staticmethod
80    def _is_field(f):
81        return hasattr(f, "owners")
82    def _recalc_layer_list(self):
83        self.layers = {owner for f in self.fields for owner in f.owners}
84    def add(self, *flds):
85        self.fields |= {f for f in flds if self._is_field(f)}
86        self._recalc_layer_list()
87    def remove(self, *flds):
88        self.fields -= set(flds)
89        self._recalc_layer_list()
90    def __contains__(self, elt):
91        if isinstance(elt, base_classes.Packet_metaclass):
92            return elt in self.layers
93        return elt in self.fields
94    def __repr__(self):
95        return "<%s [%s]>" %  (self.__class__.__name__," ".join(str(x) for x in self.fields))
96
97class Emphasize(ConfigFieldList):
98    pass
99
100class Resolve(ConfigFieldList):
101    pass
102
103
104class Num2Layer:
105    def __init__(self):
106        self.num2layer = {}
107        self.layer2num = {}
108
109    def register(self, num, layer):
110        self.register_num2layer(num, layer)
111        self.register_layer2num(num, layer)
112
113    def register_num2layer(self, num, layer):
114        self.num2layer[num] = layer
115    def register_layer2num(self, num, layer):
116        self.layer2num[layer] = num
117
118    def __getitem__(self, item):
119        if isinstance(item, base_classes.Packet_metaclass):
120            return self.layer2num[item]
121        return self.num2layer[item]
122    def __contains__(self, item):
123        if isinstance(item, base_classes.Packet_metaclass):
124            return item in self.layer2num
125        return item in self.num2layer
126    def get(self, item, default=None):
127        if item in self:
128            return self[item]
129        return default
130
131    def __repr__(self):
132        lst = []
133        for num,layer in six.iteritems(self.num2layer):
134            if layer in self.layer2num and self.layer2num[layer] == num:
135                dir = "<->"
136            else:
137                dir = " ->"
138            lst.append((num,"%#6x %s %-20s (%s)" % (num, dir, layer.__name__,
139                                                    layer._name)))
140        for layer,num in six.iteritems(self.layer2num):
141            if num not in self.num2layer or self.num2layer[num] != layer:
142                lst.append((num,"%#6x <-  %-20s (%s)" % (num, layer.__name__,
143                                                         layer._name)))
144        lst.sort()
145        return "\n".join(y for x,y in lst)
146
147
148class LayersList(list):
149    def __repr__(self):
150        s=[]
151        for l in self:
152            s.append("%-20s: %s" % (l.__name__,l.name))
153        return "\n".join(s)
154    def register(self, layer):
155        self.append(layer)
156
157class CommandsList(list):
158    def __repr__(self):
159        s=[]
160        for l in sorted(self,key=lambda x:x.__name__):
161            if l.__doc__:
162                doc = l.__doc__.split("\n")[0]
163            else:
164                doc = "--"
165            s.append("%-20s: %s" % (l.__name__,doc))
166        return "\n".join(s)
167    def register(self, cmd):
168        self.append(cmd)
169        return cmd # return cmd so that method can be used as a decorator
170
171def lsc():
172    print(repr(conf.commands))
173
174class CacheInstance(dict, object):
175    __slots__ = ["timeout", "name", "_timetable", "__dict__"]
176    def __init__(self, name="noname", timeout=None):
177        self.timeout = timeout
178        self.name = name
179        self._timetable = {}
180    def flush(self):
181        self.__init__(name=self.name, timeout=self.timeout)
182    def __getitem__(self, item):
183        if item in self.__slots__:
184            return object.__getattribute__(self, item)
185        val = dict.__getitem__(self,item)
186        if self.timeout is not None:
187            t = self._timetable[item]
188            if time.time()-t > self.timeout:
189                raise KeyError(item)
190        return val
191    def get(self, item, default=None):
192        # overloading this method is needed to force the dict to go through
193        # the timetable check
194        try:
195            return self[item]
196        except KeyError:
197            return default
198    def __setitem__(self, item, v):
199        if item in self.__slots__:
200            return object.__setattr__(self, item, v)
201        self._timetable[item] = time.time()
202        dict.__setitem__(self, item,v)
203    def update(self, other):
204        for key, value in other.iteritems():
205            # We only update an element from `other` either if it does
206            # not exist in `self` or if the entry in `self` is older.
207            if key not in self or self._timetable[key] < other._timetable[key]:
208                dict.__setitem__(self, key, value)
209                self._timetable[key] = other._timetable[key]
210    def iteritems(self):
211        if self.timeout is None:
212            return six.iteritems(self.__dict__)
213        t0=time.time()
214        return ((k,v) for (k,v) in six.iteritems(self.__dict__) if t0-self._timetable[k] < self.timeout)
215    def iterkeys(self):
216        if self.timeout is None:
217            return six.iterkeys(self.__dict__)
218        t0=time.time()
219        return (k for k in six.iterkeys(self.__dict__) if t0-self._timetable[k] < self.timeout)
220    def __iter__(self):
221        return six.iterkeys(self.__dict__)
222    def itervalues(self):
223        if self.timeout is None:
224            return six.itervalues(self.__dict__)
225        t0=time.time()
226        return (v for (k,v) in six.iteritems(self.__dict__) if t0-self._timetable[k] < self.timeout)
227    def items(self):
228        if self.timeout is None:
229            return dict.items(self)
230        t0=time.time()
231        return [(k,v) for (k,v) in six.iteritems(self.__dict__) if t0-self._timetable[k] < self.timeout]
232    def keys(self):
233        if self.timeout is None:
234            return dict.keys(self)
235        t0=time.time()
236        return [k for k in six.iterkeys(self.__dict__) if t0-self._timetable[k] < self.timeout]
237    def values(self):
238        if self.timeout is None:
239            return six.values(self)
240        t0=time.time()
241        return [v for (k,v) in six.iteritems(self.__dict__) if t0-self._timetable[k] < self.timeout]
242    def __len__(self):
243        if self.timeout is None:
244            return dict.__len__(self)
245        return len(self.keys())
246    def summary(self):
247        return "%s: %i valid items. Timeout=%rs" % (self.name, len(self), self.timeout)
248    def __repr__(self):
249        s = []
250        if self:
251            mk = max(len(k) for k in six.iterkeys(self.__dict__))
252            fmt = "%%-%is %%s" % (mk+1)
253            for item in six.iteritems(self.__dict__):
254                s.append(fmt % item)
255        return "\n".join(s)
256
257
258
259
260class NetCache:
261    def __init__(self):
262        self._caches_list = []
263
264
265    def add_cache(self, cache):
266        self._caches_list.append(cache)
267        setattr(self,cache.name,cache)
268    def new_cache(self, name, timeout=None):
269        c = CacheInstance(name=name, timeout=timeout)
270        self.add_cache(c)
271    def __delattr__(self, attr):
272        raise AttributeError("Cannot delete attributes")
273    def update(self, other):
274        for co in other._caches_list:
275            if hasattr(self, co.name):
276                getattr(self,co.name).update(co)
277            else:
278                self.add_cache(co.copy())
279    def flush(self):
280        for c in self._caches_list:
281            c.flush()
282    def __repr__(self):
283        return "\n".join(c.summary() for c in self._caches_list)
284
285
286class LogLevel(object):
287    def __get__(self, obj, otype):
288        return obj._logLevel
289    def __set__(self,obj,val):
290        log_scapy.setLevel(val)
291        obj._logLevel = val
292
293
294def isCryptographyValid():
295    """
296    Check if the cryptography library is present, and if it is recent enough
297    for most usages in scapy (v1.7 or later).
298    """
299    try:
300        import cryptography
301    except ImportError:
302        return False
303    from distutils.version import LooseVersion
304    return LooseVersion(cryptography.__version__) >= LooseVersion("1.7")
305
306
307def isCryptographyAdvanced():
308    """
309    Check if the cryptography library is present, and if it supports X25519,
310    ChaCha20Poly1305 and such (v2.0 or later).
311    """
312    try:
313        import cryptography
314    except ImportError:
315        return False
316    from distutils.version import LooseVersion
317    lib_valid = LooseVersion(cryptography.__version__) >= LooseVersion("2.0")
318    if not lib_valid:
319        return False
320
321    try:
322        from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
323        X25519PrivateKey.generate()
324    except:
325        return False
326    else:
327        return True
328
329def isPyPy():
330    """Returns either scapy is running under PyPy or not"""
331    try:
332        import __pypy__
333        return True
334    except ImportError:
335        return False
336
337def _prompt_changer(attr, val):
338    """Change the current prompt theme"""
339    try:
340        sys.ps1 = conf.color_theme.prompt(conf.prompt)
341    except:
342        pass
343    try:
344        apply_ipython_style(get_ipython())
345    except NameError:
346        pass
347
348class Conf(ConfClass):
349    """This object contains the configuration of Scapy.
350session  : filename where the session will be saved
351interactive_shell : can be "ipython", "python" or "auto". Default: Auto
352stealth  : if 1, prevents any unwanted packet to go out (ARP, DNS, ...)
353checkIPID: if 0, doesn't check that IPID matches between IP sent and ICMP IP citation received
354           if 1, checks that they either are equal or byte swapped equals (bug in some IP stacks)
355           if 2, strictly checks that they are equals
356checkIPsrc: if 1, checks IP src in IP and ICMP IP citation match (bug in some NAT stacks)
357checkIPinIP: if True, checks that IP-in-IP layers match. If False, do not
358             check IP layers that encapsulates another IP layer
359check_TCPerror_seqack: if 1, also check that TCP seq and ack match the ones in ICMP citation
360iff      : selects the default output interface for srp() and sendp(). default:"eth0")
361verb     : level of verbosity, from 0 (almost mute) to 3 (verbose)
362promisc  : default mode for listening socket (to get answers if you spoof on a lan)
363sniff_promisc : default mode for sniff()
364filter   : bpf filter added to every sniffing socket to exclude traffic from analysis
365histfile : history file
366padding  : includes padding in disassembled packets
367except_filter : BPF filter for packets to ignore
368debug_match : when 1, store received packet that are not matched into debug.recv
369route    : holds the Scapy routing table and provides methods to manipulate it
370warning_threshold : how much time between warnings from the same place
371ASN1_default_codec: Codec used by default for ASN1 objects
372mib      : holds MIB direct access dictionary
373resolve  : holds list of fields for which resolution should be done
374noenum   : holds list of enum fields for which conversion to string should NOT be done
375AS_resolver: choose the AS resolver class to use
376extensions_paths: path or list of paths where extensions are to be looked for
377contribs : a dict which can be used by contrib layers to store local configuration
378debug_tls:When 1, print some TLS session secrets when they are computed.
379"""
380    version = VERSION
381    session = ""
382    interactive = False
383    interactive_shell = ""
384    stealth = "not implemented"
385    iface = None
386    iface6 = None
387    layers = LayersList()
388    commands = CommandsList()
389    logLevel = LogLevel()
390    checkIPID = 0
391    checkIPsrc = 1
392    checkIPaddr = 1
393    checkIPinIP = True
394    check_TCPerror_seqack = 0
395    verb = 2
396    prompt = Interceptor("prompt", ">>> ", _prompt_changer)
397    promisc = 1
398    sniff_promisc = 1
399    raw_layer = None
400    raw_summary = False
401    default_l2 = None
402    l2types = Num2Layer()
403    l3types = Num2Layer()
404    L3socket = None
405    L2socket = None
406    L2listen = None
407    BTsocket = None
408    min_pkt_size = 60
409    histfile = os.getenv('SCAPY_HISTFILE',
410                         os.path.join(os.path.expanduser("~"),
411                                      ".scapy_history"))
412    padding = 1
413    except_filter = ""
414    debug_match = 0
415    debug_tls = 0
416    wepkey = ""
417    cache_iflist = {}
418    cache_ipaddrs = {}
419    route = None # Filed by route.py
420    route6 = None # Filed by route6.py
421    auto_fragment = 1
422    debug_dissector = 0
423    color_theme = Interceptor("color_theme", NoTheme(), _prompt_changer)
424    warning_threshold = 5
425    warning_next_only_once = False
426    prog = ProgPath()
427    resolve = Resolve()
428    noenum = Resolve()
429    emph = Emphasize()
430    use_pypy = isPyPy()
431    use_pcap = os.getenv("SCAPY_USE_PCAPDNET", "").lower().startswith("y")
432    use_dnet = os.getenv("SCAPY_USE_PCAPDNET", "").lower().startswith("y")
433    use_bpf = False
434    use_winpcapy = False
435    use_npcap = False
436    ipv6_enabled = socket.has_ipv6
437    ethertypes = ETHER_TYPES
438    protocols = IP_PROTOS
439    services_tcp = TCP_SERVICES
440    services_udp = UDP_SERVICES
441    extensions_paths = "."
442    manufdb = MANUFDB
443    stats_classic_protocols = []
444    stats_dot11_protocols = []
445    temp_files = []
446    netcache = NetCache()
447    geoip_city = '/usr/share/GeoIP/GeoIPCity.dat'
448    geoip_city_ipv6 = '/usr/share/GeoIP/GeoIPCityv6.dat'
449    load_layers = ["l2", "inet", "dhcp", "dns", "dot11", "gprs",
450                   "hsrp", "inet6", "ir", "isakmp", "l2tp", "mgcp",
451                   "mobileip", "netbios", "netflow", "ntp", "ppp", "pptp",
452                   "radius", "rip", "rtp", "skinny", "smb", "snmp",
453                   "tftp", "x509", "bluetooth", "dhcp6", "llmnr",
454                   "sctp", "vrrp", "ipsec", "lltd", "vxlan", "eap"]
455    contribs = dict()
456    crypto_valid = isCryptographyValid()
457    crypto_valid_advanced = isCryptographyAdvanced()
458    fancy_prompt = True
459
460
461if not Conf.ipv6_enabled:
462    log_scapy.warning("IPv6 support disabled in Python. Cannot load Scapy IPv6 layers.")
463    for m in ["inet6","dhcp6"]:
464        if m in Conf.load_layers:
465            Conf.load_layers.remove(m)
466
467if not Conf.crypto_valid:
468    log_scapy.warning("Crypto-related methods disabled for IPsec, Dot11 "
469                      "and TLS layers (needs python-cryptography v1.7+).")
470
471conf=Conf()
472conf.logLevel=30 # 30=Warning
473
474
475def crypto_validator(func):
476    """
477    This a decorator to be used for any method relying on the cryptography library.
478    Its behaviour depends on the 'crypto_valid' attribute of the global 'conf'.
479    """
480    def func_in(*args, **kwargs):
481        if not conf.crypto_valid:
482            raise ImportError("Cannot execute crypto-related method! "
483                              "Please install python-cryptography v1.7 or later.")
484        return func(*args, **kwargs)
485    return func_in
486