Sed non glouton (non greedy)
Sed est une commande unix bien pratique pour capturer une chaine de caractere dans un flux du texte. Nous allons voir comment effectuer une capture non gloutonne sans s'arracher les cheveux.
Le cas d'usage
Apres avoir implementé un hack de spf-policyd sur mon serveur de mail, j'ai voulu créé un filtre sur mes fichiers de log me permettant de savoir, en un coup d'oeil, quels sont les domaines rejetés par mon serveur.
La méthode usuelle
En general, on va avoir tendance a utiliser grep pour filter les lignes interessantes :
grep NOQUEUE /var/log/mail.log
Ce qui nous donne quelque chose comme ceci :
Nov 7 05:48:02 raspberrypi postfix/smtpd[22342]: NOQUEUE: reject: RCPT from dynrak234g-10-10-67-105.inwitelecom.net[105.67.10.10]: 550 5.7.23 <moi@mondomaine.fr>: Recipient address rejected: Votre message est en etat "rejected" car la configuration SPF (Sender Policy Framework) de votre serveur mail est incorrecte. Votre message est donc considere comme un SPAM. Details technique : Message rejected due to: no SPF record. Please see http://www.open-spf.org/Why?s=helo;id=dynrak234g-195-129-67-105.inwitelecom.net;ip=105.67.10.10;r=<UNKNOWN>; from=<heike.lindner@manos-immobilien.com> to=<moi@mondomaine.fr> proto=ESMTP helo=<dynrak234g-195-129-67-105.inwitelecom.net>
Nov 7 07:59:24 raspberrypi postfix/smtpd[22972]: NOQUEUE: reject: RCPT from unknown[186.83.41.2]: 550 5.7.23 <moi@mondomaine.fr>: Recipient address rejected: Votre message est en etat "rejected" car la configuration SPF (Sender Policy Framework) de votre serveur mail est incorrecte. Votre message est donc considere comme un SPAM. Details technique : Message rejected due to: no SPF record. Please see http://www.open-spf.org/Why?s=helo;id=dynamic-ip-18683412.cable.net.co;ip=186.83.41.2;r=<UNKNOWN>; from=<riskyesg@paraisodedonquijote.com> to=<moi@mondomaine.fr> proto=ESMTP helo=<dynamic-ip-18683412.cable.net.co>
Nov 7 08:24:35 raspberrypi postfix/smtpd[23097]: NOQUEUE: reject: RCPT from unknown[45.64.236.77]: 550 5.7.23 <moi@mondomaine.fr>: Recipient address rejected: Votre message est en etat "rejected" car la configuration SPF (Sender Policy Framework) de votre serveur mail est incorrecte. Votre message est donc considere comme un SPAM. Details technique : Message rejected due to: no SPF record. Please see http://www.open-spf.org/Why?s=helo;id=node-45-64-236-77.alliancebroadband.in;ip=45.64.236.77;r=<UNKNOWN>; from=<dgyks@sickel.com> to=<moi@mondomaine.fr> proto=ESMTP helo=<node-45-64-236-77.alliancebroadband.in>
Nov 7 08:57:55 raspberrypi postfix/smtpd[23411]: NOQUEUE: reject: RCPT from unknown[176.216.115.26]: 550 5.7.23 <moi@mondomaine.fr>: Recipient address rejected: Votre message est en etat "rejected" car la configuration SPF (Sender Policy Framework) de votre serveur mail est incorrecte. Votre message est donc considere comme un SPAM. Details technique : Message rejected due to: no SPF record. Please see http://www.open-spf.org/Why?s=helo;id=[176.216.115.26];ip=176.216.115.26;r=<UNKNOWN>; from=<jose.miguel.covelo.perez@inega.es> to=<moi@mondomaine.fr> proto=ESMTP helo=<[176.216.115.26]>
Nov 7 09:29:19 raspberrypi postfix/smtpd[23601]: NOQUEUE: reject: RCPT from unknown[185.223.216.254]: 550 5.7.23 <moi@mondomaine.fr>: Recipient address rejected: Votre message est en etat "rejected" car la configuration SPF (Sender Policy Framework) de votre serveur mail est incorrecte. Votre message est donc considere comme un SPAM. Details technique : Message rejected due to: no SPF record. Please see http://www.open-spf.org/Why?s=helo;id=[185.223.216.254];ip=185.223.216.254;r=<UNKNOWN>; from=<nishimura@jn-design.com> to=<moi@mondomaine.fr> proto=ESMTP helo=<[185.223.216.254]>
Nov 7 10:01:55 raspberrypi postfix/smtpd[23714]: NOQUEUE: reject: RCPT from unknown[61.9.108.122]: 550 5.7.23 <moi@mondomaine.fr>: Recipient address rejected: Votre message est en etat "rejected" car la configuration SPF (Sender Policy Framework) de votre serveur mail est incorrecte. Votre message est donc considere comme un SPAM. Details technique : Message rejected due to: no SPF record. Please see http://www.open-spf.org/Why?s=helo;id=[61.9.108.122];ip=61.9.108.122;r=<UNKNOWN>; from=<robin@fuchs5.com> to=<moi@mondomaine.fr> proto=ESMTP helo=<[61.9.108.122]>
Nov 7 10:57:46 raspberrypi postfix/smtpd[24007]: NOQUEUE: reject: RCPT from unknown[59.91.247.194]: 550 5.7.23 <moi@mondomaine.fr>: Recipient address rejected: Votre message est en etat "rejected" car la configuration SPF (Sender Policy Framework) de votre serveur mail est incorrecte. Votre message est donc considere comme un SPAM. Details technique : Message rejected due to: no SPF record. Please see http://www.open-spf.org/Why?s=helo;id=[59.91.247.194];ip=59.91.247.194;r=<UNKNOWN>; from=<lukasz@pakunova.pl> to=<moi@mondomaine.fr> proto=ESMTP helo=<[59.91.247.194]>
Nov 7 11:32:26 raspberrypi postfix/smtpd[24233]: NOQUEUE: reject: RCPT from unknown[197.3.10.37]: 550 5.7.23 <moi@mondomaine.fr>: Recipient address rejected: Votre message est en etat "rejected" car la configuration SPF (Sender Policy Framework) de votre serveur mail est incorrecte. Votre message est donc considere comme un SPAM. Details technique : Message rejected due to: no SPF record. Please see http://www.open-spf.org/Why?s=helo;id=[197.3.10.37];ip=197.3.10.37;r=<UNKNOWN>; from=<annadias@carbotrade-spa.com> to=<moi@mondomaine.fr> proto=ESMTP helo=<[197.3.10.37]>
Nov 7 12:29:09 raspberrypi postfix/smtpd[24500]: NOQUEUE: reject: RCPT from unknown[182.186.12.201]: 550 5.7.23 <moi@mondomaine.fr>: Recipient address rejected: Votre message est en etat "rejected" car la configuration SPF (Sender Policy Framework) de votre serveur mail est incorrecte. Votre message est donc considere comme un SPAM. Details technique : Message rejected due to: no SPF record. Please see http://www.open-spf.org/Why?s=helo;id=[182.186.12.201];ip=182.186.12.201;r=<UNKNOWN>; from=<eli@ifcship.com> to=<moi@mondomaine.fr> proto=ESMTP helo=<[182.186.12.201]>
Nov 7 13:15:28 raspberrypi postfix/smtpd[24844]: NOQUEUE: reject: RCPT from unknown[190.244.85.167]: 550 5.7.23 <moi@mondomaine.fr>: Recipient address rejected: Votre message est en etat "rejected" car la configuration SPF (Sender Policy Framework) de votre serveur mail est incorrecte. Votre message est donc considere comme un SPAM. Details technique : Message rejected due to: no SPF record. Please see http://www.open-spf.org/Why?s=helo;id=167-85-244-190.fibertel.com.ar;ip=190.244.85.167;r=<UNKNOWN>; from=<ricerx@punkass.com> to=<moi@mondomaine.fr> proto=ESMTP helo=<167-85-244-190.fibertel.com.ar>
Nov 7 13:49:11 raspberrypi postfix/smtpd[24979]: NOQUEUE: reject: RCPT from adsl-73.37.6.210.tellas.gr[37.6.210.73]: 550 5.7.23 <moi@mondomaine.fr>: Recipient address rejected: Votre message est en etat "rejected" car la configuration SPF (Sender Policy Framework) de votre serveur mail est incorrecte. Votre message est donc considere comme un SPAM. Details technique : Message rejected due to: no SPF record. Please see http://www.open-spf.org/Why?s=helo;id=adsl-73.37.6.210.tellas.gr;ip=37.6.210.73;r=<UNKNOWN>; from=<lyxbi@excite.fr> to=<moi@mondomaine.fr> proto=ESMTP helo=<adsl-73.37.6.210.tellas.gr>
Nov 7 14:17:05 raspberrypi postfix/smtpd[25090]: NOQUEUE: reject: RCPT from unknown[154.160.11.112]: 550 5.7.23 <moi@mondomaine.fr>: Recipient address rejected: Votre message est en etat "rejected" car la configuration SPF (Sender Policy Framework) de votre serveur mail est incorrecte. Votre message est donc considere comme un SPAM. Details technique : Message rejected due to: no SPF record. Please see http://www.open-spf.org/Why?s=helo;id=[154.160.11.112];ip=154.160.11.112;r=<UNKNOWN>; from=<prenault@lenko.com> to=<moi@mondomaine.fr> proto=ESMTP helo=<[154.160.11.112]>
etc...
Le résultat est pertinent, mais sa lecture est désagréable.
C'est la que sed intervient.
Faire du menage
On va utiliser sed en mode substitution et avec la fonction de capture.
La syntaxe de base de la substitution est la suivante :
sed 's/phrase a chercher/phrase de remplacement/'
La phrase a chercher peut etre une expression regulière, aussi appelée regex.
La capture permet de récuperer une partie de la phrase a chercher. Il est possible d'afficher le résultat de la capture dans la phrase de remplacement avec la notation \<numéro de la capture>.
Ex : \1 ou \2
la capture s'effectue avec les parenthèses ( et ). Il faut cependant les echapper avec le symbole "\" pour indiquer à sed qu'on veut faire une capture et non chercher les caracteres "(" et ")".
Ex :
sed 's/expression \(mot\) reste de l expression/capturé : \1/'
Affichera :
capturé : mot
T'en a trop pris gros !
Le probleme c'est que les regex de sed sont gloutonnes. Sed ne reconnait pas la notation +? ou *?.
Un exemple avec le fichier de log précédent. On souhaite extraire la valeur "annadias@carbotrade-spa.com" contenu dans les chevrons de "from=<annadias@carbotrade-spa.com>"
La commande :
grep NOQUEUE /var/log/mail.log | sed 's/.*from=<\(.*\)>.*/\1/'
Donne comme résultat :
annadias@carbotrade-spa.com> to=<moi@mondomaine.fr
Le soucis vient du fait que <.*> prend tous les caractères jusqu'au dernier ">" rencontré, même si d'autres se trouvent sur le chemin.
La négation et la solution
L'astuce est donc d'utiliser la négation du caractere délimiteur pour etre certain de s'arreter au premier rencontré.
Ex :
[^>]
Signifie : n'importe quel caractère qui n'est pas ">". On va donc pouvoir l'utiliser à la place du "." de la manière suivante :
[^>]*
L'expression a utiliser est donc la suivante :
grep NOQUEUE /var/log/mail.log | sed 's/.*from=<\([^>]*\)>.*/\1/'
Qui nous donnera comme résultat :
heike.lindner@manos-immobilien.com
riskyesg@paraisodedonquijote.com
dgyks@sickel.com
jose.miguel.covelo.perez@inega.es
nishimura@jn-design.com
robin@fuchs5.com
lukasz@pakunova.pl
annadias@carbotrade-spa.com
eli@ifcship.com
ricerx@punkass.com
lyxbi@excite.fr
prenault@lenko.com
Bonus : un peu plus de détail
En reprenant cette expression et en ajoutant une deuxieme capture, on peut egalement récupérer la date :
grep NOQUEUE /var/log/mail.log | sed 's/\(.*\)raspberrypi.*from=<\([^>]*\)>.*/\1: \2/'
Résultat :
Nov 7 05:48:02 : heike.lindner@manos-immobilien.com
Nov 7 07:59:24 : riskyesg@paraisodedonquijote.com
Nov 7 08:24:35 : dgyks@sickel.com
Nov 7 08:57:55 : jose.miguel.covelo.perez@inega.es
Nov 7 09:29:19 : nishimura@jn-design.com
Nov 7 10:01:55 : robin@fuchs5.com
Nov 7 10:57:46 : lukasz@pakunova.pl
Nov 7 11:32:26 : annadias@carbotrade-spa.com
Nov 7 12:29:09 : eli@ifcship.com
Nov 7 13:15:28 : ricerx@punkass.com
Nov 7 13:49:11 : lyxbi@excite.fr
Nov 7 14:17:05 : prenault@lenko.com
En version hardcore avec mise en forme avec la commande column :
grep NOQUEUE /var/log/mail.log | sed 's/\(.*\)raspberrypi.*from=<\([^>]*\)>.*to=<\([^>]*\)>.*helo=<\([^>]*\)>.*/\1#\2#\4#\3/' | column -t -s '#'
Ce qui va afficher les données de la manière suivant :
date expediteur serveur_emetteur destinataire
Grâce à cette ligne de commande, je peux avoir un rapport de tous les domaines pourris qui m'envoient des mail.
Le cas échéant, je peux alors ajouter une exception dans mon filtrage SPF pour laisser passer un domaine mal configuré mais avec lequel j'ai besoin de communiquer par mail.