277 lines
10 KiB
Python
Executable file
277 lines
10 KiB
Python
Executable file
#!/usr/bin/env python
|
||
# -*- coding: utf8 -*-
|
||
#
|
||
# Sclaimer
|
||
# PEB : 01/02/2013 -> now()
|
||
#
|
||
# Blablabla. (cf mac_prise.py)
|
||
|
||
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/')
|
||
import lc_ldap.shortcuts
|
||
import lc_ldap.objets
|
||
import collections
|
||
|
||
ldap = lc_ldap.shortcuts.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.objets.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 %s\n\n\n" % sys.argv[0]]
|
||
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 %s' % sys.argv[0],
|
||
'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'))
|