#!/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(u'(|(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(u'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(u'(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] = [] liste_triee[entry['mac']][entry['type']][chbres].append((entry['date'], entry['rapport'], entry['seuil'])) else: liste_triee[entry['mac']][entry['type']] = {} liste_triee[entry['mac']][entry['type']][chbres] = [] liste_triee[entry['mac']][entry['type']][chbres].append((entry['date'], entry['rapport'], entry['seuil'])) 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' try: maxlen = max([len(a[1]) for a in data]) + 4 except ValueError: maxlen = 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(u'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') if "--debug" in sys.argv or "debug" in sys.argv: print output sys.exit(0) 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 ', '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 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 ', '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'))