Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/scapy/main.py: 19%

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

426 statements  

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 code 

13import getopt 

14import glob 

15import importlib 

16import io 

17import logging 

18import os 

19import pathlib 

20import shutil 

21import sys 

22import warnings 

23 

24from itertools import zip_longest 

25from random import choice 

26 

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 

36 

37from typing import ( 

38 Any, 

39 Dict, 

40 List, 

41 Optional, 

42 Union, 

43 overload, 

44) 

45from scapy.compat import ( 

46 Literal, 

47) 

48 

49LAYER_ALIASES = { 

50 "tls": "tls.all", 

51 "msrpce": "msrpce.all", 

52} 

53 

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] 

65 

66 

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 path.mkdir(mode=0o700, exist_ok=True) 

77 except Exception: 

78 # There is a gazillion ways this can fail. Most notably, a read-only fs or no 

79 # permissions to even check for folder to exist (e.x. privileges were dropped 

80 # before scapy was started). 

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 _probe_share_folder(*cf): 

104 # type: (str) -> Optional[pathlib.Path] 

105 return _probe_xdg_folder( 

106 "XDG_DATA_HOME", 

107 os.path.join(os.path.expanduser("~"), ".local", "share"), 

108 *cf 

109 ) 

110 

111 

112def _check_perms(file: Union[pathlib.Path, str]) -> None: 

113 """ 

114 Checks that the permissions of a file are properly user-specific, if sudo is used. 

115 """ 

116 if ( 

117 not WINDOWS and 

118 "SUDO_UID" in os.environ and 

119 "SUDO_GID" in os.environ 

120 ): 

121 # Was started with sudo. Still, chown to the user. 

122 try: 

123 os.chown( 

124 file, 

125 int(os.environ["SUDO_UID"]), 

126 int(os.environ["SUDO_GID"]), 

127 ) 

128 except Exception: 

129 pass 

130 

131 

132def _read_config_file(cf, _globals=globals(), _locals=locals(), 

133 interactive=True, default=None): 

134 # type: (str, Dict[str, Any], Dict[str, Any], bool, Optional[str]) -> None 

135 """Read a config file: execute a python file while loading scapy, that 

136 may contain some pre-configured values. 

137 

138 If _globals or _locals are specified, they will be updated with 

139 the loaded vars. This allows an external program to use the 

140 function. Otherwise, vars are only available from inside the scapy 

141 console. 

142 

143 Parameters: 

144 

145 :param _globals: the globals() vars 

146 :param _locals: the locals() vars 

147 :param interactive: specified whether or not errors should be printed 

148 using the scapy console or raised. 

149 :param default: if provided, set a default value for the config file 

150 

151 ex, content of a config.py file: 

152 'conf.verb = 42\n' 

153 Manual loading: 

154 >>> _read_config_file("./config.py")) 

155 >>> conf.verb 

156 2 

157 

158 """ 

159 cf_path = pathlib.Path(cf) 

160 if not cf_path.exists(): 

161 log_loading.debug("Config file [%s] does not exist.", cf) 

162 if default is None: 

163 return 

164 # We have a default ! set it 

165 try: 

166 if not cf_path.parent.exists(): 

167 cf_path.parent.mkdir(parents=True, exist_ok=True) 

168 _check_perms(cf_path.parent) 

169 

170 with cf_path.open("w") as fd: 

171 fd.write(default) 

172 

173 _check_perms(cf_path) 

174 log_loading.debug("Config file [%s] created with default.", cf) 

175 except OSError: 

176 log_loading.warning("Config file [%s] could not be created.", cf, 

177 exc_info=True) 

178 return 

179 log_loading.debug("Loading config file [%s]", cf) 

180 try: 

181 with open(cf) as cfgf: 

182 exec( 

183 compile(cfgf.read(), cf, 'exec'), 

184 _globals, _locals 

185 ) 

186 except IOError as e: 

187 if interactive: 

188 raise 

