coda : interception des courriels sortant
authorProgfou <jean-christophe.andre@auf.org>
Tue, 1 Oct 2013 03:30:37 +0000 (23:30 -0400)
committerProgfou <jean-christophe.andre@auf.org>
Tue, 1 Oct 2013 03:30:37 +0000 (23:30 -0400)
coda/README [new file with mode: 0644]
coda/coda-filter.py [new file with mode: 0644]

diff --git a/coda/README b/coda/README
new file mode 100644 (file)
index 0000000..8091de0
--- /dev/null
@@ -0,0 +1,59 @@
+Voici la technique utilisée pour intercepter les courriels sortant de CODA.
+
+== Configuration du serveur SMTP sortant dans CODA ==
+
+ * ouvrir une session sur CODA-web utilisant le compte INSTALL
+ * aller dans le menu Administration > Transports des messages > Maitre de transport de message
+ * dans la liste déroulant sélectionner SMTP puis effectuer la recherche (cliquer sur la flèche verte)
+ * dans le champ Hôte saisir "smtp-coda.auf"
+ * dans le nom d'utilisateur saisir "reflet@ca.auf.org"  ("reflet@ca.auf.org" est un alias créé préalablement)
+ * enregistrer la configuration (cliquer sur la l'icône en forme de disquette)
+ * terminé, donc fermer la session INSTALL
+
+== Mise en place du serveur SMTP interceptant les courriels de CODA ==
+
+Mettre en place un serveur [[Debian]] standard.
+
+Installer le paquet `python`, pour le script de filtrage des courriels sortant de CODA.
+
+Créer le fichier `/usr/local/sbin/coda-filter.py` avec le contenu provenant du dépôt git : {{{
+wget -O /usr/local/sbin/coda-filter.py 'http://git.auf.org/?p=progfou.git;a=blob_plain;f=coda/coda-filter.py'
+}}}
+
+Rendre ce script exécutable : {{{
+chmod +x /usr/local/sbin/coda-filter.py
+}}}
+
+Installer Postfix en mode ''Internet avec smarthost'',
+pour qu'il puisse recevoir les courriels de Coda via SMTP,
+les traiter, puis les renvoyer au SMTP sortant local.
+
+Ajouter les lignes suivantes à `/etc/postfix/master.cf` : {{{#!postfix
+# accès au filtre pour Coda
+codafilter  unix  -     n       n       -       10      pipe
+  flags=Rq user=nobody null_sender=
+  argv=/usr/local/sbin/coda-filter.py ${sender} ${recipient}
+
+# accès pour le retour du filtre pour Coda (non filtré)
+localhost:10026 inet n  -       n       -       10      smtpd
+  -o content_filter=
+  -o receive_override_options=no_unknown_recipient_checks,no_header_body_checks,no_milters,no_address_mappings
+  -o smtpd_helo_restrictions=
+  -o smtpd_client_restrictions=
+  -o smtpd_sender_restrictions=
+  -o smtpd_recipient_restrictions=permit_mynetworks,reject
+  -o mynetworks=127.0.0.0/8
+  -o smtpd_authorized_xforward_hosts=127.0.0.0/8
+}}}
+
+Éditer le fichier `/etc/postfix/main.cf` pour s'assurer qu'il contienne les valeurs suivantes : {{{#!postfix
+myhostname = smtp-coda.auf
+mydestination = localhost
+relayhost = smtp.ca.auf.org
+mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 10.36.0.240/28
+content_filter = codafilter:dummy
+}}}
+
+Relancer le service Postfix : {{{
+service postfix restart
+}}}
diff --git a/coda/coda-filter.py b/coda/coda-filter.py
new file mode 100644 (file)
index 0000000..3e4dd76
--- /dev/null
@@ -0,0 +1,137 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# Objectifs :
+# 05. si le destinataire est noreply@auf.org, on le remplace par l'émetteur
+#     (ANNULÉ car ça ne devrait plus arriver)
+#     (REPRIS pour le cas où le destinataire est impression-coda@auf.org)
+# 10. envoyer une copie à l'émetteur, sauf s'il est déjà le destinataire
+#     (ANNULÉ car envoi déjà vers les bons destinataires)
+# 20. ajouter une en-tête X-Coda pour faciliter les filtrages 
+# 30. préfixer le sujet par '[CODA] '
+# 40. remplacer l'organisation par AuF (forme longue, au lieu de CODA)
+# 50. renommer l'attachement en utilisant le sujet
+#       Content-Type: application/pdf; name=attachment.pdf
+#       Content-Disposition: inline; filename=attachment.pdf
+#
+import sys
+import email
+from email.header import decode_header
+import smtplib
+import re
+
+# préfixe pour le sujet
+SUBJECT_PREFIX = "[CODA] "
+# contenu pour l'en-tête X-Coda qui sera ajoutée
+X_CODA_VALUE = "Filtre AuF pour Coda v2"
+# contenu pour l'en-tête Organisation
+ORGANIZATION = "Agence universitaire de la Francophonie"
+# masque de repérage du type et numéro de document
+DOC_PATTERN = r'^(.*:)?\s*(\S+)\s*/\s*(\d+)\s*$'
+# format du nouveau nom de document (à partir du type et numéro)
+DOC_FORMAT = 'CODA-%s-%08d.pdf'
+
+# codes de sortie venant de <sysexits.h>
+EX_TEMPFAIL = 75
+EX_UNAVAILABLE = 69
+
+# on récupère l'expéditeur et le(s) destinataire(s) en paramètres
+#email_from = 'jca.test@auf.org'
+email_from = sys.argv[1]
+email_to_list = sys.argv[2:]
+
+# on tente de récupérer le message arrivant sur l'entrée standard (pipe)
+try:
+    msg = email.message_from_file(sys.stdin)
+# en cas d'échec on demande à Postfix de considérer que c'est temporaire
+except Exception, err:
+    print "Error: %s" % err
+    sys.exit(EX_TEMPFAIL)
+
+#--------------------------------------------------------------------------
+# TRAITEMENTS
+#--------------------------------------------------------------------------
+
+# 05. si le destinataire est noreply@auf.org, on le remplace par l'émetteur
+#     (ANNULÉ car ça ne devrait plus arriver)
+#     (REPRIS pour le cas où le destinataire est impression-coda@auf.org)
+if msg['To'] in ['noreply@auf.org', 'impression-coda@auf.org']:
+    old_to = msg['To']
+    email_to_list.remove(old_to)
+    email_to_list.append(email_from)
+    msg.replace_header('To', email_from)
+    msg.add_header('X-Coda-Was-To', old_to)
+
+# 10. on ajoute l'émetteur aux destinataires, sauf s'il est déjà le destinataire
+#     (ANNULÉ car envoi déjà vers les bons destinataires)
+#if email_from not in email_to_list and not email_from.endswith('@ca.auf.org'):
+#    email_to_list.append(email_from)
+#    msg.add_header('Cc', email_from)
+
+# 20. on ajoute une en-tête pour aider aux filtrages
+msg.add_header('X-Coda', X_CODA_VALUE)
+
+# 30. on préfixe le sujet actuel par '[CODA] '
+subject = msg['Subject']
+try:
+    parts = decode_header(subject)
+    subject = u' '.join([unicode(s, e or 'ascii') for s,e in parts])
+except:
+    pass
+msg.replace_header('Subject', SUBJECT_PREFIX + subject)
+
+# 40. on remplace l'organisation par l'AuF
+del msg['ORGANISATON'] # non, ce n'est pas une faute de frappe…
+msg.add_header('Organization', ORGANIZATION)
+
+# 50. on renomme l'attachement
+
+# construction du nouveau nom de fichier
+# on tente de repérer le type et numéro de document
+try:
+    m = re.match(DOC_PATTERN, subject)
+    prefix, com_type, com_num = m.groups()
+    new_filename = DOC_FORMAT % (com_type, int(com_num))
+# en cas d'échec on ignore le problème
+except Exception, err:
+    print "Error: %s" % err
+    new_filename = 'document-coda.pdf'
+
+# parcours des différentes parties du courriel
+for part in msg.walk():
+    if part.get_content_type() != 'application/pdf':
+        continue
+    # adaptation du nom de fichier dans le Content-Type
+    try:
+        raw_param = part.get_param('name')
+        param = email.utils.collapse_rfc2231_value(raw_param)
+        if param == 'attachment.pdf':
+            part.set_param('name', new_filename)
+    except:
+        pass
+    # adaptation du nom de fichier dans le Content-Disposition
+    try:
+        raw_param = part.get_param('filename', header='Content-Disposition')
+        param = email.utils.collapse_rfc2231_value(raw_param)
+        if param == 'attachment.pdf':
+            part.set_param('filename', new_filename, header='Content-Disposition')
+    except:
+        pass
+
+#--------------------------------------------------------------------------
+
+# on se connecte à Postfix via le port de retour de filtrage
+client = smtplib.SMTP('localhost', 10026)
+#client.set_debuglevel(1)
+
+# on tente d'envoyer le courriel modifié
+try:
+    client.sendmail(email_from, email_to_list, msg.as_string())
+# en cas d'échec on demande à Postfix de considérer que c'est temporaire
+except Exception, err:
+    print "Error: %s" % err
+    sys.exit(EX_TEMPFAIL)
+
+# on termine proprement
+client.quit()
+sys.exit(0)