
Ignore-this: e36e7d6d0500f1281d02de548600ad49 darcs-hash:20090518055925-bd074-d544b98ac60570b92b71529cf355030637dc3a56.gz
334 lines
12 KiB
Python
334 lines
12 KiB
Python
#! /usr/bin/env python
|
|
# -*- coding: iso-8859-15 -*-
|
|
|
|
###############################################################################
|
|
# parse_auth_log.py : Détecte les problèmes d'authentifications
|
|
###############################################################################
|
|
# The authors of this code are
|
|
# Jérémie Dimino <dimino@crans.org>
|
|
#
|
|
# Copyright (C) 2006 Jérémie Dimino
|
|
# All rights reserved.
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
|
###############################################################################
|
|
|
|
import sys
|
|
|
|
sys.path.append('/usr/scripts/gestion')
|
|
|
|
from ldap_crans import crans_ldap
|
|
from annuaires import chbre_prises, reverse
|
|
from affich_tools import cprint
|
|
import sys, datetime
|
|
|
|
base = None
|
|
# Correspondance prise -> chambre
|
|
prise_chbre = {}
|
|
aujourdhui = datetime.date.today()
|
|
|
|
def trouve_chambre(bat, prise):
|
|
""" Trouve la chambre associée à une prise """
|
|
|
|
if prise in ('????', 'EXT', 'CRA'):
|
|
return prise
|
|
|
|
if not prise_chbre.has_key(bat):
|
|
prise_chbre[bat] = reverse(bat)
|
|
|
|
chbre = prise_chbre[bat].get(prise) or prise_chbre[bat].get(prise+"-")
|
|
if chbre:
|
|
chbre = chbre[0]
|
|
else:
|
|
cprint('La prise %s%s est inconnue' % (bat, prise), 'rouge')
|
|
chbre = "????"
|
|
|
|
return chbre
|
|
|
|
|
|
def trouve_prise(chbre):
|
|
""" Trouve la prise associée à une chambre """
|
|
|
|
if chbre in ('EXT', '????', 'CRA'):
|
|
return chbre
|
|
else:
|
|
bat = chbre[0].lower()
|
|
prise = bat + chbre_prises[bat][chbre[1:]]
|
|
if prise[-1] == '-':
|
|
prise = prise[:-1]
|
|
return prise
|
|
|
|
|
|
def __calcul_max(table):
|
|
""" Calcule les différents maxima (voir parse_auth_log pour plus de détails) """
|
|
|
|
nb_m = 0
|
|
nb_p = 0
|
|
for prise, erreur_prise in table.items():
|
|
nb = 0
|
|
nb_max = 0
|
|
for mac, erreur_mac in erreur_prise['macs'].items():
|
|
n = len(erreur_mac['lignes'])
|
|
erreur_mac['nombre'] = n
|
|
nb += n
|
|
nb_max += n
|
|
erreur_prise['nombre'] = nb
|
|
erreur_prise['nombre_max'] = nb_max
|
|
nb_m = max(nb_m, nb_max)
|
|
nb_p = max(nb_p, nb)
|
|
|
|
return nb_m, nb_p
|
|
|
|
|
|
def __ajoute_ligne(errs, ligne, prise, mac, info=None):
|
|
""" Ajoute une ligne dans le dico pour la prise et la mac donnée """
|
|
|
|
erreur_prise = errs.get(prise)
|
|
if not erreur_prise:
|
|
erreur_prise = { 'macs': {} }
|
|
errs[prise] = erreur_prise
|
|
|
|
erreur_mac = erreur_prise['macs'].get(mac)
|
|
if not erreur_mac:
|
|
erreur_mac = { 'lignes': [], 'info': info }
|
|
erreur_prise['macs'][mac] = erreur_mac
|
|
|
|
erreur_mac['lignes'].append(ligne)
|
|
|
|
|
|
# Correspondance 'noms de mois' -> n°
|
|
__mois_num = { 'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6,
|
|
'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12 }
|
|
|
|
def parse_auth_log(fichier_log='/var/log/freeradius/radius_auth.log', jour=None):
|
|
""" Retourne la liste des problèmes de connexions: retourne deux dicos, la première étant pour les macs qui n'ont pas été trouvées dans la base et la seconde pour les connexions qui ont eu lieu à partir d'une mauvaise prise
|
|
|
|
les deux dictionnaires ont la structure suivante:
|
|
- nombre_mac_max: nombre maximal d'erreurs trouvées pour une mac
|
|
- nombre_prise_max: nombre maximal d'erreurs trouvées pour une prise
|
|
- prises: dico associant à une prise la listes des erreurs à partir de cette prise:
|
|
- nombre: nombre total d'erreurs survenues sur la prise
|
|
- nombre_max: nombre maximal d'erreurs trouvéss pour une mac
|
|
- macs: dico associant à une mac la liste des erreurs à partir de cette mac
|
|
- nombre: le nombre d'erreurs survenues pour cette mac sur cette prise
|
|
- lignes: la liste des lignes du fichier de logs où la mac apparaît sur cette prise
|
|
- info: informations sur le propriétaire (vaut None pour la première table)
|
|
- machine: la machine en question
|
|
- prise: la prise de sa chambre
|
|
- chambre: sa chambre
|
|
- prop: le propriétaire
|
|
|
|
Si jour est spécifié (de type datetime.date), toutes les connexions avant cette dates seront ignorées.
|
|
"""
|
|
|
|
try:
|
|
log = open(fichier_log)
|
|
except:
|
|
cprint(u"Impossible d'ouvrir le fichier %s" % fichier_log, 'rouge')
|
|
sys.exit(1)
|
|
|
|
# Les macs non trouvées
|
|
errs_inconnue = {}
|
|
|
|
# Les pc connectés sur la mauvaise prise
|
|
errs_prise = {}
|
|
|
|
# Les recherches déjà effectuées sur les macs
|
|
mac_machine = {}
|
|
|
|
annee = None
|
|
mois = None
|
|
|
|
for ligne in log:
|
|
champs = ligne.split()
|
|
prise = champs[5]
|
|
mac = champs[7]
|
|
|
|
if jour != None:
|
|
nouveau_mois = __mois_num[champs[0]]
|
|
|
|
if annee == None:
|
|
if nouveau_mois > aujourdhui.month:
|
|
# Là on suppose qu'il s'agit de l'année dernière
|
|
annee = aujourdhui.year - 1
|
|
else:
|
|
annee = aujourdhui.year
|
|
elif mois > nouveau_mois:
|
|
# Là on suppose qu'on est passé à l'année suivante
|
|
annee += 1
|
|
|
|
mois = nouveau_mois
|
|
|
|
if datetime.date(annee, mois, int(champs[1])) < jour:
|
|
continue
|
|
|
|
info_mac = mac_machine.get(mac)
|
|
if info_mac:
|
|
machine = info_mac['machine']
|
|
else:
|
|
machine = base.search('mac=' + mac)['machine']
|
|
info_mac = { 'machine': machine }
|
|
mac_machine[mac] = info_mac
|
|
|
|
if machine == []:
|
|
__ajoute_ligne(errs_inconnue, ligne, prise, mac)
|
|
|
|
else:
|
|
info = info_mac.get('info_prop')
|
|
if not info:
|
|
prop = machine[0].proprietaire()
|
|
chbre = prop.chbre()
|
|
info = { 'machine': machine[0],
|
|
'prise': trouve_prise(chbre),
|
|
'chambre': chbre,
|
|
'prop': prop }
|
|
info_mac['info_prop'] = info
|
|
|
|
if prise != info['prise']:
|
|
__ajoute_ligne(errs_prise, ligne, prise, mac, info)
|
|
|
|
log.close()
|
|
|
|
nb_inconnue_m, nb_inconnue_p = __calcul_max(errs_inconnue)
|
|
nb_prise_m, nb_prise_p = __calcul_max(errs_prise)
|
|
|
|
return { 'prises': errs_inconnue,
|
|
'nombre_mac_max': nb_inconnue_m,
|
|
'nombre_prise_max': nb_inconnue_p }, \
|
|
{ 'prises': errs_prise,
|
|
'nombre_mac_max': nb_prise_m,
|
|
'nombre_prise_max': nb_prise_p }
|
|
|
|
|
|
def affiche_erreurs(errs, message, min_mac=1, min_prise=1):
|
|
""" Affiche les infos contenues dans un dico renvoyé par parse_auth_log """
|
|
|
|
if errs['nombre_mac_max'] >= min_mac and errs['nombre_prise_max'] >= min_prise:
|
|
|
|
cprint(message, 'bleu')
|
|
|
|
for prise, erreur_prise in errs['prises'].items():
|
|
|
|
if erreur_prise['nombre'] >= min_prise and erreur_prise['nombre_max'] >= min_mac:
|
|
|
|
chambre = prise[0].upper() + trouve_chambre(prise[0], prise[1:])
|
|
cprint(u' prise %s (chambre %s)' % (prise, chambre), 'gras')
|
|
|
|
for mac, erreur_mac in erreur_prise['macs'].items():
|
|
if erreur_mac['nombre'] >= min_mac:
|
|
ligne = erreur_mac['lignes'][-1].strip().split()
|
|
ligne[5] = '%8s' % ligne[5].replace("adm.crans.org-", "")
|
|
ligne = ligne[0:3] + ligne[5:]
|
|
print ' %s (x%d)' % (' '.join(ligne), erreur_mac['nombre'])
|
|
info = erreur_mac['info']
|
|
if info != None:
|
|
prop = info['prop']
|
|
cprint(u' -> machine de : %-16s %5s (prise %s) <%s>' %
|
|
(prop.Nom()[:16], info['chambre'], info['prise'], prop.email()))
|
|
|
|
|
|
def __usage(err=''):
|
|
""" Message d'erreur """
|
|
|
|
if err : cprint(err, 'rouge')
|
|
cprint(u"Tapez %s -h pour plus d'informations" % sys.argv[0].split('/')[-1].split('.')[0])
|
|
sys.exit(2)
|
|
|
|
|
|
def __param_entier(opt, val):
|
|
""" Récupère un entier passé en ligne de commande """
|
|
|
|
try:
|
|
return int(val)
|
|
except:
|
|
__usage(u'La valeur du paramètre %s est incorecte (doit être un entier positif)' % opt)
|
|
|
|
|
|
def __aide():
|
|
""" Aide """
|
|
|
|
cprint(u"""Usage: %s [OPTIONS]
|
|
Parse les logs d'authentifications et affiche les erreurs trouvées (macs inconnues ou connexion d'une machine sur une prise autre que celle du propriétaire).
|
|
|
|
Options:
|
|
-h, --help affiche cette aide
|
|
-l, --log <fichier> fichier de log à parser (par défaut: /var/log/freeradius/radius_auth.log)
|
|
-m, --min-mac <nombre> nombre minimal d'occurrences d'une mac sur une prise pour qu'elle soit reportée
|
|
-n, --min-prise <nombre> nombre minimal d'erreurs sur une prise pour qu'elle soit reportée
|
|
-i, --inconnue n'affiche que les erreurs de mac inconnues
|
|
-p, --prise n'affiche que les connexions sur une prise autre que celle du propriétaire
|
|
-d, --date <date> ignorer totalement les lignes avant cette date
|
|
|
|
<date> peut être:
|
|
- une date absolue sous la forme AAAAMMJJ
|
|
- une date relative à aujourd'hui sous la forme +<nombre de jour>
|
|
Par exemple pour ne considérer que les deux dernier jours: +2
|
|
|
|
Rapporter toutes anomalies à <dimino@crans.org>.""" % sys.argv[0].split('/')[-1].split('.')[0])
|
|
sys.exit(0)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
fichier_log = '/var/log/freeradius/radius_auth.log'
|
|
min_mac = 1
|
|
min_prise = 1
|
|
# Info que l'on veut afficher
|
|
aff_inconnue = True
|
|
aff_prise = True
|
|
# Date de départ
|
|
jour = None
|
|
|
|
import getopt, time
|
|
|
|
try:
|
|
options, arg = getopt.getopt(sys.argv[1:], 'hipl:m:n:d:', [ 'help', 'log=', 'min-mac=', 'min-prise=', 'inconnue', 'prise', 'date='])
|
|
except getopt.error, msg:
|
|
__usage(unicode(msg))
|
|
|
|
for opt, val in options:
|
|
if opt in [ '-h', '--help' ]:
|
|
__aide()
|
|
elif opt in [ '-l', '--log-file' ]:
|
|
fichier_log = val
|
|
elif opt in [ '-m', '--min-mac' ]:
|
|
min_mac = __param_entier(opt, val)
|
|
elif opt in [ '-n', '--min-prise' ]:
|
|
min_prise = __param_entier(opt, val)
|
|
elif opt in [ '-i', '--inconnue' ]:
|
|
aff_prise = 0
|
|
elif opt in [ '-p', '--prise' ]:
|
|
aff_inconnue = 0
|
|
elif opt in [ '-d', '--date' ]:
|
|
if val[0] == '+':
|
|
jour = aujourdhui - datetime.timedelta(__param_entier(opt, val[1:]))
|
|
else:
|
|
try:
|
|
jour = datetime.date(*(time.strptime(val, "%Y%m%d")[0:3]))
|
|
except:
|
|
__usage(u'La valeur du paramètre %s est incorecte (doit être de la forme AAAAMMJJ)' % opt)
|
|
else:
|
|
cprint(u'Option inconnue: %s' % opt, 'rouge')
|
|
__usage()
|
|
|
|
base = crans_ldap()
|
|
|
|
errs_inconnue, errs_prise = parse_auth_log(fichier_log, jour)
|
|
|
|
if aff_inconnue:
|
|
affiche_erreurs(errs_inconnue, u"Pour les connexions suivantes la mac n'a pas été trouvée dans la base:", min_mac, min_prise)
|
|
|
|
if aff_prise:
|
|
affiche_erreurs(errs_prise, u"Les connexions suivantes ont eu lieu sur une prise autre que celle du proriétaire:", min_mac, min_prise)
|