Page 1 sur 1

Paquet Configuration Mozilla Thunderbird

Publié : 14 avr. 2026 - 18:01
par MarjoRie
Bonjour

j'aimerais savoir si certains d'entre vous passe par WAPT pour configurer Thunderbird
avec Prénom Nom, adresse mail, login, adresse du serveur, signature ... ?
comme discuté ici viewtopic.php?f=9&t=2522 et ici https://www.youtube.com/watch?v=ZdKk6-2HpbM Vidéo de 2019
Est-ce que le paquet Mozilla Thunderbird Configuration peut encore être utilisé avec la version Thunderbird ESR actuelle (140.9.1esr 64 bits) ?
https://wapt.tranquil.it/store/fr/tis-t ... g-template

Merci pour votre réponse

Marjorie

Re: Paquet Configuration Mozilla Thunderbird

Publié : 17 avr. 2026 - 15:19
par blemoigne
Bonjour,
J'ai testé ça
il faut modifier les serveurs imap/smtp et indiquer un chemin vers un répertoire de signatures HTML (ici les noms de fichier de signature sont du type <username>.html)

Code : Tout sélectionner

# -*- coding: utf-8 -*-

import os
import sys
import re
import glob
import uuid
import platform

try:
    import configparser
except ImportError:
    import ConfigParser as configparser

# Dépendance LDAP/Kerberos déjà utilisée dans ton paquet
import pyldap


# ═══════════════════════════════════════════════════════════════
#  VARIABLES DU PAQUET
#  Garde ici tes valeurs actuelles
# ═══════════════════════════════════════════════════════════════

TLS_VERSION_MIN = 3
TLS_VERSION_MAX = 4

IMAP_HOST = "imap.domain.lan"
IMAP_PORT = 993

SMTP_HOST = "smtp.domain.lan"
SMTP_PORT = 587


# ═══════════════════════════════════════════════════════════════
#  SIGNATURE HTML
# ═══════════════════════════════════════════════════════════════

# Windows : chemin demandé
SIGNATURE_WINDOWS_BASE = (
    r"\\chemin\des\signatures\html"
)

# Linux : à adapter selon ton montage réel
# Recommandation Debian/Cinnamon : montage CIFS fixe sous /mnt ou /srv
SIGNATURE_LINUX_CANDIDATES = (
    "/mnt/chemin/signatures",
    "/srv/chemin/signatures",
    os.path.expanduser(
        "~/chemin/signatures"
    ),
)


# ═══════════════════════════════════════════════════════════════
#  DÉTECTION THUNDERBIRD
# ═══════════════════════════════════════════════════════════════

def get_thunderbird_dir():
    """
    Retourne le répertoire d'installation de Thunderbird selon l'OS.
    Sur Linux, teste plusieurs chemins possibles selon la distro/version.
    """
    system = platform.system()

    if system == "Windows":
        candidates = (
            r"C:\Program Files\Mozilla Thunderbird",
            r"C:\Program Files (x86)\Mozilla Thunderbird",
        )
        for candidate in candidates:
            if os.path.isdir(candidate):
                return candidate
        return None

    elif system == "Darwin":
        candidate = "/Applications/Thunderbird.app/Contents/Resources"
        return candidate if os.path.isdir(candidate) else None

    else:  # Linux
        for candidate in (
            "/usr/lib/thunderbird",
            "/usr/lib/thunderbird-esr",
            "/usr/lib64/thunderbird",
            "/usr/share/thunderbird",
        ):
            if os.path.isdir(candidate):
                return candidate

    return None


# ═══════════════════════════════════════════════════════════════
#  CONTENU DES FICHIERS EMBARQUÉS
# ═══════════════════════════════════════════════════════════════

AUTOCONFIG_JS = """\
pref("general.config.filename", "mozilla.cfg");
pref("general.config.obscure_value", 0);
"""

