scripts/surveillance/mac_prises/mac_prise_analyzer.py

271 lines
10 KiB
Python
Executable file
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python
# -*- coding: utf8 -*-
import psycopg2
import psycopg2.extras
import sys
import smtplib
import time
sys.path.append('/usr/scripts/gestion')
from config import mac_prise
from affich_tools import tableau
sys.path.append('/usr/scripts/lc_ldap')
import lc_ldap
import collections
ldap = lc_ldap.lc_ldap_local()
conn = psycopg2.connect(user='crans', database='mac_prises')
conn.set_session(autocommit = True)
cur = conn.cursor(cursor_factory = psycopg2.extras.DictCursor)
membres_actifs = ldap.search('(|(droits=Cableur)(droits=Nounou)(droits=Apprenti)(droits=Bureau))')
chambres_ma = []
for membre_actif in membres_actifs:
try:
chambres_ma.append(str(membre_actif['chbre'][0]).lower())
except:
pass
clubs = ldap.search('cid=*')
chambres_clubs = []
for club in clubs:
try:
chambres_clubs.append(str(club['chbre'][0]).lower())
except:
pass
requete = "SELECT * FROM signales WHERE date >= timestamp 'now' - interval '1 day';"
cur.execute(requete)
signales = cur.fetchall()
longueur = (24, 7)
titres = (u'mac', u'chambres')
alignements = ('c', 'c')
def lin(x, y, z):
"""
Calcul linéaire d'un rapport
"""
return (float(x)/float(y)-1.0)/(float(z)-1.0)
def genere_comptage(duree):
"""
Grosse fonction de hack pour ne pas écrire six fois le même formatage de sortie bidon.
Prend en argument :
* duree, qui peut valoir 'instant', 'heuristique', ou 'journalier', permet d'importer
les variables de config qui vont bien
"""
global date
global Logs
pb_comptage_suspect = {}
output = ""
requete = "SELECT array_to_string(array_agg(DISTINCT date), ', ') AS dates , mac, array_to_string(array_agg(DISTINCT chambre), ', ') AS chambres, COUNT(DISTINCT chambre) AS nb_chambres_distinctes, COUNT(chambre) AS nb_chambres, COUNT(DISTINCT date) as nb_dates_distinctes, COUNT(DISTINCT mac) as nb_macs_distinctes FROM correspondance WHERE date >= timestamp 'now' - interval '%(delay)s' GROUP BY mac;" % {'delay': mac_prise.delay[duree]}
cur.execute(requete)
fetched = cur.fetchall()
for entry in fetched:
machines = ldap.search('(macAddress=%s)' % entry['mac'])
if len(machines) > 0:
if isinstance(machines[0], lc_ldap.machineWifi):
continue
else:
continue
if entry['nb_chambres_distinctes'] >= mac_prise.suspect[duree]:
Logs.append(u"Recherche par %s, entrée suspecte : %s -> %s : %s distinctes sur %s dates distinctes\n" % ('mac', entry['mac'], entry['chambres'], entry['nb_chambres'], entry['nb_dates_distinctes']))
liste_chambres = entry['chambres'].split(', ')
# On calcule la "probabilité" qu'un truc ne soit pas clair concernant la chambre/mac
rapport = lin(entry['nb_chambres'], entry['nb_dates_distinctes'], float(entry['nb_chambres_distinctes']))
if rapport >= mac_prise.rapport_suspect[duree]:
Logs.append(u"Entrée ajoutée au tableau %s, car rapport supérieur au seuil.\n\n" % (duree))
pb_comptage_suspect[entry['mac']] = (liste_chambres, rapport, mac_prise.rapport_suspect[duree])
else:
Logs.append(u"Entrée rejetée du tableau %s, car rapport inférieur au seuil : (%s).\n\n" % (duree, rapport))
else:
pass
if len(pb_comptage_suspect) > 0:
for clef, valeur in pb_comptage_suspect.items():
requete = "INSERT INTO spotted (mac, type, chambre, date, rapport, seuil) VALUES(%s, %s, %s, %s, %s, %s);"
cur.execute(requete, (clef, duree, ", ".join(valeur[0]), date, valeur[1], valeur[2]))
return None
def summary():
"""
Fonction résumant les entrées "spotted" des dernières 24h
"""
output = u""
Logs = u""
requete = "SELECT * FROM spotted WHERE date >= timestamp 'now' - interval '1 day' ORDER BY date ASC;"
cur.execute(requete)
fetched = cur.fetchall()
liste_triee = collections.defaultdict(dict)
for entry in fetched:
chbres = entry['chambre'].split(', ')
chbres.sort()
chbres = tuple(chbres)
if liste_triee[entry['mac']].has_key(entry['type']):
if liste_triee[entry['mac']][entry['type']].has_key(chbres):
liste_triee[entry['mac']][entry['type']][chbres].append((entry['date'], entry['rapport'], entry['seuil']))
else:
liste_triee[entry['mac']][entry['type']][chbres] = []
else:
liste_triee[entry['mac']][entry['type']] = {}
for mac, ltype in liste_triee.items():
data = []
output += u"Il y a eu une activité suspecte concernant la mac %s :\n\n" % mac
Logs += u"Activité pour %s :\n" % mac
for dtype, dataset in ltype.items():
number = 0
chambres = set()
for chbres, dataframe in dataset.items():
chambres.update(chbres)
number += len(dataframe)
data += [[dtype, ", ".join(list(chbres)), donnee[0], donnee[1], donnee[2]] for donnee in dataframe]
output += u'Sur le relevé %s, la mac est apparue %s fois. Elle est apparue dans les chambres suivantes : %s.\n' % (dtype, number, ", ".join(list(chambres)))
output += u'Consulter les logs (en PJ) pour plus d\'informations.\n\n'
maxlen = max([len(a[1]) for a in data]) + 4
titres = (u'relevé', u'chambres', u'date', u'rapport', u'seuil')
alignements = ('c', 'c', 'c', 'c', 'c')
largeurs = (12, maxlen, 20, 10, 10)
Logs += tableau(data, titres, largeurs, alignements)
Logs += u"\n\n"
return output, Logs
def reperage_mac_inconnue():
"""
Fonction de repérage d'une mac qui ne devrait pas être
dans telle chambre, sur une plage de 24h, suivant un
paramètre de config pour le nombre d'occurrences.
"""
output = u""
probleme = {}
requete = "SELECT chambre, mac, COUNT(mac) as nb_min FROM correspondance WHERE date >= timestamp 'now' - interval '24 hours' GROUP BY chambre, mac ORDER BY chambre ASC;"
cur.execute(requete)
fetched = cur.fetchall()
liste_parsee = collections.defaultdict(dict)
for entry in fetched:
liste_parsee[entry['chambre']][entry['mac']] = int(entry['nb_min'])
for chambre in liste_parsee.keys():
if chambre in chambres_ma + chambres_clubs:
continue
for mac in liste_parsee[chambre].keys():
try:
proprio_associe = ldap.search('macAddress=%s' % mac)[0].proprio()
if str(proprio_associe['chbre'][0]).lower() == chambre.lower():
garbage = liste_parsee[chambre].pop(mac)
except:
pass
number = sum(liste_parsee[chambre].values())
if number >= mac_prise.max_inconnues_par_jour:
probleme[chambre] = (liste_parsee[chambre].keys(), number)
if len(probleme) > 0:
output += mac_prise.titre_mac_inconnue+"\n"
longueur_max = max([len(", ".join(a[0])) for a in probleme.values()] + [len("macs")]) + 2
largeurs = (len('chambre') + 2, longueur_max, len('compteur') + 2, len('seuil') + 2)
data = []
clefs = probleme.keys()
clefs.sort()
for clef in clefs:
data.append([clef, ", ".join(probleme[clef][0]), probleme[clef][1], mac_prise.max_inconnues_par_jour])
output += tableau(data, ('chambre', 'macs', 'compteur', 'seuil'), largeurs, ('c', 'c', 'c', 'c'))
output += u"\n\n\n"
return output
if __name__ == '__main__':
date = time.strftime('%F %T')
if len(sys.argv) >= 2:
output = u''
Logs = u''
if '--summary' in sys.argv or 'summary' in sys.argv:
# On envoie le résumé
output += u' *Résumé des apparitions de mac dans plusieurs chambres*\n\n'
texte, Logs = summary()
output += texte
output += u"\n\n"
if '--reperage' in sys.argv or 'reperage' in sys.argv:
# On envoie le repérage
output += u' *Repérage de spoof potentiel par comptage*\n\n'
output += reperage_mac_inconnue()
Logs = Logs.encode('UTF-8')
output = output.encode('UTF-8')
message = """From: %(from)s
To: %(to)s
Subject: Résumé quotidien : mac_prises.
Content-Type: multipart/mixed; boundary="_424234545aaff-ffca234efff-556adceff5646_"
--_424234545aaff-ffca234efff-556adceff5646_
Content-Type: text/plain; charset="UTF-8"
%(contenu)s
--_424234545aaff-ffca234efff-556adceff5646_
Content-Type: text/plain; charset="UTF-8"
Content-Disposition: attachment; filename="logs_analyse"
%(logs)s
--_424234545aaff-ffca234efff-556adceff5646_--
"""
corps = message % { 'from': 'Spoofing watcher <spoof-watcher@crans.org>',
'to': 'test@lists.crans.org',
'subject': 'Résumé journalier ',
'contenu': output,
'logs': Logs,
}
mail = smtplib.SMTP('localhost')
mailfrom = 'spoof-watcher@crans.org'
mailto = 'test@lists.crans.org'
mail.sendmail(mailfrom, mailto, corps)
else:
Logs = [u"Logs du script mac_prise_analyzer\n\n\n"]
genere_comptage('instantanne')
genere_comptage('moyen')
genere_comptage('journalier')
if time.localtime().tm_min % 30 == 0 and mac_prise.hargneux:
hargneux = True
else:
hargneux = False
if hargneux:
message = """From: %(from)s
To: %(to)s
Subject: %(subject)s
Content-Type: text/plain; charset="UTF-8"
%(logs)s
"""
corps = message % { 'from': 'Spoofing watcher <spoof-watcher@crans.org>',
'to': 'test@lists.crans.org',
'subject': 'Logs de mac_prises_analyzer',
'logs': "".join(Logs),
}
mail = smtplib.SMTP('localhost')
mailfrom = 'spoof-watcher@crans.org'
mailto = 'test@lists.crans.org'
mail.sendmail(mailfrom, mailto, corps.encode('utf-8'))