Adding Password Policy management to AlternC \!

This commit is contained in:
Benjamin Sonntag 2009-11-30 06:01:34 +00:00
parent 161a04cebb
commit a5ef76622e
10 changed files with 446 additions and 14 deletions

1
.gitattributes vendored
View File

@ -30,6 +30,7 @@ bureau/admin/adm_list.php -text
bureau/admin/adm_login.php -text
bureau/admin/adm_mxaccount.php -text
bureau/admin/adm_panel.php -text
bureau/admin/adm_passpolicy.php -text
bureau/admin/adm_quotadoedit.php -text
bureau/admin/adm_quotaedit.php -text
bureau/admin/adm_slaveaccount.php -text

View File

@ -55,6 +55,7 @@ include_once("head.php");
<li class="lst1"><a href="adm_variables.php"><?php __("Configure AlternC variables"); ?></a></li>
<li class="lst2"><a href="quota_show_all.php"><?php __("Show all quotas"); ?></a></li>
<li class="lst1"><a href="stats_members.php"><?php __("Account creation statistics"); ?></a></li>
<li class="lst2"><a href="adm_passpolicy.php"><?php __("Password Policies"); ?></a></li>
<?php
// here we include any "adminmenu_*" file content

View File

@ -0,0 +1,164 @@
<?php
/*
adm_passpolicy.php
----------------------------------------------------------------------
AlternC - Web Hosting System
Copyright (C) 2002-2010 by the AlternC Development Team.
http://alternc.org/
----------------------------------------------------------------------
LICENSE
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License (GPL)
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
To read the license please visit http://www.gnu.org/copyleft/gpl.html
----------------------------------------------------------------------
Original Author of file: Benjamin Sonntag
Purpose of file: Manage the password policy for AlternC
----------------------------------------------------------------------
*/
require_once("../class/config.php");
if (!$admin->enabled) {
__("This page is restricted to authorized staff");
exit();
}
$fields = array (
"edit" => array ("request", "string", ""),
"minsize" => array ("request", "integer", "0"),
"maxsize" => array ("request", "integer", "64"),
"classcount" => array ("request", "integer", "0"),
"allowlogin" => array ("request", "integer", "0"),
);
getFields($fields);
include_once("head.php");
?>
<h3><?php __("Manage Password Policy"); ?></h3>
<?php
if ($error) {
echo "<p class=\"error\">$error</p>";
}
$c=$admin->listPasswordPolicies();
//echo "<pre>"; print_r($c); echo "</pre>";
if ($doedit) {
if (!$c[$doedit]) {
echo "<p class=\"error\">"._("Policy not found")."</p>";
} else {
// Change it ;)
if ($admin->editPolicy($doedit,$minsize,$maxsize,$classcount,$allowlogin)) {
echo "<p class=\"info\">"._("Policy changed")."</p>";
unset($edit);
$c=$admin->listPasswordPolicies();
} else {
echo "<p class=\"error\">"._("Cannot edit the policy, an error occurred")."</p>";
}
}
}
if ($edit) {
if (!$c[$edit]) {
echo "<p class=\"error\">"._("Policy not found")."</p>";
} else {
?>
<p><?php __("Please choose which policy you want to apply to this password kind:"); ?></p>
<p><b><?php echo $c[$edit]["description"]; ?></b></p>
<form method="post" name="adm_passpolicy.php">
<input type="hidden" name="doedit" value="<?php echo $edit; ?>"/>
<table class="formv">
<tr class="lt1">
<th><?php __("Minimum Password Size:"); ?></th>
<td><select name="minsize" id="minsize"><?php for($i=0;$i<=64;$i++) {
echo "<option";
if ($c[$edit]["minsize"]==$i) echo " selected=\"selected\"";
echo ">$i</option>";
}
?></td></tr>
<tr class="lst2"><th><?php __("Maximum Password Size:"); ?></th>
<td><select name="maxsize" id="maxsize"><?php for($i=0;$i<=64;$i++) {
echo "<option";
if ($c[$edit]["maxsize"]==$i) echo " selected=\"selected\"";
echo ">$i</option>";
}
?></td></tr>
<tr class="lst1"> <th><?php __("In how many classes of characters must be the password (at least):"); ?></th>
<td><select name="classcount" id="classcount"><?php for($i=0;$i<=4;$i++) {
echo "<option";
if ($c[$edit]["classcount"]==$i) echo " selected=\"selected\"";
echo ">$i</option>";
}
?></td></tr>
<tr class="lst2"> <th><?php __("Do we allow the password to be like the login?"); ?></th>
<td><select name="allowlogin" id="allowlogin"><?php
echo "<option value=\"0\">"._("No")."</option>";
echo "<option value=\"1\""; if ($c[$edit]["allowlogin"]) echo " selected=\"selected\"";
echo ">"._("Yes")."</option>";
?></td></tr>
</table>
<p><input type="submit" name="go" value="<?php __("Apply this password policy"); ?>" /> &nbsp;
<input type="button" name="cancel" value="<?php __("Cancel and go back to the policy list"); ?>" onclick="history.go(-1);" /></p>
</form>
<p><?php __("The classes of characters are : <br />1. Low-case letters (a-z)<br />2. Upper-case letters (A-Z)<br />3. Figures (0-9)<br />4. Ascii symbols (!\"#$%&'()*+,-./:;<=>?@[\\]^_`)<br />5. Non-Ascii symbols (~éàâô...)"); ?></p>
</p>
<?php
require_once("foot.php");
exit();
}
}
if (is_array($c)) {
?>
<p>
<?php __("Here is the list of the password policies for each place a password may be needed in AlternC's services. For each of those password kind, you can choose which policy will be applied to passwords. A policy is a minimum and maximum password size, and how many classes of characters must appear in the password. You can also forbid (or not) to use the login or part of it as a password."); ?>
</p>
<table border="0" cellpadding="4" cellspacing="0">
<tr><th rowspan="2"></th><th rowspan="2"><?php __("Password Kind"); ?></th><th colspan="4"><?php __("Password Policy"); ?></th></tr>
<tr>
<th><?php __("Min Size"); ?></th>
<th><?php __("Max Size"); ?></th>
<th><?php __("Complexity"); ?></th>
<th><?php __("Allow Password=Login?"); ?></th>
</tr>
<?php
$col=1;
foreach($c as $v) {
$col=3-$col;
?>
<tr class="lst<?php echo $col; ?>">
<td class="center"><a href="adm_passpolicy.php?edit=<?php echo urlencode($v["name"]); ?>"><img src="images/edit.png" alt="<?php __("Change password policy"); ?>" /></a></td>
<td><?php echo $v["description"]; ?></td>
<td class="center"><?php echo $v["minsize"]; ?></td>
<td class="center"><?php echo $v["maxsize"]; ?></td>
<td class="center"><?php echo $v["classcount"]; ?></td>
<td class="center"><?php if ($v["allowlogin"]) __("Yes"); else __("No"); ?></td>
</tr>
<?php
}
?>
</table>
<?php } ?>
<?php include_once("foot.php"); ?>