MOZILLA_CFG = (
    "// mozilla.cfg — Configuration entreprise Thunderbird\n"
    "// Contient UNIQUEMENT les paramètres réseau et sécurité verrouillés.\n"
    "// Tout le reste (compte, identité, userName, signature) est géré par session_setup()\n"
    "// via lookup LDAP/Kerberos dans prefs.js.\n"
    "\n"
    "// Sécurité TLS\n"
    'lockPref("security.tls.version.min", %d);\n'
    'lockPref("security.tls.version.max", %d);\n'
    "\n"
    "// IMAP — hostname, port, chiffrement, auth\n"
    'lockPref("mail.server.server1.hostname",   "%s");\n'
    'lockPref("mail.server.server1.port",       %d);\n'
    'lockPref("mail.server.server1.socketType", 3);\n'
    'lockPref("mail.server.server1.authMethod", 5);\n'
    "\n"
    "// SMTP — hostname, port, chiffrement, auth\n"
    'lockPref("mail.smtpserver.smtp1.hostname",   "%s");\n'
    'lockPref("mail.smtpserver.smtp1.port",       %d);\n'
    'lockPref("mail.smtpserver.smtp1.try_ssl",    2);\n'
    'lockPref("mail.smtpserver.smtp1.authMethod", 5);\n'
) % (
    TLS_VERSION_MIN,
    TLS_VERSION_MAX,
    IMAP_HOST,
    IMAP_PORT,
    SMTP_HOST,
    SMTP_PORT,
)

POLICIES_JSON = (
    '{\n'
    '  "policies": {\n'
    '    "DisableSecurityBypass": { "InvalidCertificate": true },\n'
    '    "PasswordManagerEnabled": false,\n'
    '    "BlockAboutConfig": true,\n'
    '    "Preferences": {\n'
    '      "mail.server.server1.port":         { "Value": %d, "Status": "locked" },\n'
    '      "mail.server.server1.authMethod":   { "Value": 5,  "Status": "locked" },\n'
    '      "mail.smtpserver.smtp1.port":       { "Value": %d, "Status": "locked" },\n'
    '      "mail.smtpserver.smtp1.authMethod": { "Value": 5,  "Status": "locked" },\n'
    '      "security.tls.version.min":         { "Value": %d, "Status": "locked" }\n'
    '    }\n'
    '  }\n'
    '}\n'
) % (
    IMAP_PORT,
    SMTP_PORT,
    TLS_VERSION_MIN,
)


# ═══════════════════════════════════════════════════════════════
#  HELPERS COMMUNS
# ═══════════════════════════════════════════════════════════════

def get_current_user():
    """Login court, multiplateforme."""
    return os.environ.get("USERNAME") or os.environ.get("USER") or ""


def get_kerberos_realm(prefs_content=""):
    """
    Détermine le realm Kerberos de manière robuste.

    Ordre de recherche :
    1. Windows : USERDNSDOMAIN
    2. prefs.js existant : mail.server.server1.userName = login@REALM
    3. klist (sortie localisée possible)
    4. Windows : USERDOMAIN
    """
    import subprocess

    system = platform.system()

    # 1. Windows : USERDNSDOMAIN (le plus fiable en environnement AD)
    if system == "Windows":
        userdnsdomain = os.environ.get("USERDNSDOMAIN", "").strip()
        if userdnsdomain:
            realm = userdnsdomain.upper()
            print("[INFO] Realm détecté via USERDNSDOMAIN : %s" % realm)
            return realm

    # 2. prefs.js existant : on tente de relire un ancien userName
    if prefs_content:
        existing_user = get_pref_value(prefs_content, "mail.server.server1.userName")
        if existing_user and "@" in existing_user:
            realm = existing_user.split("@", 1)[1].strip().upper()
            if realm:
                print("[INFO] Realm détecté via prefs.js : %s" % realm)
                return realm

    # 3. klist : parser souple, sans dépendre d'une sortie anglaise
    if system == "Windows":
        system32 = os.path.join(
            os.environ.get("SystemRoot", r"C:\Windows"),
            "System32"
        )
        klist_cmd = os.path.join(system32, "klist.exe")
        if not os.path.isfile(klist_cmd):
            klist_cmd = "klist"
    else:
        klist_cmd = "klist"

    try:
        result = subprocess.run(
            [klist_cmd],
            capture_output=True,
            text=True,
            timeout=5
        )
        output = (result.stdout or "") + "\n" + (result.stderr or "")

        # Cherche une identité de type user@REALM sur n'importe quelle ligne
        for line in output.splitlines():
            match = re.search(r"\b[^\s@]+@([A-Z0-9._-]+\.[A-Z0-9._-]+|[A-Z0-9._-]+)\b", line, re.I)
            if match:
                realm = match.group(1).strip().upper()
                print("[INFO] Realm détecté via klist : %s" % realm)
                return realm

        print("[WARN] klist exécuté mais aucun realm exploitable n'a été trouvé")
    except Exception as e:
        print("[WARN] klist indisponible : %s" % e)

    # 4. Dernier fallback Windows : USERDOMAIN
    if system == "Windows":
        userdomain = os.environ.get("USERDOMAIN", "").strip()
        if userdomain:
            realm = userdomain.upper()
            print("[WARN] Realm déduit via USERDOMAIN : %s" % realm)
            return realm

    return None


