
Idéalement, il faudrait passer tous les mails de la conf sous ce format et les instancier tous comme ça. Ça permet d'encoder proprement les headers avec éventuellement des variables dedans.
468 lines
18 KiB
Python
Executable file
468 lines
18 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
|
|
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
|
|
|
|
# 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')
|
|
# Il faudra remplacer la ligne ci-dessous par pgsql.set_session(autocommit = True) sous wheezy
|
|
pgsql.set_isolation_level(0)
|
|
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(database='mac_prises', user='crans')
|
|
# Il faudra remplacer la ligne ci-dessous par pgsql.set_session(autocommit = True) sous wheezy
|
|
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)
|
|
upload4="""SELECT
|
|
'upload', 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
|
|
"""
|
|
upload6 = """SELECT
|
|
'upload', sum(upload)/1024/1024 AS total, ip_crans
|
|
FROM
|
|
(
|
|
SELECT DISTINCT * FROM
|
|
(
|
|
SELECT
|
|
upload6.date, mac_ip.mac AS ip_crans, upload6.ip_ext, upload6.id, upload6.port_crans, upload6.port_ext, upload6.download, upload6.upload
|
|
FROM mac_ip,upload6
|
|
WHERE
|
|
upload6.ip_crans = mac_ip.ip
|
|
AND upload6.date > mac_ip.date
|
|
AND upload6.date - interval '1 day' < mac_ip.date
|
|
AND upload6.date > timestamp 'now' - interval '1 day'
|
|
AND upload6.date < 'now'
|
|
AND upload6.upload > upload6.download
|
|
AND NOT EXISTS
|
|
(
|
|
SELECT 1
|
|
FROM exemptes
|
|
WHERE upload6.ip_crans <<= exemptes.ip_crans
|
|
AND upload6.ip_ext <<= exemptes.ip_dest
|
|
)
|
|
) AS upload
|
|
) AS upload
|
|
WHERE
|
|
upload > download
|
|
GROUP BY
|
|
ip_crans
|
|
"""
|
|
requete = """SELECT
|
|
round(total) AS total, machines.type AS type, machines.id AS id
|
|
FROM
|
|
(
|
|
(%s) UNION (%s)
|
|
)
|
|
AS
|
|
upload
|
|
INNER JOIN
|
|
machines
|
|
ON
|
|
machines.ip = upload.ip_crans
|
|
WHERE
|
|
total >= 250
|
|
GROUP BY
|
|
total, type, id
|
|
;""" % (upload4, upload6)
|
|
curseur.execute(requete)
|
|
uploadeurs = curseur.fetchall()
|
|
|
|
|
|
# On regarde s'il y a deux ipv6 identiques avec des mac non identiques
|
|
collision_mac_ip_request = "SELECT DISTINCT a.date as date1, a.mac as mac1, a.ip as ip1, b.date as date2, b.mac as mac2, b.ip as ip2 FROM mac_ip as a, mac_ip as b where a.ip=b.ip AND a.mac != b.mac AND a.date >= b.date AND a.date - b.date < interval '3 day' ORDER BY a.date;"
|
|
curseur.execute(collision_mac_ip_request)
|
|
collision_mac_ip = curseur.fetchall()
|
|
|
|
if collision_mac_ip != []:
|
|
print "Collision d'addresses ipv6 : "
|
|
for (date1, mac1, ip1, date2, mac2, ip2) in collision_mac_ip:
|
|
print "%s %s %s" % (date1, ipt.mac_addr(mac1), ip1)
|
|
print "%s %s %s" % (date2, ipt.mac_addr(mac2), ip2)
|
|
|
|
# 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))
|
|
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 = config.mails.upload.message_hard % {'from': upload.expediteur, 'to': proprio.email(), 'upload': elupload, 'proprio': proprio.Nom()}
|
|
corps = corps.encode('utf-8')
|
|
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 = 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.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 = config.mails.upload.message_soft % {'from': upload.expediteur, 'to': proprio.email(), 'upload': elupload, 'proprio': proprio.Nom()}
|
|
corps = corps.encode('utf-8')
|
|
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)
|