Stalwart vérification serveur
Notes › _DIFFUSION › Stalwart
Vérification manuelle serveur messagerie
Linux, outils ligne de commande, liens web pour faire une vérification complète de mon serveur de messagerie “Stalwart web ui”
- DOMAINE OVH yannig.net
- MX yannig.net
- IMAP SSL/TLS Port 993
- SMTP SSL/TLS Port 465
Pour vérifier complètement votre serveur Stalwart associé à yannig.net, contrôlez séparément DNS, routage SMTP, certificats TLS, IMAP, authentification SPF/DKIM/DMARC, réputation et configuration Stalwart. Les ports 993/IMAPS et 465/SMTPS sont adaptés aux clients, mais le port 25 doit également être testé pour la réception et la livraison entre serveurs.
1. Vérification DNS depuis Linux
Effectuez ces commandes depuis une machine externe au serveur, si possible un VPS ou une connexion différente :
1
2
3
4
5
6
7
8
9
10
DOMAIN=yannig.net
dig "$DOMAIN" A +short
dig "$DOMAIN" AAAA +short
dig "$DOMAIN" MX +short
dig "$DOMAIN" NS +short
dig "$DOMAIN" CAA +short
dig "$DOMAIN" TXT +short
dig "_dmarc.$DOMAIN" TXT +short
dig "$DOMAIN" DNSKEY +dnssec
Vérifiez en particulier :
- Le MX de
yannig.netdoit pointer vers un nom d’hôte stable, par exemplemail.yannig.net. - Le nom indiqué par le MX doit posséder un enregistrement
Aet éventuellementAAAA. - L’adresse IPv4 publique doit avoir un reverse DNS cohérent.
- Il ne doit exister qu’un seul enregistrement SPF
v=spf1. - Le domaine doit posséder un enregistrement DMARC.
- Le sélecteur DKIM doit être récupéré dans l’interface Stalwart, puis testé ainsi :
1
dig TXT SELECTEUR._domainkey.yannig.net +short
Remplacez SELECTEUR par le sélecteur réellement affiché par Stalwart.
Pour connaître le reverse DNS de l’adresse IPv4 :
1
dig -x ADRESSE_IPV4 +short
Le résultat devrait être un nom tel que mail.yannig.net, et ce nom devrait ensuite résoudre vers la même adresse IP :
1
dig mail.yannig.net A +short
Stalwart documente les enregistrements nécessaires pour MX, SPF, DKIM, DMARC, TLSA et les autres mécanismes DNS de messagerie.
2. Vérification des ports réseau
Depuis une machine extérieure :
1
2
3
4
nc -vz -w5 yannig.net 25
nc -vz -w5 yannig.net 465
nc -vz -w5 yannig.net 587
nc -vz -w5 yannig.net 993
Résultat attendu :
1
succeeded
Le port 25 est important même si vous n’utilisez pas ce port dans Thunderbird ou K-9 Mail : les autres serveurs SMTP l’utilisent pour remettre les messages à votre serveur.
Vous pouvez obtenir davantage d’informations avec Nmap :
1
nmap -Pn -sV -p 25,465,587,993 yannig.net
Puis analyser les suites cryptographiques :
1
2
3
nmap -Pn -p 465,993 \
--script ssl-enum-ciphers \
yannig.net
Ne vous limitez pas à un test exécuté depuis le serveur lui-même : un pare-feu local, Docker, OVH ou un routeur peut autoriser les connexions locales tout en bloquant l’accès public.
3. Test des certificats TLS
SMTP implicite sur 465
Le port 465 démarre directement en TLS :
1
2
3
4
5
6
openssl s_client \
-connect yannig.net:465 \
-servername yannig.net \
-showcerts \
-verify_return_error \
-brief </dev/null
IMAP implicite sur 993
1
2
3
4
5
6
openssl s_client \
-connect yannig.net:993 \
-servername yannig.net \
-showcerts \
-verify_return_error \
-brief </dev/null
Contrôlez notamment :
Verification: OK.- Le certificat n’est pas expiré.
- Le certificat contient
yannig.netdans le SAN. - La chaîne complète est fournie.
- Le certificat présenté sur
465et993est bien celui attendu. - TLS 1.2 ou TLS 1.3 est disponible.
- TLS 1.0 et TLS 1.1 sont désactivés.
Pour vérifier explicitement les protocoles :
1
2
3
4
5
6
7
8
for version in tls1 tls1_1 tls1_2 tls1_3; do
echo "===== $version ====="
openssl s_client \
-connect yannig.net:465 \
-servername yannig.net \
-$version </dev/null 2>&1 | grep -E \
'Protocol|Cipher|alert|handshake failure'
done
Pour le port SMTP 587, le mode correct est généralement STARTTLS :
1
2
3
4
5
6
openssl s_client \
-connect yannig.net:587 \
-servername yannig.net \
-starttls smtp \
-verify_return_error \
-brief </dev/null
Le même test peut être effectué sur le port 25 :
1
2
3
4
5
6
openssl s_client \
-connect yannig.net:25 \
-servername yannig.net \
-starttls smtp \
-verify_return_error \
-brief </dev/null
La commande openssl s_client -connect hôte:port convient aux connexions TLS implicites comme 465 et 993, tandis que -starttls smtp est nécessaire pour tester STARTTLS sur 25 ou 587.
4. Vérification du protocole IMAP
Pour vérifier que le serveur ne fait pas uniquement une négociation TLS mais répond bien en IMAP :
1
2
3
4
5
6
printf 'a001 CAPABILITY\r\na002 LOGOUT\r\n' |
openssl s_client \
-connect yannig.net:993 \
-servername yannig.net \
-quiet \
-crlf
Vous devez obtenir une réponse contenant notamment une ligne similaire à :
1
* CAPABILITY IMAP4rev1 ...
Testez ensuite l’authentification uniquement avec un compte de test :
1
2
3
4
openssl s_client \
-connect yannig.net:993 \
-servername yannig.net \
-crlf
Une fois connecté, utilisez les commandes IMAP suivantes :
1
2
3
4
a001 CAPABILITY
a002 LOGIN utilisateur@yannig.net MOT_DE_PASSE_DE_TEST
a003 LIST "" "*"
a004 LOGOUT
N’utilisez pas votre compte administrateur et ne laissez pas le mot de passe dans l’historique du shell.
5. Vérification SMTP authentifiée
Installez éventuellement swaks :
1
sudo apt install swaks
Pour tester l’envoi authentifié sur 465 :
1
2
3
4
5
6
7
8
swaks \
--server yannig.net \
--port 465 \
--tls \
--auth LOGIN \
--auth-user utilisateur@yannig.net \
--to adresse-externe@example.net \
--from utilisateur@yannig.net
swaks demandera normalement le mot de passe de manière interactive selon votre version. Utilisez une boîte de test et une adresse externe que vous contrôlez.
Testez aussi la soumission sur 587 :
1
2
3
4
5
6
7
8
swaks \
--server yannig.net \
--port 587 \
--tls \
--auth LOGIN \
--auth-user utilisateur@yannig.net \
--to adresse-externe@example.net \
--from utilisateur@yannig.net
À vérifier dans les résultats :
- Le certificat TLS est valide.
- L’authentification échoue avec un mauvais mot de passe.
- L’envoi sans authentification depuis
465/587est refusé. - Le serveur n’est pas un relais ouvert.
- L’adresse d’expéditeur est correctement contrôlée.
- Le message reçu contient
Authentication-Resultsavec SPF, DKIM et DMARC.
Pour tester rapidement l’absence de relais ouvert, utilisez :
1
2
3
4
5
swaks \
--server yannig.net \
--port 25 \
--from test-externe@example.net \
--to autre-adresse-externe@example.net
Ce test ne doit pas aboutir sans authentification lorsqu’il s’agit d’un envoi non autorisé.
6. SPF, DKIM et DMARC
SPF
1
dig TXT yannig.net +short
Il doit y avoir une politique SPF, par exemple :
1
v=spf1 mx ip4:ADRESSE_IPV4 -all
Adaptez-la aux véritables serveurs autorisés à envoyer des messages. Évitez d’avoir plusieurs TXT SPF différents.
DKIM
Depuis l’interface Stalwart, récupérez le sélecteur et la clé publique. Puis :
1
dig TXT SELECTEUR._domainkey.yannig.net +short
Envoyez ensuite un message vers une boîte Gmail, Outlook ou une autre boîte de test, puis affichez les en-têtes complets. Vous devez trouver une ligne indiquant une signature DKIM valide, par exemple :
1
dkim=pass
Stalwart génère et signe les messages sortants avec une clé DKIM dont la clé publique doit être publiée sous SELECTEUR._domainkey.yannig.net.
DMARC
1
dig TXT _dmarc.yannig.net +short
Pour commencer, une politique de surveillance peut ressembler à :
1
v=DMARC1; p=none; rua=mailto:dmarc@yannig.net
Après observation des rapports et correction des erreurs SPF/DKIM, vous pourrez évoluer vers :
1
v=DMARC1; p=quarantine; rua=mailto:dmarc@yannig.net
Puis éventuellement :
1
v=DMARC1; p=reject; rua=mailto:dmarc@yannig.net
Ne passez pas directement à p=reject si vous n’avez pas vérifié tous les systèmes qui envoient des messages pour yannig.net.
7. Tests web recommandés
Utilisez les services suivants avec yannig.net :
- Internet.nl – test de messagerie Vérifie notamment IPv6, DNSSEC, SPF, DKIM, DMARC, STARTTLS, DANE, RPKI et la sécurité TLS.
- MXToolbox
Testez successivement
MX,SPF,DMARC,DKIM, les listes noires et le DNS. MXToolbox fournit des vérificateurs spécialisés pour ces enregistrements. - Mail-Tester Générez une adresse temporaire, envoyez-lui un message depuis Stalwart et analysez le score ainsi que les résultats SPF, DKIM, DMARC, reverse DNS et réputation. Le test doit être effectué avec votre véritable serveur d’envoi.
- DNSRobot – test SMTP
Utile pour contrôler la bannière SMTP, les commandes
EHLO, STARTTLS et les ports25,465et587. - Mail SSL Checker
Vérifie directement les certificats et TLS sur
465,993, ainsi que STARTTLS sur les ports compatibles.
Les résultats d’Internet.nl sont particulièrement utiles pour une vue globale, car le service produit un rapport avec un score général et le détail de chaque sous-test.[^4]
8. Contrôles spécifiques Stalwart
Dans l’interface WebAdmin de Stalwart, vérifiez :
- Domaine
yannig.netprésent et activé. - Compte administrateur distinct des comptes de messagerie.
- Certificat TLS associé au bon nom d’hôte.
- Services IMAP, SMTP et éventuellement JMAP activés uniquement sur les interfaces nécessaires.
- Authentification SMTP obligatoire pour
465et587. - Relaying sortant limité aux utilisateurs authentifiés.
- DKIM activé pour les messages sortants.
- Vérification SPF, DKIM et DMARC activée pour les messages entrants.
- Limitation de débit et protection contre les abus.
- Journaux d’authentification et de remise surveillés.
- File d’attente SMTP vide ou sans erreurs répétées.
- Aucun compte ou alias compromis.
Suivez les journaux pendant un test :
1
sudo journalctl -fu stalwart
Si Stalwart fonctionne dans Docker :
1
docker compose logs -f --tail=200 stalwart
ou :
1
docker logs -f --tail=200 NOM_DU_CONTENEUR_STALWART
Enfin, contrôlez les règles de pare-feu :
1
2
sudo ss -lntp | grep -E ':(25|465|587|993)\b'
sudo nft list ruleset
La configuration minimale attendue est donc : 25 accessible pour les échanges SMTP entrants, 465 en TLS implicite pour les clients, 587 en STARTTLS pour la soumission, et 993 en TLS implicite pour IMAP. Le certificat doit correspondre exactement au nom utilisé par le MX et par les clients, idéalement mail.yannig.net plutôt que le domaine nu si votre MX pointe vers ce nom.
Script vérification multi-domaines
Enregistrement MX directement vers yannig.net. est valide dans votre cas, puisque le domaine possède lui-même les enregistrements A et AAAA. Le certificat TLS présenté sur les ports 465 et 993 devra donc contenir yannig.net dans son SAN, et non obligatoirement mail.yannig.net.
Le script ci-dessous vérifie le DNS, le reverse DNS, les ports IPv4/IPv6, les certificats TLS, SMTP, IMAP, SPF, DMARC et DKIM si vous fournissez le sélecteur DKIM. Il reprend vos adresses 51.38.37.240 et 2001:41d0:305:2100::c1c.
Il faut maintenant distinguer le serveur de transport commun (yannig.net) des trois domaines signés. Le MX peut rester identique pour les trois domaines :
1
2
3
yannig.net. MX 10 yannig.net.
cinay.eu. MX 10 yannig.net.
yick.eu. MX 10 yannig.net.
Les six sélecteurs DKIM doivent être testés indépendamment. Pour DKIM, ed25519 et rsa sont indiqués dans le champ k= ; le terme « SHA-256 » correspond à l’algorithme de signature, pas au type de clé DNS. Le type Ed25519 est officiellement identifié par k=ed25519.rfc-editor
Ce script vérifie :
- les enregistrements
A,AAAAetMX; - les reverse DNS IPv4/IPv6 ;
- SPF et DMARC pour les trois domaines ;
- les deux sélecteurs DKIM de chaque domaine ;
- le type de clé
ed25519oursa; - les ports
25,465,587et993; - TLS implicite sur
465et993; - STARTTLS sur
25et587; - les capacités SMTP et IMAP ;
- la correspondance du certificat avec
yannig.net.Script complet
Enregistrez-le dans check-stalwart-multi-domain.sh :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
#!/usr/bin/env bash
set -u
set -o pipefail
readonly MAIL_HOST="yannig.net"
readonly EXPECTED_IPV4="51.38.37.240"
readonly EXPECTED_IPV6="2001:41d0:305:2100::c1c"
readonly SMTP_PORT=25
readonly SMTPS_PORT=465
readonly SUBMISSION_PORT=587
readonly IMAPS_PORT=993
readonly REPORT_DIR="./stalwart-report-$(date +%Y%m%d-%H%M%S)"
readonly REPORT_FILE="$REPORT_DIR/report.txt"
readonly DOMAINS=(
"yannig.net"
"cinay.eu"
"yick.eu"
)
declare -A DKIM_SELECTORS=(
["yannig.net"]="v1-ed25519-20260720 v1-rsa-20260720"
["cinay.eu"]="v1-ed25519-20260723 v1-rsa-20260723"
["yick.eu"]="v1-ed25519-20260722 v1-rsa-20260722"
)
mkdir -p "$REPORT_DIR"
touch "$REPORT_FILE"
PASS=0
WARN=0
FAIL=0
GREEN=$'\033[32m'
YELLOW=$'\033[33m'
RED=$'\033[31m'
BLUE=$'\033[34m'
RESET=$'\033[0m'
log() {
local level="$1"
shift
local message="$*"
case "$level" in
PASS)
printf '%b[PASS]%b %s\n' "$GREEN" "$RESET" "$message"
printf '[PASS] %s\n' "$message" >>"$REPORT_FILE"
PASS=$((PASS + 1))
;;
WARN)
printf '%b[WARN]%b %s\n' "$YELLOW" "$RESET" "$message"
printf '[WARN] %s\n' "$message" >>"$REPORT_FILE"
WARN=$((WARN + 1))
;;
FAIL)
printf '%b[FAIL]%b %s\n' "$RED" "$RESET" "$message"
printf '[FAIL] %s\n' "$message" >>"$REPORT_FILE"
FAIL=$((FAIL + 1))
;;
INFO)
printf '%b[INFO]%b %s\n' "$BLUE" "$RESET" "$message"
printf '[INFO] %s\n' "$message" >>"$REPORT_FILE"
;;
esac
}
section() {
printf '\n===== %s =====\n' "$1" | tee -a "$REPORT_FILE"
}
require_command() {
if ! command -v "$1" >/dev/null 2>&1; then
log FAIL "Commande manquante : $1"
return 1
fi
}
dns_short() {
local type="$1"
local name="$2"
dig +short "$name" "$type" 2>/dev/null
}
check_dns_domain() {
local domain="$1"
local a aaaa mx txt spf dmarc
section "DNS $domain"
a="$(dns_short A "$domain")"
aaaa="$(dns_short AAAA "$domain")"
mx="$(dns_short MX "$domain")"
txt="$(dns_short TXT "$domain")"
spf="$(grep -i 'v=spf1' <<<"$txt" || true)"
dmarc="$(dns_short TXT "_dmarc.$domain")"
{
echo "[$domain]"
echo "A:"
echo "$a"
echo "AAAA:"
echo "$aaaa"
echo "MX:"
echo "$mx"
echo "TXT:"
echo "$txt"
echo "SPF:"
echo "$spf"
echo "DMARC:"
echo "$dmarc"
} >>"$REPORT_FILE"
if [[ "$domain" == "$MAIL_HOST" ]]; then
if grep -Fxq "$EXPECTED_IPV4" <<<"$a"; then
log PASS "$domain : A = $EXPECTED_IPV4"
else
log FAIL "$domain : A différent de $EXPECTED_IPV4"
fi
if grep -Fxq "$EXPECTED_IPV6" <<<"$aaaa"; then
log PASS "$domain : AAAA = $EXPECTED_IPV6"
else
log FAIL "$domain : AAAA différent de $EXPECTED_IPV6"
fi
fi
if grep -Eq '(^|[[:space:]])10[[:space:]]+yannig\.net\.?$|yannig\.net\.$' <<<"$mx"; then
log PASS "$domain : MX vers yannig.net"
else
log FAIL "$domain : MX inattendu : ${mx//$'\n'/ }"
fi
local spf_count
spf_count="$(grep -ci 'v=spf1' <<<"$txt" || true)"
if [[ "$spf_count" -eq 1 ]]; then
log PASS "$domain : un unique SPF détecté"
elif [[ "$spf_count" -eq 0 ]]; then
log FAIL "$domain : SPF absent"
else
log FAIL "$domain : plusieurs SPF détectés ($spf_count)"
fi
if grep -q 'v=DMARC1' <<<"$dmarc"; then
log PASS "$domain : DMARC détecté"
else
log FAIL "$domain : DMARC absent ou invalide"
fi
}
check_dkim_domain() {
local domain="$1"
local selectors="${DKIM_SELECTORS[$domain]}"
local selector record expected_type
section "DKIM $domain"
for selector in $selectors; do
record="$(dns_short TXT "${selector}._domainkey.${domain}")"
printf '[DKIM] %s._domainkey.%s\n%s\n' \
"$selector" "$domain" "$record" >>"$REPORT_FILE"
if [[ -z "$record" ]]; then
log FAIL "$domain / $selector : enregistrement DKIM absent"
continue
fi
if ! grep -q 'v=DKIM1' <<<"$record"; then
log FAIL "$domain / $selector : v=DKIM1 absent"
else
log PASS "$domain / $selector : v=DKIM1 présent"
fi
if ! grep -q 'p=[A-Za-z0-9+/=]' <<<"$record"; then
log FAIL "$domain / $selector : clé publique p= absente"
else
log PASS "$domain / $selector : clé publique présente"
fi
if [[ "$selector" == *ed25519* ]]; then
expected_type="ed25519"
elif [[ "$selector" == *rsa* ]]; then
expected_type="rsa"
else
expected_type=""
fi
if [[ -n "$expected_type" ]]; then
if grep -Eiq "k[[:space:]]*=[[:space:]]*$expected_type" <<<"$record"; then
log PASS "$domain / $selector : type k=$expected_type confirmé"
else
log WARN "$domain / $selector : le sélecteur suggère k=$expected_type, mais le DNS ne le confirme pas"
fi
fi
done
}
check_reverse_dns() {
section "REVERSE DNS"
local ptr4 ptr6
ptr4="$(dig -x "$EXPECTED_IPV4" PTR +short | sed 's/\.$//' | head -n1)"
ptr6="$(dig -x "$EXPECTED_IPV6" PTR +short | sed 's/\.$//' | head -n1)"
if [[ -n "$ptr4" ]]; then
log PASS "PTR IPv4 : $EXPECTED_IPV4 -> $ptr4"
if dig +short A "$ptr4" | grep -Fxq "$EXPECTED_IPV4"; then
log PASS "PTR IPv4 confirmé par résolution directe"
else
log WARN "Le PTR IPv4 ne revient pas vers $EXPECTED_IPV4"
fi
else
log FAIL "PTR IPv4 absent pour $EXPECTED_IPV4"
fi
if [[ -n "$ptr6" ]]; then
log PASS "PTR IPv6 : $EXPECTED_IPV6 -> $ptr6"
if dig +short AAAA "$ptr6" | grep -Fxq "$EXPECTED_IPV6"; then
log PASS "PTR IPv6 confirmé par résolution directe"
else
log WARN "Le PTR IPv6 ne revient pas vers $EXPECTED_IPV6"
fi
else
log WARN "PTR IPv6 absent pour $EXPECTED_IPV6"
fi
}
check_port() {
local family="$1"
local port="$2"
local label="$3"
if timeout 8 nc "$family" -zv "$MAIL_HOST" "$port" \
>>"$REPORT_FILE" 2>&1; then
log PASS "$label : $MAIL_HOST:$port accessible"
else
log FAIL "$label : $MAIL_HOST:$port inaccessible"
fi
}
check_ports() {
section "PORTS TCP"
check_port -4 "$SMTP_PORT" "IPv4 SMTP"
check_port -6 "$SMTP_PORT" "IPv6 SMTP"
check_port -4 "$SMTPS_PORT" "IPv4 SMTPS"
check_port -6 "$SMTPS_PORT" "IPv6 SMTPS"
check_port -4 "$SUBMISSION_PORT" "IPv4 Submission"
check_port -6 "$SUBMISSION_PORT" "IPv6 Submission"
check_port -4 "$IMAPS_PORT" "IPv4 IMAPS"
check_port -6 "$IMAPS_PORT" "IPv6 IMAPS"
}
check_tls() {
local family="$1"
local port="$2"
local mode="$3"
local label="$4"
local output
local family_option
family_option="$family"
if [[ "$mode" == "implicit" ]]; then
output="$(
timeout 15 openssl s_client \
"$family_option" \
-connect "$MAIL_HOST:$port" \
-servername "$MAIL_HOST" \
-verify_return_error \
-brief </dev/null 2>&1
)"
else
output="$(
timeout 15 openssl s_client \
"$family_option" \
-connect "$MAIL_HOST:$port" \
-servername "$MAIL_HOST" \
-starttls smtp \
-verify_return_error \
-brief </dev/null 2>&1
)"
fi
printf '\n[%s]\n%s\n' "$label" "$output" >>"$REPORT_FILE"
if grep -q 'Verification: OK' <<<"$output"; then
log PASS "$label : certificat validé"
elif grep -qE 'CONNECTION ESTABLISHED|CONNECTED|New, TLS' <<<"$output"; then
log WARN "$label : TLS actif, mais validation complète non confirmée"
else
log FAIL "$label : échec TLS"
fi
if grep -qiE 'TLSv1\.[23]' <<<"$output"; then
log PASS "$label : TLS 1.2 ou TLS 1.3 négocié"
fi
}
check_tls_all() {
section "TLS"
check_tls -4 "$SMTPS_PORT" implicit "IPv4 SMTPS 465"
check_tls -6 "$SMTPS_PORT" implicit "IPv6 SMTPS 465"
check_tls -4 "$IMAPS_PORT" implicit "IPv4 IMAPS 993"
check_tls -6 "$IMAPS_PORT" implicit "IPv6 IMAPS 993"
check_tls -4 "$SMTP_PORT" starttls "IPv4 SMTP STARTTLS 25"
check_tls -6 "$SMTP_PORT" starttls "IPv6 SMTP STARTTLS 25"
check_tls -4 "$SUBMISSION_PORT" starttls "IPv4 Submission STARTTLS 587"
check_tls -6 "$SUBMISSION_PORT" starttls "IPv6 Submission STARTTLS 587"
}
check_smtp() {
local family="$1"
local port="$2"
local label="$3"
local output
output="$(
timeout 12 openssl s_client \
"$family" \
-connect "$MAIL_HOST:$port" \
-servername "$MAIL_HOST" \
-quiet \
-crlf 2>/dev/null <<'EOF'
EHLO check.yannig.net
QUIT
EOF
)"
printf '\n[%s]\n%s\n' "$label" "$output" >>"$REPORT_FILE"
if grep -qiE '220 |250[- ]' <<<"$output"; then
log PASS "$label : réponse SMTP reçue"
else
log WARN "$label : réponse SMTP non détectée"
fi
}
check_imap() {
local family="$1"
local label="$2"
local output
output="$(
timeout 12 openssl s_client \
"$family" \
-connect "$MAIL_HOST:$IMAPS_PORT" \
-servername "$MAIL_HOST" \
-quiet \
-crlf 2>/dev/null <<'EOF'
a001 CAPABILITY
a002 LOGOUT
EOF
)"
printf '\n[%s]\n%s\n' "$label" "$output" >>"$REPORT_FILE"
if grep -qiE 'CAPABILITY|IMAP4' <<<"$output"; then
log PASS "$label : réponse IMAP reçue"
else
log FAIL "$label : réponse IMAP absente"
fi
}
check_protocols() {
section "PROTOCOLES"
check_smtp -4 "$SMTPS_PORT" "SMTP TLS IPv4 465"
check_smtp -6 "$SMTPS_PORT" "SMTP TLS IPv6 465"
check_imap -4 "IMAP TLS IPv4 993"
check_imap -6 "IMAP TLS IPv6 993"
}
check_certificate_names() {
section "CERTIFICAT"
local output san
output="$(
timeout 15 openssl s_client \
-connect "$MAIL_HOST:$SMTPS_PORT" \
-servername "$MAIL_HOST" \
-showcerts </dev/null 2>/dev/null |
openssl x509 -noout -subject -issuer -dates -ext subjectAltName 2>/dev/null
)"
printf '%s\n' "$output" >>"$REPORT_FILE"
san="$(grep -o 'DNS:[^, ]*' <<<"$output" || true)"
if grep -q "DNS:$MAIL_HOST" <<<"$san"; then
log PASS "Le certificat contient DNS:$MAIL_HOST"
else
log FAIL "Le certificat ne contient pas DNS:$MAIL_HOST"
fi
if grep -q 'notAfter' <<<"$output"; then
log INFO "Dates du certificat enregistrées dans le rapport"
fi
}
summary() {
section "RÉSUMÉ"
log INFO "Rapport : $REPORT_FILE"
log INFO "PASS=$PASS WARN=$WARN FAIL=$FAIL"
if [[ "$FAIL" -eq 0 ]]; then
log PASS "Aucune erreur bloquante détectée"
return 0
fi
log FAIL "$FAIL erreur(s) à corriger"
return 1
}
main() {
section "PARAMÈTRES"
log INFO "Serveur MX : $MAIL_HOST"
log INFO "IPv4 : $EXPECTED_IPV4"
log INFO "IPv6 : $EXPECTED_IPV6"
log INFO "Domaines : ${DOMAINS[*]}"
log INFO "Date : $(date --iso-8601=seconds)"
local command
for command in dig openssl nc timeout; do
require_command "$command" || exit 2
done
local domain
for domain in "${DOMAINS[@]}"; do
check_dns_domain "$domain"
check_dkim_domain "$domain"
done
check_reverse_dns
check_ports
check_tls_all
check_protocols
check_certificate_names
summary
}
main "$@"
Exécution
1
2
chmod +x check-stalwart-multi-domain.sh
./check-stalwart-multi-domain.sh
Le rapport sera généré dans un répertoire de ce type :
1
stalwart-report-20260813-063200/report.txt
Enregistrements DKIM testés
Le script interrogera automatiquement les noms DNS suivants :
1
2
3
4
5
6
7
8
v1-ed25519-20260720._domainkey.yannig.net
v1-rsa-20260720._domainkey.yannig.net
v1-ed25519-20260723._domainkey.cinay.eu
v1-rsa-20260723._domainkey.cinay.eu
v1-ed25519-20260722._domainkey.yick.eu
v1-rsa-20260722._domainkey.yick.eu
Les enregistrements attendus doivent ressembler à ceci :
v1-ed25519-20260720._domainkey.yannig.net. TXT "v=DKIM1; k=ed25519; p=..."
v1-rsa-20260720._domainkey.yannig.net. TXT "v=DKIM1; k=rsa; p=..."
Les champs p= contiennent uniquement les clés publiques. Les clés privées doivent rester dans Stalwart et ne doivent jamais être publiées dans le DNS. Le format DNS d’un enregistrement DKIM utilise le sélecteur suivi de ._domainkey, avec v=DKIM1, k= et p=.[^2]
SPF recommandé
Chaque domaine envoyant directement via le serveur yannig.net devrait autoriser les deux adresses du serveur :
yannig.net. TXT "v=spf1 ip4:51.38.37.240 ip6:2001:41d0:305:2100::c1c mx -all"
cinay.eu. TXT "v=spf1 ip4:51.38.37.240 ip6:2001:41d0:305:2100::c1c mx -all"
yick.eu. TXT "v=spf1 ip4:51.38.37.240 ip6:2001:41d0:305:2100::c1c mx -all"
Ne publiez qu’un seul enregistrement v=spf1 par domaine.
Point à vérifier dans Stalwart
Lors de la rotation automatique des clés :
- les anciennes clés doivent rester publiées pendant la durée de validité des messages déjà envoyés ;
- la nouvelle clé privée doit correspondre exactement à la clé publique DNS ;
- Stalwart doit signer avec le nouveau sélecteur ;
- l’ancien sélecteur ne doit être retiré du DNS qu’après expiration des anciens messages ;
- les en-têtes des messages doivent contenir le bon couple
d=ets=.
Dans un message envoyé, vérifiez par exemple :
1
2
3
DKIM-Signature:
d=yannig.net;
s=v1-ed25519-20260720;
ou :
1
2
3
DKIM-Signature:
d=cinay.eu;
s=v1-rsa-20260723;
Le d= doit correspondre au domaine expéditeur et le s= à l’un des sélecteurs actuellement publiés.
Niveau de SPAM ?
un moyen de tester le niveau de spam accepté par le domaine
Il faut distinguer deux notions :
- La réputation de vos domaines/IP pour les messages sortants : risque d’arriver dans le spam chez Gmail, Outlook, etc.
- Le filtrage des messages entrants par Stalwart : quantité de spam acceptée, rejetée ou classée en quarantaine.
Il n’existe pas un score public unique indiquant « le niveau de spam accepté » par yannig.net, cinay.eu ou yick.eu.
Tester la réputation sortante
1. Mail-Tester
Utilisez Mail-Tester :
- Ouvrez le site.
- Copiez l’adresse temporaire fournie.
- Envoyez un message depuis chacun de vos domaines :
test@yannig.nettest@cinay.eutest@yick.eu
- Analysez séparément les résultats.
Cela vérifie notamment :
- SPF ;
- DKIM ;
- DMARC ;
- reverse DNS ;
- réputation de l’IP ;
- présence éventuelle dans des listes noires ;
- contenu du message ;
- score SpamAssassin.
Le test doit être réalisé avec un véritable message envoyé par Stalwart, pas avec un simple message composé localement.
Avec swaks :
1
2
3
4
5
6
7
8
swaks \
--server yannig.net \
--port 465 \
--tls \
--auth LOGIN \
--auth-user test@yannig.net \
--to ADRESSE_MAIL_TESTER \
--from test@yannig.net
Répétez avec :
1
--auth-user test@cinay.eu --from test@cinay.eu
puis :
1
--auth-user test@yick.eu --from test@yick.eu
N’utilisez pas votre compte administrateur.
2. Vérification des listes noires
Testez les adresses suivantes :
Testez au minimum :
1
2
3
4
yannig.net
cinay.eu
yick.eu
51.38.37.240
Spamhaus précise que la réputation d’un domaine dépend notamment de son historique d’utilisation, de son comportement d’envoi et des signalements associés.
Attention : une IP propre ne garantit pas que les messages arriveront en boîte principale. Gmail et Outlook utilisent également l’engagement des destinataires, les plaintes, le contenu, les volumes et l’historique.
Mesurer la réputation chez Gmail
Pour un domaine qui envoie régulièrement vers Gmail, utilisez Google Postmaster Tools.
Google y expose notamment :
- le taux de messages signalés comme spam ;
- les résultats SPF, DKIM et DMARC ;
- les erreurs de livraison ;
- la réputation de l’IP et du domaine, lorsque suffisamment de données sont disponibles.
Google indique que Postmaster Tools exploite les messages authentifiés par DKIM, ou SPF si DKIM n’est pas disponible, pour afficher les données de réputation.
Ajoutez et vérifiez les trois domaines :
1
2
3
yannig.net
cinay.eu
yick.eu
Le domaine doit normalement avoir un volume suffisant de messages vers des comptes Gmail personnels avant que les statistiques soient disponibles. Avec un faible trafic, l’absence de données ne signifie pas que la réputation est mauvaise.
Pour un envoi important vers Gmail, Google demande une authentification correcte et recommande de maintenir le taux de plaintes très bas ; les exigences spécifiques aux expéditeurs de gros volume sont détaillées dans ses directives officielles.
Tester concrètement le classement en spam
Créez une matrice de test avec plusieurs fournisseurs :
| Fournisseur | Adresse de destination |
|---|---|
| Gmail | compte Gmail personnel |
| Outlook | compte Outlook.com |
| Yahoo | compte Yahoo |
| Proton Mail | compte Proton Mail |
| Mail-Tester | adresse temporaire |
| Votre propre domaine | boîte locale de test |
Pour chaque domaine, envoyez un message identique :
1
2
3
4
5
From: test@yannig.net
To: compte-de-test@gmail.com
Subject: Test délivrabilité yannig.net
Message de test de délivrabilité.
Répétez avec cinay.eu et yick.eu, puis contrôlez les en-têtes complets du message :
1
2
3
4
Authentication-Results:
spf=pass
dkim=pass
dmarc=pass
À vérifier aussi :
1
2
3
DKIM-Signature:
d=yannig.net;
s=v1-ed25519-20260720;
ou :
1
2
3
DKIM-Signature:
d=cinay.eu;
s=v1-rsa-20260723;
Un message qui arrive en spam malgré spf=pass, dkim=pass et dmarc=pass peut simplement souffrir d’une mauvaise réputation, d’un contenu douteux, d’un volume inhabituel ou d’un manque d’historique.
Tester le filtrage entrant de Stalwart
Pour mesurer ce que votre serveur accepte comme spam entrant, utilisez une boîte de test, par exemple :
1
spam-test@yannig.net
Envoyez-lui des messages de test depuis une adresse externe. Le plus pratique est d’utiliser des messages de catégories différentes :
- message normal ;
- message avec URL ;
- message contenant une pièce jointe ;
- message avec expéditeur non authentifié ;
- message avec SPF/DKIM/DMARC invalides ;
- message de test GTUBE.
Test GTUBE
GTUBE est une chaîne standard utilisée pour vérifier qu’un moteur antispam détecte un message de test. Ne l’envoyez jamais à une vraie boîte utilisateur ou à une liste de diffusion.
Utilisez une boîte dédiée, puis envoyez le contenu suivant dans le corps du message :
1
XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X
Exemple avec swaks depuis une machine externe :
1
2
3
4
5
6
7
8
9
10
swaks \
--server yannig.net \
--port 465 \
--tls \
--auth LOGIN \
--auth-user test@yannig.net \
--to spam-test@yannig.net \
--from test@externe.example \
--header "Subject: GTUBE test - ne pas supprimer" \
--body 'XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X'
Le résultat attendu dépend de votre configuration Stalwart :
- rejet SMTP immédiat ;
- acceptation avec classement spam ;
- déplacement en quarantaine ;
- ajout d’un en-tête indiquant le score antispam ;
- acceptation normale si le filtre n’est pas activé.
Surveillez les journaux pendant le test :
1
sudo journalctl -fu stalwart
Avec Docker :
1
docker compose logs -f --tail=200 stalwart
Recherchez ensuite les éléments suivants :
1
2
docker compose logs stalwart |
grep -Ei 'spam|score|quarantine|reject|gtube|dkim|spf|dmarc'
Le test GTUBE vérifie surtout que le moteur antispam réagit bien à un motif connu. Il ne mesure pas à lui seul la qualité globale du filtrage.
Contrôle rapide des en-têtes entrants
Pour voir si un message a été classé par Stalwart, utilisez :
1
2
3
grep -Ei \
'Authentication-Results|X-Spam|X-Rspamd|X-Spam-Status|Subject|Received' \
message.eml
Les noms exacts des en-têtes dépendent de votre configuration. Vous pouvez notamment rechercher :
1
2
3
4
X-Spam-Status
X-Spam-Score
X-Spam-Level
Authentication-Results
Test automatisé simple
Voici un petit script qui envoie un message de contrôle vers une boîte externe à l’aide de swaks. Il ne teste pas directement le classement spam, mais permet de suivre les résultats de livraison et les en-têtes d’authentification.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#!/usr/bin/env bash
set -u
SMTP_HOST="yannig.net"
SMTP_PORT=465
TEST_DESTINATION="${1:?Usage: $0 adresse-de-test@example.net}"
declare -A SENDERS=(
["yannig.net"]="test@yannig.net"
["cinay.eu"]="test@cinay.eu"
["yick.eu"]="test@yick.eu"
)
for domain in "${!SENDERS[@]}"; do
sender="${SENDERS[$domain]}"
echo
echo "===== Test $domain ====="
echo "Expéditeur : $sender"
echo "Destination : $TEST_DESTINATION"
swaks \
--server "$SMTP_HOST" \
--port "$SMTP_PORT" \
--tls \
--auth LOGIN \
--auth-user "$sender" \
--to "$TEST_DESTINATION" \
--from "$sender" \
--header "Subject: Test délivrabilité $domain" \
--header "X-Mail-Test-Domain: $domain" \
--body "Test de délivrabilité envoyé depuis $domain."
done
Exécution :
1
2
chmod +x test-deliverability.sh
./test-deliverability.sh votre-compte-gmail@gmail.com
Pour un résultat fiable, faites plusieurs essais espacés dans le temps et observez les mêmes messages dans Gmail, Outlook et Proton Mail. Le meilleur indicateur pratique pour vos trois domaines sera la combinaison suivante : SPF=pass, DKIM=pass, DMARC=pass, reverse DNS cohérent, aucune blacklist, puis placement réel en boîte principale chez plusieurs fournisseurs.