scripts/surveillance/deconnexion.py
Pierre-Elliott Bécue dfa71ba8f6 [comptage_upload] Maintenant, ça marche, modulo analyse.py
* Je ferai un truc mieux demain.
2014-06-18 01:39:10 +02:00

433 lines
17 KiB
Python
Executable file

#! /usr/bin/env python
# -*- coding: utf-8 -*-
"""
Script de déconnection automatique des machines du crans pour les raisons :
- upload
- p2p (plus maintenant)
- flood
- virus
Copyright (C) Xavier Pessoles, Étienne Chové, Vincent Bernat, Nicolas Salles
Licence : GPL v2
"""
################################################################################
# Import des commandes #
################################################################################
import commands
import sys
import psycopg2
sys.path.append('/usr/scripts/gestion')
from config import upload, virus, NETs, plage_ens, prefix
import config.mails.upload
import ipt
import smtplib
from ldap_crans import crans_ldap
from ldap_crans import MachineWifi
from ldap_crans import MachineFixe
from time import *
#import locale
#locale.setlocale(locale.LC_TIME, 'fr_FR')
import lock
sys.path.append('/usr/scripts/surveillance/fiche_deconnexion')
from generate import generate_ps
from affich_tools import tableau
import analyse
import mail as mail_module
# ldap
ldap = crans_ldap()
# 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 machines wifi sont toujours online
if isinstance(machine, MachineWifi):
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 :
###############
# Connection à la base sql via pgsql
pgsql = psycopg2.connect(database='filtrage', user='crans')
pgsql.set_session(autocommit=True)
curseur = pgsql.cursor()
# Le smtp est assez capricieux
def connectsmtp():
for i in range(5):
try:
mail = smtplib.SMTP('localhost')
except:
sleep(5)
if i == 4:
print "Impossible de se connecter au SMTP"
sys.exit(1)
return mail
# Pour trouver la chambre où était la machine que l'on déconnecte.
def reperage_chambre(mac):
pgsql = psycopg2.connect(host="thot.adm.crans.org", database='mac_prises', user='crans')
# A priori, pas besoin, on ne fait que des select
pgsql.set_session(autocommit=True)
curseur = pgsql.cursor()
requete = "SELECT date, chambre FROM correspondance WHERE mac=%s ORDER BY date DESC LIMIT 1;"
curseur.execute(requete, (mac,))
result = curseur.fetchall()
if result:
return result[0][0], result[0][1]
else:
return "Inconnue", "Inconnue"
################################################################################
# Vérification de l'upload #
################################################################################
# upload par entité (adhérent/club/machine crans)
requete="""SELECT
SUM(agregat.total) as tot_upload, machines.type, machines.id
FROM (
SELECT
'upload', sum(bytes)/1024/1024 AS total, mac_src
FROM
upload
WHERE
stamp_updated > now() - interval '1 day'
AND stamp_updated < now()
AND NOT (ip_dst <<= inet%(plage_ens)s OR ip_dst <<= inet%(plage_ipv6)s OR ip_dst <<= inet%(appt)s)
AND (ip_src <<= inet%(allone)s OR ip_src <<= inet%(alltwo)s OR ip_src <<= inet%(plage_ipv6)s OR ip_src <<= inet%(appt)s)
AND NOT EXISTS
(
SELECT 1
FROM exemptes
WHERE upload.ip_src <<= exemptes.ip_crans
AND upload.ip_dst <<= exemptes.ip_dest
)
AND NOT EXISTS
(
SELECT 1
FROM exemptes6
WHERE upload.mac_src = exemptes6.mac_crans
AND upload.ip_dst <<= exemptes6.ip_dest
)
GROUP BY
mac_src
) AS agregat
INNER JOIN
machines
ON
machines.mac_addr = agregat.mac_src
GROUP BY
machines.type, machines.id
ORDER BY
tot_upload;
"""
curseur.execute(requete, {'plage_ens':plage_ens, 'allone':NETs['all'][0], 'alltwo':NETs['all'][1], 'plage_ipv6':prefix['subnet'][0], 'appt':NETs['personnel-ens'][0],})
uploadeurs = curseur.fetchall()
# Table des avertis
###################
# Avertis upload hard
requete = "SELECT type,id FROM avertis_upload_hard where date>timestamp 'now' - interval '1 day'"
curseur.execute(requete)
avertis_upload_hard = curseur.fetchall()
# Avertis upload soft
requete = "SELECT type,id FROM avertis_upload_soft where date>timestamp 'now' - interval '1 day'"
curseur.execute(requete)
avertis_upload_soft = curseur.fetchall()
# 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_upload_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 cherche à savoir où et quand on
# a vu les machines du proprio pour la dernière fois
####################################################
machines = proprio.machines()
macs_dates_chambres = []
for machine in machines:
if isinstance(machine, MachineFixe):
mac = machine.mac()
date, chambre = reperage_chambre(mac)
macs_dates_chambres.append([mac, date, chambre])
mdcf = tableau(macs_dates_chambres, ('mac', 'date', 'chambre'), (20, 21, 7), ('c', 'c', 'c'))
# On sanctionne
###############
debut = int(time())
fin = debut + 24*3600
try:
proprio.blacklist([debut, fin, 'autodisc_upload', "Déconn auto. %s Mo" % elupload])
proprio.save()
# On inscrit l'instance dans la table des avertis_hard
######################################################
curseur.execute("INSERT INTO avertis_upload_hard (type,id,date) VALUES ('%s','%d','now')"%(eltype,elid))
#analyse.stats_fork('-%s' % debut, ip_crans=[m.ip() for m in proprio.machines()], show_limit=1000)
except Exception as error:
sys.stderr.write("Blacklist de id=%s pour %s Mo échoué, %s\n" % (proprio.id(), elupload, error))
continue
# On envoie un mail à l'adhérent
################################
mail = connectsmtp()
corps = mail_module.generate('upload_hard', {'from': upload.expediteur, 'to': proprio.email(), 'upload': elupload, 'proprio': proprio.Nom(), 'lang_info':'English version below'}).as_string()
mail.sendmail(upload.expediteur, proprio.email(), corps)
# On envoie un mail à disconnect
################################
if upload.disconnect_mail_hard:
if eltype == "club":
theid = "cid="
else:
theid = "aid="
mail_disconnect_hard = config.mails.upload.Message_disconnect_hard(upload.expediteur, upload.expediteur, proprio=proprio.Nom(), id=theid + proprio.id(), upload=elupload, mdc=mdcf, chambre=proprio.chbre())
mail_disconnect_hard.send()
# Vérification du nombre de déconnexions
#########################################
nb_decos = len([ x for x in proprio.blacklist() if int(x.split('$')[0]) > time()-30*24*3600 and x.split('$')[2] == 'autodisc_upload' ])
if nb_decos >= config.upload.max_decos:
# Génération du fichier postscript
try:
fichier_ps = generate_ps('upload', proprio, ldap)
except:
fichier_ps = ("ERREUR lors de la génération. Merci de regénérer manuellement la fiche avec la commande :\n"
+ "/usr/scripts/surveillance/fiche_deconnexion/generate.py --upload aid=%d" % int(proprio.id()))
# Envoi du mail à disconnect
corps = config.mails.upload.message_disconnect_multi % {'from': upload.expediteur, 'to': upload.expediteur, 'nbdeco': nb_decos, 'proprio': proprio.Nom(), 'ps': fichier_ps}
corps = corps.encode('utf-8')
mail.sendmail(upload.expediteur, upload.expediteur, corps)
elif elupload >= upload.soft:
# L'adhérent a t il été averti ou est déjà déco ?
#################################################
if (eltype, elid) in avertis_upload_soft or (eltype, elid) in avertis_upload_hard:
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 envoie un mail à l'adhérent
################################
mail = connectsmtp()
corps = mail_module.generate('upload_soft', {'from': upload.expediteur, 'to': proprio.email(), 'upload': elupload, 'proprio': proprio.Nom(), 'lang_info':'English version below'}).as_string()
mail.sendmail(upload.expediteur, proprio.email(), corps)
# On envoie un mail à disconnect
################################
if upload.disconnect_mail_soft:
corps = config.mails.upload.message_disconnect_soft % {'from': upload.expediteur, 'to': upload.expediteur, 'upload': elupload, 'proprio': proprio.Nom()}
corps = corps.encode('utf-8')
mail.sendmail(upload.expediteur, upload.expediteur, corps)
# On supprime les vieux avertisements
curseur.execute("DELETE FROM avertis_upload_hard WHERE date < timestamp 'now' - interval '85200 seconds'") # 23h et 40min pour prolonger les blacklists toujours au dessus de la limite
curseur.execute("DELETE FROM avertis_upload_soft WHERE date < timestamp 'now' - interval '1 day'")
################################################################################
# Détection de l'existence de virus #
################################################################################
# Dans la table virus on sélectionne les ip_src qui appartiennent au réseau
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()
# Récupération des infectés 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 infectés
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 où il y a marqué virus
index = [blacklist.index(x) for x in blacklist if 'autodisc_virus' in x ]
if index:
# L'adhérent est déjà blacklisté
proprio.blacklist((index[0], ['now', '-', 'autodisc_virus', hostname]))
proprio.save()
else:
# L'adhérent n'est pas encore blacklisté
proprio.blacklist(['now', '-', 'autodisc_virus', hostname])
proprio.save()
################################################################################
# Détection des virus qui floodent #
################################################################################
# Dans la table virus on sélectionne les ip_src qui appartiennent au réseau
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()
# Récupération des infectés 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, ou qu'on est déjà averti, on laisse passer
if nombre < virus.flood or (ip) in infectes_old:
continue
# Lecture des infos de ldap
try :
machine = ldap.search('ipHostNumber=%s' % ip, 'w' )['machine'][0]
except IndexError :
# Dans le cas où l'ip détectée n'est pas enregistrée
print "La machine avec l'ip %s n'est pas declaree !" % ip
continue
hostname = machine.nom()
proprio = machine.proprietaire()
blacklist = proprio.blacklist()
# Inscription dans la table des infectés
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 où il y a marqué virus
index = [ blacklist.index(x) for x in blacklist if 'autodisc_virus' in x ]
try:
if index:
# L'adhérent est déjà blacklisté
proprio.blacklist((index[0], ['now', '-', 'autodisc_virus', hostname]))
proprio.save()
else:
# L'adhérent n'est pas encore blacklisté
proprio.blacklist(['now', '-', 'autodisc_virus', hostname])
proprio.save()
except ValueError: # On a essayé de blacklister un proporiétaire virtuel
pass # Le message d'erreur a déjà été affiché (changer ça ?)
# 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 requêtes 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 requêtes 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 ne 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:
try:
machine = ldap.search('ipHostNumber=%s' % IP, 'w' )['machine'][0]
except IndexError:
print "Suppression de %s des machines infectees (la machine n'existe plus)"%IP
requete = "DELETE FROM avertis_virus where ip_crans='%s'"%IP
curseur.execute(requete)
continue # la machine n'existe plus, on passe à l'infecté suivant
# Si la machine n'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.
lignes_enlevees = 0
for ligne in bl:
if '$-$autodisc_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()
lignes_enlevees += 1
if lignes_enlevees == 0:
print "Suppression de %s des machines infectees, mais aucune blackliste"%hostname
requete = "DELETE FROM avertis_virus where ip_crans='%s'"%IP
curseur.execute(requete)