# Support for coordinator rold in secinfo-xmpp
#
#   Copyright (c) 2025 John Lines - MIT/Expat

import logging
import configparser
import os
import sys
import socket
import json
import subprocess
import time

# for databasse processing
import sqlite3

# Analyse attack sources by country
import GeoIP

# For web server.
from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.parse import urlparse

from aiohttp import web



class MyHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        parsed_path = urlparse(self.path)
        if parsed_path.path == '/':
            self.send_response(200)
            self.send_header("Content-type", "text/html")
            self.end_headers()
            self.wfile.write(b"<h1>Secinfo</h1> <a href=/countries>Counties</a>")
        elif parsed_path.path == '/countries':
            self.send_response(200)
            self.send_header("Content-type", "text/html")
            self.end_headers()
            # tresponse = __main__.coord.responseCounties()
            # response = "<h1>Attackers by Country</h1>"
            repl = ["<h1>Attackers by Country</h1>" + "<table>"]
            repl.append("<tr><th>Country</th><th>Count</th></tr></thead>")
            repl.append("<tbody>")
            repl.append("<tr><td>China</td><td>124</td></tr>")
            repl.append("<tr><td>Netherlands</td>><td>99</td></tr>")
            repl.append("</tbody>")
            repl.append("</table")
            response = " ".join(repl)
            self.wfile.write(response.encode("utf-8"))
        elif parsed_path.path == '/about':
            self.send_response(200)
            self.send_header("Content-type", "text/html")
            self.end_headers()
            self.wfile.write(b"This is the About Page.")
        else:
            self.send_response(404)
            self.end_headers()
            self.wfile.write(b"404 Not Found")

class SecinfoCoordinator():
    #
    logfile = "/var/log/secinfo-xmpp/secinfo.log"
    log = None

    dbfile = "/var/local/lib/secinfo-xmpp/secinfo.db"
    con = None
    cur = None
    # The current database version
    dbversion = 1

    gi = None

    isWebServer = False
    global webServer

    async def handle(self, request):
        return web.Response(text="Hello, World!")

    def __init__(self, webPort, dbfile):
        print('SecinfoCoorindator init webport', webPort)
        if os.path.isfile(self.logfile):
            self.log=open(self.logfile,"a")
            # print('logfile '+self.logfile+' opened for append')
            logging.debug('logfile %s opened for append', self.logfile)
        else:
            self.log=open(self.logfile,"w")
            # print('new logfile '+self.logfile+' opened for write')
            logging.debug('new logfile %s opened for write', self.logfile)
        # Initailise GeoIP database
        self.gi = GeoIP.new(GeoIP.GEOIP_MEMORY_CACHE)
        # Initialise the database
        self.con = sqlite3.connect(self.dbfile)
        self.cur = self.con.cursor()
        # Check if table 'admin' exists and if not create it
        res = self.cur.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='admin'")
#       if res == []:
        if self.cur.fetchone()[0] == 0:
            # print('Table admin does not exist - creating it')
            self.cur.execute("CREATE TABLE admin(db_version int, starttime real)")
            self.cur.execute("INSERT INTO admin VALUES("+str(self.dbversion)+",julianday('now')")
            self.con.commit()
        else:
            #  print('Table admin exists')
            # check if admin has any rows
            self.cur.execute("SELECT COUNT(*) FROM admin")
            adminRows = self.cur.fetchone()[0]
            # print('adminrows = ', adminRows)
            if adminRows == 0:
                self.cur.execute("INSERT INTO admin VALUES( ? ,julianday('now'))", (str(self.dbversion),))
                self.con.commit()
            adminRes = self.cur.execute("SELECT db_version, starttime FROM admin")
            # for i in adminRes:
                #print("Version:"+str(i[0])+" Starttime:"+str(i[1]))
            # Check here for new dbversion -

        # make a table for Attacks if it does not exist
        res = self.cur.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='attack'")
        if res == None:
            # print ('res is None from trying to find attack table try creating it')
            self.cur.execute("CREATE TABLE attack(time real, ip_addr text, jail text, sechost text)")
            self.con.commit()