def find_or_create_thunderbird_profile():
    """
    Retourne le chemin du profil Thunderbird par défaut.
    Si le profil n'existe pas encore, crée le dossier de profil
    et profiles.ini à la volée.
    """
    system = platform.system()

    if system == "Windows":
        tb_base = os.path.join(os.environ.get("APPDATA", ""), "Thunderbird")
        profiles_dir = os.path.join(tb_base, "Profiles")
    elif system == "Darwin":
        tb_base = os.path.expanduser("~/Library/Thunderbird")
        profiles_dir = os.path.join(tb_base, "Profiles")
    else:  # Linux
        tb_base = os.path.expanduser("~/.thunderbird")
        profiles_dir = tb_base

    profiles_ini = os.path.join(tb_base, "profiles.ini")

    # 1. installs.ini prioritaire
    installs_ini = os.path.join(tb_base, "installs.ini")
    if os.path.isfile(installs_ini):
        cfg = configparser.ConfigParser()
        cfg.read(installs_ini, encoding="utf-8")
        for section in cfg.sections():
            rel_path = cfg.get(section, "Default", fallback=None)
            if rel_path:
                full_path = os.path.normpath(os.path.join(tb_base, rel_path))
                print("[INFO] Profil trouvé via installs.ini : %s" % full_path)
                return full_path

    # 2. profiles.ini fallback
    if os.path.isfile(profiles_ini):
        cfg = configparser.ConfigParser()
        cfg.read(profiles_ini, encoding="utf-8")
        for section in cfg.sections():
            if cfg.get(section, "Default", fallback=None) == "1":
                is_relative = cfg.get(section, "IsRelative", fallback="1")
                rel_path = cfg.get(section, "Path", fallback=None)
                if rel_path:
                    if is_relative == "1":
                        return os.path.normpath(os.path.join(tb_base, rel_path))
                    return rel_path

        candidates_release = glob.glob(os.path.join(profiles_dir, "*.default-release"))
        candidates_default = glob.glob(os.path.join(profiles_dir, "*.default"))
        candidates_any = glob.glob(os.path.join(profiles_dir, "*.default*"))
        chosen = (candidates_release or candidates_default or candidates_any)
        if chosen:
            print("[INFO] Profil sélectionné (fallback glob) : %s" % chosen[0])
            return chosen[0]

    # Création d'un profil vide
    print("[INFO] Aucun profil Thunderbird trouvé, création d'un profil vide.")

    profile_dirname = "%s.default-release" % uuid.uuid4().hex[:8]
    profile_dir = os.path.join(profiles_dir, profile_dirname)

    os.makedirs(profile_dir, exist_ok=True)
    os.makedirs(tb_base, exist_ok=True)

    rel = os.path.relpath(profile_dir, tb_base).replace("\\", "/")

    ini_lines = [
        "[General]",
        "StartWithLastProfile=1",
        "Version=2",
        "",
        "[Profile0]",
        "Name=default-release",
        "IsRelative=1",
        "Path=" + rel,
        "Default=1",
        "",
    ]

    with open(profiles_ini, "w", encoding="utf-8") as f:
        f.write("\n".join(ini_lines))

    print("[OK] Profil créé : %s" % profile_dir)
    return profile_dir


