scripts/surveillance/deconnexion.py
glondu 1c7207577b Plus d'invits.
darcs-hash:20060323150946-68412-e53639e21442ce0cd6006e8eff8cb521344eb9a8.gz
2006-03-23 16:09:46 +01:00

495 lines
19 KiB
Python
Executable file

#! /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, base_classes_crans
from ldap_crans import MachineWifi
from time import *
import locale
locale.setlocale(locale.LC_TIME,'fr_FR')
import lock
# 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]
def generate_ps(sanction, proprio) :
"""En fonction de la sanction à appliquer au propriétaire,
on génère la feuille de déconnexion et on retourne son nom et
emplacement."""
# Dossier de génération du ps
dossier = '/usr/scripts/surveillance/fiche_deconnexion'
# Base pour le nom du fichier
fichier = strftime('%Y-%m-%d-%H-%M') + '-%s-%s' % (sanction, proprio.Nom().lower().replace(' ','-'))
# Création du fichier tex
format_date = '%A %d %B %Y'
template = file('%s/deconnexion_%s.tex' % (dossier, sanction)).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~',strftime(format_date,localtime()))
template = template.replace('~fin~',strftime(format_date,localtime(time()+14*86400)))
historique = [ bl.encode('iso-8859-15').split(',') for bl in proprio.blacklist() if bl.split(',')[2]=='autodisc_upload' ] # 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)
template = template.replace('~limitehard~', str(upload.hard))
template = template.replace('~nbadher~', str(len(ldap.search('paiement=ok')['adherent'])))
file('%s/%s.tex' % (dossier, fichier),'w').write(template)
# Compilation du fichier latex
commands.getstatusoutput('PATH="/bin:/usr/bin" cd %(dossier)s && latex %(base)s.tex && dvips %(base)s.dvi && rm -f %(base)s.dvi %(base)s.aux %(base)s.log %(base)s.tex'%{'dossier':dossier,'base':fichier})
return '%s/%s.ps' % (dossier, fichier)
# 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 = PgSQL.connect(host='/var/run/postgresql', database='filtrage', user='crans')
pgsql.autocommit = True
curseur = pgsql.cursor()
# Le smtp est assez capricieux
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)
# 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 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 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))
# On sanctionne
###############
debut = localtime(time())
fin = localtime(time()+60*60*24)
proprio.blacklist(["%.2d/%.2d/%.4d %.2d:%.2d" % (debut[2],debut[1],debut[0],debut[3],debut[4]),"%.2d/%.2d/%.4d %.2d:%.2d" % (fin[2],fin[1],fin[0],fin[3],fin[4]),'autodisc_upload',"Déconn auto. %s Mo" % elupload])
proprio.save()
# On envoie un mail à l'adhérent
################################
corps = upload.message_soft % {'from':upload.expediteur, 'to':proprio.email(), 'upload':elupload, 'proprio':proprio.Nom()}
corps = corps.encode('iso 8859-15')
mail.sendmail(upload.expediteur,proprio.email(),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éconnexions
#########################################
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_upload' ])
if nb_decos >= 3:
# Génération du fichier postscript
fichier_ps = generate_ps('upload', proprio)
# Envoi du mail à disconnect
corps = upload.message_disconnect_multi % {'from':upload.expediteur, 'to':upload.expediteur, 'nbdeco':nb_decos, 'proprio':proprio.Nom(), 'ps':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_upload_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 envoie un mail à l'adhérent
################################
corps = upload.message_soft % {'from':upload.expediteur, 'to':proprio.email(), 'upload':elupload, 'proprio':proprio.Nom()}
corps = corps.encode('iso 8859-15')
mail.sendmail(upload.expediteur,proprio.email(),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 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 'virus' in x ]
if index :
# L'adhérent est déjà blacklisté
proprio.blacklist(( index[0] , ['now','-','virus',hostname] ))
proprio.save()
else :
# L'adhérent n'est pas encore blacklisté
proprio.blacklist(['now','-','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
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 'virus' in x ]
if index :
# L'adhérent est déjà blacklisté
proprio.blacklist(( index[0] , ['now','-','virus',hostname] ))
proprio.save()
else :
# L'adhérent n'est pas encore 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 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 :
machine = ldap.search('ipHostNumber=%s' % IP,'w' )['machine'][0]
# 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.
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 la table p2p on sélectionne les ip_src qui appartiennent au réseau
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()
# Récupération des fraudeurs pour ne pas les resanctionner
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.limite[protocole] or [ip, protocole] in avertisp2p :
continue
# Récupération des ref de la machine
machine = ldap.search('ipHostNumber=%s' % ip,'w' )['machine'][0]
hostname = machine.nom()
proprio = machine.proprietaire()
blacklist = proprio.blacklist()
# Envoi du mail à disconnect
if p2p.disconnect_mail :
requete="select date from p2p where date > timestamp 'now' - interval '1 day' and ip_src='%s' order by date limit 1"%ip
curseur.execute(requete)
date=curseur.fetchall()[0][0]
corps = p2p.avertissement % { 'From': upload.expediteur, 'To': upload.expediteur, 'protocole': protocole, 'hostname':hostname, 'nb_paquets':nombre, 'datedebut':date}
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)
# On envoie un mail a l'adhérent
################################
corps = p2p.deconnexion % {'From':p2p.expediteur, 'To':proprio.email(), 'protocole': protocole, 'hostname':hostname}
corps = corps.encode('iso 8859-15')
mail.sendmail(p2p.expediteur,proprio.email(),corps)
# Vérification du nombre de déconnexions
#########################################
nb_decos = len([ x for x in proprio.blacklist() if mktime(strptime(x.split(',')[0],'%d/%m/%Y %H:%M')) > mktime(localtime())-365*24*60*60 and x.split(',')[2]=='autodisc_p2p' ])
if nb_decos >= 3 :
fichier_ps = generate_ps('p2p', proprio)
# Envoi du mail à disconnect
corps = p2p.message_disconnect_multi % {'from':p2p.expediteur, 'to':p2p.expediteur, 'nbdeco':nb_decos, 'proprio':proprio.Nom(), 'ps':fichier_ps}
corps = corps.encode('iso 8859-15')
mail.sendmail(p2p.expediteur,p2p.expediteur,corps)
# On récupère les index des lignes de bl où il y a marqué autodisc_p2p
index = [blacklist.index(x) for x in blacklist if 'autodisc_p2p' in x ]
if index :
# L'adhérent est déja blacklisté
fin = localtime(time()+60*60*24)
proprio.blacklist(( index[0] , ['now',"%.2d/%.2d/%.4d %.2d:%.2d" % (fin[2],fin[1],fin[0],fin[3],fin[4]),'autodisc_p2p',hostname] ))
proprio.save()
else :
# L'adhérent n'est pas encore blacklisté
fin = localtime(time()+60*60*24)
proprio.blacklist(['now',"%.2d/%.2d/%.4d %.2d:%.2d" % (fin[2],fin[1],fin[0],fin[3],fin[4]),'autodisc_p2p',hostname])
proprio.save()