scripts/surveillance/fichiers_copyrightes.py
Valentin Samir 94eaddc883 [fichiers_copyrightes] On n'envoit les notifications seulement à bureau@crans.org
Ignore-this: c8770ab7e0b8cab7478cd91231c24dd8

darcs-hash:20130126184831-3a55a-be310f9743a6c9298845f6ab2c2dcb96d0765079.gz
2013-01-26 19:48:31 +01:00

322 lines
11 KiB
Python
Executable file

#! /usr/bin/env python
# -*- coding:utf-8 -*-
"""
Script de détection de fichiers potentiellement copyrightés dans les
répertoires des adhérents. Les tests se basent sur le nom et la taille
des fichiers.
Copyright (C) Nicolas Bruot
Licence : GPL v2
"""
import sys
import os
import re
import time
import shutil
# Variables générales
sendto = 'bureau@lists.crans.org'
analysed_path = '/home-adh/'
old_path = '/usr/scripts/var/copyrighted-content.old'
new_path = '/usr/scripts/var/copyrighted-content.new'
diffs_path = '/usr/scripts/var/copyrighted-content.diffs'
alert_path = '/usr/scripts/var/copyrighted-content.alert'
# ======================================================================
# Variables pour le calcul du score.
# ==================================
# Score minimum pour être enregistré dans le fichier des différences.
min_score = 10
# Score à partir duquel une notification est envoyée immédiatement par
# email (sans attendre l'exécution avec l'option --report).
notif_score = 40
# Taille en dessous de laquelle aucun test n'est effectué sur le
# fichier (augmenter pour améliorer la rapidité du script).
pass_size = 10e3
# Taille en dessous de laquelle seuls les filtres regexps_light sont
# testés.
light_size = 40e3
# Caractères non alphanumériques ("_" inclus)
blank = '[^a-zA-Z0-9]'
lblank = '([^a-zA-Z0-9]|^)' # blanc ou début de chaine
rblank = '([^a-zA-Z0-9]|$)' # blanc ou fin de chaine
# regexps, regexps_light : motifs contribuant au score du fichier.
# Pour qu'un motif contribue au score, il doit être matché et, si le
# champ min_size existe, respecter la condition de taille minimum du
# fichier.
# Si un ou plusieurs motifs avec un champ crit_size sont matchés,
# size_score est ajouté au score si la taille du fichier dépasse la
# valeur minimale des crit_size matchés.
# Seuls les motifs de regexp_light sont testés pour les fichiers d'une
# taille inférieure à light_size. Pour ce dictionnaire, il ne peut pas
# y avoir de champs min_size ou crit_size.
size_score = 10
regexps_light = {'\.torrent$': {'score':40},
'\.srt$': {'score':40},
'key.?gen': {'score':40}}
regexps = {'\.avi$': {'score':10, 'min_size':1e6, 'crit_size':1e8},
'\.mkv$': {'score':10, 'min_size':1e6, 'crit_size':1e8},
'\.mov$': {'score':10, 'min_size':1e6, 'crit_size':1e8},
'\.mpeg$': {'score':10, 'min_size':1e6, 'crit_size':1e8},
'\.mp4$': {'score':10, 'min_size':1e6, 'crit_size':1e8},
'\.mpg$': {'score':10, 'min_size':1e6, 'crit_size':1e8},
'\.ogm$': {'score':10, 'min_size':1e5, 'crit_size':1e8},
'\.wmv$': {'score':10, 'min_size':1e5, 'crit_size':1e8},
'\.flv$': {'score':10, 'min_size':1e6, 'crit_size':1e8},
'\.mp3$': {'score':10, 'min_size':1e5},
'\.m4a$': {'score':10, 'min_size':1e5},
'\.zip$': {'score':5, 'min_size':1e5, 'crit_size':1e8},
'\.gz$': {'score':3, 'min_size':1e5, 'crit_size':1e8},
'\.rar$': {'score':5, 'min_size':1e5, 'crit_size':1e8},
'\.dmg$': {'score':10, 'min_size':1e5, 'crit_size':1e8},
'\.pdf$': {'score':5, 'min_size':1e5, 'crit_size':2e7},
'\.ogg$': {'score':10, 'min_size':1e5},
'\.iso$': {'score':15, 'min_size':1e5, 'crit_size':1e8},
# Episodes (S01E01, Episode01...)
's\d{1,2}e\d': {'score':20, 'min_size':1e8},
lblank + '\d{1,2}x\d{1,2}' + rblank: {'score':20, 'min_size':1e8},
'episode.?\d': {'score':20},
lblank + 's\d{1,2}' + rblank: {'score':20, 'min_size':1e7},
lblank + 'e\d{1,2}' + rblank: {'score':20, 'min_size':1e7},
lblank + 'vol.{0,2}\d{1,2}' + rblank: {'score':20},
# Crochets []
'\[(?!pot).*\]': {'score':5},
# "01 Toto.mp3"...
'^\d{1,2}' + blank + '.*\.(mp3|ogg|wmv)$': {'score':10},
# Autres mots-clés
blank + 'fr' + rblank: {'score':10},
blank + 'eng' + rblank: {'score':10},
blank + 'vo' + rblank: {'score':10, 'min_size':1e7},
blank + 'vost' + rblank: {'score':30},
blank + 'vostfr' + rblank: {'score':30},
blank + 'vosten' + rblank: {'score':30},
blank * 3: {'score':3},
lblank + 'dvd.?(rip|)' + rblank: {'score':20},
lblank + 'cd' + rblank: {'score':10, 'min_size':1e8},
lblank + 'xvid' + rblank: {'score':20},
lblank + 'hdtv' + rblank: {'score':20},
lblank + 'hq' + rblank: {'score':20},
lblank + 'vtv' + rblank: {'score':20},
lblank + 'tvrip' + rblank: {'score':20},
lblank + 'dvtv' + rblank: {'score':20},
lblank + 'bt' + rblank: {'score':20},
lblank + 'fqm' + rblank: {'score':20},
lblank + 'web.?rip' + rblank: {'score':20},
lblank + 'natzox' + rblank: {'score':10},
lblank + 'sfm' + rblank: {'score':20},
lblank + 'lbp' + rblank: {'score':20},
lblank + 'hd' + rblank: {'score':10},
lblank + 'hdp' + rblank: {'score':10},
lblank + 'itdk' + rblank: {'score':20},
lblank + 'ust' + rblank: {'score':20},
lblank + 'fxg' + rblank: {'score':20},
lblank + 'part.?\d': {'score':10, 'min_size':1e7}}
# Si aucune expression n'a été matchée, un fichier d'une taille
# supérieure à suspect_size se verra attribuer un score de
# suspect_size_score.
suspect_size = 200e6
suspect_size_score = 30
# ======================================================================
# Pour les gros fichiers, on teste l'ensemble des expressions
# régulières :
regexps_full = dict(regexps_light, **regexps)
# Compilation des expressions régulières :
for key in regexps_light.keys():
regexps_light[re.compile(key, re.I).findall] = regexps_light[key]
del regexps_light[key]
for key in regexps_full.keys():
regexps_full[re.compile(key, re.I).findall] = regexps_full[key]
del regexps_full[key]
def human_readable(num):
"""Convertit le nombre en entrée (taille d'un fichier) en une
chaine "human-readable". Exemple: 12000 => 12K"""
for x in ['', 'K', 'M', 'G', 'T']:
if num < 1000.:
return '%3.0f%s' % (num, x)
num /= 1000.
def get_score(file, size):
"""Calcule le score d'un fichier d'après des expressions
régulières et des conditions de taille."""
score = 0
if size < light_size:
# On ne fait que les tests simples.
for key in regexps_light:
m = key(file) # liste d'expressions matchées
param = regexps_light[key]
score += len(m)*param['score']
return score
else:
# On effectue les tests complexes.
crit_size = 1e99
# On parcourt les expressions régulières.
for key in regexps_full:
m = key(file) # liste d'expressions matchées
param = regexps_full[key]
# Tests avec min_size.
if not param.has_key('min_size') or size >= param['min_size']:
score += len(m)*param['score']
# Mise à jour de crit_size.
if param.has_key('crit_size'):
crit_size = min(crit_size, param['crit_size'])
# Si le fichier est gros par rapport à une taille définie par les
# expressions matchées, on augmente le score.
if size >= crit_size:
score += size_score
# Si le score est nul, les fichiers gros sont quand même suspects.
if score == 0 and size >= suspect_size:
score = suspect_size_score
return score
def analyse():
"""Liste les fichiers dans le dossier à analyser et ajoute les
nouveaux fichiers à copyrighted.diffs. Envoie une notification si
des scores trop élevés sont trouvés."""
if os.path.isfile(new_path):
# Rotation des fichiers
shutil.move(new_path, old_path)
# Lecture de old_files
old_files = open(old_path).read().split('\n')
else:
old_files = []
# Ouverture des autres fichiers
new_file = open(new_path, 'w')
diffs_file = open(diffs_path, 'a')
alert_file = open(alert_path, 'w')
os.chmod(new_path, 0600)
os.chmod(diffs_path, 0600)
os.chmod(alert_path, 0600)
# Listage des fichiers
for root, sub_folders, files in os.walk(analysed_path):
for file in files:
filename = os.path.join(root, file)
try:
filesize = os.path.getsize(filename)
except OSError: # le fichier a pu être supprimé entre temps
filesize = 0
if filesize < pass_size:
# On va pas tester les fichiers trop petits.
continue
score = get_score(file, filesize)
if score >= min_score:
# On inscrit le fichier.
new_file.write(filename + '\n')
# Si c'est un nouveau fichier, on l'ajoute au
# fichier diffs.
if not filename in old_files:
date = time.strftime("%Y-%m-%d %H:%M",
time.localtime())
line = (str(score) + '\t' + date +
'\t' + human_readable(filesize) +
'\t' + filename + '\n')
diffs_file.write(line)
# Pour des scores vraiment élevés on va alerter
# par email immédiatement.
if score >= notif_score:
alert_file.write(line)
# On ne parcourt pas les dossiers "Mail".
if len(root.split('/')) == 3 and 'Mail' in sub_folders:
sub_folders.remove('Mail')
new_file.close()
diffs_file.close()
alert_file.close()
# Envoi d'une notification le cas échéant.
if os.path.getsize(alert_path) > 0:
sujet = "Alerte de fichiers potentiellement copyrightes"
os.system('sort -n -r ' + alert_path + ' | mail -s "' +
sujet + '" ' + sendto)
os.remove(alert_path)
def report():
"""Envoie un email récapitulatif du fichier de
copyrighted-content.diffs et purge le fichier."""
# Envoi de l'email contenant diffs_path
if os.path.isfile(diffs_path):
sujet = "Rapport des fichiers potentiellement copyrightes"
os.system('sort -n -r ' + diffs_path + ' | mail -s "' +
sujet + '" ' + sendto)
os.remove(diffs_path)
if __name__ == '__main__':
help = """Détection de fichiers potentiellement copyrightés.
usage: fichiers_copyrightes.py [OPTION]
OPTION doit être l'une des suivantes :
--analyse
Ajoute dans le fichier copyrighted-content.diffs les nouveaux
fichiers du répertoire exploré attribués de leur score. Si des
fichiers de score élevé sont trouvés, un email de notification est
envoyé.
--report
Envoie un email récapitulatif du fichier copyrighted-content.diffs
et purge le fichier."""
# Gestion des options
if len(sys.argv) != 2:
print help
sys.exit(0)
elif sys.argv[1] == '--analyse':
analyse()
elif sys.argv[1] == '--report':
report()
else:
print help
sys.exit(0)