Pour l’association le Retzien.fr (dont je fais parti) j’ai effectué une migration de Plesk 18 vers ISPconfig 3. Plesk avait été mis en œuvre avant mon arrivé car l’administrateur en place connaissait le produit. Mais c’est un produit payant, de plus en plus cher, et pour une association qui a du mal à avoir un équilibre financier c’était difficile à assumer. Plesk est très « beau » et user friendly mais en tant qu’administrateur système je le trouve plutôt opaque (on ne sais pas exactement ce qu’il fait, pas trop de respect des « us et coutumes » de Linux (position des fichiers de config…), ET CE N’EST PAS LIBRE !!!
ISPconfig est libre et propose un script de migration (ISPConfig Migration Toolkit) mais ne supporte pas les dernières versions de Plesk, car le chiffrement des mots de passe n’est plus compatible. Du coup j’ai dû bricoler… Ce bricolage est l’objet de cet article.
Note : ici il n’est question que de la partie mail/DNS et compte client, car c’est la seule fonctionnalité du serveur, les fonctions web n’ont pas été testées.
Installation ISPconfig + Migration Toolkit
Je ne détaille pas ici la procédure d’installation d’ISPconfig 3 et l’usage du « Migration Toolkit » qui sont déjà bien documentés, dans mon cas :
- Installation : https://www.howtoforge.com/perfect-server-debian-10-buster-apache-bind-dovecot-ispconfig-3-1/
- Migration : https://www.howtoforge.com/tutorial/how-to-migrate-ispconfig-confixx-plesk-to-ispconfig-31-single-server/
Suite à l’usage du « Migration toolkit », même si le Plesk source n’est pas supporté, je n’ai pas d’erreur mais j’ai plusieurs problèmes que je vais essayer de régler par différents scripts.
Préambules : Les scripts suivants utilisent l’API d’ISPconfig (il faut donc créer un utilisateur distant, dans Système/utilisateur distant). ils doivent donc les lancer depuis le serveur source (Plesk).
Authentification IMAP
Le problème
Il est impossible de se connecter au serveur avec les comptes IMAP. Le log dit :
Error: sql(xxxxxxx@retzien.fr,::1,<MdFia/2k3toAAAAAAAAAAAAAAAAAAAAB>): Invalid password in passdb: crypt() failed: Invalid argument
Effectivement le chiffrement en base est différent :
Mais on va s’en sortir en rusant, en effet Plesk ne crypte/ »hash » pas les mots de passe mais les chiffre :
- Crypter / « hasher » : c’est convertir définitivement un chaîne (ici un mot de passe) en un code. Pour savoir si le mot de passe proposé pour l’authentification est bon il faut crypter / « hasher » le mot de passe et le comparer.
- Chiffrer : c’est convertir une chaîne (le mot de passe ici) avec une clé, cette fonction est réversible, et qui détient la clé peut donc déchiffrer la chaîne (le mot de passe)
- Plus d’info sur la différence ici
Les mots de passe dans Plesk sont donc « lisibles » pour qui possède la clé de déchiffrement (l’administrateur par exemple ou un hacker qui obtiendrait l’accès au serveur…)
Pour vous en persuader, lancez la commande ci après et vous obtiendrez un beau tableau 1er colonne « émail », 2ème « mot de passe en clair »
/usr/local/psa/admin/sbin/mail_auth_view
Mais pourquoi les mots de passes ne sont ils pas cryptés ?
Plesk propose le support de l’authentification « mot de passe chiffré ». Et cette fonctionnalité est de fait contrainte à avoir le mot de passe en clair sur le serveur car il faut pouvoir le déchiffrer.
ISPconfig ne supporte pas cette fonctionnalité, car qu’il considère que ce serait un trop gros trou dans la sécurité du serveur, et qu’avec le chiffrement de la connexion SSL/TLS ou START/TLS ce n’est pas nécessaire d’en remettre une couche.
Effet de bord important : si vos utilisateurs ont configurés leur client de messagerie en « mot de passe chiffré » il faudra leur faire modifier ce paramétrage (IMAP & SMTP)
De ce que je comprends, cette méthode d’authentification chiffrée avait sa place quand les serveur IMAP/SMTP ne proposait pas de chiffrement de connexion SSL/TLS ou START/TLS.
Les sources de ce que j’avance :
- https://www.howtoforge.com/community/threads/cram-md5-authentication.852/
- https://forum.howtoforge.de/threads/ispconfig-3-cram-md5.1489/
- https://doc.dovecot.org/configuration_manual/authentication/authentication_mechanisms/
La solution
La solution consiste, sur le serveur Plesk à lancer le script ci-après qui :
- Lance la commande Plesk « magique » pour voir les mots de passe déchiffrés
- « Parse » le résultat… Se connecte à ISPconfig et change le mot de chaque boîte émail
<?php
/*
* licence Beerware
* By David Mercereau : http://david.mercereau.info
*/
// Plan B : https://www.besuchet.net/2016/06/plesk-11-encrypted-hashed-password-authentication-php-on-psa-database/
// ISPconfig Remote Config
$CONFIG['remoteUser'] = 'retzien';
$CONFIG['remotePassword'] = 'XXXXXXXXXX';
$CONFIG['remoteSoapLocation'] = 'https://192.168.1.64:8080/remote/index.php';
$CONFIG['remoteSoapUri'] = 'https://192.168.1.64:8080/remote/';
$CONFIG['logfile'] = '/tmp/migration-email-password.log';
function toLog($txt, $level) {
file_put_contents($GLOBALS['CONFIG']['logfile'],date("[j/m/y H:i:s]")." - ".$level." - $txt \r\n",FILE_APPEND);
echo "[$level] $txt\n";
}
// Connexion to ISPconfig
$client = new SoapClient(null, array('location' => $CONFIG['remoteSoapLocation'],
'uri' => $CONFIG['remoteSoapUri'],
'stream_context'=> stream_context_create(array('ssl'=> array('verify_peer'=>false,'verify_peer_name'=>false))),
'trace' => 1));
// Login
if($session_id = $client->login($CONFIG['remoteUser'], $CONFIG['remotePassword'])) {
toLog('Login Ok. Session ID:'.$session_id, 'info');
}
# Launch plesk command for auth view in plain text
exec('/usr/local/psa/admin/sbin/mail_auth_view', $out, $return);
if ($return == 0 || $out[0] == null){
foreach ($out as $line) {
$lineExplode = explode('|', $line);
if (isset($lineExplode[1]) && isset($lineExplode[3])) {
$email = str_replace(' ', '', $lineExplode[1]);
$password = str_replace(' ', '', $lineExplode[3]);
# Verification data
if (filter_var($email, FILTER_VALIDATE_EMAIL) && $password != '') {
# mail_user_get
try {
$mail_user_get = $client->mail_user_get($session_id, array('email' => $email));
if (count($mail_user_get) > 0) {
# Update data
$mail_user_get['password']=$password;
$mail_user_get['move_junk']='y';
$affected_rows = $client->mail_user_update($session_id, 0, $mail_user_get[0]['mailuser_id'], $mail_user_get);
if ($affected_rows == 0) {
toLog('No change for '.$email, 'error');
} else {
toLog('Ok for '.$email, 'info');
}
} else {
toLog('email '.$email.' not present in ISPconfig', 'warn');
}
} catch (SoapFault $e) {
echo $client->__getLastResponse();
die('SOAP Error: '.$e->getMessage());
}
}
}
}
}
if($client->logout($session_id)) {
toLog('Logged out.', 'info');
}
?>
IMAP subscriptions
La souscription au dossier IMAP (le fait de voir ou non certains dossiers et/ou d’en cacher d’autres) n’a pas migré, donc si l’utilisateur avait ajouté un dossier IMAP, il ne le voyez plus sur le nouveau serveur, pourtant le dossier est bien là.. c’est fâcheux. Pour corriger ça, lancez ce petit script sur le Plesk (source) :
#!/bin/bash
target='root@192.168.1.64'
find /var/qmail/mailnames/ -name subscriptions | while IFS=$'\n' read f ; do
domain=$(echo $f | cut -d/ -f5)
user=$(echo $f | cut -d/ -f6)
echo ${user}@${domain}
scp /var/qmail/mailnames/${domain}/${user}/Maildir/subscriptions ${target}:/var/vmail/${domain}/${user}/Maildir/
done
DKIM & Spam policy
Ce script ajoute une politique de spam “normal” (modifiable) et récupère les anciennes clefs DKIM générées par Plesk pour les appliquer dans ISPconfig. Ceci pour éviter d’avoir à retoucher au DNS pendant la migration (toujours à lancer depuis le Plesk) :
<?php
/*
* licence Beerware
* By David Mercereau : http://david.mercereau.info
*/
// ISPconfig Remote Config
$CONFIG['remoteUser'] = 'retzien';
$CONFIG['remotePassword'] = 'XXXXXXX';
$CONFIG['remoteSoapLocation'] = 'https://192.168.1.64:8080/remote/index.php';
$CONFIG['remoteSoapUri'] = 'https://192.168.1.64:8080/remote/';
$CONFIG['logfile'] = '/tmp/migration-email-password.log';
$CONFIG['DKIM-Selector'] = 'default';
$CONFIG['policy_id'] = 5;
$CONFIG['priority'] = 5;
function toLog($txt, $level) {
file_put_contents($GLOBALS['CONFIG']['logfile'],date("[j/m/y H:i:s]")." - ".$level." - $txt \r\n",FILE_APPEND);
echo "[$level] $txt\n";
}
// Connexion to ISPconfig
$client = new SoapClient(null, array('location' => $CONFIG['remoteSoapLocation'],
'uri' => $CONFIG['remoteSoapUri'],
'stream_context'=> stream_context_create(array('ssl'=> array('verify_peer'=>false,'verify_peer_name'=>false))),
'trace' => 1));
// Login
if($session_id = $client->login($CONFIG['remoteUser'], $CONFIG['remotePassword'])) {
toLog('Login Ok. Session ID:'.$session_id, 'info');
}
$mail_domain_record = $client->mail_domain_get($session_id, array('active' => 'y'));
foreach ($mail_domain_record as $mail_domain) {
# DKIM
if (is_dir('/etc/domainkeys/'.$mail_domain['domain'])) {
$getPrivateKey=file_get_contents('/etc/domainkeys/'.$mail_domain['domain'].'/'.$CONFIG['DKIM-Selector']);
exec('openssl rsa -in /etc/domainkeys/'.$mail_domain['domain'].'/'.$CONFIG['DKIM-Selector'].' -pubout', $out, $return);
if ($return == 0 || $out[0] == null){
$getPublicKey='';
foreach ($out as $line) {
$getPublicKey.=$line."\n";
//~ if (!preg_match('/^---/', $line)) {
//~ $getPublicKeyDns.=$line;
//~ }
}
$mail_domain['dkim'] = 'y';
$mail_domain['dkim_selector'] = $CONFIG['DKIM-Selector'];
$mail_domain['dkim_private'] = $getPrivateKey;
$mail_domain['dkim_public'] = $getPublicKey;
$affected_rows = $client->mail_domain_update($session_id, 0, $mail_domain['domain_id'], $mail_domain);
if ($affected_rows == 0) {
toLog('DKIM No change for '.$mail_domain['domain']. ' already exist ?', 'error');
} else {
toLog('DKIM Ok for '.$mail_domain['domain'], 'info');
}
}
} else {
toLog($mail_domain['domain'].' haven\'t got dkim key on plesk', 'warn');
}
# Spam Policy add in domain
try {
$client_id = 1;
$params = array(
'server_id' => 1,
'priority' => $CONFIG['priority'],
'policy_id' => $CONFIG['policy_id'],
'email' => '@'.$mail_domain['domain'],
'fullname' => '@'.$mail_domain['domain'],
'local' => 'Y'
);
$affected_rows = $client->mail_spamfilter_user_add($session_id, $client_id, $params);
toLog('Spam policy Ok for '.$mail_domain['domain'], 'info');
} catch (SoapFault $e) {
toLog('Spam policy no change for '.$mail_domain['domain']. ' already exist ?', 'error');
}
}
if($client->logout($session_id)) {
toLog('Logged out.', 'info');
}
?>
Les mailings lists
Les mailings lists ne sont pas migrées, pourtant c’est du « Mailman 2 » des 2 côtés donc ça ne devrait pas être très compliqué.
Ce script est à lancer depuis le Plesk en mode « CLI » (ligne de commande) et vous avez quelques paramètres à modifier au début.
Le script :
- Liste les mailings lists avec la commande /var/lib/mailman/bin/list_lists
- Les créer dans ispconfig
- Donne les commandes rsync pour synchroniser les données (archives, paramètres…)
<?php
/*
* licence Beerware
* By David Mercereau : http://david.mercereau.info
*/
// ISPconfig Remote Config
$CONFIG['remoteUser'] = 'retzien';
$CONFIG['remotePassword'] = 'XXXXX';
$CONFIG['remoteSoapLocation'] = 'https://192.168.1.64:8080/remote/index.php';
$CONFIG['remoteSoapUri'] = 'https://192.168.1.64:8080/remote/';
$CONFIG['logfile'] = '/tmp/migration-email-password.log';
$CONFIG['clientId'] = 3;
function toLog($txt, $level) {
file_put_contents($GLOBALS['CONFIG']['logfile'],date("[j/m/y H:i:s]")." - ".$level." - $txt \r\n",FILE_APPEND);
echo "[$level] $txt\n";
}
// Connexion to ISPconfig
$client = new SoapClient(null, array('location' => $CONFIG['remoteSoapLocation'],
'uri' => $CONFIG['remoteSoapUri'],
'stream_context'=> stream_context_create(array('ssl'=> array('verify_peer'=>false,'verify_peer_name'=>false))),
'trace' => 1));
// Login
if($session_id = $client->login($CONFIG['remoteUser'], $CONFIG['remotePassword'])) {
toLog('Login Ok. Session ID:'.$session_id, 'info');
}
# Launch plesk command for auth view in plain text
exec('/var/lib/mailman/bin/list_lists -b', $out, $return);
if ($return == 0 || $out[0] == null){
foreach ($out as $line) {
unset($host_name);
unset($owner);
exec('/usr/lib/mailman/bin/config_list -o /tmp/'.$line.' '.$line.' ; grep ^host_name /tmp/'.$line.' | cut -d"\'" -f2', $host_name, $return);
exec('/usr/lib/mailman/bin/config_list -o /tmp/'.$line.' '.$line.' ; grep ^owner /tmp/'.$line.' | cut -d"\'" -f2', $owner, $return);
try {
$params = array(
'server_id' => 1,
'domain' => $host_name[0],
'listname' => $line,
'email' => $owner[0],
'password' => 'temp123!'
);
$mailinglist_id = $client->mail_mailinglist_add($session_id, $CONFIG['clientId'], $params);
toLog('Ok for '.$line.'@'.$host_name[0]);
} catch (SoapFault $e) {
toLog('SOAP Error: '.$line. ' - '. $e->getMessage(), 'error');
}
}
}
if($client->logout($session_id)) {
toLog('Logged out.', 'info');
}
echo "Patienteez 5min que Ispconfig est tout ajouté puis : \n";
echo "for list in \$(/var/lib/mailman/bin/list_lists -b); do\n
rsync -az --progress /var/lib/mailman/archives/public/\$list/ root@192.168.1.64:/var/lib/mailman/archives/public/\$list/\n
rsync -az --progress /var/lib/mailman/archives/private/\$list/ root@192.168.1.64:/var/lib/mailman/archives/private/\$list/\n
rsync -az --progress /var/lib/mailman/archives/private/\${list}.mbox root@192.168.1.64:/var/lib/mailman/archives/private/\n
rsync -az --progress /var/lib/mailman/lists/\$list/ root@192.168.1.64:/var/lib/mailman/lists/\$list/\n
done\n";
?>
En sortie le script donne quelques commandes « rsync » à exécuter par un copier / coller direct dans le terminal du serveur source (Plesk) pour synchroniser les données (archives, paramètres…)
Ensuite sur le serveur decdestination (ISPconfig) lancez la commande :
/var/lib/mailman/bin/check_perms -f
Conclusion
La migration c’est bien passée, c’est transparent pour l’utilisateur, à part le problème de « mot de passe chiffré » à « mot de passe normal », qu’il faut faire changer avant la migration de préférence.
Beau boulot…
Très fouillé !
Je peux t’aider à corriger qq fôtes.