189 log_loading.warning("Cannot read config file [%s] [%s]", cf, e) 

190 except Exception: 

191 if interactive: 

192 raise 

193 log_loading.exception("Error during evaluation of config file [%s]", 

194 cf) 

195 

196 

197def _validate_local(k): 

198 # type: (str) -> bool 

199 """Returns whether or not a variable should be imported.""" 

200 return k[0] != "_" and k not in ["range", "map"] 

201 

202 

203# This is ~/.config/scapy 

204SCAPY_CONFIG_FOLDER = _probe_config_folder("scapy") 

205SCAPY_CACHE_FOLDER = _probe_cache_folder("scapy") 

206 

207if SCAPY_CONFIG_FOLDER: 

208 DEFAULT_PRESTART_FILE: Optional[str] = str(SCAPY_CONFIG_FOLDER / "prestart.py") 

209 DEFAULT_STARTUP_FILE: Optional[str] = str(SCAPY_CONFIG_FOLDER / "startup.py") 

210else: 

211 DEFAULT_PRESTART_FILE = None 

212 DEFAULT_STARTUP_FILE = None 

213 

214# https://github.com/scop/bash-completion/blob/main/README.md#faq 

215if "BASH_COMPLETION_USER_DIR" in os.environ: 

216 BASH_COMPLETION_USER_DIR: Optional[pathlib.Path] = pathlib.Path( 

217 os.environ["BASH_COMPLETION_USER_DIR"] 

218 ) 

219else: 

220 BASH_COMPLETION_USER_DIR = _probe_share_folder("bash-completion") 

221 

222if BASH_COMPLETION_USER_DIR: 

223 BASH_COMPLETION_FOLDER: Optional[pathlib.Path] = ( 

224 BASH_COMPLETION_USER_DIR / "completions" 

225 ) 

226else: 

227 BASH_COMPLETION_FOLDER = None 

228 

229 

230# Default scapy prestart.py config file 

231 

232DEFAULT_PRESTART = """ 

233# Scapy CLI 'pre-start' config file 

234# see https://scapy.readthedocs.io/en/latest/api/scapy.config.html#scapy.config.Conf 

235# for all available options 

236 

237# default interpreter 

238conf.interactive_shell = "auto" 

239 

240# color theme (DefaultTheme, BrightTheme, ColorOnBlackTheme, BlackAndWhite, ...) 

241conf.color_theme = DefaultTheme() 

242 

243# disable INFO: tags related to dependencies missing 

244# log_loading.setLevel(logging.WARNING) 

245 

246# extensions to load by default 

247conf.load_extensions = [ 

248 # "scapy-red", 

249 # "scapy-rpc", 

250] 

251 

252# force-use libpcap 

253# conf.use_pcap = True 

254""".strip() 

255 

256 

257def _usage(): 

258 # type: () -> None 

259 print( 

260 "Usage: scapy.py [-c new_startup_file] " 

261 "[-p new_prestart_file] [-C] [-P] [-H]\n" 

262 "Args:\n" 

263 "\t-H: header-less start\n" 

264 "\t-C: do not read startup file\n" 

265 "\t-P: do not read pre-startup file\n" 

266 ) 

267 sys.exit(0) 

268 

269 

270def _add_bash_autocompletion(fname: str, script: pathlib.Path) -> None: 

271 """ 

272 Util function used most notably in setup.py to add a bash autocompletion script. 

273 """ 

274 try: 

275 if BASH_COMPLETION_FOLDER is None: 

276 raise OSError() 

277 

278 # If already defined, exit. 

279 dest = BASH_COMPLETION_FOLDER / fname 

280 if dest.exists(): 

281 return 

282 

283 # Check that bash autocompletion folder exists 

284 if not BASH_COMPLETION_FOLDER.exists(): 

285 BASH_COMPLETION_FOLDER.mkdir(parents=True, exist_ok=True) 

286 _check_perms(BASH_COMPLETION_FOLDER) 

287 

