#!/usr/bin/perl ########################################################################### # # Program : Log Analyzer for DansGuardian # Author : Jimmy Myrick (jmyrick@cherokeek12.org) # Translation / Traduction française : Philippe Hermès (ph35sm@free.fr) # Version : 1.0 # Released : October 10, 2005 # Translation Released : January 20, 2006 # # 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. # # # If you like it and want to send me something, that's ok too. # How about a gift certificate to amazon.com or a donation to DansGuardian # on my behalf? # ########################################################################### ########################################################################### # # Changer si nécessaire pour indiquer le chemin du répertoire des fichiers # journal de Dansguardian # NOTE: Le dernier caractère / est nécessaire !! # ########################################################################### $logdir = '/var/log/dansguardian/'; ########################################################################### # # Nom du fichier journal : Changer pour faire correspondre avec le préfixe # de vos fichiers journaux. # Par défaut c'est access.log et cela ne devrait pas être modifié # # Tout fichier journal dans $logdir dont le préfixe correspond au contenu de # de la varialbe $logfile et qui compressé avec une extention .gz sera # aussi lu. Le résultat sera affiché dans l'ordre chronologique inverse # des noms de fichiers. # # Exemple : # Si vous avre les fichiers : access.log access.log.0.gz access.log.1.gz # allant du plus récent au plus vieux, alors toute occurence trouvée # dans access.log.1.gz sera affiché en premier, suivi par celles # dans access.log.0.gz et enfin par celles dans access.log # # Aucun tri n'est effectué par le programme et les résultats sont affichés # dans l'ordre des fichiers journaux. Si vos résultats ne sont pas en séquence # vérifier les dates des fichiers pour être sûr qu'ils sont compressés # et permutés correctement. Si vous utilisez le programme FreeBSD newsyslog.conf # pour permuter vos fichiers journaux, cela ne devrait pas être un problème. # ########################################################################### $logfile = 'access.log'; ########################################################################### # # Format des fichiers journaux : Changer pour indiquer dans quel format # sont les fichiers jouraux. # Cela doit correspondre avec ce qui est indiqué dans le fichier dansguardian.conf. # Positionné incorrectement ce paramètre peut produire des résultats étranges. # # 1 = DansGuardian format 2 = CSV-style format # ########################################################################### $logformat = 1; ########################################################################### # # Si vous avez besoin des modules perl ci-dessous, télécharger puis # décompresser les dans un répertoire. # Ensuite positionner vous dans ce répetoire avec la commande cd et # passer les commandes : # perl Makefile.PL; make; make test; make install # # Si vous avez besoin d'instructions complémentaires, # aller ici : http://www.cpan.org/modules/INSTALL.html # # Le téléchargement c'est ici : # http://www.cpan.org/authors/id/LDS/CGI.pm-2.81.tar.gz # ########################################################################### use CGI; ########################################################################### # # C'est nécessaire pour dé/compresser les fichiers journaux à la volée # Le téléchargement c'est ici : # http://www.cpan.org/authors/id/PMQS/Compress-Zlib-1.16.tar.gz # ########################################################################### use Compress::Zlib; ########################################################################### # # Cela indique d'où le programme est automatiquement appellé par le serveur web. # Si c'est pas le cas, décommenter la première ligne, changer le chemin # et le nom du serveur avec les votres et commenter la deuxième ligne. # Vous pouvez utiliser les restrictions implémentées dans Apache pour # bloquer l'accès à ce fichier. # ########################################################################### #$cgipath = 'http://your.server.com/cgi-bin/dglog/dglog.pl'; $cgipath = $ENV{SCRIPT_NAME}; ########################################################################### # # VOUS NE DEVRIEZ RIEN MODIFIER EN-DESSOUS DE CETTE LIGNE # ########################################################################### $q = new CGI; ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); $mon = $mon + 1; # Lundi démarre à 0 $year = $year + 1900; # 1900 doit être ajouté à l'année $pagename = 'Page d\'analyse des fichiers journaux pour DansGuardian'; $a = $q->param('a'); if ($a eq 'i') { # Recherche dans les fichiers journaux # Ce sont les valeurs qui peuvent être envoyées par l'utilisateur avec le navigateur $sIP = "ALL"; # Addresse IP $sUN = "ALL"; # Nom de l'utilisateur $sURL = "ALL"; # URL visualiser ou tracer un site interdit - C'est l'URL pour tracer $sSD = "ALL"; # Date complète de départ $sSDY = "ALL"; # Année de départ $sSDM = "ALL"; # Mois de départ $sSDD = "ALL"; # Jour de départ $sED = "ALL"; # Date complète de fin $sEDY = "ALL"; # Année de fin $sEDM = "ALL"; # Mois de fin $sEDD = "ALL"; # Jour de fin $sA = "ALL"; # Action $sSumCnt = "20"; # Nombre de résumé sites à montrer $sSumDen = "off"; # Afficher le récapitulatif pour les sites interdits ? on/off $sSumAlw = "off"; # Afficher le récapitulatif pour les sites autorisés ? on/off $sSumOrd = "URL"; # Par défaut l'url est affiché pour le résumé des sites interdits/autorisés $sL = "off"; # Changer les URL en liens ? on/off $sZ = "off"; # Examiner les fichiers compressés (.gz) ? on/off $sIP = &validateIP($q->param('sIP')) if $q->param('sIP') ne ""; $sUN = $q->param('sUN') if $q->param('sUN') ne ""; $sURL = $q->param('sURL') if $q->param('sURL') ne ""; if ($q->param('sSDY') ne "" && $q->param('sSDY') ne 'ALL' && $q->param('sSDM') ne "" && $q->param('sSDM') ne 'ALL' && $q->param('sSDD') ne "" && $q->param('sSDD') ne 'ALL' && $q->param('sEDY') ne "" && $q->param('sEDY') ne 'ALL' && $q->param('sEDM') ne "" && $q->param('sEDM') ne 'ALL' && $q->param('sEDD') ne "" && $q->param('sEDD') ne 'ALL') { $sSDY = $q->param('sSDY'); $sSDM = $q->param('sSDM'); $sSDD = $q->param('sSDD'); $sEDY = $q->param('sEDY'); $sEDM = $q->param('sEDM'); $sEDD = $q->param('sEDD'); $sSD = $sSDY.'.'.$sSDM.'.'.$sSDD; $sSD = convertDate($sSD); $sED = $sEDY.'.'.$sEDM.'.'.$sEDD; $sED = convertDate($sED); if ($sSD > $sED) { $msg = "La date de fin est plus récente que la date de départ"; &printMenu; } } $sA = &validateAction($q->param('sA')) if $q->param('sA') ne ""; # Action $sSumCnt = &validateSummary($q->param('sSumCnt')) if $q->param('sSumCnt') ne ""; $sSumDen = $q->param('sSumDen') if $q->param('sSumDen') eq 'on'; $sSumAlw = $q->param('sSumAlw') if $q->param('sSumAlw') eq 'on'; $sSumOrd = $q->param('sSumOrd') if $q->param('sSumOrd') ne ''; $sL = $q->param('sL') if $q->param('sL') eq 'on'; $sZ = $q->param('sZ') if $q->param('sZ') eq 'on'; # On a besoin de quelques variables globales $linesRead, $allowTotal, $blockTotal, $grandTotal = 0; &searchLog; } elsif ($a eq 'h') { &displayHelp; } else { &printMenu; } ############# sub searchLog ############# { my $first = 0; &printHeader; print ""; print "Rapport pour :
Date de départ: $sSD | Date de fin: $sED | Utilisateur: $sUN | IP: $sIP | Action: $sA | URL: $sURL
\n"; print ""; opendir(D, $logdir); @files = grep {/^$logfile/} readdir(D); @files = sort {$b cmp $a} @files; closedir(D); foreach $file (@files) { if ($file =~ /\.gz/) { if ($sZ eq 'off') { if ($first == 0) { print "les fichiers gzip $logdir sont ignorés: "; $first = 1; } print "$file | "; next; } $gz = gzopen($logdir.$file,r); if (!$gz) { $msg = "Le fichier $logdir$file ne peut être lu. Vérifier les permissions.