View File

@ -1104,8 +1104,64 @@ EOF;
*
*/
function checkPolicy($policy,$login,$password) {
global $db;
global $db,$err;
$pol=$this->listPasswordPolicies();
if (!$pol[$policy]) {
$err->raise("admin",14);
return false;
}
$pol=$pol[$policy];
// Ok, now let's check it :
$plen=strlen($password);
if ($plen<$pol["minsize"]) {
$err->raise("admin",15);
return false;
}
if ($plen>$pol["maxsize"]) {
$err->raise("admin",16);
return false;
}
if (!$pol["allowlogin"]) {
// We do misc check on password versus login :
$l2=str_replace("_","@",$l2);
$l2=str_replace(".","@",$l2);
$logins=explode("@",$login);
$logins[]=$login;
foreach($logins as $l) {
if (strpos($l,$password)!==false) {
$err->raise("admin",17);
return false;
}
}
}
if ($pol["classcount"]>0) {
$cls=array(0,0,0,0,0);
for($i=0;$i<strlen($password);$i++) {
$p=substr($password,$i,1);
if (strpos("abcdefghijklmnopqrstuvwxyz",$p)!==false) {
$cls[0]=1;
} elseif (strpos("ABCDEFGHIJKLMNOPQRSTUVWXYZ",$p)!==false) {
$cls[1]=1;
} elseif (strpos("0123456789",$p)!==false) {
$cls[2]=1;
} elseif (strpos('!"#$%&\'()*+,-./:;<=>?@[\\]^_`',$p)!==false) {
$cls[3]=1;
} else {
$cls[4]=1;
}
} // foreach
$clc=array_sum($cls);
if ($clc<$pol["classcount"]) {
$err->raise("admin",18,$pol["classcount"],$clc);
return false;
}
}
return true; // congratulations !
}

