#! /usr/bin/env python # -*- coding: utf-8 -*- ############################################################################### # config_mail : gestion du .forward et .procmailrc des adhérents ############################################################################### # The authors of this code are # Etienne Chové # # Copyright (C) 2006 Etienne Chové # 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 ############################################################################### ############################################################################ ## Chaines de formatage pour le procmailrc """ Script permetant de gérer sa configuration mail Usage : ConfigMail.py [--forward=(|)] [--spam=(accepte|marque|deplace|supprime)] Retourne : forward=(|) spam=(accepte|marque|deplace|supprime) """ procmail_warning = """################################################################ # Ce fichier de configuration a été automatiquement généré par # # l'intranet. # # # # ATTENTION : ne le modifiez que si vous savez ce que vous # # faites !!! # ################################################################ """ # Pour éviter des crash procmail_mark_old = """# Passage des mails dans spamassassin :0 Whbf * < 256000 | spamc # Serveur blacklisté :0 * ^X-Reject: 554 * !^X-Spam-Status: Yes { :0 Whf * ^Subject:\/.* | formail -i "Subject: *****SPAM***** $MATCH" } """ procmail_mark = """# Passage des mails dans spamassassin :0 Whbf * < 256000 | spamc # Serveur blacklisté :0 * ^X-Reject: 554 * !^X-Spam-Status: Yes { :0 Whf * ^Subject:\/.* | formail -i "Subject: *****SPAM***** $MATCH" } :0 * ^X-Spam-Status: Yes { :0 Whf * ^Subject:\/.* | formail -i "Subject: *****SPAM***** $MATCH" } """ procmail_delete_spam = """# Suppression des spams :0 Whf * ^Subject: *****SPAM***** /dev/null """ procmail_move = """#Déplacement des spams :0 Whbf * < 256000 | spamc # Serveur blacklisté :0 * ^X-Reject: 554 * !^X-Spam-Status: Yes .%(spamdir)s # Mail de spam :0 * ^X-Spam-Status: Yes .%(spamdir)s """ procmail_forward = """# Redirection des mails :0 !%s """ forward_procmail = '"|exec /usr/bin/procmail"\n' ## ############################################################################ from commands import getstatusoutput import os import sys import pwd import getopt home = pwd.getpwuid(os.getuid())[5] import re class MailConfigError(ReferenceError): pass def Log(content): with open('/tmp/intranet.log', 'a') as log: log.writelines(content+"\n") ############################################################################ ## Fonctions utiles def _IsMail(mail): """ Dit si la chaine fournie est une adresse mail valide """ return bool(re.match('[a-z0-9_\.-]+@[a-z0-9_-]+(\.[a-z0-9_-]+)+',mail.lower())) def _Clean(texte): """ Nettoie une chaine de caractère/liste en supprimant les lignes vides/commentés, et retourne une liste """ if type(texte) != list: texte = texte.split('\n') return [ x.strip() for x in texte if x.strip() and x[0]!='#' ] def _GetConfig(): """ Retourne la configuration de l'utilisateur courant, sous forme d'un dictionnaire """ ## lecture du fichier .forward try: fic_forward = _Clean( open('%s/.forward'%home).readlines() ) if len(fic_forward) != 1: raise MailConfigError, 'Fichier forward trop long' fic_forward = fic_forward[0] except IOError: return {'forward':'', 'spam':'accepte', 'spamdir':None} # forward simple if _IsMail(fic_forward): return {'forward':fic_forward, 'spam':'accepte', 'spamdir':None} # utilisation de procmail if fic_forward != _Clean(forward_procmail)[0]: raise MailConfigError, 'Fichier forward non compréhensible' ## lecture du .procmailrc fic_procmail = _Clean( open('%s/.procmailrc'%home).readlines() ) # forward if _IsMail( fic_procmail[-1][1:].strip() ) and fic_procmail[-2] == ":0" : forward = fic_procmail[-1][1:].strip() fic_procmail = fic_procmail[:-2] else: forward = '' # forward simple dans le procmailrc if not fic_procmail: return {'forward':forward, 'spam':'accepte', 'spamdir':None} # marquage des spams tmp = _Clean( procmail_mark ) tmp2 = _Clean( procmail_move ) tmp3 = _Clean( procmail_mark_old ) mark = False move = False if fic_procmail[:len(tmp)] != tmp and fic_procmail[:len(tmp)] != tmp3: if len(fic_procmail) != len(tmp2): raise MailConfigError, 'Fichier de procmail non compréhensible' else: spamdir = fic_procmail[-1][1:] fic_procmail = fic_procmail[len(tmp2):] move = True else: fic_procmail = fic_procmail[len(tmp):] spamdir = "" mark = True # suppression des spams ? if (not fic_procmail) and mark: return {'forward':forward, 'spam':'marque', 'spamdir':None} elif (not fic_procmail) and move: return {'forward':forward, 'spam':'deplace', 'spamdir':spamdir} elif fic_procmail == _Clean(procmail_delete_spam): return {'forward':forward, 'spam':'supprime', 'spamdir':None} else: raise MailConfigError, 'Fichier de procmail non compréhensible' def _SetConfig(forward = None, spam= None, spamdir=None): """ Modifie la configuration de l'utilisateur courant """ # variable new_spam if spam in ['accepte', 'deplace', 'supprime','marque']: new_spam = spam elif spam == None: new_spam = _GetConfig()['spam'] else: raise ValueError, 'Valeur interdite pour le paramètre spam' # variable forward if forward == None: new_forward = _GetConfig()['forward'] elif _IsMail(forward) or forward=='': new_forward = forward else: raise ValueError, 'Adresse mail invalide' if spamdir == None: new_spamdir = _GetConfig()['spamdir'] elif re.match('^[a-zA-Z0-9_-]+$', spamdir): new_spamdir = spamdir else: raise ValueError, 'Nom de dossier invalide' # génération des fichiers if new_spam=='accepte': # suppression du .procmailrc try: os.remove('%s/.procmailrc'%home) except: pass # remplissage du .forward if new_forward: open('%s/.forward'%home,'w').write('%s\n'%new_forward) else: os.remove('%s/.forward'%home) else: # écriture du .procmailc if new_spam == 'marque': txt = procmail_warning + procmail_mark if new_spam=='supprime': txt = procmail_warning + procmail_mark + procmail_delete_spam if new_spam == 'deplace': txt = procmail_warning + procmail_move % ({'spamdir':new_spamdir}) if new_forward: txt += procmail_forward % new_forward open('%s/.procmailrc'%home,'w').write(txt) # écriture du .forward open('%s/.forward'%home,'w').write(forward_procmail) def _Sudo(uid, forward=None, spam=None, spamdir=None): """ Execute le script pour un autre utilisateur """ # construction de la ligne de commande if __file__[-4:]=='.pyc': f = __file__[:-1] else: f = __file__ c = "/usr/bin/sudo -u %s %s" % (uid, os.path.abspath(f)) if forward!=None: c += " --forward=%s" % forward if spam!=None: c += " --spam=%s" % spam if spamdir!=None: c += " --spamdir=%s" %spamdir # execution de la commande status, output = getstatusoutput(c) # code d'erreur if status: sys.stderr.write("Erreur sudo : %s\n"%c) sys.stderr.write(output) sys.stderr.flush() sys.exit(status) # valeurs de retour res = {} for line in output.split('\n'): line = line.split('=') res[line[0]] = len(line)==2 and line[1] or '' return res ## ############################################################################ def MailConfig(uid=None, forward=None, spam=None, spamdir=None): """ Modifie ou retourne la configuration mail de l'utilisateur user = utilisateur à configurer, si None configure l'utilisateur courant forward = adresse vers laquelle rediriger les mails, chaine vide si pas de redirection spam = action à effectuer sur les spams (accepte, supprime, marque) spamdir = dossier où déplacer le spam. Pour les champs forward et spam, la valeur None ne touche pas au champ. Retourne un dictionnaire { 'forward':'', 'spam':'', 'spamdir:''} """ ## demande pour un autre utilisateur if uid: return _Sudo(uid=uid, forward=forward, spam=spam, spamdir=spamdir) ## nettoyage des variables cfg = _GetConfig() if forward == cfg['forward']: forward = None if spam == cfg['spam']: spam = None if spamdir == cfg['spamdir']: spamdir = None if spamdir == "": spamdir = None ## modifications if forward != None or spam != None or spamdir != None: _SetConfig(forward=forward, spam=spam, spamdir=spamdir) ## on renvoie la configuration return _GetConfig() if __name__=="__main__": ## parsage des arguments forward = None spam = None spamdir = None opts, args = getopt.getopt(sys.argv[1:], "", ['forward=', 'spam=', 'spamdir=']) if ('--spam','deplace') in opts: optgood = False for o, v in opts: if o == '--spamdir': optgood = True if not optgood: raise ValueError, 'Il faut renseigner un dossier pour déplacer le spam' for o, v in opts: if o == "--forward": forward = v elif o == '--spam': spam = v elif o == '--spamdir': spamdir = v ## execution de MailConfig res = MailConfig(forward=forward, spam=spam, spamdir=spamdir) ## affichage des résultats for i in res.items(): print "%s=%s" % i