Essayer de positionner les droits sur les répertoires chmod 755 et sur les fichiers journaux chmod 644."; &printMenu; } while ($gz->gzreadline($line)) { &checkLine($line); } $gz->gzclose; } else { print "

"; unless (open(F,$logdir.$file)) { $msg = "Le fichier $logdir$file ne peut être lu. Vérifier les permissions.

Essayer de positionner les droits sur les répertoires chmod 755 et sur les fichiers journaux chmod 644."; &printMenu; } while ($line = ) { &checkLine($line); } close(F); } } if ($sSumAlw eq "on" && $allowTotal != 0) { &showSummarySites($allowTotal,'AUTORISES',$sSumCnt,$sSumOrd,%topSites); } if ($sSumDen eq "on" && $blockTotal != 0) { &showSummarySites($blockTotal,'INTERDITS(ES)',$sSumCnt,$sSumOrd,%blockSites); } print "


Nombre total de correspondance trouvées : $grandTotal | Nombre total de requêtes: $linesRead
"; print "
Retour au Menu
"; } ############# sub checkLine ############# { my ($line) = @_; $linesRead++; # Print out a '.' Toutes les 1000 lignes lues, on garde le navigateur connecté if (($linesRead % 1000) == 0) { print " "; } if ($logformat == 2) { # Si c'est au format CSV, alors on convertit au format DansGuardian # $c1=date+time,$c5=action, $c6=method, $c7=size ($c1,$user,$ip,$url,$c5,$c6,$c7) = split(/","/,$line,7); ($date,$time) = split(/ /,$c1); # Les quotes en trop sont supprimées, ce n'est pas élégant mais ça marche # En faisant le découpage ci-dessus il devrait être possible de lire une ligne # incorrectement dans le cas ou une URL contiendrait ce type de séquence de # caractère ... mais dans la pluspart des cas cela fonctionne correctement. $date =~ s/\"//; $c7 =~ s/\"//; $toeol = $c5 . ' ' . $c6. ' ' . $c7; } else { ($date,$time,$user,$ip,$url,$toeol) = split(/ /,$line,6); } # On élimine en premier les correspondances les plus faciles return if ($sIP ne "ALL" && $sIP ne $ip); return if ($sUN ne "ALL" && $sUN ne $user); # Pas de comparaison de date, sauf si cela est demandé if ($sSD ne "ALL" || $sED ne "ALL") { $dgDate = &convertDate($date); return if (!($dgDate ge $sSD && $dgDate le $sED)); } $url =~ /(\w+):\/\/([\w\.-]+)\/?(\S*)/; $protocol = $1; # HTTP, FTP $baseurl = $2; # Le nom de domaine sans http:// ou ftp:// return if ($sURL ne "ALL" && $sURL ne $baseurl); $toeol =~ /(\*.+\*)? ?(.+)? (\w+) (\d+)$/; $action = $1; # *DENIED# or *EXCEPTION* etc., si cela existe $reason = $2; # Raison pour #1 si cela existe $method = $3; # Méthode (GET POST) $size = $4; # Taille if ($sA ne "ALL") { return if ($sA eq "denAll" && $action ne "*DENIED*"); return if ($sA eq "excAll" && $action ne "*EXCEPTION*"); return if ($sA eq "denSite" && !($reason =~ /^Banned site/)); return if ($sA eq "denRegURL" && !($reason =~ /^Banned Regular Expression URL/)); return if ($sA eq "denPhrase" && !($reason =~ /^Banned Phrase/)); return if ($sA eq "denCombPhrase" && !($reason =~ /^Banned combination phrase/)); return if ($sA eq "denWeightPhrase" && !($reason =~ /^Weighted phrase limit/)); return if ($sA eq "denExt" && !($reason =~ /^Banned extension/)); return if ($sA eq "denMIME" && !($reason =~ /^Banned MIME Type/)); return if ($sA eq "denICRA" && !($reason =~ /^ICRA/)); return if ($sA eq "denBlanketIP" && !($reason =~ /^Blanket IP Block/)); return if ($sA eq "excSite" && !($reason =~ /^Exception site/)); return if ($sA eq "excPhrase" && !($reason =~ /^Exception phrase/)); return if ($sA eq "excCombPhrase" && !($reason =~ /^Combination exception phrase/)); } # Un compte doit être fait pour grandTotal si le résumé des autorisés ou permis est demandé if ($sSumAlw eq "on" || $sSumDen eq "on") { if ($action ne '*DENIED*') { $allowTotal++; $grandTotal++; # Pas de mémoire à perdre mais on doit compter pour grandTotal $topSites{$baseurl}++ if ($sSumAlw eq "on" && $sSumOrd eq "URL"); $topSites{$ip}++ if ($sSumAlw eq "on" && $sSumOrd eq "IP"); $topSites{$user}++ if ($sSumAlw eq "on" && $sSumOrd eq "User"); } else { $blockTotal++; $grandTotal++; # Pas de mémoire à perdre mais on doit compter pour grandTotal $blockSites{$baseurl}++ if ($sSumDen eq "on" && $sSumOrd eq "URL"); $blockSites{$ip}++ if ($sSumDen eq "on" && $sSumOrd eq "IP"); $blockSites{$user}++ if ($sSumDen eq "on" && $sSumOrd eq "User"); } } else { print "$date   $time   "; print "$ip   $user
"; if ($sL eq 'on') { print "$url $method $size
"; } else { print "$url $method $size
"; } if ($action ne "" && $reason ne "") { print "$action : $reason

