You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
257 lines
8.7 KiB
257 lines
8.7 KiB
""" |
|
This module contains all the api calls written for use in plugins. If you want |
|
to do anything that is't allowed through these function calls or through the |
|
functions provided for macros, contact me and I'll see what I can do to add some |
|
more functionality into the next version. |
|
""" |
|
|
|
import glob |
|
import imp |
|
import os |
|
import pappyproxy |
|
import stat |
|
import crochet |
|
|
|
from twisted.internet import defer |
|
from .colors import Colors |
|
from .util import PappyException |
|
|
|
class Plugin(object): |
|
|
|
def __init__(self, cmd, fname=None): |
|
self.cmd = cmd |
|
self.filename = '' |
|
self.source = None |
|
self.module_name = '' |
|
|
|
if fname: |
|
self.filename = fname |
|
self.load_file(fname) |
|
|
|
def load_file(self, fname): |
|
module_name = os.path.basename(os.path.splitext(fname)[0]) |
|
if os.path.basename(fname) == '__init__.py': |
|
return |
|
st = os.stat(fname) |
|
if (st.st_mode & stat.S_IWOTH): |
|
raise PappyException("Refusing to load world-writable plugin: %s" % fname) |
|
self.source = imp.load_source('%s'%module_name, fname) |
|
if hasattr(self.source, 'load_cmds'): |
|
self.source.load_cmds(self.cmd) |
|
else: |
|
print ('WARNING: %s does not define load_cmds. It will not be ' |
|
'possible to interact with the plugin through the console.' % fname) |
|
self.module_name = module_name |
|
|
|
|
|
class PluginLoader(object): |
|
|
|
def __init__(self, cmd): |
|
self.cmd = cmd |
|
self.loaded_plugins = [] |
|
self.plugins_by_name = {} |
|
|
|
def load_plugin(self, fname): |
|
p = Plugin(self.cmd, fname) |
|
self.loaded_plugins.append(p) |
|
self.plugins_by_name[p.module_name] = p |
|
|
|
def load_directory(self, directory): |
|
fnames = glob.glob(os.path.join(directory, '*.py')) |
|
for fname in fnames: |
|
try: |
|
self.load_plugin(fname) |
|
except PappyException as e: |
|
print str(e) |
|
|
|
########################## |
|
## Plugin helper functions |
|
|
|
def plugin_by_name(name): |
|
""" |
|
Returns an interface to access the methods of a plugin from its |
|
name. For example, to call the ``foo`` function from the ``bar`` |
|
plugin you would call ``plugin_by_name('bar').foo()``. |
|
""" |
|
import pappyproxy.pappy |
|
if name in pappyproxy.pappy.plugin_loader.plugins_by_name: |
|
return pappyproxy.pappy.plugin_loader.plugins_by_name[name].source |
|
else: |
|
raise PappyException('No plugin with name %s is loaded' % name) |
|
|
|
def add_intercepting_macro(name, macro): |
|
""" |
|
Adds an intercepting macro to the proxy. You can either use a |
|
:class:`pappyproxy.macros.FileInterceptMacro` to load an |
|
intercepting macro from the disk, or you can create your own using |
|
an :class:`pappyproxy.macros.InterceptMacro` for a base class. You |
|
must give a unique name that will be used in |
|
:func:`pappyproxy.plugin.remove_intercepting_macro` to deactivate |
|
it. Remember that activating an intercepting macro will disable |
|
request streaming and will affect performance. So please try and |
|
only use this if you may need to modify messages before they are |
|
passed along. |
|
""" |
|
for factory in pappyproxy.pappy.session.server_factories: |
|
factory.add_intercepting_macro(macro, name=name) |
|
|
|
def remove_intercepting_macro(name): |
|
""" |
|
Stops an active intercepting macro. You must pass in the name that |
|
you used when calling |
|
:func:`pappyproxy.plugin.add_intercepting_macro` to identify which |
|
macro you would like to stop. |
|
""" |
|
for factory in pappyproxy.pappy.session.server_factories: |
|
factory.remove_intercepting_macro(name=name) |
|
|
|
def active_intercepting_macros(): |
|
""" |
|
Returns a dict of the active intercepting macro objects. Modifying |
|
this list will not affect which macros are active. |
|
""" |
|
# every factory should have the same int macros so screw it we'll |
|
# just use the macros from the first one |
|
ret = [] |
|
if len(pappyproxy.pappy.session.server_factories) > 0: |
|
ret = pappyproxy.pappy.session.server_factories[0].get_macro_list() |
|
return ret |
|
|
|
def in_memory_reqs(): |
|
""" |
|
Returns a list containing the ids of the requests which exist in |
|
memory only (requests with an m## style id). You can call either |
|
:func:`pappyproxy.http.Request.save` or |
|
:func:`pappyproxy.http.Request.async_deep_save` to save the |
|
request to the data file. |
|
""" |
|
return list(pappyproxy.http.Request.cache.inmem_reqs) |
|
|
|
def req_history(num=-1, ids=None, include_unmangled=False): |
|
""" |
|
Returns an a generator that generates deferreds which resolve to |
|
requests in history, ignoring the current context. If ``n`` is |
|
given, it will stop after ``n`` requests have been generated. If |
|
``ids`` is given, it will only include those IDs. If |
|
``include_unmangled`` is True, then the iterator will include |
|
requests which are the unmangled version of other requests. |
|
|
|
An example of using the iterator to print the 10 most recent requests:: |
|
|
|
@defer.inlineCallbacks |
|
def find_food(): |
|
for req_d in req_history(10): |
|
req = yield req_d |
|
print '-'*10 |
|
print req.full_message_pretty |
|
""" |
|
return pappyproxy.Request.cache.req_it(num=num, ids=ids, include_unmangled=include_unmangled) |
|
|
|
def async_main_context_ids(n=-1): |
|
""" |
|
Returns a deferred that resolves into a list of up to ``n`` of the |
|
most recent requests in the main context. You can then use |
|
:func:`pappyproxy.http.Request.load_request` to load the requests |
|
in the current context. If no value is passed for ``n``, this will |
|
return all of the IDs in the context. |
|
""" |
|
return pappyproxy.pappy.main_context.get_reqs(n) |
|
|
|
@crochet.wait_for(timeout=None) |
|
@defer.inlineCallbacks |
|
def main_context_ids(*args, **kwargs): |
|
""" |
|
Same as :func:`pappyproxy.plugin.async_main_context_ids` but can be called |
|
from macros and other non-async only functions. Cannot be called in async |
|
functions. |
|
""" |
|
ret = yield async_main_context_ids(*args, **kwargs) |
|
defer.returnValue(ret) |
|
|
|
def add_to_history(req): |
|
""" |
|
Save a request to history without saving it to the data file. The request |
|
will only be saved in memory, so when the program is exited or `clrmem` |
|
is run, the request will be deleted. |
|
|
|
:param req: The request to add to history |
|
:type req: :class:`pappyproxy.http.Request` |
|
""" |
|
pappyproxy.http.Request.cache.add(req) |
|
pappyproxy.context.reset_context_caches() |
|
|
|
def get_active_filter_strings(): |
|
""" |
|
Returns a list of filter strings representing the currently active filters |
|
""" |
|
filts = pappyproxy.pappy.main_context.active_filters |
|
strs = [] |
|
for f in filts: |
|
strs.append(f.filter_string) |
|
return strs |
|
|
|
def run_cmd(cmd): |
|
""" |
|
Run a command as if you typed it into the console. Try and use |
|
existing APIs to do what you want before using this. |
|
""" |
|
pappyproxy.pappy.cons.onecmd(cmd) |
|
|
|
def require_modules(*largs): |
|
""" |
|
A wrapper to make sure that plugin dependencies are installed. For example, |
|
if a command requires the ``psutil`` and ``objgraph`` package, you should |
|
format your command like:: |
|
|
|
@require_modules('psutil', 'objgraph') |
|
def my_command(line): |
|
import objgraph |
|
import psutil |
|
# ... rest of command ... |
|
|
|
If you try to run the command without being able to import all of the required |
|
modules, the command will print an error and not run the command. |
|
""" |
|
def wr(func): |
|
def wr2(*args, **kwargs): |
|
missing = [] |
|
for l in largs: |
|
try: |
|
imp.find_module(l) |
|
except ImportError: |
|
missing.append(l) |
|
if missing: |
|
print 'Command requires %s module(s)' % (', '.join([Colors.RED+m+Colors.ENDC for m in missing])) |
|
else: |
|
return func(*args, **kwargs) |
|
return wr2 |
|
return wr |
|
|
|
def set_context_to_saved(name): |
|
""" |
|
Sets the current context to the context saved under the given name. |
|
Raises PappyException if name does not exist |
|
""" |
|
|
|
@crochet.wait_for(timeout=None) |
|
@defer.inlineCallbacks |
|
def delete_saved_context(name): |
|
""" |
|
Deletes the saved context with the given name. |
|
Raises PappyException if name does not exist |
|
""" |
|
|
|
def save_current_context(name): |
|
""" |
|
Saves the current context under the given name. |
|
""" |
|
|
|
def save_context(name, filter_strs): |
|
""" |
|
Takes a list of filter strings and saves it as a context under the given name. |
|
|
|
:param name: The name to save the context under |
|
:type name: string |
|
:param filter_strs: The in-order list of filter strings of the context to save. |
|
:type filter_strs: List of strings |
|
"""
|
|
|