288 # Copy file 

289 shutil.copy(script, BASH_COMPLETION_FOLDER) 

290 except OSError: 

291 log_loading.warning("Bash autocompletion script could not be copied.", 

292 exc_info=True) 

293 

294 

295###################### 

296# Extension system # 

297###################### 

298 

299 

300def _load(module, globals_dict=None, symb_list=None): 

301 # type: (str, Optional[Dict[str, Any]], Optional[List[str]]) -> None 

302 """Loads a Python module to make variables, objects and functions 

303available globally. 

304 

305 The idea is to load the module using importlib, then copy the 

306symbols to the global symbol table. 

307 

308 """ 

309 if globals_dict is None: 

310 globals_dict = builtins.__dict__ 

311 try: 

312 mod = importlib.import_module(module) 

313 if '__all__' in mod.__dict__: 

314 # import listed symbols 

315 for name in mod.__dict__['__all__']: 

316 if symb_list is not None: 

317 symb_list.append(name) 

318 globals_dict[name] = mod.__dict__[name] 

319 else: 

320 # only import non-private symbols 

321 for name, sym in mod.__dict__.items(): 

322 if _validate_local(name): 

323 if symb_list is not None: 

324 symb_list.append(name) 

325 globals_dict[name] = sym 

326 except Exception: 

327 log_interactive.error("Loading module %s", module, exc_info=True) 

328 

329 

330def load_module(name, globals_dict=None, symb_list=None): 

331 # type: (str, Optional[Dict[str, Any]], Optional[List[str]]) -> None 

332 """Loads a Scapy module to make variables, objects and functions 

333 available globally. 

334 

335 """ 

336 _load("scapy.modules." + name, 

337 globals_dict=globals_dict, symb_list=symb_list) 

338 

339 

340def load_layer(name, globals_dict=None, symb_list=None): 

341 # type: (str, Optional[Dict[str, Any]], Optional[List[str]]) -> None 

342 """Loads a Scapy layer module to make variables, objects and functions 

343 available globally. 

344 

345 """ 

346 _load("scapy.layers." + LAYER_ALIASES.get(name, name), 

347 globals_dict=globals_dict, symb_list=symb_list) 

348 

349 

350def load_contrib(name, globals_dict=None, symb_list=None): 

351 # type: (str, Optional[Dict[str, Any]], Optional[List[str]]) -> None 

352 """Loads a Scapy contrib module to make variables, objects and 

353 functions available globally. 

354 

355 If no contrib module can be found with the given name, try to find 

356 a layer module, since a contrib module may become a layer module. 

357 

358 """ 

359 try: 

360 importlib.import_module("scapy.contrib." + name) 

361 _load("scapy.contrib." + name, 

362 globals_dict=globals_dict, symb_list=symb_list) 

363 except ImportError as e: 

364 # if layer not found in contrib, try in layers 

365 try: 

366 load_layer(name, 

367 globals_dict=globals_dict, symb_list=symb_list) 

368 except ImportError: 

369 raise e # Let's raise the original error to avoid confusion 

370 

371 

372def list_contrib(name=None, # type: Optional[str] 

373 ret=False, # type: bool 

374 _debug=False # type: bool 

375 ): 

376 # type: (...) -> Optional[List[Dict[str, str]]] 

377 """Show the list of all existing contribs. 

378 

379 :param name: filter to search the contribs 

380 :param ret: whether the function should return a dict instead of 

381 printing it 

382 :returns: None or a dictionary containing the results if ret=True 

383 """ 

384 # _debug: checks that all contrib modules have correctly defined: 

385 # # scapy.contrib.description = [...] 

386 # # scapy.contrib.status = [...] 

387 # # scapy.contrib.name = [...] (optional) 

388 # or set the flag: 

389 # # scapy.contrib.description = skip 

390 # to skip the file 

391 if name is None: 

392 name = "*.py" 

393 elif "*" not in name and "?" not in name and not name.endswith(".py"): 