#        if res != None and self.cur.fetchone()[0] == 0:
#       if res == []:
#            print('Table attack does not exist - creating it')
#            self.cur.execute("CREATE TABLE attack(time real, ip_addr text, jail text, sechost text)")
        else:
            logging.debug("res was not None - try to check number of rows from sqlite_master query")
            rowsInMaster = self.cur.fetchone()[0]
            logging.debug('rows in master %d',rowsInMaster)
            if rowsInMaster == 0:
                print('Try to create the table again')
                self.cur.execute("CREATE TABLE attack(time real, ip_addr text, jail text, sechost text)")
                self.con.commit()
            self.cur.execute("SELECT COUNT(*) FROM attack")
            attackRows = self.cur.fetchall()[0]
            print('Table attack exists with ',attackRows,' rows')
        # make a table for Attacker if it does not exist
        res = self.cur.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='attacker'")
        if res == []:
            logging.debug('Table attacker does not exist - creating it')
            self.cur.execute("CREATE TABLE attacker(ip_addr text, firstseen real, latest real, jails text, country text, hoster text)")
        # Make a table for Country if it does not exist
        res = self.cur.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='country'")
        if res == []:
            logging.debug('Table country does not exist - creating it')
            self.cur.execute("CREATE TABLE country(country_code text, total int)")

        # Once all the tables have been created
        self.con.commit()

        # If webPort is non zero then set up webserver
        if webPort != 0:
            isWebServer = True
#           self.webServer(webPort)
            print('about to invoke startWebServer')
            self.startWebServer(webPort)

    async def startWebServer(self, webPort):
            print('at the start of startWebServer')
            webServer = web.Application()
            webServer.add_routes([web.get('/countries', self.webGetCountries),
                web.get('/', self.webGetIndex) ] )
            webrunner = web.AppRunner(webServer)
            print('about to await webrunner.setup')
            await webrunner.setup()
            print('after webrunner.setup')
            # web.run_app(webServer, port = webPort)
            mysite = web.TCPSite(webrunner, "localhost", port = webPort)
            print('about to await mysite.start')
            await mysite.start()

#            while True:
#                await asyncio.sleep(3600)



    async def webGetIndex(self, request):
        return web.Response(text=
            "<h1>Secinfo</h1> <a href=/countries>Countries</a>",
            content_type='text/html')

    async def webGetCountries(self, request):
        # use a local query connection
        qcon = sqlite3.connect( self.dbfile )
        qcur = qcon.cursor()
        repl = ["<h1>Attackers by Country</h1>" + "<table>"]
        repl.append("<tr><th>Country</th><th>Count</th></tr></thead>")
        repl.append("<tbody>")
        for qrow in qcur.execute("SELECT country_code, total FROM country ORDER BY total DESC"):
            # print(qrow)
            qcc, qtot = qrow
            repl.append("<tr><td>"+str(qcc)+"</td><td>"+str(qtot)+"</td></tr>")
        repl.append("</tbody>")
        repl.append("</table")
        myresponse = " ".join(repl)
        # print('webGetCountires response is '+myresponse)
        qcon.close()
        return web.Response( text = myresponse, content_type='text/html')

    def handleF2bBanUnban(self,f2b_ban_message,sender,ban):
        # print('secinfocoordinator handlefail2ban '+f2b_ban_message)
        #  not printing this now - keep to turn into debug log entry
        splitsender = sender.jid.split("/")
        sendernick = splitsender[1]
        fb_dict = dict(element.split("=") for element in f2b_ban_message.split(","))
        thisJail = ''
        if  'j' in fb_dict:
            thisJail = fb_dict['j']
        thisIp = fb_dict['i']
        self.log.write(f2b_ban_message)
        self.log.write(" ")
        self.log.write(sendernick)
        self.log.write("\n")
        # Log an attack in the attacks database.
        attackRes = self.cur.execute("SELECT time, ip_addr, jail, sechost FROM attack WHERE ip_addr = ? AND jail = ?", (thisIp,thisJail))
        numres = 0
        for i in attackRes:
            numres += 1
            # Don't need to print these now
            # print("attackRes ["+str(i)+"] time ",i[0]," ip_addr ",i[1]," jail ",i[2]," sechost ", i[3])
        if numres == 0:
            logging.debug('secinfo-coordinator Seeing attack from %s on %s for the first time - add it to the database',thisIp, thisJail)
            # add the attack
            self.cur.execute("INSERT INTO attack VALUES( julianday('now'), ?,?,?)", (thisIp, thisJail, sendernick))
            self.con.commit()
            logging.debug("attack added to database %s %s %s ", thisIp, thisJail, sendernick)
            # Add the attacker
            self.analyseIpData(thisIp,thisJail,sendernick)


    def analyseIpData(self,thisIp,thisJail,sendernick):
        logging.debug('secinfo-coordinator analyse %s for %s from %s', thisIp, thisJail, sendernick)
        ipdata =  json.loads(subprocess.getoutput("rdap --json "+thisIp))
        # extract information from ipdata
        if 'country' in ipdata:
            country = ipdata['country']
        else:
            country = self.gi.country_code_by_addr(thisIp)
            # country = 'Unknown'
        if 'name' in ipdata:
            name = ipdata['name']
        else:
            name = 'Unknown'
        # Add the country to the database.
        #  Is it a known country
        numcountry = 0
        countryRes = self.cur.execute("SELECT country_code, total FROM country WHERE country_code = ?", (country,))
        for i in countryRes:
            numcountry += 1
        if numcountry == 0:
            # New country - first attack
            self.cur.execute("INSERT INTO country VALUES(?, 1)", (country,))
            logging.debug('New country %s added', country)
        else:
            # Increment count for existing country
            self.cur.execute("UPDATE country SET total = total + 1 WHERE country_code = ?",(country,))
            logging.debug('Country count increased for %s ',country)
        self.con.commit()

