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

533 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 gzip 

16import importlib 

17import io 

18import logging 

19import os 

20import pathlib 

21import pickle 

22import shutil 

23import sys 

24import types 

25import warnings 

26 

27from itertools import zip_longest 

28from random import choice 

29 

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 

39 

40from typing import ( 

41 Any, 

42 Dict, 

43 List, 

44 Optional, 

45 Union, 

46 overload, 

47) 

48from scapy.compat import ( 

49 Literal, 

50) 

51 

52LAYER_ALIASES = { 

53 "tls": "tls.all", 

54 "msrpce": "msrpce.all", 

55} 

56 

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] 

68 

69 

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() 

86 

87 

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 ) 

95 

96 

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 ) 

104 

105 

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 ) 

113 

114 

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 

133 

134 

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. 

140 

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. 

145 

146 Parameters: 

147 

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 

153 

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 

160 

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) 

172 

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

174 fd.write(default) 

175 

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) 

198 

199 

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"] 

204 

205 

206# This is ~/.config/scapy 

207SCAPY_CONFIG_FOLDER = _probe_config_folder("scapy") 

208SCAPY_CACHE_FOLDER = _probe_cache_folder("scapy") 

209 

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 

216 

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") 

224 

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 

231 

232 

233# Default scapy prestart.py config file 

234 

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 

239 

240# default interpreter 

241conf.interactive_shell = "auto" 

242 

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

244conf.color_theme = DefaultTheme() 

245 

246# disable INFO: tags related to dependencies missing 

247# log_loading.setLevel(logging.WARNING) 

248 

249# extensions to load by default 

250conf.load_extensions = [ 

251 # "scapy-red", 

252 # "scapy-rpc", 

253] 

254 

255# force-use libpcap 

256# conf.use_pcap = True 

257""".strip() 

258 

259 

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) 

271 

272 

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() 

280 

281 # If already defined, exit. 

282 dest = BASH_COMPLETION_FOLDER / fname 

283 if dest.exists(): 

284 return 

285 

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) 

290 

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) 

296 

297 

298###################### 

299# Extension system # 

300###################### 

301 

302 

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. 

307 

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

309symbols to the global symbol table. 

310 

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) 

331 

332 

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. 

337 

338 """ 

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

340 globals_dict=globals_dict, symb_list=symb_list) 

341 

342 

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. 

347 

348 """ 

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

350 globals_dict=globals_dict, symb_list=symb_list) 

351 

352 

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. 

357 

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. 

360 

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 

373 

374 

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. 

381 

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 

442 

443 

444############################## 

445# Session saving/restoring # 

446############################## 

447 

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 

459 

460 

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 } 

469 

470 

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 } 

479 

480 

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 

495 

496 

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. 

500 

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) 

512 

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"] 

519 

520 if not session: 

521 log_interactive.error("No session found ?!") 

522 return 

523 

524 ignore = session.get("_scpybuiltins", []) 

525 hard_ignore = ["scapy_session", "In", "Out", "open"] 

526 to_be_saved = session.copy() 

527 

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)) 

547 

548 try: 

549 os.rename(fname, fname + ".bak") 

550 except OSError: 

551 pass 

552 

553 f = gzip.open(fname, "wb") 

554 pickle.dump(to_be_saved, f, pickleProto) 

555 f.close() 

556 

557 

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. 

562 

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 

576 

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) 

582 

583 log_loading.info("Loaded session [%s]", fname) 

584 

585 

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. 

589 

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) 

602 

603 

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 

611 

612 

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 

620 

621 

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]] 

629 

630 # Load Scapy 

631 scapy_builtins = _scapy_builtins() 

632 

633 # Load exts 

634 scapy_builtins.update(_scapy_exts()) 

635 

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) 

656 

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} 

669 

670 SESSION.update(scapy_builtins) 

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

672 builtins.__dict__["scapy_session"] = SESSION 

673 

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 

680 

681################ 

682# Main # 

683################ 

684 

685 

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. 

690 

691 """ 

692 _quote = quote.split(' ') 

693 max_len -= 6 

694 lines = [] 

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

696 

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 

711 

712 

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

714 """ 

715 Generates the fancy Scapy banner 

716 

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 

725 

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 ] 

747 

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 ] 

760 

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 ] 

773 

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 ) 

789 

790 

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") 

802 

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 

809 

810 STARTUP_FILE = DEFAULT_STARTUP_FILE 

811 PRESTART_FILE = DEFAULT_PRESTART_FILE 

812 

813 session_name = None 

814 

815 if argv is None: 

816 argv = sys.argv 

817 

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) 

839 

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

841 raise getopt.GetoptError( 

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

843 ) 

844 

845 except getopt.GetoptError as msg: 

846 log_loading.error(msg) 

847 sys.exit(1) 

848 

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

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

851 

852 if PRESTART_FILE: 

853 _read_config_file( 

854 PRESTART_FILE, 

855 interactive=True, 

856 _locals=_scapy_prestart_builtins(), 

857 default=DEFAULT_PRESTART, 

858 ) 

859 

860 SESSION = init_session(session_name, mydict=mydict, ret=True) 

861 

862 if STARTUP_FILE: 

863 _read_config_file( 

864 STARTUP_FILE, 

865 interactive=True, 

866 _locals=SESSION 

867 ) 

868 

869 # Load extensions (Python 3.8 Only) 

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

871 conf.exts.loadall() 

872 

873 if conf.fancy_banner: 

874 banner_text = get_fancy_banner() 

875 else: 

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

877 

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 

885 

886 # Configure interactive terminal 

887 

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" 

897 

898 # Auto detect available shells. 

899 # Order: 

900 # 1. IPython 

901 # 2. bpython 

902 # 3. ptpython 

903 

904 _IMPORTS = { 

905 "ipython": ["IPython"], 

906 "bpython": ["bpython"], 

907 "ptpython": ["ptpython"], 

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

909 } 

910 

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" 

926 

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" 

935 

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 ) 

960 

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") 

978 

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__ 

1002 

1003 if mybanner is not None: 

1004 if mybanneronly: 

1005 banner = "" 

1006 banner += "\n" 

1007 banner += mybanner 

1008 

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") 

1096 

1097 if conf.session: 

1098 save_session(conf.session, SESSION) 

1099 

1100 

1101if __name__ == "__main__": 

1102 interact()