394 name += ".py" 

395 results = [] # type: List[Dict[str, str]] 

396 dir_path = os.path.join(os.path.dirname(__file__), "contrib") 

397 if sys.version_info >= (3, 5): 

398 name = os.path.join(dir_path, "**", name) 

399 iterator = glob.iglob(name, recursive=True) 

400 else: 

401 name = os.path.join(dir_path, name) 

402 iterator = glob.iglob(name) 

403 for f in iterator: 

404 mod = f.replace(os.path.sep, ".").partition("contrib.")[2] 

405 if mod.startswith("__"): 

406 continue 

407 if mod.endswith(".py"): 

408 mod = mod[:-3] 

409 desc = {"description": "", "status": "", "name": mod} 

410 with io.open(f, errors="replace") as fd: 

411 for line in fd: 

412 if line[0] != "#": 

413 continue 

414 p = line.find("scapy.contrib.") 

415 if p >= 0: 

416 p += 14 

417 q = line.find("=", p) 

418 key = line[p:q].strip() 

419 value = line[q + 1:].strip() 

420 desc[key] = value 

421 if desc["status"] == "skip": 

422 break 

423 if desc["description"] and desc["status"]: 

424 results.append(desc) 

425 break 

426 if _debug: 

427 if desc["status"] == "skip": 

428 pass 

429 elif not desc["description"] or not desc["status"]: 

430 raise Scapy_Exception("Module %s is missing its " 

431 "contrib infos !" % mod) 

432 results.sort(key=lambda x: x["name"]) 

433 if ret: 

434 return results 

435 else: 

436 for desc in results: 

437 print("%(name)-20s: %(description)-40s status=%(status)s" % desc) 

438 return None 

439 

440 

441############################## 

442# Session saving/restoring # 

443############################## 

444 

445def update_ipython_session(session): 

446 # type: (Dict[str, Any]) -> None 

447 """Updates IPython session with a custom one""" 

448 if "_oh" not in session: 

449 session["_oh"] = session["Out"] = {} 

450 session["In"] = {} 

451 try: 

452 from IPython import get_ipython 

453 get_ipython().user_ns.update(session) 

454 except Exception: 

455 pass 

456 

457 

458def _scapy_prestart_builtins(): 

459 # type: () -> Dict[str, Any] 

460 """Load Scapy prestart and return all builtins""" 

461 return { 

462 k: v 

463 for k, v in importlib.import_module(".config", "scapy").__dict__.copy().items() 

464 if _validate_local(k) 

465 } 

466 

467 

468def _scapy_builtins(): 

469 # type: () -> Dict[str, Any] 

470 """Load Scapy and return all builtins""" 

471 return { 

472 k: v 

473 for k, v in importlib.import_module(".all", "scapy").__dict__.copy().items() 

474 if _validate_local(k) 

475 } 

476 

477 

478def _scapy_exts(): 

479 # type: () -> Dict[str, Any] 

480 """Load Scapy exts and return their builtins""" 

481 from scapy.config import conf 

482 res = {} 

483 for modname, spec in conf.exts.all_specs.items(): 

484 if spec.default: 

485 mod = sys.modules[modname] 

486 res.update({ 

487 k: v 

488 for k, v in mod.__dict__.copy().items() 

489 if _validate_local(k) 

490 }) 

491 return res 

492 

493 

494@overload 

495def init_session(mydict, # type: Optional[Union[Dict[str, Any], None]] 

496 ret, # type: Literal[True] 

497 ): 

498 # type: (...) -> Dict[str, Any] 

499 pass 

500 

501 

502@overload 

503def init_session(mydict=None, # type: Optional[Union[Dict[str, Any], None]] 

504 ret=False, # type: Literal[False] 

505 ): 

506 # type: (...) -> None 

507 pass 

508 

509 

510def init_session(mydict=None, # type: Optional[Union[Dict[str, Any], None]] 

511 ret=False, # type: bool 

512 ): 

