1## This file is part of Scapy 2## See http://www.secdev.org/projects/scapy for more informations 3## Copyright (C) Philippe Biondi <phil@secdev.org> 4## This program is published under a GPLv2 license 5 6""" 7Main module for interactive startup. 8""" 9 10from __future__ import absolute_import 11from __future__ import print_function 12 13import sys, os, getopt, re, code 14import gzip, glob 15import importlib 16import logging 17from random import choice 18import types 19import io 20 21# Never add any global import, in main.py, that would trigger a warning messsage 22# before the console handlers gets added in interact() 23from scapy.error import log_interactive, log_loading, log_scapy, warning 24import scapy.modules.six as six 25from scapy.themes import DefaultTheme, apply_ipython_style 26 27IGNORED = list(six.moves.builtins.__dict__) 28 29GLOBKEYS = [] 30 31LAYER_ALIASES = { 32 "tls": "tls.all" 33} 34 35QUOTES = [ 36 ("Craft packets like it is your last day on earth.", "Lao-Tze"), 37 ("Craft packets like I craft my beer.", "Jean De Clerck"), 38 ("Craft packets before they craft you.", "Socrate"), 39 ("Craft me if you can.", "IPv6 layer"), 40 ("To craft a packet, you have to be a packet, and learn how to swim in the " 41 "wires and in the waves.", "Jean-Claude Van Damme"), 42] 43 44def _probe_config_file(cf): 45 cf_path = os.path.join(os.path.expanduser("~"), cf) 46 try: 47 os.stat(cf_path) 48 except OSError: 49 return None 50 else: 51 return cf_path 52 53def _read_config_file(cf, _globals=globals(), _locals=locals(), interactive=True): 54 """Read a config file: execute a python file while loading scapy, that may contain 55 some pre-configured values. 56 57 If _globals or _locals are specified, they will be updated with the loaded vars. 58 This allows an external program to use the function. Otherwise, vars are only available 59 from inside the scapy console. 60 61 params: 62 - _globals: the globals() vars 63 - _locals: the locals() vars 64 - interactive: specified whether or not errors should be printed using the scapy console or 65 raised. 66 67 ex, content of a config.py file: 68 'conf.verb = 42\n' 69 Manual loading: 70 >>> _read_config_file("./config.py")) 71 >>> conf.verb 72 42 73 """ 74 log_loading.debug("Loading config file [%s]", cf) 75 try: 76 exec(compile(open(cf).read(), cf, 'exec'), _globals, _locals) 77 except IOError as e: 78 if interactive: 79 raise 80 log_loading.warning("Cannot read config file [%s] [%s]", cf, e) 81 except Exception as e: 82 if interactive: 83 raise 84 log_loading.exception("Error during evaluation of config file [%s]", cf) 85 86def _validate_local(x): 87 """Returns whether or not a variable should be imported. 88 Will return False for any default modules (sys), or if 89 they are detected as private vars (starting with a _)""" 90 global IGNORED 91 return x[0] != "_" and not x in IGNORED 92 93DEFAULT_PRESTART_FILE = _probe_config_file(".scapy_prestart.py") 94DEFAULT_STARTUP_FILE = _probe_config_file(".scapy_startup.py") 95SESSION = None 96 97def _usage(): 98 print("""Usage: scapy.py [-s sessionfile] [-c new_startup_file] [-p new_prestart_file] [-C] [-P] 99 -C: do not read startup file 100 -P: do not read pre-startup file""") 101 sys.exit(0) 102 103 104###################### 105## Extension system ## 106###################### 107 108 109def _load(module, globals_dict=None, symb_list=None): 110 """Loads a Python module to make variables, objects and functions 111available globally. 112 113 The idea is to load the module using importlib, then copy the 114symbols to the global symbol table. 115 116 """ 117 if globals_dict is None: 118 globals_dict = six.moves.builtins.__dict__ 119 try: 120 mod = importlib.import_module(module) 121 if '__all__' in mod.__dict__: 122 # import listed symbols 123 for name in mod.__dict__['__all__']: 124 if symb_list is not None: 125 symb_list.append(name) 126 globals_dict[name] = mod.__dict__[name] 127 else: 128 # only import non-private symbols 129 for name, sym in six.iteritems(mod.__dict__): 130 if _validate_local(name): 131 if symb_list is not None: 132 symb_list.append(name) 133 globals_dict[name] = sym 134 except Exception: 135 log_interactive.error("Loading module %s", module, exc_info=True) 136 137def load_module(name): 138 """Loads a Scapy module to make variables, objects and functions 139 available globally. 140 141 """ 142 _load("scapy.modules."+name) 143 144def load_layer(name, globals_dict=None, symb_list=None): 145 """Loads a Scapy layer module to make variables, objects and functions 146 available globally. 147 148 """ 149 _load("scapy.layers." + LAYER_ALIASES.get(name, name), 150 globals_dict=globals_dict, symb_list=symb_list) 151 152def load_contrib(name): 153 """Loads a Scapy contrib module to make variables, objects and 154 functions available globally. 155 156 If no contrib module can be found with the given name, try to find 157 a layer module, since a contrib module may become a layer module. 158 159 """ 160 try: 161 importlib.import_module("scapy.contrib." + name) 162 _load("scapy.contrib." + name) 163 except ImportError: 164 # if layer not found in contrib, try in layers 165 load_layer(name) 166 167def list_contrib(name=None): 168 if name is None: 169 name="*.py" 170 elif "*" not in name and "?" not in name and not name.endswith(".py"): 171 name += ".py" 172 name = os.path.join(os.path.dirname(__file__), "contrib", name) 173 for f in sorted(glob.glob(name)): 174 mod = os.path.basename(f) 175 if mod.startswith("__"): 176 continue 177 if mod.endswith(".py"): 178 mod = mod[:-3] 179 desc = { "description":"-", "status":"?", "name":mod } 180 for l in io.open(f, errors="replace"): 181 p = l.find("scapy.contrib.") 182 if p >= 0: 183 p += 14 184 q = l.find("=", p) 185 key = l[p:q].strip() 186 value = l[q+1:].strip() 187 desc[key] = value 188 print("%(name)-20s: %(description)-40s status=%(status)s" % desc) 189 190 191 192 193 194 195############################## 196## Session saving/restoring ## 197############################## 198 199def update_ipython_session(session): 200 """Updates IPython session with a custom one""" 201 try: 202 get_ipython().user_ns.update(session) 203 except: 204 pass 205 206def save_session(fname=None, session=None, pickleProto=-1): 207 """Save current Scapy session to the file specified in the fname arg. 208 209 params: 210 - fname: file to save the scapy session in 211 - session: scapy session to use. If None, the console one will be used 212 - pickleProto: pickle proto version (default: -1 = latest)""" 213 from scapy import utils 214 if fname is None: 215 fname = conf.session 216 if not fname: 217 conf.session = fname = utils.get_temp_file(keep=True) 218 log_interactive.info("Use [%s] as session file" % fname) 219 220 if session is None: 221 try: 222 session = get_ipython().user_ns 223 except: 224 session = six.moves.builtins.__dict__["scapy_session"] 225 226 to_be_saved = session.copy() 227 if "__builtins__" in to_be_saved: 228 del(to_be_saved["__builtins__"]) 229 230 for k in list(to_be_saved): 231 i = to_be_saved[k] 232 if hasattr(i, "__module__") and (k[0] == "_" or i.__module__.startswith("IPython")): 233 del(to_be_saved[k]) 234 if isinstance(i, ConfClass): 235 del(to_be_saved[k]) 236 elif isinstance(i, (type, type, types.ModuleType)): 237 if k[0] != "_": 238 log_interactive.error("[%s] (%s) can't be saved.", k, type(to_be_saved[k])) 239 del(to_be_saved[k]) 240 241 try: 242 os.rename(fname, fname+".bak") 243 except OSError: 244 pass 245 246 f=gzip.open(fname,"wb") 247 six.moves.cPickle.dump(to_be_saved, f, pickleProto) 248 f.close() 249 del f 250 251def load_session(fname=None): 252 """Load current Scapy session from the file specified in the fname arg. 253 This will erase any existing session. 254 255 params: 256 - fname: file to load the scapy session from""" 257 if fname is None: 258 fname = conf.session 259 try: 260 s = six.moves.cPickle.load(gzip.open(fname,"rb")) 261 except IOError: 262 try: 263 s = six.moves.cPickle.load(open(fname,"rb")) 264 except IOError: 265 # Raise "No such file exception" 266 raise 267 268 scapy_session = six.moves.builtins.__dict__["scapy_session"] 269 scapy_session.clear() 270 scapy_session.update(s) 271 update_ipython_session(scapy_session) 272 273 log_loading.info("Loaded session [%s]" % fname) 274 275def update_session(fname=None): 276 """Update current Scapy session from the file specified in the fname arg. 277 278 params: 279 - fname: file to load the scapy session from""" 280 if fname is None: 281 fname = conf.session 282 try: 283 s = six.moves.cPickle.load(gzip.open(fname,"rb")) 284 except IOError: 285 s = six.moves.cPickle.load(open(fname,"rb")) 286 scapy_session = six.moves.builtins.__dict__["scapy_session"] 287 scapy_session.update(s) 288 update_ipython_session(scapy_session) 289 290def init_session(session_name, mydict=None): 291 global SESSION 292 global GLOBKEYS 293 294 scapy_builtins = {k: v for k, v in six.iteritems(importlib.import_module(".all", "scapy").__dict__) if _validate_local(k)} 295 six.moves.builtins.__dict__.update(scapy_builtins) 296 GLOBKEYS.extend(scapy_builtins) 297 GLOBKEYS.append("scapy_session") 298 scapy_builtins=None # XXX replace with "with" statement 299 300 if session_name: 301 try: 302 os.stat(session_name) 303 except OSError: 304 log_loading.info("New session [%s]" % session_name) 305 else: 306 try: 307 try: 308 SESSION = six.moves.cPickle.load(gzip.open(session_name,"rb")) 309 except IOError: 310 SESSION = six.moves.cPickle.load(open(session_name,"rb")) 311 log_loading.info("Using session [%s]" % session_name) 312 except EOFError: 313 log_loading.error("Error opening session [%s]" % session_name) 314 except AttributeError: 315 log_loading.error("Error opening session [%s]. Attribute missing" % session_name) 316 317 if SESSION: 318 if "conf" in SESSION: 319 conf.configure(SESSION["conf"]) 320 SESSION["conf"] = conf 321 else: 322 conf.session = session_name 323 SESSION = {"conf":conf} 324 else: 325 SESSION = {"conf": conf} 326 327 six.moves.builtins.__dict__["scapy_session"] = SESSION 328 329 if mydict is not None: 330 six.moves.builtins.__dict__["scapy_session"].update(mydict) 331 update_ipython_session(mydict) 332 GLOBKEYS.extend(mydict) 333 334################ 335##### Main ##### 336################ 337 338def scapy_delete_temp_files(): 339 for f in conf.temp_files: 340 try: 341 os.unlink(f) 342 except: 343 pass 344 del(conf.temp_files[:]) 345 346def _prepare_quote(quote, author, max_len=78): 347 """This function processes a quote and returns a string that is ready 348to be used in the fancy prompt. 349 350 """ 351 quote = quote.split(' ') 352 max_len -= 6 353 lines = [] 354 cur_line = [] 355 def _len(line): 356 return sum(len(elt) for elt in line) + len(line) - 1 357 while quote: 358 if not cur_line or (_len(cur_line) + len(quote[0]) - 1 <= max_len): 359 cur_line.append(quote.pop(0)) 360 continue 361 lines.append(' | %s' % ' '.join(cur_line)) 362 cur_line = [] 363 if cur_line: 364 lines.append(' | %s' % ' '.join(cur_line)) 365 cur_line = [] 366 lines.append(' | %s-- %s' % (" " * (max_len - len(author) - 5), author)) 367 return lines 368 369def interact(mydict=None,argv=None,mybanner=None,loglevel=20): 370 global SESSION 371 global GLOBKEYS 372 373 console_handler = logging.StreamHandler() 374 console_handler.setFormatter(logging.Formatter("%(levelname)s: %(message)s")) 375 log_scapy.addHandler(console_handler) 376 377 from scapy.config import conf 378 conf.color_theme = DefaultTheme() 379 conf.interactive = True 380 if loglevel is not None: 381 conf.logLevel = loglevel 382 383 STARTUP_FILE = DEFAULT_STARTUP_FILE 384 PRESTART_FILE = DEFAULT_PRESTART_FILE 385 386 session_name = None 387 388 if argv is None: 389 argv = sys.argv 390 391 try: 392 opts = getopt.getopt(argv[1:], "hs:Cc:Pp:d") 393 for opt, parm in opts[0]: 394 if opt == "-h": 395 _usage() 396 elif opt == "-s": 397 session_name = parm 398 elif opt == "-c": 399 STARTUP_FILE = parm 400 elif opt == "-C": 401 STARTUP_FILE = None 402 elif opt == "-p": 403 PRESTART_FILE = parm 404 elif opt == "-P": 405 PRESTART_FILE = None 406 elif opt == "-d": 407 conf.logLevel = max(1, conf.logLevel-10) 408 409 if len(opts[1]) > 0: 410 raise getopt.GetoptError("Too many parameters : [%s]" % " ".join(opts[1])) 411 412 413 except getopt.GetoptError as msg: 414 log_loading.error(msg) 415 sys.exit(1) 416 417 init_session(session_name, mydict) 418 419 if STARTUP_FILE: 420 _read_config_file(STARTUP_FILE, interactive=True) 421 if PRESTART_FILE: 422 _read_config_file(PRESTART_FILE, interactive=True) 423 424 if conf.fancy_prompt: 425 426 the_logo = [ 427 " ", 428 " aSPY//YASa ", 429 " apyyyyCY//////////YCa ", 430 " sY//////YSpcs scpCY//Pp ", 431 " ayp ayyyyyyySCP//Pp syY//C ", 432 " AYAsAYYYYYYYY///Ps cY//S", 433 " pCCCCY//p cSSps y//Y", 434 " SPPPP///a pP///AC//Y", 435 " A//A cyP////C", 436 " p///Ac sC///a", 437 " P////YCpc A//A", 438 " scccccp///pSP///p p//Y", 439 " sY/////////y caa S//P", 440 " cayCyayP//Ya pY/Ya", 441 " sY/PsY////YCc aC//Yp ", 442 " sc sccaCY//PCypaapyCP//YSs ", 443 " spCPY//////YPSps ", 444 " ccaacs ", 445 " ", 446 ] 447 448 the_banner = [ 449 "", 450 "", 451 " |", 452 " | Welcome to Scapy", 453 " | Version %s" % conf.version, 454 " |", 455 " | https://github.com/secdev/scapy", 456 " |", 457 " | Have fun!", 458 " |", 459 ] 460 461 quote, author = choice(QUOTES) 462 the_banner.extend(_prepare_quote(quote, author, max_len=39)) 463 the_banner.append(" |") 464 the_banner = "\n".join( 465 logo + banner for logo, banner in six.moves.zip_longest( 466 (conf.color_theme.logo(line) for line in the_logo), 467 (conf.color_theme.success(line) for line in the_banner), 468 fillvalue="" 469 ) 470 ) 471 else: 472 the_banner = "Welcome to Scapy (%s)" % conf.version 473 if mybanner is not None: 474 the_banner += "\n" 475 the_banner += mybanner 476 477 if not conf.interactive_shell or conf.interactive_shell.lower() in [ 478 "ipython", "auto" 479 ]: 480 try: 481 import IPython 482 from IPython.terminal.embed import InteractiveShellEmbed 483 except ImportError: 484 log_loading.warning( 485 "IPython not available. Using standard Python shell " 486 "instead.\nAutoCompletion, History are disabled." 487 ) 488 IPYTHON = False 489 else: 490 IPYTHON = True 491 else: 492 IPYTHON = False 493 494 init_session(session_name, mydict) 495 496 if IPYTHON: 497 banner = the_banner + " using IPython %s\n" % IPython.__version__ 498 try: 499 from traitlets.config.loader import Config 500 except ImportError: 501 log_loading.warning( 502 "traitlets not available. Some Scapy shell features won't be " 503 "available." 504 ) 505 try: 506 ipshell = InteractiveShellEmbed( 507 banner1=banner, 508 user_ns=SESSION, 509 ) 510 except: 511 code.interact(banner = the_banner, local=SESSION) 512 else: 513 cfg = Config() 514 try: 515 get_ipython 516 except NameError: 517 # Set "classic" prompt style when launched from run_scapy(.bat) files 518 # Register and apply scapy color+prompt style 519 apply_ipython_style(shell=cfg.TerminalInteractiveShell) 520 cfg.TerminalInteractiveShell.confirm_exit = False 521 cfg.TerminalInteractiveShell.separate_in = u'' 522 cfg.TerminalInteractiveShell.hist_file = conf.histfile 523 # configuration can thus be specified here. 524 try: 525 ipshell = InteractiveShellEmbed(config=cfg, 526 banner1=banner, 527 hist_file=conf.histfile if conf.histfile else None, 528 user_ns=SESSION) 529 except (AttributeError, TypeError): 530 log_loading.warning("IPython too old. Won't support history and color style.") 531 try: 532 ipshell = InteractiveShellEmbed( 533 banner1=banner, 534 user_ns=SESSION, 535 ) 536 except: 537 code.interact(banner = the_banner, local=SESSION) 538 ipshell(local_ns=SESSION) 539 else: 540 code.interact(banner = the_banner, local=SESSION) 541 542 if conf.session: 543 save_session(conf.session, SESSION) 544 545 for k in GLOBKEYS: 546 try: 547 del(six.moves.builtins.__dict__[k]) 548 except: 549 pass 550 551if __name__ == "__main__": 552 interact() 553