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