513 # type: (...) -> Union[Dict[str, Any], None] 

514 from scapy.config import conf 

515 

516 # Load Scapy 

517 scapy_builtins = _scapy_builtins() 

518 

519 # Load exts 

520 scapy_builtins.update(_scapy_exts()) 

521 

522 SESSION = {"conf": conf} # type: Dict[str, Any] 

523 

524 SESSION.update(scapy_builtins) 

525 SESSION["_scpybuiltins"] = scapy_builtins.keys() 

526 builtins.__dict__["scapy_session"] = SESSION 

527 

528 if mydict is not None: 

529 builtins.__dict__["scapy_session"].update(mydict) 

530 update_ipython_session(mydict) 

531 if ret: 

532 return SESSION 

533 return None 

534 

535 

536################ 

537# Main # 

538################ 

539 

540 

541def _prepare_quote(quote, author, max_len=78): 

542 # type: (str, str, int) -> List[str] 

543 """This function processes a quote and returns a string that is ready 

544to be used in the fancy banner. 

545 

546 """ 

547 _quote = quote.split(' ') 

548 max_len -= 6 

549 lines = [] 

550 cur_line = [] # type: List[str] 

551 

552 def _len(line): 

553 # type: (List[str]) -> int 

554 return sum(len(elt) for elt in line) + len(line) - 1 

555 while _quote: 

556 if not cur_line or (_len(cur_line) + len(_quote[0]) - 1 <= max_len): 

557 cur_line.append(_quote.pop(0)) 

558 continue 

559 lines.append(' | %s' % ' '.join(cur_line)) 

560 cur_line = [] 

561 if cur_line: 

562 lines.append(' | %s' % ' '.join(cur_line)) 

563 cur_line = [] 

564 lines.append(' | %s-- %s' % (" " * (max_len - len(author) - 5), author)) 

565 return lines 

566 

567 

568def get_fancy_banner(mini: Optional[bool] = None) -> str: 

569 """ 

570 Generates the fancy Scapy banner 

571 

572 :param mini: if set, force a mini banner or not. Otherwise detect 

573 """ 

574 from scapy.config import conf 

575 from scapy.utils import get_terminal_width 

576 if mini is None: 

577 mini_banner = (get_terminal_width() or 84) <= 75 

578 else: 

579 mini_banner = mini 

580 

581 the_logo = [ 

582 " ", 

583 " aSPY//YASa ", 

584 " apyyyyCY//////////YCa ", 

585 " sY//////YSpcs scpCY//Pp ", 

586 " ayp ayyyyyyySCP//Pp syY//C ", 

587 " AYAsAYYYYYYYY///Ps cY//S", 

588 " pCCCCY//p cSSps y//Y", 

589 " SPPPP///a pP///AC//Y", 

590 " A//A cyP////C", 

591 " p///Ac sC///a", 

592 " P////YCpc A//A", 

593 " scccccp///pSP///p p//Y", 

594 " sY/////////y caa S//P", 

595 " cayCyayP//Ya pY/Ya", 

596 " sY/PsY////YCc aC//Yp ", 

597 " sc sccaCY//PCypaapyCP//YSs ", 

598 " spCPY//////YPSps ", 

599 " ccaacs ", 

600 " ", 

601 ] 

602 

603 # Used on mini screens 

604 the_logo_mini = [ 

605 " .SYPACCCSASYY ", 

606 "P /SCS/CCS ACS", 

607 " /A AC", 

608 " A/PS /SPPS", 

609 " YP (SC", 

610 " SPS/A. SC", 

611 " Y/PACC PP", 

612 " PY*AYC CAA", 

613 " YYCY//SCYP ", 

614 ] 

615 

616 the_banner = [ 

617 "", 

618 "", 

619 " |", 

620 " | Welcome to Scapy", 

621 " | Version %s" % conf.version, 

622 " |", 

623 " | https://github.com/secdev/scapy", 

624 " |", 

625 " | Have fun!", 

626 " |", 

627 ] 

