nuage de tags sur la liste, suppression dropdown navbar, rôles/droits sur le profil

This commit is contained in:
Cedric Abonnel
2026-05-12 20:07:33 +02:00
parent 1d2e3d9a24
commit 6e438835f8
3470 changed files with 97124 additions and 109 deletions
@@ -0,0 +1,52 @@
<svg width="100%" viewBox="0 0 680 420" xmlns="http://www.w3.org/2000/svg" role="img" style=""><defs><mask id="imagine-text-gaps-vz689l" maskUnits="userSpaceOnUse"><rect x="0" y="0" width="680" height="420" fill="white"/><rect x="14.191667556762695" y="8.750000953674316" width="41.616661071777344" height="17.749998092651367" fill="black" rx="2"/><rect x="11.925002098083496" y="22.250001907348633" width="46.14999771118164" height="21.499998092651367" fill="black" rx="2"/><rect x="-56.63332748413086" y="-27.249998092651367" width="113.26665496826172" height="30.249996185302734" fill="black" rx="2"/><rect x="-31.49166488647461" y="4.750001430511475" width="62.98332595825195" height="17.749998092651367" fill="black" rx="2"/></mask></defs>
<title style="fill:rgb(0, 0, 0);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">Illustration : filtrage SPF des mails</title>
<desc style="fill:rgb(0, 0, 0);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">Une enveloppe estampillée SPF Fail avec un tampon rouge de rejet, dans un style éditorial dessiné.</desc>
<g transform="translate(140, 60) rotate(-4 200 130)" style="fill:rgb(0, 0, 0);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<path d="M0 30 Q0 18 12 18 L388 18 Q400 18 400 30 L400 240 Q400 252 388 252 L12 252 Q0 252 0 240 Z" fill="#F4EFE2" stroke="#2C2C2A" stroke-width="2.5" stroke-linejoin="round" style="fill:rgb(244, 239, 226);stroke:rgb(44, 44, 42);color:rgb(0, 0, 0);stroke-width:2.5px;stroke-linecap:butt;stroke-linejoin:round;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<path d="M0 30 L200 160 L400 30" fill="none" stroke="#2C2C2A" stroke-width="2" stroke-linejoin="round" mask="url(#imagine-text-gaps-vz689l)" style="fill:none;stroke:rgb(44, 44, 42);color:rgb(0, 0, 0);stroke-width:2px;stroke-linecap:butt;stroke-linejoin:round;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<g stroke="#2C2C2A" stroke-width="1.5" fill="none" opacity="0.5" style="fill:none;stroke:rgb(44, 44, 42);color:rgb(0, 0, 0);stroke-width:1.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.5;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<path d="M40 180 L180 180" style="fill:none;stroke:rgb(44, 44, 42);color:rgb(0, 0, 0);stroke-width:1.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<path d="M40 198 L220 198" style="fill:none;stroke:rgb(44, 44, 42);color:rgb(0, 0, 0);stroke-width:1.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<path d="M40 216 L160 216" style="fill:none;stroke:rgb(44, 44, 42);color:rgb(0, 0, 0);stroke-width:1.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
</g>
<g transform="translate(310, 40)" style="fill:rgb(0, 0, 0);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="0" y="0" width="70" height="50" fill="#FAF6EC" stroke="#A32D2D" stroke-width="2" style="fill:rgb(250, 246, 236);stroke:rgb(163, 45, 45);color:rgb(0, 0, 0);stroke-width:2px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="35" y="22" text-anchor="middle" font-family="Georgia, serif" font-size="11" font-weight="bold" fill="#A32D2D" style="fill:rgb(163, 45, 45);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:Georgia, serif;font-size:11px;font-weight:700;text-anchor:middle;dominant-baseline:auto">SMTP</text>
<text x="35" y="38" text-anchor="middle" font-family="Georgia, serif" font-size="14" font-weight="bold" fill="#185FA5" style="fill:rgb(24, 95, 165);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:Georgia, serif;font-size:14px;font-weight:700;text-anchor:middle;dominant-baseline:auto">+SPF</text>
<g stroke="#A32D2D" stroke-width="1.5" fill="none" style="fill:none;stroke:rgb(163, 45, 45);color:rgb(0, 0, 0);stroke-width:1.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<path d="M78 8 L96 8" style="fill:none;stroke:rgb(163, 45, 45);color:rgb(0, 0, 0);stroke-width:1.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<path d="M78 16 L96 16" style="fill:none;stroke:rgb(163, 45, 45);color:rgb(0, 0, 0);stroke-width:1.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<path d="M78 24 L96 24" style="fill:none;stroke:rgb(163, 45, 45);color:rgb(0, 0, 0);stroke-width:1.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
</g>
</g>
<g transform="translate(70, 95) rotate(-12)" style="fill:rgb(0, 0, 0);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="-90" y="-32" width="180" height="64" fill="none" stroke="#A32D2D" stroke-width="4" rx="4" style="fill:none;stroke:rgb(163, 45, 45);color:rgb(0, 0, 0);stroke-width:4px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<rect x="-84" y="-26" width="168" height="52" fill="none" stroke="#A32D2D" stroke-width="2" rx="2" style="fill:none;stroke:rgb(163, 45, 45);color:rgb(0, 0, 0);stroke-width:2px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="0" y="-4" text-anchor="middle" font-family="Georgia, serif" font-weight="bold" font-size="22" fill="#A32D2D" letter-spacing="2" style="fill:rgb(163, 45, 45);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:Georgia, serif;font-size:22px;font-weight:700;text-anchor:middle;dominant-baseline:auto">REJETÉ</text>
<text x="0" y="18" text-anchor="middle" font-family="Georgia, serif" font-size="11" fill="#A32D2D" letter-spacing="1" style="fill:rgb(163, 45, 45);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:Georgia, serif;font-size:11px;font-weight:400;text-anchor:middle;dominant-baseline:auto">SPF FAIL</text>
</g>
</g>
<g transform="translate(50, 320)" opacity="0.7" style="fill:rgb(0, 0, 0);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.7;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<circle cx="0" cy="0" r="28" fill="none" stroke="#2C2C2A" stroke-width="2.5" style="fill:none;stroke:rgb(44, 44, 42);color:rgb(0, 0, 0);stroke-width:2.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<line x1="20" y1="20" x2="42" y2="42" stroke="#2C2C2A" stroke-width="3" stroke-linecap="round" mask="url(#imagine-text-gaps-vz689l)" style="fill:rgb(0, 0, 0);stroke:rgb(44, 44, 42);color:rgb(0, 0, 0);stroke-width:3px;stroke-linecap:round;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<path d="M-12 -4 L-4 6 L12 -10" fill="none" stroke="#A32D2D" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" mask="url(#imagine-text-gaps-vz689l)" style="fill:none;stroke:rgb(163, 45, 45);color:rgb(0, 0, 0);stroke-width:3px;stroke-linecap:round;stroke-linejoin:round;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
</g>
<g stroke="#2C2C2A" stroke-width="1.5" fill="none" opacity="0.4" style="fill:none;stroke:rgb(44, 44, 42);color:rgb(0, 0, 0);stroke-width:1.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.4;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<path d="M580 90 Q592 95 580 105 Q568 115 580 125" style="fill:none;stroke:rgb(44, 44, 42);color:rgb(0, 0, 0);stroke-width:1.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<path d="M598 88 Q610 95 598 105 Q586 115 598 125" style="fill:none;stroke:rgb(44, 44, 42);color:rgb(0, 0, 0);stroke-width:1.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<path d="M616 90 Q628 95 616 105 Q604 115 616 125" style="fill:none;stroke:rgb(44, 44, 42);color:rgb(0, 0, 0);stroke-width:1.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
</g>
<g opacity="0.35" style="fill:rgb(0, 0, 0);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.35;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<circle cx="100" cy="60" r="3" fill="#A32D2D" style="fill:rgb(163, 45, 45);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<circle cx="600" cy="350" r="3" fill="#185FA5" style="fill:rgb(24, 95, 165);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<circle cx="60" cy="380" r="2" fill="#2C2C2A" style="fill:rgb(44, 44, 42);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 13 KiB

