diff --git a/surveillance/deconnexion-ng.py b/surveillance/deconnexion-ng.py new file mode 100755 index 00000000..4780c69d --- /dev/null +++ b/surveillance/deconnexion-ng.py @@ -0,0 +1,470 @@ +#! /usr/bin/env python +# -*- coding: iso-8859-15 -*- + +""" +Script de déconnection automatique des machines du crans pour les raisons : + - upload + - p2p + - flood + - virus + +Copyright (C) Xavier Pessoles, Étienne Chové, Vincent Bernat +Licence : GPL v2 +""" + +################################################################################ +# Import des commandes # +################################################################################ + +import commands +import sys +from pyPgSQL import PgSQL +sys.path.append('/usr/scripts/gestion') +from config import upload, virus, p2p, NETs +import smtplib +from ldap_crans import crans_ldap, crans, invite, base_classes_crans +from time import * +import locale +locale.setlocale(locale.LC_TIME,'fr_FR') + +debug = '--debug' in sys.argv + +# Quelques fonctions +#################### + +def machine_online(machine) : + """ + Retourne True si la machine est connectée au réseau et False si elle ne l'est pas + """ + # les wifi sont toujours online + if machine.ipsec() : + return True + # arping pour les fixes + return not commands.getstatusoutput('/usr/sbin/arping -c 3 %s' % machine.mac())[0] + +# Variables utiles +################## + +# création d'une chaine qui ressemble à : (ip_src<<=inet('138.231.136.0/21') or ip_src<<=inet('138.231.148.0/22')) +ip_src_in_crans = '(%s)' % ' or '.join([ "ip_src<<=inet('%s')" % net for net in NETs['all'] ]) + +# Connections : +############### + +# pgsql +pgsql = PgSQL.connect(host='/var/run/postgresql', database='filtrage', user='crans') +pgsql.autocommit = True +curseur = pgsql.cursor() + +# smtp +mail = smtplib.SMTP('localhost') + +# ldap +ldap = crans_ldap() + +################################################################################ +# Vérification de l'upload # +################################################################################ + +# upload par entité (adhérent/club/machine crans) +requete = """SELECT + sum(total), type, id +FROM + ( + SELECT + sum(total) AS total, ip_crans + FROM + ( + ( -- upload terminé par ip + SELECT + 'upload',round(sum(upload)/1024/1024) AS total, ip_crans + FROM + upload + WHERE + upload>download + AND date>timestamp 'now' - interval '1 day' + AND date<'now' + AND NOT EXISTS + ( + SELECT + 1 FROM exemptes + WHERE upload.ip_crans<<=exemptes.ip_crans + AND upload.ip_ext<<=exemptes.ip_dest + ) + GROUP BY + ip_crans + ) + UNION + ( -- upload en cours par ip + SELECT + 'dump', round(sum(upload)/1024/1024) AS total, ip_crans + FROM + dump AS upload + WHERE + upload>download + AND date>timestamp 'now' - interval '1 day' + AND date<'now' + AND NOT EXISTS + ( + SELECT + 1 FROM exemptes + WHERE upload.ip_crans<<=exemptes.ip_crans + AND upload.ip_ext<<=exemptes.ip_dest + ) + GROUP BY + ip_crans + ORDER BY + total desc + ) + ) + AS + total + GROUP BY + ip_crans + ) +AS + total +INNER JOIN + machines +ON + total.ip_crans = machines.ip +GROUP BY + type, id +;""" +curseur.execute(requete) +uploadeurs = curseur.fetchall() + +# Table des avertis +################### + +# Avertis hard +requete = "SELECT type,id FROM avertis_upload_hard where date>timestamp 'now' - interval '1 day'" +curseur.execute(requete) +avertis_hard = curseur.fetchall() + +# Avertis soft +requete = "SELECT type,id FROM avertis_upload_soft where date>timestamp 'now' - interval '1 day'" +curseur.execute(requete) +avertis_soft = curseur.fetchall() + +uploadeurs.append([1000,'adherent',367]) + +# Vérification : +################ + +for elupload, eltype, elid in uploadeurs: + if elupload >= upload.hard : + # L'adhérent a t il été blacklisté ? + #################################### + if [eltype,elid] in avertis_hard: + continue + + # Propriétaire issu de LDAP + ########################### + if eltype == 'club': + proprio=ldap.search('cid=%d'%elid,'w')['club'] + elif eltype == 'adherent': + proprio=ldap.search('aid=%d'%elid,'w')['adherent'] + else: + continue + + if len(proprio)!=1: + print 'Erreur : Proprio non trouvé (%s) %d'%(eltype, elid) + continue + proprio=proprio[0] + + # On inscrit l'ip dans la table des avertis_hard + ################################################ + curseur.execute("INSERT INTO avertis_upload_hard (type,id,date) VALUES ('%s','%d','now')"%(eltype,elid)) + + # on s'arrete ici pour le debug + if debug: + print 'Sanction de %s pour upload (%dMo)'%(proprio.Nom(),elupload) + + # On sanctionne + ############### + debut = localtime(time()) + fin = localtime(time()+60*60*24) + #proprio.blacklist(["%d/%d/%d %d:%d" % (debut[2],debut[1],debut[0],debut[3],debut[4]),"%d/%d/%d %d:%d" % (fin[2],fin[1],fin[0],fin[3],fin[4]),'autodisc',"Déconn auto. %s Mo" % elupload]) + #proprio.save() + + # On envoie un mail a l'adhérent + ################################ + proprio_mail = proprio.email() + corps = upload.message_soft % {'from':upload.expediteur, 'to':proprio_mail, 'upload':elupload, 'proprio':proprio.Nom()} + corps = corps.encode('iso 8859-15') + #mail.sendmail(upload.expediteur,proprio,corps) + + # On envoie un mail à disconnect + ################################ + if upload.disconnect_mail_hard : + corps = upload.message_disconnect_hard % {'from':upload.expediteur, 'to':upload.expediteur, 'upload':elupload, 'proprio':proprio.Nom()} + corps = corps.encode('iso 8859-15') + #mail.sendmail(upload.expediteur,upload.expediteur,corps) + + # Vérification du nombre de déconnections + ######################################### + nb_decos = len([ x for x in proprio.blacklist() if mktime(strptime(x.split(',')[0],'%d/%m/%Y %H:%M')) > mktime(localtime())-30*24*60*60 and x.split(',')[2]=='autodisc' ]) + if nb_decos >= 3: + if debug: + print 'Recidiviste !!!' + + # dossier de génération du pdf + dossier = '/usr/scripts/surveillance/fiche_deconnection/' + + # base pour le dossier de destination + fichier = strftime('%Y-%m-%d-%H-%M') + '-' + proprio.Nom().lower().replace(' ','-') + + # création du fichier tex + template = file(dossier + 'deconnexion_upload.tex').read() + template = template.replace('~prenom~',proprio.prenom().encode('iso8859-15')) + template = template.replace('~nom~',proprio.nom().encode('iso8859-15')) + template = template.replace('~chambre~',proprio.chbre().encode('iso8859-15')) + template = template.replace('~mail~',proprio.email().encode('iso8859-15')) + template = template.replace('~debut~',proprio.prenom().encode('iso8859-15')) + template = template.replace('~fin~',proprio.prenom().encode('iso8859-15')) + historique = [ bl.encode('iso-8859-15').split(',') for bl in proprio.blacklist() if bl.split(',')[2]=='autodisc' ] # filtrage des autodisc + historique = [ (strftime('%A %d %B %Y',strptime(bl[0],'%d/%m/%Y %H:%M')), bl[-1].split(' ')[-2]) for bl in historique ] # transfomation en tupple (date, upload) + historique = [ '%s & %s & Mo'%(bl[0],bl[1]) for bl in historique ] # tranformation en ligne + historique = '\\\\\n'.join(historique) # assemblage des lignes + template = template.replace('~historique~', historique) + + file(dossier+fichier+'.tex','w').write(template) + + # compilation + commands.getstatusoutput('PATH="/bin:/usr/bin" cd %(dossier)s && latex %(base)s.tex && dvips %(base)s.ps && rm -f %(base)s.dvi %(base)s.aux %(base)s.log %(base)s.tex'%{'dossier':dossier,'base':fichier}) + + # envoie du mail à disconnect + corps = upload.message_disconnect_multi % {'from':upload.expediteur, 'to':upload.expediteur, 'nbdeco':nb_decos, 'proprio':proprio.Nom(), 'ps':dossier+fichier+'.ps'} + corps = corps.encode('iso 8859-15') + mail.sendmail(upload.expediteur,upload.expediteur,corps) + + elif elupload >= upload.soft : + # L'adhérent a t il été averti + ############################## + if [eltype,elid] in avertis_soft: + continue + + # Objets LDAP + ############# + if eltype == 'club': + proprio=ldap.search('cid=%d'%elid)['club'] + elif eltype == 'adherent': + proprio=ldap.search('aid=%d'%elid)['adherent'] + else: + continue + + if len(proprio)!=1: + print 'Proprio non trouvé (%s) %d'%(eltype, elid) + continue + proprio=proprio[0] + + # On inscrit l'ip dans la table des avertis soft + ################################################ + curseur.execute("INSERT INTO avertis_upload_soft (type,id,date) VALUES ('%s','%d','now')"%(eltype,elid)) + + # on s'arrete ici pour le debug + if debug: + print 'Upload soft de %s (%dMo)'%(proprio.Nom(),elupload) + continue + + # On envoie un mail a l'adhérent + ################################ + if debug: print 'Avertissement de %s pour upload'%proprio.Nom() + proprio_mail = proprio.mail() + if '@' not in proprio_mail : + proprio_mail += '@crans.org' + corps = upload.message_soft % {'from':upload.expediteur, 'to':proprio_mail, 'upload':elupload, 'proprio':proprio.Nom()} + corps = corps.encode('iso 8859-15') + #mail.sendmail(upload.expediteur,proprio,corps) + + # On envoie un mail à disconnect + ################################ + if upload.disconnect_mail_soft : + corps = upload.message_disconnect_soft % {'from':upload.expediteur, 'to':upload.expediteur, 'upload':elupload, 'proprio':proprio.Nom()} + corps = corps.encode('iso 8859-15') + #mail.sendmail(upload.expediteur,upload.expediteur,corps) + +# On supprime les vieux avertisements +curseur.execute("DELETE FROM avertis_upload_hard WHERE date < timestamp 'now' - interval '1 day'") +curseur.execute("DELETE FROM avertis_upload_soft WHERE date < timestamp 'now' - interval '1 day'") + +################################################################################ +# Détection de l'existence de virus # +################################################################################ + +# Dans le table virus on sélectionne les ip_src qui appartiennent au reseau +requete = "SELECT ip_src,count(ip_src) FROM virus WHERE %s and date > timestamp 'now' - interval '1 hour' group by ip_src" % ip_src_in_crans +curseur.execute(requete) +infectes = curseur.fetchall() + +# Recuperation des infectes pour ne pas les reblacklister +requete = "SELECT ip_crans FROM avertis_virus" +curseur.execute(requete) +infectes_old = curseur.fetchall() + +for ip, nombre in infectes: + + # si on est en dessous du seuil, on laisse passer + if nombre < virus.virus: + continue + + # si on est déja avertis, on laisse passer + if [ip] in infectes_old: + continue + + # lecture des infos de ldap + machine = ldap.search('ipHostNumber=%s' % ip,'w' )['machine'][0] + hostname = machine.nom() + proprio = machine.proprietaire() + blacklist = proprio.blacklist() + + # inscription dans la table des infectes + requete="INSERT INTO avertis_virus (ip_crans,date) VALUES ('%s','now')" % ip + curseur.execute(requete) + + # on récupère les index des lignes de bl ou il y a marqué virus + index = [blacklist.index(x) for x in blacklist if 'virus' in x ] + if index : + # adhérent déja blacklisté + proprio.blacklist(( index[0] , ['now','-','virus',hostname] )) + proprio.save() + else : + # adhérent non blacklisté + proprio.blacklist(['now','-','virus',hostname]) + proprio.save() + +################################################################################ +# Détection des virus qui floodent # +################################################################################ + +# Dans le table virus on sélectionne les ip_src qui appartiennent au reseau +requete = "SELECT ip_src,count(ip_src) FROM flood WHERE %s and date > timestamp 'now' - interval '1 hour' GROUP BY ip_src" % ip_src_in_crans +curseur.execute(requete) +infectes = curseur.fetchall() + +# Recuperation des infectes pour ne pas les reblacklister +requete = "SELECT ip_crans FROM avertis_virus" +curseur.execute(requete) +infectes_old = curseur.fetchall() + +for ip, nombre in infectes: + + # si on est en dessous du seuil, on laisse passer + if nombre < virus.flood: + continue + + # si on est déja avertis, on laisse passer + if [ip] in infectes_old : + continue + + # lecture des infos de ldap + machine = ldap.search('ipHostNumber=%s' % ip,'w' )['machine'][0] + hostname = machine.nom() + proprio = machine.proprietaire() + blacklist = proprio.blacklist() + + # inscription dans la table des infectes + requete="INSERT INTO avertis_virus (ip_crans,date) VALUES ('%s','now')" % ip + curseur.execute(requete) + + # on récupère les index des lignes de bl ou il y a marqué virus + index = [blacklist.index(x) for x in blacklist if 'virus' in x ] + if index : + # adhérent déja blacklisté + proprio.blacklist(( index[0] , ['now','-','virus',hostname] )) + proprio.save() + else : + # adhérent non blacklisté + proprio.blacklist(['now','-','virus',hostname]) + proprio.save() + + +# Reconnexion si le virus/flood a disparu +######################################### + +# Dans la table avertis_virus on récupère la liste des infectés +requete = "SELECT ip_crans FROM avertis_virus where date < timestamp 'now' - interval '1 hour'" +curseur.execute(requete) +infectes = [ x[0] for x in curseur.fetchall() ] + +for IP in infectes : + + # Nombre de requets de virus + requete1="SELECT COUNT(ip_src) FROM virus where ip_src='%s' and date > timestamp 'now' - interval '1 hour'" % IP + curseur.execute(requete1) + nb_virus = curseur.fetchall() + + # Nombre de requetes de flood + requete2="SELECT COUNT(ip_src) FROM flood where ip_src='%s' and date > timestamp 'now' - interval '1 hour'" % IP + curseur.execute(requete2) + nb_flood = curseur.fetchall() + + # On traite que les IP qui sont descendues en dessous des seuils + if nb_virus[0][0] < virus.virus and nb_flood[0][0] < virus.flood : + machine = ldap.search('ipHostNumber=%s' % IP,'w' )['machine'][0] + # si la machine est pas online, on reconnecte + #if machine_online(machine) : + proprio = machine.proprietaire() + bl = proprio.blacklist() + hostname = machine.nom() + # On stoppe la sanction pour une ligne existante de la blackliste + # En prenant en compte le fait que d'autres lignes de blackliste + # ont pu s'ajouter. + for ligne in bl: + if ',-,virus,%s'%hostname in ligne: + liste=ligne.split(',') + argument=[liste[0],'now',liste[2],liste[3]] + index = bl.index(ligne) + proprio.blacklist((index,argument)) + proprio.save() + requete="DELETE FROM avertis_virus where ip_crans='%s'"%IP + curseur.execute(requete) + +################################################################################ +# Gestion du peer to peer # +################################################################################ + +# Dans le table p2p on sélectionne les ip_src qui appartiennent au reseau +requete = "SELECT ip_src,id_p2p,count(ip_src) FROM p2p WHERE %s AND date > timestamp 'now' - interval '1 day' GROUP BY ip_src,id_p2p ORDER BY ip_src" % ip_src_in_crans +curseur.execute(requete) +fraudeurs = curseur.fetchall() + +# Recuperation des infectes pour ne pas les reblacklister +requete = "SELECT ip_crans,protocole FROM avertis_p2p WHERE date > timestamp 'now' - interval '1 day'" +curseur.execute(requete) +avertisp2p = curseur.fetchall() + +for ip, id_p2p, nombre in fraudeurs : + + # On récupére le protocole de p2p : + requete="SELECT nom FROM protocole_p2p WHERE id_p2p=%d" % id_p2p + curseur.execute(requete) + protocole = curseur.fetchall()[0][0] + + # On ne prend pas en compte s'il est sous le seuil admis, ou s'il est averti + if nombre <= p2p.tag or [ip, protocole] in avertisp2p : + continue + + # Recuperation des ref de la machine + machine = ldap.search('ipHostNumber=%s' % ip,'w' )['machine'][0] + hostname = machine.nom() + # proprio = machine.proprietaire() + + # Envoie du mail à disconnect + if p2p.disconnect_mail : + corps = p2p.avertissement % { 'From': upload.expediteur, 'To': upload.expediteur, 'protocole': protocole, 'hostname':hostname} + corps = corps.encode('iso 8859-15') + #mail.sendmail(upload.expediteur,upload.expediteur,corps) + + # inscription dans la base des avertis + requete="INSERT INTO avertis_p2p (ip_crans,date,protocole) VALUES ('%s','now','%s')" % (ip, protocole) + curseur.execute(requete) + + # Blacklistage + # date = time() + # debut = localtime(date) + # 7 jours + # fin = localtime(date+60*60*24*7) + # proprio.blacklist(["%d/%d/%d %d:%d" % (debut[2],debut[1],debut[0],debut[3],debut[4]),"%d/%d/%d %d:%d" % (fin[2],fin[1],fin[0],fin[3],fin[4]),'p2p',"P2P (auto)" % protocole]) + # proprio.save()