628 

629 if mini_banner: 

630 the_logo = the_logo_mini 

631 the_banner = [x[2:] for x in the_banner[3:-1]] 

632 the_banner = [""] + the_banner + [""] 

633 else: 

634 quote, author = choice(QUOTES) 

635 the_banner.extend(_prepare_quote(quote, author, max_len=39)) 

636 the_banner.append(" |") 

637 return "\n".join( 

638 logo + banner for logo, banner in zip_longest( 

639 (conf.color_theme.logo(line) for line in the_logo), 

640 (conf.color_theme.success(line) for line in the_banner), 

641 fillvalue="" 

642 ) 

643 ) 

644 

645 

646def interact(mydict=None, 

647 argv=None, 

648 mybanner=None, 

649 mybanneronly=False, 

650 loglevel=logging.INFO): 

651 # type: (Optional[Any], Optional[Any], Optional[Any], bool, int) -> None 

652 """ 

653 Starts Scapy's console. 

654 """ 

655 # We're in interactive mode, let's throw the DeprecationWarnings 

656 warnings.simplefilter("always") 

657 

658 # Set interactive mode, load the color scheme 

659 from scapy.config import conf 

660 conf.interactive = True 

661 conf.color_theme = DefaultTheme() 

662 if loglevel is not None: 

663 conf.logLevel = loglevel 

664 

665 STARTUP_FILE = DEFAULT_STARTUP_FILE 

666 PRESTART_FILE = DEFAULT_PRESTART_FILE 

667 

668 if argv is None: 

669 argv = sys.argv 

670 

671 try: 

672 opts = getopt.getopt(argv[1:], "hs:Cc:Pp:d:H") 

673 for opt, param in opts[0]: 

674 if opt == "-h": 

675 _usage() 

676 elif opt == "-H": 

677 conf.fancy_banner = False 

678 conf.verb = 1 

679 conf.logLevel = logging.WARNING 

680 elif opt == "-c": 

681 STARTUP_FILE = param 

682 elif opt == "-C": 

683 STARTUP_FILE = None 

684 elif opt == "-p": 

685 PRESTART_FILE = param 

686 elif opt == "-P": 

687 PRESTART_FILE = None 

688 elif opt == "-d": 

689 conf.logLevel = max(1, conf.logLevel - 10) 

690 

691 if len(opts[1]) > 0: 

692 raise getopt.GetoptError( 

693 "Too many parameters : [%s]" % " ".join(opts[1]) 

694 ) 

695 

696 except getopt.GetoptError as msg: 

697 log_loading.error(msg) 

698 sys.exit(1) 

699 

700 # Reset sys.argv, otherwise IPython thinks it is for him 

701 sys.argv = sys.argv[:1] 

702 

703 if PRESTART_FILE: 

704 _read_config_file( 

705 PRESTART_FILE, 

706 interactive=True, 

707 _locals=_scapy_prestart_builtins(), 

708 default=DEFAULT_PRESTART, 

709 ) 

710 

711 SESSION = init_session(mydict=mydict, ret=True) 

712 

713 if STARTUP_FILE: 

714 _read_config_file( 

715 STARTUP_FILE, 

716 interactive=True, 

717 _locals=SESSION 

718 ) 

719 

720 # Load extensions (Python 3.8 Only) 

721 if sys.version_info >= (3, 8): 

722 conf.exts.loadall() 

723 

724 if conf.fancy_banner: 

725 banner_text = get_fancy_banner() 

726 else: 

727 banner_text = "Welcome to Scapy (%s)" % conf.version 

728 

729 # Make sure the history file has proper permissions 

730 try: 

731 if not pathlib.Path(conf.histfile).exists(): 

732 pathlib.Path(conf.histfile).touch() 

733 _check_perms(conf.histfile) 

734 except OSError: 

735 pass 

736 

737 # Configure interactive terminal 

738 

