• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# SPDX-License-Identifier: GPL-2.0-only
2# This file is part of Scapy
3# See https://scapy.net/ for more information
4# Copyright (C) Philippe Biondi <phil@secdev.org>
5
6"""
7Main module for interactive startup.
8"""
9
10
11import builtins
12import pathlib
13import sys
14import os
15import getopt
16import code
17import gzip
18import glob
19import importlib
20import io
21from itertools import zip_longest
22import logging
23import pickle
24import types
25import warnings
26from random import choice
27
28# Never add any global import, in main.py, that would trigger a
29# warning message before the console handlers gets added in interact()
30from scapy.error import (
31    log_interactive,
32    log_loading,
33    Scapy_Exception,
34)
35from scapy.themes import DefaultTheme, BlackAndWhite, apply_ipython_style
36from scapy.consts import WINDOWS
37
38from typing import (
39    Any,
40    Dict,
41    List,
42    Optional,
43    Union,
44    overload,
45)
46from scapy.compat import (
47    Literal,
48)
49
50LAYER_ALIASES = {
51    "tls": "tls.all",
52    "msrpce": "msrpce.all",
53}
54
55QUOTES = [
56    ("Craft packets like it is your last day on earth.", "Lao-Tze"),
57    ("Craft packets like I craft my beer.", "Jean De Clerck"),
58    ("Craft packets before they craft you.", "Socrate"),
59    ("Craft me if you can.", "IPv6 layer"),
60    ("To craft a packet, you have to be a packet, and learn how to swim in "
61     "the wires and in the waves.", "Jean-Claude Van Damme"),
62    ("We are in France, we say Skappee. OK? Merci.", "Sebastien Chabal"),
63    ("Wanna support scapy? Star us on GitHub!", "Satoshi Nakamoto"),
64    ("I'll be back.", "Python 2"),
65]
66
67
68def _probe_xdg_folder(var, default, *cf):
69    # type: (str, str, *str) -> Optional[pathlib.Path]
70    path = pathlib.Path(os.environ.get(var, default))
71    if not path.exists():
72        # ~ folder doesn't exist. Create according to spec
73        # https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
74        # "If, when attempting to write a file, the destination directory is
75        # non-existent an attempt should be made to create it with permission 0700."
76        try:
77            path.mkdir(mode=0o700, exist_ok=True)
78        except Exception:
79            # There is a gazillion ways this can fail. Most notably,
80            # a read-only fs.
81            return None
82    return path.joinpath(*cf).resolve()
83
84
85def _probe_config_folder(*cf):
86    # type: (str) -> Optional[pathlib.Path]
87    return _probe_xdg_folder(
88        "XDG_CONFIG_HOME",
89        os.path.join(os.path.expanduser("~"), ".config"),
90        *cf
91    )
92
93
94def _probe_cache_folder(*cf):
95    # type: (str) -> Optional[pathlib.Path]
96    return _probe_xdg_folder(
97        "XDG_CACHE_HOME",
98        os.path.join(os.path.expanduser("~"), ".cache"),
99        *cf
100    )
101
102
103def _read_config_file(cf, _globals=globals(), _locals=locals(),
104                      interactive=True, default=None):
105    # type: (str, Dict[str, Any], Dict[str, Any], bool, Optional[str]) -> None
106    """Read a config file: execute a python file while loading scapy, that
107    may contain some pre-configured values.
108
109    If _globals or _locals are specified, they will be updated with
110    the loaded vars.  This allows an external program to use the
111    function. Otherwise, vars are only available from inside the scapy
112    console.
113
114    Parameters:
115
116    :param _globals: the globals() vars
117    :param _locals: the locals() vars
118    :param interactive: specified whether or not errors should be printed
119    using the scapy console or raised.
120    :param default: if provided, set a default value for the config file
121
122    ex, content of a config.py file:
123        'conf.verb = 42\n'
124    Manual loading:
125        >>> _read_config_file("./config.py"))
126        >>> conf.verb
127        2
128
129    """
130    cf_path = pathlib.Path(cf)
131    if not cf_path.exists():
132        log_loading.debug("Config file [%s] does not exist.", cf)
133        if default is None:
134            return
135        # We have a default ! set it
136        try:
137            if not cf_path.parent.exists():
138                cf_path.parent.mkdir(parents=True, exist_ok=True)
139                if (
140                    not WINDOWS and
141                    "SUDO_UID" in os.environ and
142                    "SUDO_GID" in os.environ
143                ):
144                    # Was started with sudo. Still, chown to the user.
145                    try:
146                        os.chown(
147                            cf_path.parent,
148                            int(os.environ["SUDO_UID"]),
149                            int(os.environ["SUDO_GID"]),
150                        )
151                    except Exception:
152                        pass
153            with cf_path.open("w") as fd:
154                fd.write(default)
155            if (
156                not WINDOWS and
157                "SUDO_UID" in os.environ and
158                "SUDO_GID" in os.environ
159            ):
160                # Was started with sudo. Still, chown to the user.
161                try:
162                    os.chown(
163                        cf_path,
164                        int(os.environ["SUDO_UID"]),
165                        int(os.environ["SUDO_GID"]),
166                    )
167                except Exception:
168                    pass
169            log_loading.debug("Config file [%s] created with default.", cf)
170        except OSError:
171            log_loading.warning("Config file [%s] could not be created.", cf,
172                                exc_info=True)
173            return
174    log_loading.debug("Loading config file [%s]", cf)
175    try:
176        with open(cf) as cfgf:
177            exec(
178                compile(cfgf.read(), cf, 'exec'),
179                _globals, _locals
180            )
181    except IOError as e:
182        if interactive:
183            raise
184        log_loading.warning("Cannot read config file [%s] [%s]", cf, e)
185    except Exception:
186        if interactive:
187            raise
188        log_loading.exception("Error during evaluation of config file [%s]",
189                              cf)
190
191
192def _validate_local(k):
193    # type: (str) -> bool
194    """Returns whether or not a variable should be imported."""
195    return k[0] != "_" and k not in ["range", "map"]
196
197
198# This is ~/.config/scapy
199SCAPY_CONFIG_FOLDER = _probe_config_folder("scapy")
200SCAPY_CACHE_FOLDER = _probe_cache_folder("scapy")
201
202if SCAPY_CONFIG_FOLDER:
203    DEFAULT_PRESTART_FILE: Optional[str] = str(SCAPY_CONFIG_FOLDER / "prestart.py")
204    DEFAULT_STARTUP_FILE: Optional[str] = str(SCAPY_CONFIG_FOLDER / "startup.py")
205else:
206    DEFAULT_PRESTART_FILE = None
207    DEFAULT_STARTUP_FILE = None
208
209# Default scapy prestart.py config file
210
211DEFAULT_PRESTART = """
212# Scapy CLI 'pre-start' config file
213# see https://scapy.readthedocs.io/en/latest/api/scapy.config.html#scapy.config.Conf
214# for all available options
215
216# default interpreter
217conf.interactive_shell = "auto"
218
219# color theme (DefaultTheme, BrightTheme, ColorOnBlackTheme, BlackAndWhite, ...)
220conf.color_theme = DefaultTheme()
221
222# disable INFO: tags related to dependencies missing
223# log_loading.setLevel(logging.WARNING)
224
225# force-use libpcap
226# conf.use_pcap = True
227""".strip()
228
229
230def _usage():
231    # type: () -> None
232    print(
233        "Usage: scapy.py [-s sessionfile] [-c new_startup_file] "
234        "[-p new_prestart_file] [-C] [-P] [-H]\n"
235        "Args:\n"
236        "\t-H: header-less start\n"
237        "\t-C: do not read startup file\n"
238        "\t-P: do not read pre-startup file\n"
239    )
240    sys.exit(0)
241
242
243######################
244#  Extension system  #
245######################
246
247
248def _load(module, globals_dict=None, symb_list=None):
249    # type: (str, Optional[Dict[str, Any]], Optional[List[str]]) -> None
250    """Loads a Python module to make variables, objects and functions
251available globally.
252
253    The idea is to load the module using importlib, then copy the
254symbols to the global symbol table.
255
256    """
257    if globals_dict is None:
258        globals_dict = builtins.__dict__
259    try:
260        mod = importlib.import_module(module)
261        if '__all__' in mod.__dict__:
262            # import listed symbols
263            for name in mod.__dict__['__all__']:
264                if symb_list is not None:
265                    symb_list.append(name)
266                globals_dict[name] = mod.__dict__[name]
267        else:
268            # only import non-private symbols
269            for name, sym in mod.__dict__.items():
270                if _validate_local(name):
271                    if symb_list is not None:
272                        symb_list.append(name)
273                    globals_dict[name] = sym
274    except Exception:
275        log_interactive.error("Loading module %s", module, exc_info=True)
276
277
278def load_module(name, globals_dict=None, symb_list=None):
279    # type: (str, Optional[Dict[str, Any]], Optional[List[str]]) -> None
280    """Loads a Scapy module to make variables, objects and functions
281    available globally.
282
283    """
284    _load("scapy.modules." + name,
285          globals_dict=globals_dict, symb_list=symb_list)
286
287
288def load_layer(name, globals_dict=None, symb_list=None):
289    # type: (str, Optional[Dict[str, Any]], Optional[List[str]]) -> None
290    """Loads a Scapy layer module to make variables, objects and functions
291    available globally.
292
293    """
294    _load("scapy.layers." + LAYER_ALIASES.get(name, name),
295          globals_dict=globals_dict, symb_list=symb_list)
296
297
298def load_contrib(name, globals_dict=None, symb_list=None):
299    # type: (str, Optional[Dict[str, Any]], Optional[List[str]]) -> None
300    """Loads a Scapy contrib module to make variables, objects and
301    functions available globally.
302
303    If no contrib module can be found with the given name, try to find
304    a layer module, since a contrib module may become a layer module.
305
306    """
307    try:
308        importlib.import_module("scapy.contrib." + name)
309        _load("scapy.contrib." + name,
310              globals_dict=globals_dict, symb_list=symb_list)
311    except ImportError as e:
312        # if layer not found in contrib, try in layers
313        try:
314            load_layer(name,
315                       globals_dict=globals_dict, symb_list=symb_list)
316        except ImportError:
317            raise e  # Let's raise the original error to avoid confusion
318
319
320def list_contrib(name=None,  # type: Optional[str]
321                 ret=False,  # type: bool
322                 _debug=False  # type: bool
323                 ):
324    # type: (...) -> Optional[List[Dict[str, str]]]
325    """Show the list of all existing contribs.
326
327    :param name: filter to search the contribs
328    :param ret: whether the function should return a dict instead of
329        printing it
330    :returns: None or a dictionary containing the results if ret=True
331    """
332    # _debug: checks that all contrib modules have correctly defined:
333    # # scapy.contrib.description = [...]
334    # # scapy.contrib.status = [...]
335    # # scapy.contrib.name = [...] (optional)
336    # or set the flag:
337    # # scapy.contrib.description = skip
338    # to skip the file
339    if name is None:
340        name = "*.py"
341    elif "*" not in name and "?" not in name and not name.endswith(".py"):
342        name += ".py"
343    results = []  # type: List[Dict[str, str]]
344    dir_path = os.path.join(os.path.dirname(__file__), "contrib")
345    if sys.version_info >= (3, 5):
346        name = os.path.join(dir_path, "**", name)
347        iterator = glob.iglob(name, recursive=True)
348    else:
349        name = os.path.join(dir_path, name)
350        iterator = glob.iglob(name)
351    for f in iterator:
352        mod = f.replace(os.path.sep, ".").partition("contrib.")[2]
353        if mod.startswith("__"):
354            continue
355        if mod.endswith(".py"):
356            mod = mod[:-3]
357        desc = {"description": "", "status": "", "name": mod}
358        with io.open(f, errors="replace") as fd:
359            for line in fd:
360                if line[0] != "#":
361                    continue
362                p = line.find("scapy.contrib.")
363                if p >= 0:
364                    p += 14
365                    q = line.find("=", p)
366                    key = line[p:q].strip()
367                    value = line[q + 1:].strip()
368                    desc[key] = value
369                if desc["status"] == "skip":
370                    break
371                if desc["description"] and desc["status"]:
372                    results.append(desc)
373                    break
374        if _debug:
375            if desc["status"] == "skip":
376                pass
377            elif not desc["description"] or not desc["status"]:
378                raise Scapy_Exception("Module %s is missing its "
379                                      "contrib infos !" % mod)
380    results.sort(key=lambda x: x["name"])
381    if ret:
382        return results
383    else:
384        for desc in results:
385            print("%(name)-20s: %(description)-40s status=%(status)s" % desc)
386        return None
387
388
389##############################
390#  Session saving/restoring  #
391##############################
392
393def update_ipython_session(session):
394    # type: (Dict[str, Any]) -> None
395    """Updates IPython session with a custom one"""
396    if "_oh" not in session:
397        session["_oh"] = session["Out"] = {}
398        session["In"] = {}
399    try:
400        from IPython import get_ipython
401        get_ipython().user_ns.update(session)
402    except Exception:
403        pass
404
405
406def _scapy_prestart_builtins():
407    # type: () -> Dict[str, Any]
408    """Load Scapy prestart and return all builtins"""
409    return {
410        k: v
411        for k, v in importlib.import_module(".config", "scapy").__dict__.copy().items()
412        if _validate_local(k)
413    }
414
415
416def _scapy_builtins():
417    # type: () -> Dict[str, Any]
418    """Load Scapy and return all builtins"""
419    return {
420        k: v
421        for k, v in importlib.import_module(".all", "scapy").__dict__.copy().items()
422        if _validate_local(k)
423    }
424
425
426def _scapy_exts():
427    # type: () -> Dict[str, Any]
428    """Load Scapy exts and return their builtins"""
429    from scapy.config import conf
430    res = {}
431    for modname, spec in conf.exts.all_specs.items():
432        if spec.default:
433            mod = sys.modules[modname]
434            res.update({
435                k: v
436                for k, v in mod.__dict__.copy().items()
437                if _validate_local(k)
438            })
439    return res
440
441
442def save_session(fname="", session=None, pickleProto=-1):
443    # type: (str, Optional[Dict[str, Any]], int) -> None
444    """Save current Scapy session to the file specified in the fname arg.
445
446    params:
447     - fname: file to save the scapy session in
448     - session: scapy session to use. If None, the console one will be used
449     - pickleProto: pickle proto version (default: -1 = latest)"""
450    from scapy import utils
451    from scapy.config import conf, ConfClass
452    if not fname:
453        fname = conf.session
454        if not fname:
455            conf.session = fname = utils.get_temp_file(keep=True)
456    log_interactive.info("Saving session into [%s]", fname)
457
458    if not session:
459        if conf.interactive_shell in ["ipython", "ptipython"]:
460            from IPython import get_ipython
461            session = get_ipython().user_ns
462        else:
463            session = builtins.__dict__["scapy_session"]
464
465    if not session:
466        log_interactive.error("No session found ?!")
467        return
468
469    ignore = session.get("_scpybuiltins", [])
470    hard_ignore = ["scapy_session", "In", "Out", "open"]
471    to_be_saved = session.copy()
472
473    for k in list(to_be_saved):
474        i = to_be_saved[k]
475        if k[0] == "_":
476            del to_be_saved[k]
477        elif hasattr(i, "__module__") and i.__module__.startswith("IPython"):
478            del to_be_saved[k]
479        elif isinstance(i, ConfClass):
480            del to_be_saved[k]
481        elif k in ignore or k in hard_ignore:
482            del to_be_saved[k]
483        elif isinstance(i, (type, types.ModuleType, types.FunctionType)):
484            if k[0] != "_":
485                log_interactive.warning("[%s] (%s) can't be saved.", k, type(i))
486            del to_be_saved[k]
487        else:
488            try:
489                pickle.dumps(i)
490            except Exception:
491                log_interactive.warning("[%s] (%s) can't be saved.", k, type(i))
492
493    try:
494        os.rename(fname, fname + ".bak")
495    except OSError:
496        pass
497
498    f = gzip.open(fname, "wb")
499    pickle.dump(to_be_saved, f, pickleProto)
500    f.close()
501
502
503def load_session(fname=None):
504    # type: (Optional[Union[str, None]]) -> None
505    """Load current Scapy session from the file specified in the fname arg.
506    This will erase any existing session.
507
508    params:
509     - fname: file to load the scapy session from"""
510    from scapy.config import conf
511    if fname is None:
512        fname = conf.session
513    try:
514        s = pickle.load(gzip.open(fname, "rb"))
515    except IOError:
516        try:
517            s = pickle.load(open(fname, "rb"))
518        except IOError:
519            # Raise "No such file exception"
520            raise
521
522    scapy_session = builtins.__dict__["scapy_session"]
523    s.update({k: scapy_session[k] for k in scapy_session["_scpybuiltins"]})
524    scapy_session.clear()
525    scapy_session.update(s)
526    update_ipython_session(scapy_session)
527
528    log_loading.info("Loaded session [%s]", fname)
529
530
531def update_session(fname=None):
532    # type: (Optional[Union[str, None]]) -> None
533    """Update current Scapy session from the file specified in the fname arg.
534
535    params:
536     - fname: file to load the scapy session from"""
537    from scapy.config import conf
538    if fname is None:
539        fname = conf.session
540    try:
541        s = pickle.load(gzip.open(fname, "rb"))
542    except IOError:
543        s = pickle.load(open(fname, "rb"))
544    scapy_session = builtins.__dict__["scapy_session"]
545    scapy_session.update(s)
546    update_ipython_session(scapy_session)
547
548
549@overload
550def init_session(session_name,  # type: Optional[Union[str, None]]
551                 mydict,  # type: Optional[Union[Dict[str, Any], None]]
552                 ret,  # type: Literal[True]
553                 ):
554    # type: (...) -> Dict[str, Any]
555    pass
556
557
558@overload
559def init_session(session_name,  # type: Optional[Union[str, None]]
560                 mydict=None,  # type: Optional[Union[Dict[str, Any], None]]
561                 ret=False,  # type: Literal[False]
562                 ):
563    # type: (...) -> None
564    pass
565
566
567def init_session(session_name,  # type: Optional[Union[str, None]]
568                 mydict=None,  # type: Optional[Union[Dict[str, Any], None]]
569                 ret=False,  # type: bool
570                 ):
571    # type: (...) -> Union[Dict[str, Any], None]
572    from scapy.config import conf
573    SESSION = {}  # type: Optional[Dict[str, Any]]
574
575    # Load Scapy
576    scapy_builtins = _scapy_builtins()
577
578    # Load exts
579    scapy_builtins.update(_scapy_exts())
580
581    if session_name:
582        try:
583            os.stat(session_name)
584        except OSError:
585            log_loading.info("New session [%s]", session_name)
586        else:
587            try:
588                try:
589                    SESSION = pickle.load(gzip.open(session_name, "rb"))
590                except IOError:
591                    SESSION = pickle.load(open(session_name, "rb"))
592                log_loading.info("Using existing session [%s]", session_name)
593            except ValueError:
594                msg = "Error opening Python3 pickled session on Python2 [%s]"
595                log_loading.error(msg, session_name)
596            except EOFError:
597                log_loading.error("Error opening session [%s]", session_name)
598            except AttributeError:
599                log_loading.error("Error opening session [%s]. "
600                                  "Attribute missing", session_name)
601
602        if SESSION:
603            if "conf" in SESSION:
604                conf.configure(SESSION["conf"])
605                conf.session = session_name
606                SESSION["conf"] = conf
607            else:
608                conf.session = session_name
609        else:
610            conf.session = session_name
611            SESSION = {"conf": conf}
612    else:
613        SESSION = {"conf": conf}
614
615    SESSION.update(scapy_builtins)
616    SESSION["_scpybuiltins"] = scapy_builtins.keys()
617    builtins.__dict__["scapy_session"] = SESSION
618
619    if mydict is not None:
620        builtins.__dict__["scapy_session"].update(mydict)
621        update_ipython_session(mydict)
622    if ret:
623        return SESSION
624    return None
625
626################
627#     Main     #
628################
629
630
631def _prepare_quote(quote, author, max_len=78):
632    # type: (str, str, int) -> List[str]
633    """This function processes a quote and returns a string that is ready
634to be used in the fancy banner.
635
636    """
637    _quote = quote.split(' ')
638    max_len -= 6
639    lines = []
640    cur_line = []  # type: List[str]
641
642    def _len(line):
643        # type: (List[str]) -> int
644        return sum(len(elt) for elt in line) + len(line) - 1
645    while _quote:
646        if not cur_line or (_len(cur_line) + len(_quote[0]) - 1 <= max_len):
647            cur_line.append(_quote.pop(0))
648            continue
649        lines.append('   | %s' % ' '.join(cur_line))
650        cur_line = []
651    if cur_line:
652        lines.append('   | %s' % ' '.join(cur_line))
653        cur_line = []
654    lines.append('   | %s-- %s' % (" " * (max_len - len(author) - 5), author))
655    return lines
656
657
658def get_fancy_banner(mini: Optional[bool] = None) -> str:
659    """
660    Generates the fancy Scapy banner
661
662    :param mini: if set, force a mini banner or not. Otherwise detect
663    """
664    from scapy.config import conf
665    from scapy.utils import get_terminal_width
666    if mini is None:
667        mini_banner = (get_terminal_width() or 84) <= 75
668    else:
669        mini_banner = mini
670
671    the_logo = [
672        "                                      ",
673        "                     aSPY//YASa       ",
674        "             apyyyyCY//////////YCa    ",
675        "            sY//////YSpcs  scpCY//Pp  ",
676        " ayp ayyyyyyySCP//Pp           syY//C ",
677        " AYAsAYYYYYYYY///Ps              cY//S",
678        "         pCCCCY//p          cSSps y//Y",
679        "         SPPPP///a          pP///AC//Y",
680        "              A//A            cyP////C",
681        "              p///Ac            sC///a",
682        "              P////YCpc           A//A",
683        "       scccccp///pSP///p          p//Y",
684        "      sY/////////y  caa           S//P",
685        "       cayCyayP//Ya              pY/Ya",
686        "        sY/PsY////YCc          aC//Yp ",
687        "         sc  sccaCY//PCypaapyCP//YSs  ",
688        "                  spCPY//////YPSps    ",
689        "                       ccaacs         ",
690        "                                      ",
691    ]
692
693    # Used on mini screens
694    the_logo_mini = [
695        "      .SYPACCCSASYY  ",
696        "P /SCS/CCS        ACS",
697        "       /A          AC",
698        "     A/PS       /SPPS",
699        "        YP        (SC",
700        "       SPS/A.      SC",
701        "   Y/PACC          PP",
702        "    PY*AYC        CAA",
703        "         YYCY//SCYP  ",
704    ]
705
706    the_banner = [
707        "",
708        "",
709        "   |",
710        "   | Welcome to Scapy",
711        "   | Version %s" % conf.version,
712        "   |",
713        "   | https://github.com/secdev/scapy",
714        "   |",
715        "   | Have fun!",
716        "   |",
717    ]
718
719    if mini_banner:
720        the_logo = the_logo_mini
721        the_banner = [x[2:] for x in the_banner[3:-1]]
722        the_banner = [""] + the_banner + [""]
723    else:
724        quote, author = choice(QUOTES)
725        the_banner.extend(_prepare_quote(quote, author, max_len=39))
726        the_banner.append("   |")
727    return "\n".join(
728        logo + banner for logo, banner in zip_longest(
729            (conf.color_theme.logo(line) for line in the_logo),
730            (conf.color_theme.success(line) for line in the_banner),
731            fillvalue=""
732        )
733    )
734
735
736def interact(mydict=None, argv=None, mybanner=None, loglevel=logging.INFO):
737    # type: (Optional[Any], Optional[Any], Optional[Any], int) -> None
738    """
739    Starts Scapy's console.
740    """
741    # We're in interactive mode, let's throw the DeprecationWarnings
742    warnings.simplefilter("always")
743
744    # Set interactive mode, load the color scheme
745    from scapy.config import conf
746    conf.interactive = True
747    conf.color_theme = DefaultTheme()
748    if loglevel is not None:
749        conf.logLevel = loglevel
750
751    STARTUP_FILE = DEFAULT_STARTUP_FILE
752    PRESTART_FILE = DEFAULT_PRESTART_FILE
753
754    session_name = None
755
756    if argv is None:
757        argv = sys.argv
758
759    try:
760        opts = getopt.getopt(argv[1:], "hs:Cc:Pp:d:H")
761        for opt, param in opts[0]:
762            if opt == "-h":
763                _usage()
764            elif opt == "-H":
765                conf.fancy_banner = False
766                conf.verb = 1
767                conf.logLevel = logging.WARNING
768            elif opt == "-s":
769                session_name = param
770            elif opt == "-c":
771                STARTUP_FILE = param
772            elif opt == "-C":
773                STARTUP_FILE = None
774            elif opt == "-p":
775                PRESTART_FILE = param
776            elif opt == "-P":
777                PRESTART_FILE = None
778            elif opt == "-d":
779                conf.logLevel = max(1, conf.logLevel - 10)
780
781        if len(opts[1]) > 0:
782            raise getopt.GetoptError(
783                "Too many parameters : [%s]" % " ".join(opts[1])
784            )
785
786    except getopt.GetoptError as msg:
787        log_loading.error(msg)
788        sys.exit(1)
789
790    # Reset sys.argv, otherwise IPython thinks it is for him
791    sys.argv = sys.argv[:1]
792
793    if PRESTART_FILE:
794        _read_config_file(
795            PRESTART_FILE,
796            interactive=True,
797            _locals=_scapy_prestart_builtins(),
798            default=DEFAULT_PRESTART,
799        )
800
801    SESSION = init_session(session_name, mydict=mydict, ret=True)
802
803    if STARTUP_FILE:
804        _read_config_file(
805            STARTUP_FILE,
806            interactive=True,
807            _locals=SESSION
808        )
809
810    if conf.fancy_banner:
811        banner_text = get_fancy_banner()
812    else:
813        banner_text = "Welcome to Scapy (%s)" % conf.version
814    if mybanner is not None:
815        banner_text += "\n"
816        banner_text += mybanner
817
818    # Configure interactive terminal
819
820    if conf.interactive_shell not in [
821            "ipython",
822            "python",
823            "ptpython",
824            "ptipython",
825            "bpython",
826            "auto"]:
827        log_loading.warning("Unknown conf.interactive_shell ! Using 'auto'")
828        conf.interactive_shell = "auto"
829
830    # Auto detect available shells.
831    # Order:
832    # 1. IPython
833    # 2. bpython
834    # 3. ptpython
835
836    _IMPORTS = {
837        "ipython": ["IPython"],
838        "bpython": ["bpython"],
839        "ptpython": ["ptpython"],
840        "ptipython": ["IPython", "ptpython"],
841    }
842
843    if conf.interactive_shell == "auto":
844        # Auto detect
845        for imp in ["IPython", "bpython", "ptpython"]:
846            try:
847                importlib.import_module(imp)
848                conf.interactive_shell = imp.lower()
849                break
850            except ImportError:
851                continue
852        else:
853            log_loading.warning(
854                "No alternative Python interpreters found ! "
855                "Using standard Python shell instead."
856            )
857            conf.interactive_shell = "python"
858
859    if conf.interactive_shell in _IMPORTS:
860        # Check import
861        for imp in _IMPORTS[conf.interactive_shell]:
862            try:
863                importlib.import_module(imp)
864            except ImportError:
865                log_loading.warning("%s requested but not found !" % imp)
866                conf.interactive_shell = "python"
867
868    # Default shell
869    if conf.interactive_shell == "python":
870        disabled = ["History"]
871        if WINDOWS:
872            disabled.append("Colors")
873            conf.color_theme = BlackAndWhite()
874        else:
875            try:
876                # Bad completer.. but better than nothing
877                import rlcompleter
878                import readline
879                readline.set_completer(
880                    rlcompleter.Completer(namespace=SESSION).complete
881                )
882                readline.parse_and_bind('tab: complete')
883            except ImportError:
884                disabled.insert(0, "AutoCompletion")
885        # Display warning when using the default REPL
886        log_loading.info(
887            "Using the default Python shell: %s %s disabled." % (
888                ",".join(disabled),
889                "is" if len(disabled) == 1 else "are"
890            )
891        )
892
893    # ptpython configure function
894    def ptpython_configure(repl):
895        # type: (Any) -> None
896        # Hide status bar
897        repl.show_status_bar = False
898        # Complete while typing (versus only when pressing tab)
899        repl.complete_while_typing = False
900        # Enable auto-suggestions
901        repl.enable_auto_suggest = True
902        # Disable exit confirmation
903        repl.confirm_exit = False
904        # Show signature
905        repl.show_signature = True
906        # Apply Scapy color theme: TODO
907        # repl.install_ui_colorscheme("scapy",
908        #                             Style.from_dict(_custom_ui_colorscheme))
909        # repl.use_ui_colorscheme("scapy")
910
911    # Extend banner text
912    if conf.interactive_shell in ["ipython", "ptipython"]:
913        import IPython
914        if conf.interactive_shell == "ptipython":
915            banner = banner_text + " using IPython %s" % IPython.__version__
916            try:
917                from importlib.metadata import version
918                ptpython_version = " " + version('ptpython')
919            except ImportError:
920                ptpython_version = ""
921            banner += " and ptpython%s" % ptpython_version
922        else:
923            banner = banner_text + " using IPython %s" % IPython.__version__
924    elif conf.interactive_shell == "ptpython":
925        try:
926            from importlib.metadata import version
927            ptpython_version = " " + version('ptpython')
928        except ImportError:
929            ptpython_version = ""
930        banner = banner_text + " using ptpython%s" % ptpython_version
931    elif conf.interactive_shell == "bpython":
932        import bpython
933        banner = banner_text + " using bpython %s" % bpython.__version__
934
935    # Start IPython or ptipython
936    if conf.interactive_shell in ["ipython", "ptipython"]:
937        banner += "\n"
938        if conf.interactive_shell == "ptipython":
939            from ptpython.ipython import embed
940        else:
941            from IPython import embed
942        try:
943            from traitlets.config.loader import Config
944        except ImportError:
945            log_loading.warning(
946                "traitlets not available. Some Scapy shell features won't be "
947                "available."
948            )
949            try:
950                embed(
951                    display_banner=False,
952                    user_ns=SESSION,
953                    exec_lines=["print(\"\"\"" + banner + "\"\"\")"]
954                )
955            except Exception:
956                code.interact(banner=banner_text, local=SESSION)
957        else:
958            cfg = Config()
959            try:
960                from IPython import get_ipython
961                if not get_ipython():
962                    raise ImportError
963            except ImportError:
964                # Set "classic" prompt style when launched from
965                # run_scapy(.bat) files Register and apply scapy
966                # color+prompt style
967                apply_ipython_style(shell=cfg.InteractiveShellEmbed)
968                cfg.InteractiveShellEmbed.confirm_exit = False
969                cfg.InteractiveShellEmbed.separate_in = u''
970            if int(IPython.__version__[0]) >= 6:
971                cfg.InteractiveShellEmbed.term_title = True
972                cfg.InteractiveShellEmbed.term_title_format = ("Scapy %s" %
973                                                               conf.version)
974                # As of IPython 6-7, the jedi completion module is a dumpster
975                # of fire that should be scrapped never to be seen again.
976                # This is why the following defaults to False. Feel free to hurt
977                # yourself (#GH4056) :P
978                cfg.Completer.use_jedi = conf.ipython_use_jedi
979            else:
980                cfg.InteractiveShellEmbed.term_title = False
981            cfg.HistoryAccessor.hist_file = conf.histfile
982            cfg.InteractiveShell.banner1 = banner
983            # configuration can thus be specified here.
984            _kwargs = {}
985            if conf.interactive_shell == "ptipython":
986                _kwargs["configure"] = ptpython_configure
987            try:
988                embed(config=cfg, user_ns=SESSION, **_kwargs)
989            except (AttributeError, TypeError):
990                code.interact(banner=banner_text, local=SESSION)
991    # Start ptpython
992    elif conf.interactive_shell == "ptpython":
993        # ptpython has special, non-default handling of __repr__ which breaks Scapy.
994        # For instance: >>> IP()
995        log_loading.warning("ptpython support is currently partially broken")
996        from ptpython.repl import embed
997        # ptpython has no banner option
998        banner += "\n"
999        print(banner)
1000        embed(
1001            locals=SESSION,
1002            history_filename=conf.histfile,
1003            title="Scapy %s" % conf.version,
1004            configure=ptpython_configure
1005        )
1006    # Start bpython
1007    elif conf.interactive_shell == "bpython":
1008        from bpython.curtsies import main as embed
1009        embed(
1010            args=["-q", "-i"],
1011            locals_=SESSION,
1012            banner=banner,
1013            welcome_message=""
1014        )
1015    # Start Python
1016    elif conf.interactive_shell == "python":
1017        code.interact(banner=banner_text, local=SESSION)
1018    else:
1019        raise ValueError("Invalid conf.interactive_shell")
1020
1021    if conf.session:
1022        save_session(conf.session, SESSION)
1023
1024
1025if __name__ == "__main__":
1026    interact()
1027