La sonde demandait `doveadm user`, que le userdb static (static_allow_all_users,
requis par la remise LMTP) satisfait pour n'importe quel nom : elle disait
« daniel » resolu chez deux locataires ou ce compte n'existe pas. Elle interroge
desormais le passdb (`doveadm auth lookup`), sans mot de passe, et cherche le
compte d'amorcage (amorcage_acces_courriel) plutot qu'un nom ecrit.
Le 143 (IMAP en clair) ecoutait par defaut a cote du 993 : eteint par
`inet_listener imap { port = 0 }`, eprouve sur banc Debian 13.
Deploye chez les deux locataires : 24 et 993 seulement ; sonde verte sur
sysadmin, rouge sur un compte inexistant. test_dovecot_sonde_et_imap.py garde
les deux ; 95 preuves.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
65 lines
3.1 KiB
Python
65 lines
3.1 KiB
Python
#!/usr/bin/env python3
|
|
"""La sonde « boites » cherche un vrai compte, et Dovecot n'ouvre plus l'IMAP en clair.
|
|
|
|
POURQUOI (2026-10-12).
|
|
1. La sonde demandait `doveadm user`, que le userdb `static` (`static_allow_all_users`,
|
|
requis par la remise LMTP) satisfait pour N'IMPORTE QUEL nom. Elle disait « daniel »
|
|
resolu chez deux locataires ou ce compte n'existe pas.
|
|
2. Le 143 (IMAP en clair) ecoutait par defaut a cote du 993.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import jinja2
|
|
import yaml
|
|
|
|
RACINE = Path(__file__).resolve().parents[2]
|
|
DOVECOT = RACINE / "roles/serveur_dovecot"
|
|
|
|
|
|
def main() -> int:
|
|
echecs: list[str] = []
|
|
|
|
# 1. La sonde interroge le passdb, pas le userdb statique.
|
|
sonde = (DOVECOT / "templates/sonde-boites.sh.j2").read_text(encoding="utf-8")
|
|
code = "\n".join(l for l in sonde.splitlines() if not l.lstrip().startswith("#"))
|
|
if "doveadm auth lookup" not in code:
|
|
echecs.append("la sonde « boites » n'interroge pas le passdb (`doveadm auth lookup`)")
|
|
if re.search(r"doveadm\s+user\b", code):
|
|
echecs.append("la sonde « boites » interroge encore le userdb statique (`doveadm user`)")
|
|
if "67" not in code:
|
|
echecs.append("la sonde « boites » ne distingue pas le compte absent (rc 67)")
|
|
|
|
# 1b. Le compte cherche se derive du compte d'amorcage, il n'est pas ecrit.
|
|
defauts = yaml.safe_load((DOVECOT / "defaults/main.yml").read_text(encoding="utf-8"))
|
|
cible = str(defauts.get("serveur_dovecot_sonde_utilisateur", ""))
|
|
if "amorcage_acces_courriel" not in cible:
|
|
echecs.append(f"serveur_dovecot_sonde_utilisateur = {cible!r} : un nom ecrit, pas le compte d'amorcage")
|
|
|
|
# 2. Le 99-setops.conf rendu eteint l'ecoute IMAP en clair (port 0).
|
|
rendu = jinja2.Environment().from_string(
|
|
(DOVECOT / "templates/99-setops.conf.j2").read_text(encoding="utf-8")).render(
|
|
serveur_dovecot_mail_path="/var/vmail/%{user}", serveur_dovecot_vmail_uid=5000,
|
|
serveur_dovecot_vmail_gid=5000, serveur_dovecot_tls_dir="/etc/dovecot/tls",
|
|
serveur_dovecot_ldap_uris="ldaps://idm-01.exemple.internal:636",
|
|
serveur_dovecot_ldap_base="dc=exemple", serveur_dovecot_ldap_bind_dn="cn=dovecot",
|
|
serveur_dovecot_ldap_bind_password="banc", serveur_dovecot_ldap_filter="(mail=%{user})",
|
|
serveur_dovecot_vmail_utilisateur="vmail", serveur_dovecot_lmtp_reseau=True,
|
|
serveur_dovecot_lmtp_port=24, serveur_dovecot_lmtp_tls=True,
|
|
serveur_dovecot_postfix_present=False, serveur_dovecot_postfix_socket_dir="/x")
|
|
bloc = re.search(r"service imap-login \{\s*inet_listener imap \{\s*port = (\d+)", rendu)
|
|
if not bloc or bloc.group(1) != "0":
|
|
echecs.append("99-setops.conf n'eteint pas l'ecoute IMAP en clair (`inet_listener imap { port = 0 }`)")
|
|
|
|
for e in echecs:
|
|
print(f"ECHEC : {e}")
|
|
if not echecs:
|
|
print("OK : la sonde « boites » cherche le compte d'amorcage dans le passdb ; l'IMAP n'ecoute qu'en TLS (993).")
|
|
return 1 if echecs else 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|