739 if conf.interactive_shell not in [ 

740 "ipython", 

741 "python", 

742 "ptpython", 

743 "ptipython", 

744 "bpython", 

745 "auto"]: 

746 log_loading.warning("Unknown conf.interactive_shell ! Using 'auto'") 

747 conf.interactive_shell = "auto" 

748 

749 # Auto detect available shells. 

750 # Order: 

751 # 1. IPython 

752 # 2. bpython 

753 # 3. ptpython 

754 

755 _IMPORTS = { 

756 "ipython": ["IPython"], 

757 "bpython": ["bpython"], 

758 "ptpython": ["ptpython"], 

759 "ptipython": ["IPython", "ptpython"], 

760 } 

761 

762 if conf.interactive_shell == "auto": 

763 # Auto detect 

764 for imp in ["IPython", "bpython", "ptpython"]: 

765 try: 

766 importlib.import_module(imp) 

767 conf.interactive_shell = imp.lower() 

768 break 

769 except ImportError: 

770 continue 

771 else: 

772 log_loading.warning( 

773 "No alternative Python interpreters found ! " 

774 "Using standard Python shell instead." 

775 ) 

776 conf.interactive_shell = "python" 

777 

778 if conf.interactive_shell in _IMPORTS: 

779 # Check import 

780 for imp in _IMPORTS[conf.interactive_shell]: 

781 try: 

782 importlib.import_module(imp) 

783 except ImportError: 

784 log_loading.warning("%s requested but not found !" % imp) 

785 conf.interactive_shell = "python" 

786 

787 # Default shell 

788 if conf.interactive_shell == "python": 

789 disabled = ["History"] 

790 if WINDOWS: 

791 disabled.append("Colors") 

792 conf.color_theme = BlackAndWhite() 

793 else: 

794 try: 

795 # Bad completer.. but better than nothing 

796 import rlcompleter 

797 import readline 

798 readline.set_completer( 

799 rlcompleter.Completer(namespace=SESSION).complete 

800 ) 

801 readline.parse_and_bind('tab: complete') 

802 except ImportError: 

803 disabled.insert(0, "AutoCompletion") 

804 # Display warning when using the default REPL 

805 log_loading.info( 

806 "Using the default Python shell: %s %s disabled." % ( 

807 ",".join(disabled), 

808 "is" if len(disabled) == 1 else "are" 

809 ) 

810 ) 

811 

812 # ptpython configure function 

813 def ptpython_configure(repl): 

814 # type: (Any) -> None 

815 # Hide status bar 

816 repl.show_status_bar = False 

817 # Complete while typing (versus only when pressing tab) 

818 repl.complete_while_typing = False 

819 # Enable auto-suggestions 

820 repl.enable_auto_suggest = True 

821 # Disable exit confirmation 

822 repl.confirm_exit = False 

823 # Show signature 

824 repl.show_signature = True 

825 # Apply Scapy color theme: TODO 

826 # repl.install_ui_colorscheme("scapy", 

827 # Style.from_dict(_custom_ui_colorscheme)) 

828 # repl.use_ui_colorscheme("scapy") 

829 

830 # Extend banner text 

831 if conf.interactive_shell in ["ipython", "ptipython"]: 

832 import IPython 

833 if conf.interactive_shell == "ptipython": 

834 banner = banner_text + " using IPython %s" % IPython.__version__ 

835 try: 

836 from importlib.metadata import version 

837 ptpython_version = " " + version('ptpython') 

838 except ImportError: 

839 ptpython_version = "" 

840 banner += " and ptpython%s" % ptpython_version 

841 else: 

842 banner = banner_text + " using IPython %s" % IPython.__version__ 

843 elif conf.interactive_shell == "ptpython": 

844 try: 

845 from importlib.metadata import version 

846 ptpython_version = " " + version('ptpython') 

847 except ImportError: 

848 ptpython_version = "" 

849 banner = banner_text + " using ptpython%s" % ptpython_version 

