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