#!/bin/bash /usr/scripts/python.sh # -*- coding: utf8 -*- # # Sclaimer # PEB : 01/02/2013 -> now() # # Blablabla. (cf mac_prise.py) import psycopg2 import psycopg2.extras import sys import smtplib import time if '/usr/scripts' not in sys.path: sys.path.append('/usr/scripts') from gestion.config import mac_prise from gestion.affich_tools import tableau import lc_ldap.shortcuts import lc_ldap.objets import collections from email.header import Header 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 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 dans de multiples chambres simultanément. 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'+---------------------------------------------------------------+\n' output += u'| Résumé des apparitions de mac dans plusieurs chambres |\n' output += u'+---------------------------------------------------------------+\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'+---------------------------------------------------------------+\n' output += u'| Repérage de spoof potentiel par comptage |\n' output += u'+---------------------------------------------------------------+\n\n' output += u"""Attention, cette fonctionnalité est plus une forme de stalking que très utile.\n" Elle visait initialement à repérer les machines qui étaient trop longtemps dans une chambre qui n'est pas celle où elles devraient être, cependant, les divers tests menés n'ont pas prouvé une grande efficacité de cette méthode.\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: %(subject)s 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': mac_prise.mail_to, 'subject': Header(u"Résumé quotidien des correspondances mac_prises.", "UTF-8"), 'contenu': output, 'logs': Logs, } mail = smtplib.SMTP('localhost') mailfrom = 'disconnect@lists.crans.org' mailto = mac_prise.mail_to 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': mac_prise.mail_to, 'subject': 'Logs de %s' % sys.argv[0], 'logs': "".join(Logs), } mail = smtplib.SMTP('localhost') mailfrom = 'disconnect@lists.crans.org' mailto = mac_prise.mail_to mail.sendmail(mailfrom, mailto, corps.encode('utf-8'))