#       if 'events' in ipdata:
#  want to extract registration and  last changed dates
# to see if attacks come from recently registered IPs
#
        reportUrl = ''
        digitalOceanNames = ['DigitalOcean','DIGITALOCEAN-142-93-0-0',
            'DIGITALOCEAN-137-184-0-0','DIGITALOCEAN-134-199-128-0',
            'DIGITALOCEAN-167-99-0-0','DIGITALOCEAN-AMS-5',
            'DIGITALOCEAN-206-189-0-0','EU-DIGITALOCEAN-NL1',
            'DIGITALOCEAN-104-248-0-0','DIGITALOCEAN-134-209-0-0',
            'DIGITALOCEAN-AP','DIGITALOCEAN-161-35-0-0',
            'DIGITALOCEAN-24-199-64-0','DIGITALOCEAN-67-207-64-0']
        microsoftNames = ['MSFT']
        awsNames = []
        googleNames = ['GOOGL-2']
        if name in digitalOceanNames:
            reportUrl = 'https://www.digitalocean.com/company/contact/abuse#intrusion'
        elif name in microsoftNames:
            reportUrl = 'https://msrc.microsoft.com/report/abuse?ThreatType=IpAddress&IncidentType=BruteForce'
        elif name in awsNames:
            reportUrl = 'https://support.aws.amazon.com/#/contacts/report-abuse'
        elif name in googleNames:
            reportUrl = 'https://support.google.com/code/contact/cloud_platform_report'

        self.log.write('country ')
        self.log.write(country)
        self.log.write(' name ')
        self.log.write(name)
        if reportUrl != '':
            self.log.write(' (')
            self.log.write(reportUrl)
            self.log.write(')')
        self.log.write("\n")
        logging.debug('secinfo-coordinator - country %s name %s', country, name)
        # See if this is a known attacker.
        attackerRes = self.cur.execute("SELECT ip_addr, jails FROM attacker WHERE ip_addr = ?", (thisIp,))
        numres = 0
        for i in attackerRes:
            numres += 1
            # Don't need to print these now as only need to know if it is new or not
            # print("attackerRes ip_addr ",i[0]," jails ",i[2]," sechost ", i[3])
        if numres == 0:
            logging.debug('New attacker %s Jail %s country %s hoster %s',thisIp, thisJail,country, name)
            self.cur.execute("INSERT INTO attacker VALUES(?, julianday('now'),julianday('now') ,?,?,?)", (thisIp, thisJail, country, name))
        else:
            oldJail = i[1]
            logging.debug('Existing attacker %s Jail %s country %s hoster %s',thisIp, thisJail,country, name)
            # country and hoster should not change
            # Update latest time
            self.cur.execute("UPDATE attacker SET latest = julianday('now') WHERE ip_addr = ?", (thisIp,))
            # Check if the attack was to a different jail and update
            if thisJail not in oldJail.split(","):
                newJail = oldJail + ',' + thisJail
                logging.debug('updating jail %s to %s ',oldJail, newJail)
                self.cur.execute("UPDATE attacker SET jails = ? WHERE ip_addr = ?", (newJail,thisIp))

        self.con.commit()

    def repsonseCountries(self):
        respl = ["<h1>Attackers by Country</h1>" + "<table>"]
        repl = repl.append("<tr><th>Country</th><th>Count</th></tr></thead>")
        repl = repl.append("<tbody>")
        repl = repl.append("<tr><td>China</td><td>124</td></tr>")
        repl = repl.append("<tr><td>Netherlandstd></tr>")
        repl = repl.append("</tbody>")
        repl = repl.append("</table")
        return " ".join(repl)


 #   def webServer(self, webport):
 #       print ("web server wanted on port",webport)
 #       server_address = ('', webport)
 #       httpd = HTTPServer(server_address, MyHandler)
 #       httpd.serve_forever()