"; } else { print "

"; } $grandTotal++; } } #################### sub showSummarySites { #################### my ($subTotal, $whatToShow, $topNum, $sumOrder, %sites) = @_; my $count = 1; print "
"; foreach $key (sort {$sites{$b} <=> $sites{$a}} keys %sites) { if ($count <= $topNum) { print ""; print ""; print ""; $count++; } break; } print ""; print "

Les $topNum premiers sites $whatToShow par $sumOrder
Rang URL Nombre \% de
$whatToShow
\% du
Total
Investiguer
$count.  "; if ($sL eq 'on' && $sumOrder eq 'URL') { print "$key"; } else { print "$key"; } print "$sites{$key}"; printf("   %2.2f  ",($sites{$key}/$subTotal)*100); print ""; printf("   %2.2f",($sites{$key}/$grandTotal)*100); print " Trace

Requêtes totales $whatToShow (seuls les $topNum premiers sites affichés) : $subTotal


"; } ################### sub validateSummary ################### { my ($count) = @_; if ($count < 0 || $count > 100) { $count = 20; } return($count); } ############## sub validateIP ############## { my ($checkIP) = @_; if ($checkIP eq 'ALL') { return('ALL'); } elsif ($checkIP =~ /^((2([0-4]\d|5[0-5])|1?\d{1,2})(\.|$)){4}/) { return ($checkIP); } else { $msg = "Adresse IP entrée invalide."; &printMenu; } } ################## sub validateAction { ################## my ($action) = @_; # Les actions doivent être "hachées" et on doit les référencés de cette façon # C'est plus facile pour ajouter/modifier et pour valider peut être plus tard if ($action eq "none") { return ("ALL"); } return ($action); } ############### sub convertDate { ############### my ($workDate) = @_; ($year, $mon, $day) = split(/\./,$workDate); if (length($mon) == 1) { $mon = '0'.$mon; } if (length($day) == 1) { $day = '0'.$day; } if (($mon ge "01" && $mon le "12") && ($day ge "01" && $day le "31") && ($year ge "2000" && $year le "2035")) { $goodDate = $year.$mon.$day; return ($goodDate); } else { $msg = "Date invalide détectée - $workDate - Soyez sûr que le format du fichier journal est correctement positionné."; &printMenu; } } ############### sub buildSelect ############### { my ($start, $end, $type) = @_; my $x = 0; ## print "