#!/bin/bash /usr/scripts/python.sh # -*- coding: utf-8 -*- """ Script de déconnexion automatique des machines du crans pour les raisons : - upload Copyright (C) Xavier Pessoles, Étienne Chové, Vincent Bernat, Nicolas Salles Licence : GPL v2 """ ################################################################################ # Import des commandes # ################################################################################ import sys import psycopg2 import smtplib from time import * from gestion.config import NETs, plage_ens, prefix from gestion.config import upload as upload import gestion.config.encoding import gestion.config.mails.upload as mails_upload from gestion.ldap_crans import crans_ldap from gestion.ldap_crans import MachineFixe from surveillance.fiche_deconnexion.generate import generate_ps from gestion.affich_tools import tableau import surveillance.analyse2 as analyse import gestion.mail as mail_module # ldap ldap = crans_ldap() # 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. # TODO: mettre ça dans annuaires_pg 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="""WITH machines_sans_doublon AS ( SELECT DISTINCT ON(mac_addr) * FROM machines ) SELECT SUM(agregat.total) as tot_upload, machines_sans_doublon.type, machines_sans_doublon.id FROM ( SELECT 'upload', sum(bytes)/1024/1024 AS total, mac_src FROM upload WHERE stamp_inserted > now() - interval '1 day' AND stamp_inserted < now() AND NOT (ip_dst <<= inet%(plage_ens)s OR ip_dst <<= inet%(plage_ipv6)s OR ip_dst <<= inet%(appt)s OR ip_src <<= inet%(ipv6_local)s OR ip_src=inet'0.0.0.0' OR ip_src <<= inet%(plage_adm)s OR ip_dst <<= inet%(plage_adm)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_sans_doublon ON machines_sans_doublon.mac_addr = agregat.mac_src GROUP BY machines_sans_doublon.type, machines_sans_doublon.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], 'ipv6_local': 'fe80::/8', 'plage_adm': NETs['adm'][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'] self_call_type = "cid" elif eltype == 'adherent': proprio = ldap.search('aid=%d'%elid, 'w')['adherent'] self_call_type = "aid" 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')) # Début de remplissage du mail ############################## if eltype == "club": theid = "cid=" else: theid = "aid=" theid += proprio.id() # Test: validation_url('upload') try: data = {'dn': theid, 'blid': len(proprio.blacklist())} reco_url = mail_module.validation_url('upload', data) reco_url_error = "" except Exception as e: reco_url_error = "[[erreur de génération: %r]]" % e reco_url = "" mail_data = { 'from': upload.expediteur, 'to': proprio.email(), 'upload': "%.2f" % (elupload,), 'proprio': proprio.Nom(), 'lang_info':'English version below', 'mdc': mdcf, 'chambre': proprio.chbre(), 'id': theid, 'reco_url': reco_url, 'reco_url_error': reco_url_error, } # On sanctionne ############### debut = int(time()) # On ne reconnecte pas auto si url dispo if reco_url: fin = "-" else: fin = debut + 24*3600 # Pour l'analyse orig = strftime("%Y/%m/%d %H:%M:%S", localtime(debut - 86400)) end = strftime("%Y/%m/%d %H:%M:%S", localtime(debut)) 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.self_call(["--%s" % (self_call_type), "%s" % (elid), "--dns", "--begin", "%s" % (orig), "--end", "%s" % (end), "--limit", "1000", "--fichier", "/usr/scripts/var/analyse/%s_%s_%s.txt" % (end.replace("/", "_").replace(":", "_").replace(" ", "_"), self_call_type, 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 = mail_module.generate('upload_hard', mail_data).as_string() mail.sendmail(upload.expediteur, proprio.email(), corps) # On envoie un mail à disconnect ################################ mail_data['to'] = upload.expediteur corps = mail_module.generate('upload_notif', mail_data).as_string() mail.sendmail(upload.expediteur, upload.expediteur, corps) # 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 >= 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 = 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': "%.2f" % (elupload,), 'proprio': proprio.Nom(), 'lang_info':'English version below', 'limite_soft': upload.soft, 'limite_hard': upload.hard}).as_string() mail.sendmail(upload.expediteur, proprio.email(), corps) # On envoie un mail à disconnect ################################ if upload.disconnect_mail_soft: corps = mails_upload.message_disconnect_soft % {'from': upload.expediteur, 'to': upload.expediteur, 'upload': "%.2f" % (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'")