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