Cette page décrit la technique utilisée pour intercepter les courriels sortant de Coda.

Configuration du serveur SMTP sortant dans Coda

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 suivant :

   1 #!/usr/bin/python
   2 # -*- coding: utf-8 -*-
   3 #
   4 # Objectifs :
   5 # 10. envoyer une copie à l'émetteur
   6 # 20. ajouter une en-tête X-Coda pour faciliter les filtrages 
   7 # 30. préfixer le sujet par '[CODA] '
   8 # 40. remplacer l'organisation par AuF (forme longue, au lieu de CODA)
   9 # 50. renommer l'attachement en utilisant le sujet
  10 #       Content-Type: application/pdf; name=attachment.pdf
  11 #       Content-Disposition: inline; filename=attachment.pdf
  12 #
  13 import sys
  14 import email
  15 import smtplib
  16 import re
  17 
  18 # préfixe pour le sujet
  19 SUBJECT_PREFIX = "[CODA] "
  20 # contenu pour l'en-tête X-Coda qui sera ajoutée
  21 X_CODA_VALUE = "Filtre AuF pour Coda v1"
  22 # contenu pour l'en-tête Organisation
  23 ORGANIZATION = "Agence universitaire de la Francophonie"
  24 # masque de repérage du type et numéro de document
  25 DOC_PATTERN = r'^(.*:)?\s*(\S+)\s*/\s*(\d+)\s*$'
  26 # format du nouveau nom de document (à partir du type et numéro)
  27 DOC_FORMAT = 'CODA-%s-%08d.pdf'
  28 
  29 # codes de sortie venant de <sysexits.h>
  30 EX_TEMPFAIL = 75
  31 EX_UNAVAILABLE = 69
  32 
  33 # on récupère l'expéditeur et le(s) destinataire(s) en paramètres
  34 #email_from = 'jca.test@auf.org'
  35 email_from = sys.argv[1]
  36 email_to = sys.argv[2:]
  37 
  38 # on tente de récupérer le message arrivant sur l'entrée standard (pipe)
  39 try:
  40     msg = email.message_from_file(sys.stdin)
  41 # en cas d'échec on demande à Postfix de considérer que c'est temporaire
  42 except Exception, err:
  43     print "Error: %s" % err
  44     sys.exit(EX_TEMPFAIL)
  45 
  46 #--------------------------------------------------------------------------
  47 # TRAITEMENTS
  48 #--------------------------------------------------------------------------
  49 
  50 # 10. on ajoute l'expéditeur aux destinataires
  51 email_to.append(email_from)
  52 msg.add_header('Cc', email_from)
  53 
  54 # 20. on ajoute une en-tête pour aider aux filtrages
  55 msg.add_header('X-Coda', X_CODA_VALUE)
  56 
  57 # 30. on préfixe le sujet actuel par '[CODA] '
  58 subject = msg['Subject']
  59 msg.replace_header('Subject', SUBJECT_PREFIX + subject)
  60 
  61 # 40. on remplace l'organisation par l'AuF
  62 del msg['ORGANISATON'] # non, ce n'est pas une faute de frappe…
  63 msg.add_header('Organization', ORGANIZATION)
  64 
  65 # 50. on renomme l'attachement
  66 
  67 # construction du nouveau nom de fichier
  68 # on tente de repérer le type et numéro de document
  69 try:
  70     m = re.match(DOC_PATTERN, subject)
  71     prefix, com_type, com_num = m.groups()
  72     new_filename = DOC_FORMAT % (com_type, int(com_num))
  73 # en cas d'échec on ignore le problème
  74 except Exception, err:
  75     print "Error: %s" % err
  76     new_filename = 'document-coda.pdf'
  77 
  78 # parcours des différentes parties du courriel
  79 for part in msg.walk():
  80     if part.get_content_type() != 'application/pdf':
  81         continue
  82     # adaptation du nom de fichier dans le Content-Type
  83     try:
  84         raw_param = part.get_param('name')
  85         param = email.utils.collapse_rfc2231_value(raw_param)
  86         if param == 'attachment.pdf':
  87             part.set_param('name', new_filename)
  88     except:
  89         pass
  90     # adaptation du nom de fichier dans le Content-Disposition
  91     try:
  92         raw_param = part.get_param('filename', header='Content-Disposition')
  93         param = email.utils.collapse_rfc2231_value(raw_param)
  94         if param == 'attachment.pdf':
  95             part.set_param('filename', new_filename, header='Content-Disposition')
  96     except:
  97         pass
  98 
  99 #--------------------------------------------------------------------------
 100 
 101 # on se connecte à Postfix via le port de retour de filtrage
 102 client = smtplib.SMTP('localhost', 10026)
 103 #client.set_debuglevel(1)
 104 
 105 # on tente d'envoyer le courriel modifié
 106 try:
 107     client.sendmail(email_from, email_to, msg.as_string())
 108 # en cas d'échec on demande à Postfix de considérer que c'est temporaire
 109 except Exception, err:
 110     print "Error: %s" % err
 111     sys.exit(EX_TEMPFAIL)
 112 
 113 # on termine proprement
 114 client.quit()
 115 sys.exit(0)

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 :

# 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 :

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