def is_thunderbird_configured(prefs_path):
    """
    Retourne True si prefs.js contient une config IMAP complète.
    """
    if not os.path.isfile(prefs_path):
        return False

    with open(prefs_path, encoding="utf-8") as f:
        content = f.read()

    if "mail.server.server1.type" not in content:
        return False

    match = re.search(
        r'user_pref\("mail\.server\.server1\.userName",\s*"([^"]*)"\)',
        content
    )
    if not match or not match.group(1).strip():
        print("[INFO] userName manquant ou vide, reconfiguration nécessaire.")
        return False

    return True


def get_pref_value(content, key):
    pattern = r'user_pref\("%s",\s*"([^"]*)"\);' % re.escape(key)
    match = re.search(pattern, content)
    if match:
        return match.group(1)
    return None

def pref_js_escape(value):
    """
    Échappe une chaîne pour prefs.js.
    Important pour les chemins Windows.
    """
    return value.replace("\\", "\\\\").replace('"', '\\"')


def upsert_pref(content, key, value):
    """
    Insère ou met à jour une entrée user_pref() dans prefs.js.
    """
    if isinstance(value, str):
        serialized = '"%s"' % pref_js_escape(value)
    elif isinstance(value, bool):
        serialized = "true" if value else "false"
    else:
        serialized = str(value)

    pattern = r'user_pref\("%s",\s*[^)]+\);' % re.escape(key)
    new_line = 'user_pref("%s", %s);' % (key, serialized)

    if re.search(pattern, content):
        return re.sub(pattern, new_line, content)

    if content and not content.endswith("\n"):
        content += "\n"
    return content + new_line + "\n"


# ═══════════════════════════════════════════════════════════════
#  SIGNATURE HTML
# ═══════════════════════════════════════════════════════════════

def get_signature_html_path(login):
    """
    Retourne le chemin de signature HTML pour l'utilisateur.
    Teste d'abord <login>.html puis <login_lower>.html.
    """
    filenames = []
    if login:
        filenames.append("%s.html" % login)
        login_lower = login.lower()
        if login_lower != login:
            filenames.append("%s.html" % login_lower)

    system = platform.system()

    if system == "Windows":
        for filename in filenames:
            path = os.path.join(SIGNATURE_WINDOWS_BASE, filename)
            if os.path.isfile(path):
                return path
        return None

    if system == "Linux":
        for base in SIGNATURE_LINUX_CANDIDATES:
            for filename in filenames:
                path = os.path.join(base, filename)
                if os.path.isfile(path):
                    return path
        return None

    return None


def ensure_signature_only(prefs_path, login):
    content = ""
    if os.path.isfile(prefs_path):
        with open(prefs_path, encoding="utf-8") as f:
            content = f.read()

    new_content = apply_signature_prefs(content, login)

    if new_content != content:
        with open(prefs_path, "w", encoding="utf-8") as f:
            f.write(new_content)
        print("[OK] Signature HTML appliquée pour %s." % login)
    else:
        print("[INFO] Aucune modification de signature pour %s." % login)

def apply_signature_prefs(content, login):
    """
    Applique la signature HTML externe si le fichier existe.
    """
    sig_path = get_signature_html_path(login)

    if not sig_path:
        print("[WARN] Aucune signature HTML trouvée pour %s." % login)
        return content

    print("[INFO] Signature HTML détectée : %s" % sig_path)

    content = upsert_pref(content, "mail.identity.id1.compose_html", True)
    content = upsert_pref(content, "mail.identity.id1.attach_signature", True)
    content = upsert_pref(content, "mail.identity.id1.sig_file", sig_path)

    # Optionnel mais généralement souhaité
    content = upsert_pref(content, "mail.identity.id1.sig_on_reply", True)
    content = upsert_pref(content, "mail.identity.id1.sig_on_fwd", True)
    content = upsert_pref(
        content,
        "mail.identity.id1.suppress_signature_separator",
        True
    )

    return content


# ═══════════════════════════════════════════════════════════════
#  LOOKUP LDAP / KERBEROS
# ═══════════════════════════════════════════════════════════════