View File

@ -162,7 +162,7 @@ class m_ftp {
* @return boolean TRUE si le compte a été modifié, FALSE si une erreur est survenue.
*/
function put_ftp_details($id,$prefixe,$login,$pass,$dir) {
global $mem,$db,$err,$bro,$cuid;
global $mem,$db,$err,$bro,$cuid,$admin;
$err->log("ftp","put_ftp_details",$id);
$db->query("SELECT count(*) AS cnt FROM ftpusers WHERE id='$id' and uid='$cuid';");
$db->next_record();
@ -197,6 +197,14 @@ class m_ftp {
return false;
}
if (trim($pass)) {
// Check this password against the password policy using common API :
if (is_callable(array($admin,"checkPolicy"))) {
if (!$admin->checkPolicy("ftp",$prefixe.$login,$pass)) {
return false; // The error has been raised by checkPolicy()
}
}
$db->query("UPDATE ftpusers SET name='".$prefixe.$login."', password='', encrypted_password=ENCRYPT('$pass'), homedir='/var/alternc/html/$l/$lo/$dir', uid='$cuid' WHERE id='$id';");
} else {
$db->query("UPDATE ftpusers SET name='".$prefixe.$login."', homedir='/var/alternc/html/$l/$lo/$dir', uid='$cuid' WHERE id='$id';");
@ -204,6 +212,7 @@ class m_ftp {
return true;
}
/* ----------------------------------------------------------------- */
/** Efface le compte ftp spécifié.
* @param integer $id Numéro du compte FTP à supprimer.
@ -233,7 +242,7 @@ class m_ftp {
*
*/
function add_ftp($prefixe,$login,$pass,$dir) {
global $mem,$db,$err,$quota,$bro,$cuid;
global $mem,$db,$err,$quota,$bro,$cuid,$admin;
$err->log("ftp","add_ftp",$prefixe."_".$login);
$dir=$bro->convertabsolute($dir);
if (substr($dir,0,1)=="/") {
@ -263,6 +272,14 @@ class m_ftp {
$err->raise("ftp",6);
return false;
}
// Check this password against the password policy using common API :
if (is_callable(array($admin,"checkPolicy"))) {
if (!$admin->checkPolicy("ftp",$prefixe.$login,$pass)) {
return false; // The error has been raised by checkPolicy()
}
}
if ($quota->cancreate("ftp")) {
$db->query("INSERT INTO ftpusers (name,password, encrypted_password,homedir,uid) VALUES ('".$prefixe.$login."', '', ENCRYPT('$pass'), '/var/alternc/html/$l/$lo/$dir', '$cuid')");
return true;

View File

@ -48,6 +48,14 @@ class m_hta {
}
/**
* Password kind used in this class (hook for admin class)
*/
function alternc_password_policy() {
return array("hta"=>"Protected folders passwords");
}
/*---------------------------------------------------------------------------*/
/**
* Create a protected folder (.htaccess et .htpasswd)
@ -194,7 +202,7 @@ class m_hta {
* @return boolean TRUE if the user has been added, or FALSE if an error occurred
*/
function add_user($user,$password,$dir) {
global $err, $bro;
global $err, $bro, $admin;
$err->log("hta","add_user",$user."/".$dir);
$absolute=$bro->convertabsolute($dir,0);
if (!file_exists($absolute)) {
@ -202,6 +210,13 @@ class m_hta {
return false;
}
if (checkloginmail($user)){
// Check this password against the password policy using common API :
if (is_callable(array($admin,"checkPolicy"))) {
if (!$admin->checkPolicy("hta",$user,$password)) {
return false; // The error has been raised by checkPolicy()
}
}
$file = @fopen("$absolute/.htpasswd","a+");
if (!$file) {
$err->raise("hta",12);
@ -279,13 +294,21 @@ class m_hta {
* @return boolean TRUE if the password has been changed, or FALSE if an error occurred
*/
function change_pass($user,$newpass,$dir) {
global $bro,$err;
global $bro,$err,$admin;
$err->log("hta","change_pass",$user."/".$dir);
$absolute=$bro->convertabsolute($dir,0);
if (!file_exists($absolute)) {
$err->raise("hta",8,$dir);
return false;
}
// Check this password against the password policy using common API :
if (is_callable(array($admin,"checkPolicy"))) {
if (!$admin->checkPolicy("hta",$user,$password)) {
return false; // The error has been raised by checkPolicy()
}
}
touch("$absolute/.htpasswd.new");
$file = fopen("$absolute/.htpasswd","r");
$newf = fopen("$absolute/.htpasswd.new","a");

View File

@ -288,7 +288,7 @@ class m_mail {
* @return boolean TRUE si l'email a bien été modifié, FALSE si une erreur s'est produite.
*/
function put_mail_details($mail,$pop,$pass,$alias) {
global $err,$cuid,$db;
global $err,$cuid,$db,$admin;
$err->log("mail","put_mail_details",$mail);
$mail=strtolower($mail);
$t=explode("@",$mail);
@ -336,6 +336,12 @@ class m_mail {
$err->raise("mail",4);
return false;
}
// Check this password against the password policy using common API :
if (is_callable(array($admin,"checkPolicy"))) {
if (!$admin->checkPolicy("pop",$email."@".$dom,$pass)) {
return false; // The error has been raised by checkPolicy()
}
}
}
$db->query("UPDATE mail_domain SET alias='".implode("\n",$account)."', pop='$pop' WHERE mail='$mail';");
@ -368,7 +374,7 @@ class m_mail {
* @return boolean TRUE si le compte a bien été créé, FALSE si une erreur s'est produite.
*/
function add_mail($dom,$mail,$pop,$pass,$alias) {
global $quota,$err,$cuid,$db;
global $quota,$err,$cuid,$db,$admin;
$err->log("mail","add_mail",$dom."/".$mail);
$account=array();
$mail=strtolower($mail);
@ -384,6 +390,16 @@ class m_mail {
$err->raise("mail",4);
return false;
}
if ($pop=="1") {
// Check this password against the password policy using common API :
if (is_callable(array($admin,"checkPolicy"))) {
if (!$admin->checkPolicy("pop",$mail."@".$dom,$pass)) {
return false; // The error has been raised by checkPolicy()
}
}
}
if ($pop=="1"){
$account[]=$mail."_".$dom;
}

View File

@ -62,7 +62,7 @@ class m_mysql {
* Password kind used in this class (hook for admin class)
*/
function alternc_password_policy() {
return array("mysql_users"=>"MySQL users");
return array("mysql"=>"MySQL users");
}
@ -251,7 +251,7 @@ class m_mysql {
* @return boolean TRUE if the password has been successfully changed, FALSE else.
*/
function put_mysql_details($password) {
global $db,$err,$mem,$cuid;
global $db,$err,$mem,$cuid,$admin;
$err->log("mysql","put_mysql_details");
$db->query("SELECT * FROM db WHERE uid='$cuid';");
if (!$db->num_rows()) {
@ -265,6 +265,14 @@ class m_mysql {
$err->raise("mysql",8);
return false;
}
// Check this password against the password policy using common API :
if (is_callable(array($admin,"checkPolicy"))) {
if (!$admin->checkPolicy("mysql",$login,$password)) {
return false; // The error has been raised by checkPolicy()
}
}
// Update all the "pass" fields for this user :
$db->query("UPDATE db SET pass='$password' WHERE uid='$cuid';");
$db->query("SET PASSWORD FOR '$login'@'$this->client' = PASSWORD('$password')");
@ -277,7 +285,7 @@ class m_mysql {
* It also create the first database.
*/
function new_mysql($password) {
global $db,$err,$mem,$cuid;
global $db,$err,$mem,$cuid,$admin;
$err->log("mysql","new_mysql");
if (strlen($password)>16) {
$err->raise("mysql",8);
@ -290,6 +298,14 @@ class m_mysql {
}
$login=$mem->user["login"];
$dbname=$mem->user["login"];
// Check this password against the password policy using common API :
if (is_callable(array($admin,"checkPolicy"))) {
if (!$admin->checkPolicy("mysql",$login,$password)) {
return false; // The error has been raised by checkPolicy()
}
}
// OK, creation now...
$db->query("INSERT INTO db (uid,login,pass,db) VALUES ('$cuid','".$login."','$password','".$dbname."');");
// give everything but GRANT on $user.*
@ -456,8 +472,17 @@ class m_mysql {
return $c;
}
/* ------------------------------------------------------------ */
/**
* Create a new user in MySQL rights tables
* @param $usern the username (we will add _[alternc-account] to it)
* @param $password The password for this username
* @param $passconf The password confirmation
* @return TRUE if the user has been created in MySQL or FALSE if an error occurred
**/
function add_user($usern,$password,$passconf) {
global $db,$err,$quota,$mem,$cuid;
global $db,$err,$quota,$mem,$cuid,$admin;
$err->log("mysql","add_user",$usern);
$user=addslashes($mem->user["login"]."_$usern");
@ -486,6 +511,12 @@ class m_mysql {
return false;
}
// Check this password against the password policy using common API :
if (is_callable(array($admin,"checkPolicy"))) {
if (!$admin->checkPolicy("mysql",$user,$password)) {
return false; // The error has been raised by checkPolicy()
}
}
// We create the user account (the "file" right is the only one we need globally to be able to use load data into outfile)
$db->query("GRANT file ON *.* TO '$user'@'$this->client' IDENTIFIED BY '$pass';");
@ -494,6 +525,14 @@ class m_mysql {
return true;
}
/* ------------------------------------------------------------ */
/**
* Delete a new user in MySQL rights tables
* @param $user the username (we will add _[alternc-account] to it) to delete
* @return TRUE if the user has been deleted in MySQL or FALSE if an error occurred
**/
function del_user($user) {
global $db,$err,$mem,$cuid,$L_MYSQL_DATABASE;
$err->log("mysql","del_user",$user);
@ -518,6 +557,13 @@ class m_mysql {
return true;
}
/* ------------------------------------------------------------ */
/**
* Return the list of the database rights of user $user
* @param $user the username
* @return array An array of database name and rights
**/
function get_user_dblist($user) {
global $db,$err,$mem,$cuid,$L_MYSQL_DATABASE;
$err->log("mysql","get_user_dblist");
@ -536,6 +582,17 @@ class m_mysql {
return $r;
}
/* ------------------------------------------------------------ */
/**
* Set the access rights of user $user to database $dbn to be rights $rights
* @param $user the username to give rights to
* @param $dbn The database to give rights to
* @param $rights The rights as an array of MySQL keywords (insert, select ...)
* @return boolean TRUE if the rights has been applied or FALSE if an error occurred
*
**/
function set_user_rights($user,$dbn,$rights) {
global $mem, $db;
@ -580,8 +637,7 @@ class m_mysql {
}
}
// On remet à zéro tous les droits de l'utilisateur
// We reset all user rights on this DB :
$db->query("SELECT * FROM mysql.db WHERE User = '$usern' AND Db = '$dbname';");
if($db->num_rows())
$db->query("REVOKE ALL PRIVILEGES ON $dbname.* FROM '$usern'@'$this->client';");
@ -593,6 +649,8 @@ class m_mysql {
return TRUE;
}
} /* Class m_mysql */
?>

View File

@ -73,6 +73,27 @@ msgstr "Ce TLD existe d
msgid "err_admin_13"
msgstr "Le login est trop long (16 caractères maximum)"
#. There is no password policy available, this password has been denied (it's a coding error though...)
msgid "err_admin_14"
msgstr "La politique de mot de passe demandée n'a pas été trouvée, Ce password est refusé (c'est une erreur de programmation ...)"
#. The password is too short according to your policy, please check it
msgid "err_admin_15"
msgstr "Le mot de passe est trop court selon votre politique de mot de passe, merci de vérifier"
#. The password is too long according to your policy, please check it
msgid "err_admin_16"
msgstr "Le mot de passe est trop long selon votre politique de mot de passe, merci de vérifier"
#. The password cannot be part of the login according to your policy, please check it
msgid "err_admin_17"
msgstr "Le mot de passe ne peut pas être le même que le nom d'utilisateur (ou quelque chose de similaire) selon votre politique de mot de passe, merci de vérifier"
#. The password must contains characters from %s different classes according to your policy (it only contains %s classes), please check it
msgid "err_admin_18"
msgstr "Le mot de passe doit contenir des caractères de %s classes différentes selon votre politique de mot de passe (il n'en contient que %s), merci de vérifier"
#. Domain names
msgid "quota_dom"
msgstr "Noms de domaines"
@ -378,6 +399,10 @@ msgstr "L'utilisateur '%s' existe d
msgid "err_hta_11"
msgstr "Veuillez saisir un nom d'utilisateur valide"
#. The file .htpasswd does not exist
msgid "err_hta_12"
msgstr "Le fichier .htpasswd n'existe pas"
#. Email Accounts
msgid "quota_mail"
msgstr "Comptes emails"
@ -730,6 +755,16 @@ msgstr ""
msgid "%3$d-%2$d-%1$d %4$d:%5$d"
msgstr "%1$d/%2$d/%3$d %4$d:%5$d"
msgid "AlternC's account password"
msgstr "Mots de passe des comptes AlternC"
msgid "POP/IMAP account passwords"
msgstr "Mots de passe des comptes POP/IMAP"
msgid "Protected folders passwords"
msgstr "Mots de passe des dossiers protégés"
#~ msgid "Editing subdomain %s"
#~ msgstr "Edition du sous-domaine %s"

View File

@ -2627,6 +2627,67 @@ msgstr "Valider les changements"
msgid "User Quotas"
msgstr "Quotas Utilisateurs"
msgid "Password Policies"
msgstr "Politiques de mot de passe"
msgid "Manage Password Policy"
msgstr "Gestion des politiques de mot de passe"
msgid "Here is the list of the password policies for each place a password may be needed in AlternC's services. For each of those password kind, you can choose which policy will be applied to passwords. A policy is a minimum and maximum password size, and how many classes of characters must appear in the password. You can also forbid (or not) to use the login or part of it as a password."
msgstr "Voici la liste de politiques de mot de passe pour chaque endroit où un mot de passe est requis dans les service d'AlternC. Pour chaque type de mot de passe, vous pouvez choisir quelle politique y sera appliquée. Une politique consiste en une taille minimale et maximale pour le mot de passe, et combien de classes de caractères différentes ce mot de passe doit contenir. Vous pouvez aussi interdire (ou pas) que le mot de passe ressemble au nom d'utilisateur."
msgid "Password Kind"
msgstr "Type de mot de passe"
msgid "Password Policy"
msgstr "Politique de mot de passe"
msgid "Min Size"
msgstr "Taille minimale"
msgid "Max Size"
msgstr "Taille maximale"
msgid "Complexity"
msgstr "Complexité"
msgid "Allow Password=Login?"
msgstr "Autorise Mot de passe=Login ?"
msgid "Please choose which policy you want to apply to this password kind:"
msgstr "Merci de choisir la politique à appliquer à ce type de mot de passe : "
msgid "Minimum Password Size:"
msgstr "Taille minimale du mot de passe :"
msgid "Maximum Password Size:"
msgstr "Taille maximale du mot de passe :"
msgid "In how many classes of characters must be the password (at least):"
msgstr "Dans combien de classes de caractères doit être ce mot de passe (au minimum) :"
msgid "Do we allow the password to be like the login?"
msgstr "Autorise-t-on le mot de passe à ressembler au nom d'utilisateur ?"
msgid "Apply this password policy"
msgstr "Appliquer cette politique de mot de passe"
msgid "Cancel and go back to the policy list"
msgstr "Annuler et revenir à la liste des politiques de mots de passe"
msgid "The classes of characters are : <br />1. Low-case letters (a-z)<br />2. Upper-case letters (A-Z)<br />3. Figures (0-9)<br />4. Ascii symbols (!\"#$%&'()*+,-./:;<=>?@[\\]^_`)<br />5. Non-Ascii symbols (~éàâô...)"
msgstr "Les classes de caractère sont : <br />1. Lettres minuscules (a-z)<br />2. Lettres majuscules (A-Z)<br />3. Chiffres (0-9)<br />4. Caractères Ascii (!\"#$%&'()*+,-./:;<=>?@[\\]^_`)<br />5. Caractères non-Ascii (~éàâô...)"
msgid "Policy not found"
msgstr "Politique non trouvée"
msgid "Policy changed"
msgstr "Politique de mot de passe modifiée"
msgid "Cannot edit the policy, an error occurred"
msgstr "Impossible de modifier la politique de mot de passe, une erreur est apparue"
#~ msgid "Create the domain username."
#~ msgstr "Installer le domaine login."