Selenith
Projets, mémos et infos diverses

Sed non glouton (non greedy)

Publié le 07/11/2020 | Édité le 06/01/2024

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.

pacman

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.