def fetch_user_info(login, realm):
    """
    Interroge l'AD via GSSAPI/Kerberos avec le ticket de session.
    Retourne (email, displayname).
    """
    client = pyldap.PyLdapClient(domain_name=realm.lower())
    r = client.bind_sasl_kerberos()

    if not r[0]:
        raise RuntimeError("Échec de la connexion Kerberos à l'Active Directory")

    ldap_filter = "(samaccountname=%s)" % login
    response = client.search_all(client.default_dn(), ldap_filter)

    mail = ""
    displayname = ""

    for entry in response:
        attrs = response[entry]

        if attrs.get("mail"):
            mail = attrs["mail"][0]

        if attrs.get("displayName"):
            displayname = attrs["displayName"][0]

        if not displayname:
            given = attrs.get("givenName", [""])[0]
            sn = attrs.get("sn", [""])[0]
            displayname = (given + " " + sn).strip()

    if not mail:
        raise RuntimeError("Attribut 'mail' introuvable dans l'AD pour : %s" % login)

    return mail, displayname


# ═══════════════════════════════════════════════════════════════
#  WAPT — install()  [contexte système]
# ═══════════════════════════════════════════════════════════════

def install():
    """
    Déploie mozilla.cfg, autoconfig.js et policies.json dans le
    répertoire d'installation de Thunderbird.
    """
    tb_dir = get_thunderbird_dir()
    if not tb_dir or not os.path.isdir(tb_dir):
        print("[WARN] Répertoire Thunderbird introuvable (%s), install ignorée." % tb_dir)
        return

    pref_dir = os.path.join(tb_dir, "defaults", "pref")
    distrib_dir = os.path.join(tb_dir, "distribution")

    for d in (pref_dir, distrib_dir):
        if not os.path.isdir(d):
            os.makedirs(d)

    def write_file(path, content):
        with open(path, "w", encoding="utf-8") as f:
            f.write(content)
        print("[OK] Écrit : %s" % path)

    write_file(os.path.join(tb_dir, "mozilla.cfg"), MOZILLA_CFG)
    write_file(os.path.join(pref_dir, "autoconfig.js"), AUTOCONFIG_JS)
    write_file(os.path.join(distrib_dir, "policies.json"), POLICIES_JSON)

    print("[OK] Fichiers de verrouillage Thunderbird déployés.")


# ═══════════════════════════════════════════════════════════════
#  WAPT — session_setup()  [contexte utilisateur]
# ═══════════════════════════════════════════════════════════════

