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