850 elif conf.interactive_shell == "bpython": 

851 import bpython 

852 banner = banner_text + " using bpython %s" % bpython.__version__ 

853 

854 if mybanner is not None: 

855 if mybanneronly: 

856 banner = "" 

857 banner += "\n" 

858 banner += mybanner 

859 

860 # Start IPython or ptipython 

861 if conf.interactive_shell in ["ipython", "ptipython"]: 

862 banner += "\n" 

863 if conf.interactive_shell == "ptipython": 

864 from ptpython.ipython import embed 

865 else: 

866 from IPython import embed 

867 try: 

868 from traitlets.config.loader import Config 

869 except ImportError: 

870 log_loading.warning( 

871 "traitlets not available. Some Scapy shell features won't be " 

872 "available." 

873 ) 

874 try: 

875 embed( 

876 display_banner=False, 

877 user_ns=SESSION, 

878 exec_lines=["print(\"\"\"" + banner + "\"\"\")"] 

879 ) 

880 except Exception: 

881 code.interact(banner=banner_text, local=SESSION) 

882 else: 

883 cfg = Config() 

884 try: 

885 from IPython import get_ipython 

886 if not get_ipython(): 

887 raise ImportError 

888 except ImportError: 

889 # Set "classic" prompt style when launched from 

890 # run_scapy(.bat) files Register and apply scapy 

891 # color+prompt style 

892 apply_ipython_style(shell=cfg.InteractiveShellEmbed) 

893 cfg.InteractiveShellEmbed.confirm_exit = False 

894 cfg.InteractiveShellEmbed.separate_in = u'' 

895 if int(IPython.__version__[0]) >= 6: 

896 cfg.InteractiveShellEmbed.term_title = True 

897 cfg.InteractiveShellEmbed.term_title_format = ("Scapy %s" % 

898 conf.version) 

899 # As of IPython 6-7, the jedi completion module is a dumpster 

900 # of fire that should be scrapped never to be seen again. 

901 # This is why the following defaults to False. Feel free to hurt 

902 # yourself (#GH4056) :P 

903 cfg.Completer.use_jedi = conf.ipython_use_jedi 

904 else: 

905 cfg.InteractiveShellEmbed.term_title = False 

906 cfg.HistoryAccessor.hist_file = conf.histfile 

907 cfg.InteractiveShell.banner1 = banner 

908 if conf.verb < 2: 

909 cfg.InteractiveShellEmbed.enable_tip = False 

910 # configuration can thus be specified here. 

911 _kwargs = {} 

912 if conf.interactive_shell == "ptipython": 

913 _kwargs["configure"] = ptpython_configure 

914 try: 

915 embed(config=cfg, user_ns=SESSION, **_kwargs) 

916 except (AttributeError, TypeError): 

917 code.interact(banner=banner_text, local=SESSION) 

918 # Start ptpython 

919 elif conf.interactive_shell == "ptpython": 

920 # ptpython has special, non-default handling of __repr__ which breaks Scapy. 

921 # For instance: >>> IP() 

922 log_loading.warning("ptpython support is currently partially broken") 

923 from ptpython.repl import embed 

924 # ptpython has no banner option 

925 banner += "\n" 

926 print(banner) 

927 embed( 

928 locals=SESSION, 

929 history_filename=conf.histfile, 

930 title="Scapy %s" % conf.version, 

931 configure=ptpython_configure 

932 ) 

933 # Start bpython 

934 elif conf.interactive_shell == "bpython": 

935 from bpython.curtsies import main as embed 

936 embed( 

937 args=["-q", "-i"], 

938 locals_=SESSION, 

939 banner=banner, 

940 welcome_message="" 

941 ) 

942 # Start Python 

943 elif conf.interactive_shell == "python": 

944 code.interact(banner=banner_text, local=SESSION) 

945 else: 

946 raise ValueError("Invalid conf.interactive_shell") 

947 

948 

949if __name__ == "__main__": 

950 interact()