def session_setup():
    """
    Exécuté en contexte utilisateur à chaque ouverture de session WAPT.
    1. Localise ou crée le profil Thunderbird.
    2. Si déjà configuré : met à jour la signature uniquement.
    3. Sinon : tente le lookup LDAP/Kerberos pour créer la config.
    4. Applique ensuite la signature HTML.
    """
    login = get_current_user()
    if not login:
        print("[ERREUR] Impossible de déterminer le login utilisateur.", file=sys.stderr)
        return

    profile_dir = find_or_create_thunderbird_profile()
    prefs_path = os.path.join(profile_dir, "prefs.js")

    print("[INFO] Profil cible   : %s" % profile_dir)
    print("[INFO] prefs.js cible : %s" % prefs_path)

    content = ""
    if os.path.isfile(prefs_path):
        with open(prefs_path, encoding="utf-8") as f:
            content = f.read()

    # 1. Si déjà configuré, on ne dépend surtout pas du Kerberos pour la signature
    if is_thunderbird_configured(prefs_path):
        print("[INFO] Thunderbird déjà configuré pour %s." % login)
        content = apply_signature_prefs(content, login)
        with open(prefs_path, "w", encoding="utf-8") as f:
            f.write(content)
        print("[OK] Signature / prefs mis à jour pour %s." % login)
        return

    # 2. Sinon seulement, on essaie le provisioning LDAP/Kerberos
    realm = get_kerberos_realm(content)
    if not realm:
        print("[ERREUR] Impossible de déterminer le realm Kerberos.", file=sys.stderr)

        # On tente malgré tout d'appliquer la signature, même si le compte n'est pas provisionné
        content = apply_signature_prefs(content, login)
        with open(prefs_path, "w", encoding="utf-8") as f:
            f.write(content)
        print("[WARN] Signature appliquée, mais provisioning LDAP non effectué.")
        return

    kerberos_user = "%s@%s" % (login, realm)

    try:
        email, displayname = fetch_user_info(login, realm)
    except Exception as e:
        print("[ERREUR] Lookup LDAP échoué pour %s : %s" % (login, e), file=sys.stderr)

        # Même logique : on n'empêche pas la signature
        content = apply_signature_prefs(content, login)
        with open(prefs_path, "w", encoding="utf-8") as f:
            f.write(content)
        print("[WARN] Signature appliquée malgré l'échec LDAP.")
        return

    print("[INFO] Login       : %s" % login)
    print("[INFO] Kerberos    : %s" % kerberos_user)
    print("[INFO] Mail        : %s" % email)
    print("[INFO] DisplayName : %s" % displayname)

    kerberos_user_encoded = kerberos_user.replace("@", "%40")
    imap_base_uri = "imap://%s@%s" % (kerberos_user_encoded, IMAP_HOST)

    dynamic_prefs = [
        ("mail.accountmanager.accounts", "account1,account2"),
        ("mail.accountmanager.defaultaccount", "account1"),
        ("mail.accountmanager.localfoldersserver", "server2"),
        ("mail.account.account1.identities", "id1"),
        ("mail.account.account1.server", "server1"),
        ("mail.account.account2.server", "server2"),
        ("mail.account.lastKey", 2),

        ("mail.identity.id1.fullName", displayname),
        ("mail.identity.id1.useremail", email),
        ("mail.identity.id1.smtpServer", "smtp1"),
        ("mail.identity.id1.valid", True),
        ("mail.identity.id1.reply_on_top", 1),

        ("mail.identity.id1.fcc_folder", "%s/Sent" % imap_base_uri),
        ("mail.identity.id1.fcc_folder_picker_mode", "0"),
        ("mail.identity.id1.draft_folder", "%s/Drafts" % imap_base_uri),
        ("mail.identity.id1.drafts_folder_picker_mode", "0"),
        ("mail.identity.id1.archive_folder", "%s/Archives" % imap_base_uri),
        ("mail.identity.id1.archives_folder_picker_mode", "1"),
        ("mail.identity.id1.stationery_folder", "%s/Templates" % imap_base_uri),
        ("mail.identity.id1.tmpl_folder_picker_mode", "0"),

        ("mail.server.server1.type", "imap"),
        ("mail.server.server1.name", email),
        ("mail.server.server1.userName", kerberos_user),
        ("mail.server.server1.check_new_mail", True),
        ("mail.server.server1.login_at_startup", True),
        ("mail.server.server1.storeContractID", "@mozilla.org/msgstore/berkeleystore;1"),
        ("mail.server.server1.max_cached_connections", 5),
        ("mail.server.server1.trash_folder_name", "Trash"),
        ("mail.server.server1.spamActionTargetAccount", "imap://%s@%s" % (kerberos_user_encoded, IMAP_HOST)),

        ("mail.server.server2.type", "none"),
        ("mail.server.server2.hostname", "Local Folders"),
        ("mail.server.server2.name", "Dossiers locaux"),
        ("mail.server.server2.userName", "nobody"),
        ("mail.server.server2.storeContractID", "@mozilla.org/msgstore/berkeleystore;1"),
        ("mail.server.server2.spamActionTargetAccount", "mailbox://nobody@Local%20Folders"),

        ("network.proxy.type", 4),

        ("mail.smtpservers", "smtp1"),
        ("mail.smtp.defaultserver", "smtp1"),
        ("mail.smtpserver.smtp1.username", kerberos_user),
        ("mail.smtpserver.smtp1.type", "smtp"),
    ]

    for key, value in dynamic_prefs:
        content = upsert_pref(content, key, value)

    # Toujours la signature à la fin
    content = apply_signature_prefs(content, login)

    with open(prefs_path, "w", encoding="utf-8") as f:
        f.write(content)

    print("[OK] prefs.js configuré avec succès pour %s." % login)