@@ -0,0 +1,67 @@
Quand on gère un serveur de messagerie, le SPF (Sender Policy Framework) est l'un des premiers remparts contre l'usurpation d'identité. Concrètement, il permet à un domaine de déclarer dans son DNS quelles adresses IP ont le droit d'envoyer des mails en son nom. À la réception, le serveur vérifie cette correspondance et écrit le résultat dans un en-tête : `Received-SPF`.
Reste à savoir quoi faire de ce résultat. Tous les verdicts SPF ne se valent pas, et rejeter trop large revient vite à perdre des mails légitimes.
## Les codes de retour SPF
Voici les sept valeurs qu'on peut rencontrer dans `Received-SPF`, et ce qu'il faut en faire :
| Code | Signification | Rejet conseillé |
|------|---------------|-----------------|
| `Pass` | L'IP est explicitement autorisée par le domaine | Non |
| `Fail` | L'IP n'est pas autorisée | Oui |
| `Softfail` | L'IP n'est probablement pas autorisée | Optionnel |
| `Neutral` | Le domaine ne se prononce pas | Non |
| `None` | Pas d'enregistrement SPF publié | Optionnel |
| `Permerror` | Erreur permanente (syntaxe SPF invalide, boucle…) | Oui |
| `Temperror` | Erreur temporaire (DNS injoignable, timeout) | Non recommandé |
Les deux candidats évidents au rejet automatique sont **`Fail`** et **`Permerror`**. Le premier dit clairement « cette IP n'a rien à faire ici » ; le second signale un enregistrement cassé, ce qui est rare chez un expéditeur sérieux.
`Softfail` est plus délicat : beaucoup de domaines mal configurés y atterrissent, donc rejeter sur ce critère seul génère des faux positifs. Mieux vaut l'utiliser comme un signal parmi d'autres (typiquement via un score SpamAssassin) plutôt que comme motif de rejet sec.
`Temperror` ne doit jamais déclencher un rejet définitif : le problème vient d'un DNS qui ne répond pas, pas du mail lui-même. Un rejet temporaire (code 4xx) est en revanche acceptable, le serveur émetteur réessaiera.
## Mise en pratique avec Postfix
Postfix permet de filtrer sur les en-têtes via la directive `header_checks`. Dans `main.cf` :
```
header_checks = regexp:/etc/postfix/header_checks
```
Puis dans `/etc/postfix/header_checks` :
```
/^Received-SPF: Fail/ REJECT SPF Fail - IP non autorisee
/^Received-SPF: Permerror/ REJECT SPF Permerror - enregistrement SPF invalide
```
Après modification, recharger Postfix :
```
postfix reload
```
À noter : `header_checks` agit sur les en-têtes déjà présents. Si on veut que la vérification SPF soit faite par Postfix lui-même au moment de la réception, le bon outil est plutôt **policyd-spf** (paquet `postfix-policyd-spf-python` sous Debian), qui s'intègre via `smtpd_recipient_restrictions` et permet de rejeter avant même que le mail soit accepté.
## L'approche par scoring avec SpamAssassin
Plutôt que de couper net, on peut intégrer SPF à un score global. SpamAssassin expose plusieurs règles dédiées :
- `SPF_FAIL` : ajoute un score significatif
- `SPF_SOFTFAIL` : score modéré
- `SPF_PERMERROR` : signale une config cassée
- `SPF_HELO_FAIL` : échec sur l'identité HELO
L'intérêt du scoring, c'est de combiner SPF avec d'autres signaux (DKIM, DMARC, listes noires, contenu). Un mail qui échoue uniquement à SPF mais passe tout le reste mérite peut-être un passage en spam plutôt qu'un rejet pur. Inversement, un cumul `SPF_FAIL` + DKIM cassé + IP sur une RBL ne laisse pas beaucoup de doute.
## Quelques précautions avant de mettre en production
Avant d'activer un rejet sur `Fail`, deux réflexes utiles :
D'abord, regarder les logs en mode passif pendant quelques jours. Loguer les verdicts SPF sans rejeter permet de mesurer le volume concerné et de repérer les expéditeurs légitimes mal configurés (il y en a toujours, y compris des prestataires connus).
Ensuite, vérifier que les mails forwardés ne sont pas pénalisés. Un mail transféré perd souvent son alignement SPF puisque l'IP de réémission n'est pas celle déclarée par le domaine d'origine. C'est précisément le problème que DKIM et ARC sont censés résoudre, mais tous les serveurs ne les implémentent pas correctement.
Le SPF est un bon premier filtre, pas une solution complète. Combiné à DKIM et DMARC, il forme la base de l'authentification mail moderne ; isolé, il reste utile mais imparfait.
@@ -0,0 +1,30 @@
{
"uuid": "83cabd62-617e-418c-a890-76e205bf5551",
"slug": "rejet-des-mails-avec-l-en-tete-received-spf",
"title": "Filtrer les mails selon le résultat SPF",
"author": "cedric@abonnel.fr",
"published": true,
"published_at": "2025-05-20 16:46",
"created_at": "2025-05-20 16:46:00",
"updated_at": "2026-05-12 11:17:15",
"revisions": [
{
"n": 1,
"date": "2026-05-12 11:17:15",
"comment": "Contenu modifié, couverture modifiée, métadonnées fichiers modifiées",
"title": "Filtrer les mails selon le résultat SPF"
}
],
"cover": "cover.svg",
"files_meta": {
"80df51587e63642b-13724.svg": {
"author": "Cédrix / Générée par IA",
"source_url": ""
}
},
"external_links": [],
"seo_title": "",
"seo_description": "Filtrer les mails par SPF : guide DevOps débutant. Codes Fail, Permerror, Softfail expliqués, exemples Postfix et SpamAssassin inclus.",
"og_image": "https://varlog.a5l.fr/file?uuid=83cabd62-617e-418c-a890-76e205bf5551&name=80df51587e63642b-13724.svg",
"category": "informatique"
}
@@ -0,0 +1,67 @@
Quand on gère un serveur de messagerie, le SPF (Sender Policy Framework) est l'un des premiers remparts contre l'usurpation d'identité. Concrètement, il permet à un domaine de déclarer dans son DNS quelles adresses IP ont le droit d'envoyer des mails en son nom. À la réception, le serveur vérifie cette correspondance et écrit le résultat dans un en-tête : `Received-SPF`.
Reste à savoir quoi faire de ce résultat. Tous les verdicts SPF ne se valent pas, et rejeter trop large revient vite à perdre des mails légitimes.
## Les codes de retour SPF
Voici les sept valeurs qu'on peut rencontrer dans `Received-SPF`, et ce qu'il faut en faire :
| Code | Signification | Rejet conseillé |
|------|---------------|-----------------|
| `Pass` | L'IP est explicitement autorisée par le domaine | Non |
| `Fail` | L'IP n'est pas autorisée | Oui |
| `Softfail` | L'IP n'est probablement pas autorisée | Optionnel |
| `Neutral` | Le domaine ne se prononce pas | Non |
| `None` | Pas d'enregistrement SPF publié | Optionnel |
| `Permerror` | Erreur permanente (syntaxe SPF invalide, boucle…) | Oui |
| `Temperror` | Erreur temporaire (DNS injoignable, timeout) | Non recommandé |
Les deux candidats évidents au rejet automatique sont **`Fail`** et **`Permerror`**. Le premier dit clairement « cette IP n'a rien à faire ici » ; le second signale un enregistrement cassé, ce qui est rare chez un expéditeur sérieux.
`Softfail` est plus délicat : beaucoup de domaines mal configurés y atterrissent, donc rejeter sur ce critère seul génère des faux positifs. Mieux vaut l'utiliser comme un signal parmi d'autres (typiquement via un score SpamAssassin) plutôt que comme motif de rejet sec.
`Temperror` ne doit jamais déclencher un rejet définitif : le problème vient d'un DNS qui ne répond pas, pas du mail lui-même. Un rejet temporaire (code 4xx) est en revanche acceptable, le serveur émetteur réessaiera.
## Mise en pratique avec Postfix
Postfix permet de filtrer sur les en-têtes via la directive `header_checks`. Dans `main.cf` :
```
header_checks = regexp:/etc/postfix/header_checks
```
Puis dans `/etc/postfix/header_checks` :
```
/^Received-SPF: Fail/ REJECT SPF Fail - IP non autorisee
/^Received-SPF: Permerror/ REJECT SPF Permerror - enregistrement SPF invalide
```
Après modification, recharger Postfix :
```
postfix reload
```
À noter : `header_checks` agit sur les en-têtes déjà présents. Si on veut que la vérification SPF soit faite par Postfix lui-même au moment de la réception, le bon outil est plutôt **policyd-spf** (paquet `postfix-policyd-spf-python` sous Debian), qui s'intègre via `smtpd_recipient_restrictions` et permet de rejeter avant même que le mail soit accepté.
## L'approche par scoring avec SpamAssassin
Plutôt que de couper net, on peut intégrer SPF à un score global. SpamAssassin expose plusieurs règles dédiées :
- `SPF_FAIL` : ajoute un score significatif
- `SPF_SOFTFAIL` : score modéré
- `SPF_PERMERROR` : signale une config cassée
- `SPF_HELO_FAIL` : échec sur l'identité HELO
L'intérêt du scoring, c'est de combiner SPF avec d'autres signaux (DKIM, DMARC, listes noires, contenu). Un mail qui échoue uniquement à SPF mais passe tout le reste mérite peut-être un passage en spam plutôt qu'un rejet pur. Inversement, un cumul `SPF_FAIL` + DKIM cassé + IP sur une RBL ne laisse pas beaucoup de doute.
## Quelques précautions avant de mettre en production
Avant d'activer un rejet sur `Fail`, deux réflexes utiles :
D'abord, regarder les logs en mode passif pendant quelques jours. Loguer les verdicts SPF sans rejeter permet de mesurer le volume concerné et de repérer les expéditeurs légitimes mal configurés (il y en a toujours, y compris des prestataires connus).
Ensuite, vérifier que les mails forwardés ne sont pas pénalisés. Un mail transféré perd souvent son alignement SPF puisque l'IP de réémission n'est pas celle déclarée par le domaine d'origine. C'est précisément le problème que DKIM et ARC sont censés résoudre, mais tous les serveurs ne les implémentent pas correctement.
Le SPF est un bon premier filtre, pas une solution complète. Combiné à DKIM et DMARC, il forme la base de l'authentification mail moderne ; isolé, il reste utile mais imparfait.