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)