Proposing ppolicy extended module for OpenLDAP (issue #7832)

This commit is contained in:
David Coutadeur 2021-02-23 21:23:23 +01:00 committed by Quanah Gibson-Mount
parent f2ddf89e3c
commit f3975b3f90
10 changed files with 1511 additions and 0 deletions

View File

@ -0,0 +1,44 @@
INSTALLATION
============
Build dependencies
------------------
OpenLDAP sources must be available. For an easier build, copy all ppm module
into contrib/slapd-modules OpenLDAP source directory.
Build
-----
Be sure to have copied ppm module into contrib/slapd-modules OpenLDAP source
directory.
Adapt the Makefile command to indicate:
OLDAP_SOURCES : should point to OpenLDAP source directory
CONFIG: where the ppm.conf example configuration file will finally stand
note: ppm configuration now lies into pwdCheckModuleArg password policy attribute
provided config file is only helpful as an example or for testing
LIBDIR: where the library will be installed
DEBUG: If defined, ppm logs its actions with syslog
If necessary, you can also adapt some OpenLDAP source directories (if changed):
LDAP_INC : OpenLDAP headers directory
LDAP_LIBS : OpenLDAP built libraries directory
then type:
```
make clean
make CONFIG=/etc/openldap/ppm.conf OLDAP_SOURCES=../../..
make test
make install CONFIG=/etc/openldap/ppm.conf LIBDIR=/usr/lib/openldap
```
For LTB build, use rather:
```
make clean
make "CONFIG=/usr/local/openldap/etc/openldap/ppm.conf" "OLDAP_SOURCES=.."
make test
make install CONFIG=/usr/local/openldap/etc/openldap/ppm.conf LIBDIR=/usr/local/openldap/lib64
```

View File

@ -0,0 +1,50 @@
OpenLDAP Public License
The OpenLDAP Public License
Version 2.8.1, 25 November 2003
Redistribution and use of this software and associated documentation
("Software"), with or without modification, are permitted provided
that the following conditions are met:
1. Redistributions in source form must retain copyright statements
and notices,
2. Redistributions in binary form must reproduce applicable copyright
statements and notices, this list of conditions, and the following
disclaimer in the documentation and/or other materials provided
with the distribution, and
3. Redistributions must contain a verbatim copy of this document.
The OpenLDAP Foundation may revise this license from time to time.
Each revision is distinguished by a version number. You may use
this Software under terms of this license revision or under the
terms of any subsequent revision of the license.
THIS SOFTWARE IS PROVIDED BY THE OPENLDAP FOUNDATION AND ITS
CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE OPENLDAP FOUNDATION, ITS CONTRIBUTORS, OR THE AUTHOR(S)
OR OWNER(S) OF THE SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
The names of the authors and copyright holders must not be used in
advertising or otherwise to promote the sale, use or other dealing
in this Software without specific, written prior permission. Title
to copyright in this Software shall at all times remain with copyright
holders.
OpenLDAP is a registered trademark of the OpenLDAP Foundation.
Copyright 1999-2003 The OpenLDAP Foundation, Redwood City,
California, USA. All rights reserved. Permission to copy and
distribute verbatim copies of this document is granted.

View File

@ -0,0 +1,67 @@
# contrib/slapd-modules/ppm/Makefile
# Copyright 2014 David Coutadeur, Paris. All Rights Reserved.
#
CC=gcc
# Path of OpenLDAP sources
OLDAP_SOURCES=../../..
# Where the ppm configuration file should be installed
CONFIG=/etc/openldap/ppm.conf
# Path of OpenLDAP installed libs, where the ppm library should be installed
LIBDIR=/usr/lib/openldap
OPT=-g -O2 -Wall -fpic \
-DCONFIG_FILE="\"$(CONFIG)\"" \
-DDEBUG
# Where to find the OpenLDAP headers.
LDAP_INC=-I$(OLDAP_SOURCES)/include \
-I$(OLDAP_SOURCES)/servers/slapd
# Where to find the OpenLDAP libraries.
LDAP_LIBS=-L$(OLDAP_SOURCES)/libraries/liblber/.libs \
-L$(OLDAP_SOURCES)/libraries/libldap/.libs
CRACK_INC=-DCRACKLIB
INCS=$(LDAP_INC) $(CRACK_INC)
LDAP_LIB=-lldap -llber
CRACK_LIB=-lcrack
LIBS=$(LDAP_LIB) $(CRACK_LIB)
TESTS=./unit_tests.sh
all: ppm ppm_test
ppm_test:
$(CC) -g $(LDAP_INC) $(LDAP_LIBS) -Wl,-rpath=. -o ppm_test ppm_test.c ppm.so $(LIBS)
ppm.o:
$(CC) $(OPT) -c $(INCS) ppm.c
ppm: ppm.o
$(CC) $(LDAP_INC) -shared -o ppm.so ppm.o $(CRACK_LIB)
install: ppm
cp -f ppm.so $(LIBDIR)
cp -f ppm_test $(LIBDIR)
cp -f ppm.conf $(CONFIG)
.PHONY: clean
clean:
$(RM) -f ppm.o ppm.so ppm.lo ppm_test
$(RM) -rf .libs
test: ppm ppm_test
$(TESTS)

View File

@ -0,0 +1,5 @@
The attached modifications to OpenLDAP Software are subject to the following notice:
Copyright 2021 David Coutadeur
Redistribution and use in source and binary forms, with or without modification, are permitted only as authorized by the OpenLDAP Public License.

View File

@ -0,0 +1,292 @@
ppm.c - OpenLDAP password policy module
version 2.0
ppm.c is an OpenLDAP module for checking password quality when they are modified.
Passwords are checked against the presence or absence of certain character classes.
This module is used as an extension of the OpenLDAP password policy controls,
see slapo-ppolicy(5) section pwdCheckModule.
contributions
-------------
* 2014 - 2021 - David Coutadeur <david.coutadeur@gmail.com> - maintainer
* 2015 - Daly Chikhaoui - Janua <dchikhaoui@janua.fr> - contribution on RDN checks
* 2017 - tdb - Tim Bishop - contribution on some compilation improvements
INSTALLATION
------------
See INSTALL file
USAGE
-----
Create a password policy entry and indicate the fresh compiled
library ppm.so:
dn: cn=default,ou=policies,dc=my-domain,dc=com
objectClass: pwdPolicy
objectClass: pwdPolicyChecker
objectClass: person
objectClass: top
cn: default
sn: default
pwdAttribute: userPassword
pwdCheckQuality: 2
...
pwdCheckModule: /path/to/new/ppm.so
pwdCheckModuleArg: [see configuration section]
See slapo-ppolicy for more information, but to sum up:
- enable ppolicy overlay in your database.
This example show the activation for a slapd.conf file
(see slapd-config and slapo-ppolicy for more information for
cn=config configuration)
```
overlay ppolicy
ppolicy_default "cn=default,ou=policies,dc=my-domain,dc=com"
#ppolicy_use_lockout # for having more infos about the lockout
```
- define a default password policy in OpenLDAP configuration or
use pwdPolicySubentry attribute to point to the given policy.
Password checks
---------------
- 4 character classes are defined by default:
upper case, lower case, digits and special characters.
- more character classes can be defined, just write your own.
- passwords must match the amount of quality points.
A point is validated when at least m characters of the corresponding
character class are present in the password.
- passwords must have at least n of the corresponding character class
present, else they are rejected.
- the two previous criterias are checked against any specific character class
defined.
- if a password contains any of the forbidden characters, then it is
rejected.
- if a password contains tokens from the RDN, then it is rejected.
- if a password is too long, it can be rejected.
- if a password does not pass cracklib check, it can be rejected.
Configuration
-------------
Since OpenLDAP 2.5 version, ppm configuration is held in a binary
attribute of the password policy: pwdCheckModuleArg
The configuration file (/etc/openldap/ppm.conf by default) is to be
considered as an example configuration, to import in the pwdCheckModuleArg
attribute. It is also used for testing passwords with the test program
provided.
If for some reasons, any parameter is not found, it will be given its
default value.
Note: you can still compile ppm to use the configuration file, by enabling
PPM_READ_FILE in ppm.h (but this is deprecated now). If you decide to do so,
you can use the PPM_CONFIG_FILE environment variable for overloading the
configuration file path.
The syntax of a configuration line is:
parameter value [min] [minForPoint]
with spaces being delimiters and Line Feed (LF) ending the line.
Parameter names ARE case sensitive.
The default configuration is the following:
```
# minQuality parameter
# Format:
# minQuality [NUMBER]
# Description:
# One point is granted for each class for which MIN_FOR_POINT criteria is fulfilled.
# defines the minimum point numbers for the password to be accepted.
minQuality 3
# checkRDN parameter
# Format:
# checkRDN [0 | 1]
# Description:
# If set to 1, password must not contain a token from the RDN.
# Tokens are separated by the following delimiters : space tabulation _ - , ; £
checkRDN 0
# forbiddenChars parameter
# Format:
# forbiddenChars [CHARACTERS_FORBIDDEN]
# Description:
# Defines the forbidden characters list (no separator).
# If one of them is found in the password, then it is rejected.
forbiddenChars
# maxConsecutivePerClass parameter
# Format:
# maxConsecutivePerClass [NUMBER]
# Description:
# Defines the maximum number of consecutive character allowed for any class
maxConsecutivePerClass 0
# useCracklib parameter
# Format:
# useCracklib [0 | 1]
# Description:
# If set to 1, the password must pass the cracklib check
useCracklib 0
# cracklibDict parameter
# Format:
# cracklibDict [path_to_cracklib_dictionnary]
# Description:
# directory+filename-prefix that your version of CrackLib will go hunting for
# For example, /var/pw_dict resolves as /var/pw_dict.pwd,
# /var/pw_dict.pwi and /var/pw_dict.hwm dictionnary files
cracklibDict /var/cache/cracklib/cracklib_dict
# classes parameter
# Format:
# class-[CLASS_NAME] [CHARACTERS_DEFINING_CLASS] [MIN] [MIN_FOR_POINT]
# Description:
# [CHARACTERS_DEFINING_CLASS]: characters defining the class (no separator)
# [MIN]: If at least [MIN] characters of this class is not found in the password, then it is rejected
# [MIN_FOR_POINT]: one point is granted if password contains at least [MIN_FOR_POINT] character numbers of this class
class-upperCase ABCDEFGHIJKLMNOPQRSTUVWXYZ 0 1
class-lowerCase abcdefghijklmnopqrstuvwxyz 0 1
class-digit 0123456789 0 1
class-special <>,?;.:/!§ù%*µ^¨$£²&é~"#'{([-|è`_\ç^à@)]°=}+ 0 1
```
Example
-------
With this policy:
```
minQuality 4
forbiddenChars .?,
checkRDN 1
class-upperCase ABCDEFGHIJKLMNOPQRSTUVWXYZ 0 5
class-lowerCase abcdefghijklmnopqrstuvwxyz 0 12
class-digit 0123456789 0 1
class-special <>,?;.:/!§ù%*µ^¨$£²&é~"#'{([-|è`_\ç^à@)]°=}+ 0 1
class-myClass :) 1 1``
```
the password
ThereIsNoCowLevel)
is working, because,
- it has 4 character classes validated : upper, lower, special, and myClass
- it has no character among .?,
- it has at least one character among : or )
but it won't work for the user uid=John Cowlevel,ou=people,cn=example,cn=com,
because the token "Cowlevel" from his RDN exists in the password (case insensitive).
Logs
----
If a user password is rejected by ppm, the user will get this type of message:
Typical user message from ldappasswd(5):
Result: Constraint violation (19)
Additional info: Password for dn=\"%s\" does not pass required number of strength checks (2 of 3)
A more detailed message is written to the server log.
Server log:
```
Jul 27 20:09:14 machine slapd[20270]: ppm: Opening file /etc/openldap/ppm.conf
Jul 27 20:09:14 machine slapd[20270]: ppm: Param = minQuality, value = 3, min = (null), minForPoint= (null)
Jul 27 20:09:14 machine slapd[20270]: ppm: Accepted replaced value: 3
Jul 27 20:09:14 machine slapd[20270]: ppm: Param = forbiddenChars, value = , min = (null), minForPoint= (null)
Jul 27 20:09:14 machine slapd[20270]: ppm: Accepted replaced value:
Jul 27 20:09:14 machine slapd[20270]: ppm: Param = class-upperCase, value = ABCDEFGHIJKLMNOPQRSTUVWXYZ, min = 0, minForPoint= 5
Jul 27 20:09:14 machine slapd[20270]: ppm: Accepted replaced value: ABCDEFGHIJKLMNOPQRSTUVWXYZ
Jul 27 20:09:14 machine slapd[20270]: ppm: Param = class-lowerCase, value = abcdefghijklmnopqrstuvwxyz, min = 0, minForPoint= 12
Jul 27 20:09:14 machine slapd[20270]: ppm: Accepted replaced value: abcdefghijklmnopqrstuvwxyz
Jul 27 20:09:14 machine slapd[20270]: ppm: Param = class-digit, value = 0123456789, min = 0, minForPoint= 1
Jul 27 20:09:14 machine slapd[20270]: ppm: Accepted replaced value: 0123456789
Jul 27 20:09:14 machine slapd[20270]: ppm: Param = class-special, value = <>,?;.:/!§ù%*µ^¨$£²&é~"#'{([-|è`_\ç^à@)]°=}+, min = 0, minForPoint= 1
Jul 27 20:09:14 machine slapd[20270]: ppm: Accepted replaced value: <>,?;.:/!§ù%*µ^¨$£²&é~"#'{([-|è`_\ç^à@)]°=}+
Jul 27 20:09:14 machine slapd[20270]: ppm: Param = class-myClass, value = :), min = 1, minForPoint= 1
Jul 27 20:09:14 machine slapd[20270]: ppm: Accepted new value:
Jul 27 20:09:14 machine slapd[20270]: ppm: 1 point granted for class class-upperCase
Jul 27 20:09:14 machine slapd[20270]: ppm: 1 point granted for class class-lowerCase
Jul 27 20:09:14 machine slapd[20270]: ppm: 1 point granted for class class-digit
```
Tests
-----
There is a unit test script: "unit_tests.sh" that illustrates checking some passwords.
It is possible to test one particular password using directly the test program:
```
cd /usr/local/openldap/lib64
LD_LIBRARY_PATH=. ./ppm_test "uid=test,ou=users,dc=my-domain,dc=com" "my_password" "/usr/local/openldap/etc/openldap/ppm.conf" && echo OK
```
HISTORY
-------
* 2021-02-23 David Coutadeur <david.coutadeur@gmail.com>
remove maxLength attribute (#21)
adapt the readme and documentation of ppm (#22)
prepare ppolicy10 in OpenLDAP 2.5 (#20, #23 and #24)
add pwdCheckModuleArg feature
Version 2.0
* 2019-08-20 David Coutadeur <david.coutadeur@gmail.com>
adding debug symbols for ppm_test,
improve tests with the possibility to add username,
fix openldap crash when checkRDN=1 and username contains too short parts
Version 1.8
* 2018-03-30 David Coutadeur <david.coutadeur@gmail.com>
various minor improvements provided by Tim Bishop (tdb) (compilation, test program,
imprvts in Makefile: new OLDAP_SOURCES variable pointing to OLDAP instal. directory
Version 1.7
* 2017-05-19 David Coutadeur <david.coutadeur@gmail.com>
Adds cracklib support
Readme adaptations and cleaning
Version 1.6
* 2017-02-07 David Coutadeur <david.coutadeur@gmail.com>
Adds maxConsecutivePerClass (idea from Trevor Vaughan / tvaughan@onyxpoint.com)
Version 1.5
* 2016-08-22 David Coutadeur <david.coutadeur@gmail.com>
Get config file from environment variable
Version 1.4
* 2014-12-20 Daly Chikhaoui <dchikhaoui@janua.fr>
Adding checkRDN parameter
Version 1.3
* 2014-10-28 David Coutadeur <david.coutadeur@gmail.com>
Adding maxLength parameter
Version 1.2
* 2014-07-27 David Coutadeur <david.coutadeur@gmail.com>
Changing the configuration file and the configuration data structure
Version 1.1
* 2014-04-04 David Coutadeur <david.coutadeur@gmail.com>
Version 1.0

View File

@ -0,0 +1,679 @@
/*
* ppm.c for OpenLDAP
*
* See LICENSE, README and INSTALL files
*/
/*
password policy module is called with:
int check_password (char *pPasswd, char **ppErrStr, Entry *e, void *pArg)
*pPasswd: new password
**ppErrStr: pointer to the string containing the error message
*e: pointer to the current user entry
*pArg: pointer to a struct berval holding the value of pwdCheckModuleArg attr
*/
#include <stdlib.h> // for type conversion, such as atoi...
#include <regex.h> // for matching allowedParameters / conf file
#include <string.h>
#include <ctype.h>
#include <portable.h>
#include <slap.h>
#include <stdarg.h> // for variable nb of arguments functions
#include "ppm.h"
#ifdef CRACKLIB
#include "crack.h" // use cracklib to check password
#endif
void
ppm_log(int priority, const char *format, ...)
{
// if DEBUG flag is set
// logs into syslog (for OpenLDAP) or to stdout (for tests)
#if defined(DEBUG)
if(ppm_test != 1)
{
va_list syslog_args;
va_start(syslog_args, format);
vsyslog(priority, format, syslog_args);
va_end(syslog_args);
}
else
{
va_list stdout_args;
va_start(stdout_args, format);
vprintf(format, stdout_args);
printf("\n");
fflush(stdout);
va_end(stdout_args);
}
#endif
}
void
strcpy_safe(char *dest, char *src, int length_dest)
{
if(src == NULL)
{
dest[0] = '\0';
}
else
{
int length_src = strlen(src);
int n = (length_dest < length_src) ? length_dest : length_src;
// Copy the string — dont copy too many bytes.
strncpy(dest, src, n);
// Ensure null-termination.
dest[n] = '\0';
}
}
genValue*
getValue(conf *fileConf, int numParam, char* param)
{
int i = 0;
// First scan parameters
for (i = 0; i < numParam; i++) {
if ((strlen(param) == strlen(fileConf[i].param))
&& (strncmp(param, fileConf[i].param, strlen(fileConf[i].param))
== 0)) {
return &(fileConf[i].value);
}
}
return NULL;
}
int maxConsPerClass(char *password, char *charClass)
{
// find maximum number of consecutive class characters in the password
int bestMax = 0;
int max = 0;
int i;
for(i=0 ; i<strlen(password) ; i++)
{
if(strchr(charClass,password[i]) != NULL)
{
// current character is in class
max++;
// is the new max a better candidate to maxConsecutivePerClass?
if(max > bestMax)
{
// found a better maxConsecutivePerClass
bestMax = max;
}
}
else
{
// current character is not in class
// reinitialize max
max=0;
}
}
return bestMax;
}
void
storeEntry(char *param, char *value, valueType valType,
char *min, char *minForPoint, conf * fileConf, int *numParam)
{
int i = 0;
int iMin;
int iMinForPoint;
if (min == NULL || strcmp(min,"") == 0)
iMin = 0;
else
iMin = atoi(min);
if (minForPoint == NULL || strcmp(minForPoint,"") == 0)
iMinForPoint = 0;
else
iMinForPoint = atoi(minForPoint);
// First scan parameters
for (i = 0; i < *numParam; i++) {
if ((strlen(param) == strlen(fileConf[i].param))
&& (strncmp(param, fileConf[i].param, strlen(fileConf[i].param))
== 0)) {
// entry found, replace values
if(valType == typeInt)
fileConf[i].value.iVal = atoi(value);
else
strcpy_safe(fileConf[i].value.sVal, value, VALUE_MAX_LEN);
fileConf[i].min = iMin;
fileConf[i].minForPoint = iMinForPoint;
if(valType == typeInt)
ppm_log(LOG_NOTICE, "ppm: Accepted replaced value: %d",
fileConf[i].value.iVal);
else
ppm_log(LOG_NOTICE, "ppm: Accepted replaced value: %s",
fileConf[i].value.sVal);
return;
}
}
// entry not found, add values
strcpy_safe(fileConf[*numParam].param, param, PARAM_MAX_LEN);
fileConf[*numParam].iType = valType;
if(valType == typeInt)
fileConf[i].value.iVal = atoi(value);
else
strcpy_safe(fileConf[i].value.sVal, value, VALUE_MAX_LEN);
fileConf[*numParam].min = iMin;
fileConf[*numParam].minForPoint = iMinForPoint;
++(*numParam);
if(valType == typeInt)
ppm_log(LOG_NOTICE, "ppm: Accepted new value: %d",
fileConf[*numParam].value.iVal);
else
ppm_log(LOG_NOTICE, "ppm: Accepted new value: %s",
fileConf[*numParam].value.sVal);
}
int
typeParam(char* param)
{
int i;
int n = sizeof(allowedParameters)/sizeof(params);
regex_t regex;
int reti;
for(i = 0 ; i < n ; i++ )
{
// Compile regular expression
reti = regcomp(&regex, allowedParameters[i].param, 0);
if (reti) {
ppm_log(LOG_ERR, "ppm: Cannot compile regex: %s",
allowedParameters[i].param);
exit(EXIT_FAILURE);
}
// Execute regular expression
reti = regexec(&regex, param, 0, NULL, 0);
if (!reti)
{
regfree(&regex);
return i;
}
regfree(&regex);
}
return n;
}
#ifndef PPM_READ_FILE
/*
* read configuration into pwdCheckModuleArg attribute
* */
static void
read_config_attr(conf * fileConf, int *numParam, char *ppm_config_attr)
{
int nParam = 0; // position of found parameter in allowedParameters
int sAllowedParameters = sizeof(allowedParameters)/sizeof(params);
char arg[260*256];
char *token;
char *saveptr1;
char *saveptr2;
strcpy_safe(arg, ppm_config_attr, 260*256);
ppm_log(LOG_NOTICE, "ppm: Parsing pwdCheckModuleArg attribute");
token = strtok_r(arg, "\n", &saveptr1);
while (token != NULL) {
ppm_log(LOG_NOTICE, "ppm: get line: %s",token);
char *start = token;
char *word, *value;
char *min, *minForPoint;;
while (isspace(*start) && isascii(*start))
start++;
if (!isascii(*start))
{
token = strtok_r(NULL, "\n", &saveptr1);
continue;
}
if (start[0] == '#')
{
token = strtok_r(NULL, "\n", &saveptr1);
continue;
}
if ((word = strtok_r(start, " \t", &saveptr2))) {
if ((value = strtok_r(NULL, " \t", &saveptr2)) == NULL)
{
saveptr2 = NULL;
ppm_log(LOG_NOTICE, "ppm: No value, goto next parameter");
token = strtok_r(NULL, "\n", &saveptr1);
continue;
}
if (strchr(value, '\n') != NULL)
strchr(value, '\n')[0] = '\0';
min = strtok_r(NULL, " \t", &saveptr2);
if (min != NULL)
if (strchr(min, '\n') != NULL)
strchr(min, '\n')[0] = '\0';
minForPoint = strtok_r(NULL, " \t", &saveptr2);
if (minForPoint != NULL)
if (strchr(minForPoint, '\n') != NULL)
strchr(minForPoint, '\n')[0] = '\0';
nParam = typeParam(word); // search for param in allowedParameters
if (nParam != sAllowedParameters) // param has been found
{
ppm_log(LOG_NOTICE,
"ppm: Param = %s, value = %s, min = %s, minForPoint= %s",
word, value, min, minForPoint);
storeEntry(word, value, allowedParameters[nParam].iType,
min, minForPoint, fileConf, numParam);
}
else
{
ppm_log(LOG_NOTICE,
"ppm: Parameter '%s' rejected", word);
}
}
token = strtok_r(NULL, "\n", &saveptr1);
}
}
#endif
#ifdef PPM_READ_FILE
/*
* read configuration file (DEPRECATED)
* */
static void
read_config_file(conf * fileConf, int *numParam, char *ppm_config_file)
{
FILE *config;
char line[260] = "";
int nParam = 0; // position of found parameter in allowedParameters
int sAllowedParameters = sizeof(allowedParameters)/sizeof(params);
ppm_log(LOG_NOTICE, "ppm: Opening file %s", ppm_config_file);
if ((config = fopen(ppm_config_file, "r")) == NULL) {
ppm_log(LOG_ERR, "ppm: Opening file %s failed", ppm_config_file);
exit(EXIT_FAILURE);
}
while (fgets(line, 256, config) != NULL) {
char *start = line;
char *word, *value;
char *min, *minForPoint;;
while (isspace(*start) && isascii(*start))
start++;
if (!isascii(*start))
continue;
if (start[0] == '#')
continue;
if ((word = strtok(start, " \t"))) {
if ((value = strtok(NULL, " \t")) == NULL)
continue;
if (strchr(value, '\n') != NULL)
strchr(value, '\n')[0] = '\0';
min = strtok(NULL, " \t");
if (min != NULL)
if (strchr(min, '\n') != NULL)
strchr(min, '\n')[0] = '\0';
minForPoint = strtok(NULL, " \t");
if (minForPoint != NULL)
if (strchr(minForPoint, '\n') != NULL)
strchr(minForPoint, '\n')[0] = '\0';
nParam = typeParam(word); // search for param in allowedParameters
if (nParam != sAllowedParameters) // param has been found
{
ppm_log(LOG_NOTICE,
"ppm: Param = %s, value = %s, min = %s, minForPoint= %s",
word, value, min, minForPoint);
storeEntry(word, value, allowedParameters[nParam].iType,
min, minForPoint, fileConf, numParam);
}
else
{
ppm_log(LOG_NOTICE,
"ppm: Parameter '%s' rejected", word);
}
}
}
fclose(config);
}
#endif
static int
realloc_error_message(char **target, int curlen, int nextlen)
{
if (curlen < nextlen + MEMORY_MARGIN) {
ppm_log(LOG_WARNING,
"ppm: Reallocating szErrStr from %d to %d", curlen,
nextlen + MEMORY_MARGIN);
ber_memfree(*target);
curlen = nextlen + MEMORY_MARGIN;
*target = (char *) ber_memalloc(curlen);
}
return curlen;
}
// Does the password contains a token from the RDN ?
int
containsRDN(char* passwd, char* DN)
{
char lDN[DN_MAX_LEN];
char * tmpToken;
char * token;
regex_t regex;
int reti;
strcpy_safe(lDN, DN, DN_MAX_LEN);
// Extract the RDN from the DN
tmpToken = strtok(lDN, ",+");
tmpToken = strtok(tmpToken, "=");
tmpToken = strtok(NULL, "=");
// Search for each token in the password */
token = strtok(tmpToken, TOKENS_DELIMITERS);
while (token != NULL)
{
if (strlen(token) > 2)
{
ppm_log(LOG_NOTICE, "ppm: Checking if %s part of RDN matches the password", token);
// Compile regular expression
reti = regcomp(&regex, token, REG_ICASE);
if (reti) {
ppm_log(LOG_ERR, "ppm: Cannot compile regex: %s", token);
exit(EXIT_FAILURE);
}
// Execute regular expression
reti = regexec(&regex, passwd, 0, NULL, 0);
if (!reti)
{
regfree(&regex);
return 1;
}
regfree(&regex);
}
else
{
ppm_log(LOG_NOTICE, "ppm: %s part of RDN is too short to be checked", token);
}
token = strtok(NULL, TOKENS_DELIMITERS);
}
return 0;
}
int
check_password(char *pPasswd, char **ppErrStr, Entry *e, void *pArg)
{
Entry *pEntry = e;
ppm_log(LOG_NOTICE, "ppm: entry %s", pEntry->e_nname.bv_val);
struct berval *pwdCheckModuleArg = pArg;
/* Determine if config file is to be read (DEPRECATED) */
#ifdef PPM_READ_FILE
ppm_log(LOG_NOTICE, "ppm: Not reading pwdCheckModuleArg attribute");
ppm_log(LOG_NOTICE, "ppm: instead, read configuration file (deprecated)");
#else
ppm_log(LOG_NOTICE, "ppm: Reading pwdCheckModuleArg attribute");
ppm_log(LOG_NOTICE, "ppm: RAW configuration: %s",
(*(struct berval*)pwdCheckModuleArg).bv_val);
#endif
char *szErrStr = (char *) ber_memalloc(MEM_INIT_SZ);
int mem_len = MEM_INIT_SZ;
int numParam = 0; // Number of params in current configuration
int useCracklib;
char cracklibDict[VALUE_MAX_LEN];
char cracklibDictFiles[3][(VALUE_MAX_LEN+5)];
char const* cracklibExt[] = { ".hwm", ".pwd", ".pwi" };
FILE* fd;
char* res;
int minQuality;
int checkRDN;
char forbiddenChars[VALUE_MAX_LEN];
int nForbiddenChars = 0;
int nQuality = 0;
int maxConsecutivePerClass;
int nbInClass[CONF_MAX_SIZE];
int i,j;
/* Determine config file (DEPRECATED) */
#ifdef PPM_READ_FILE
char ppm_config_file[FILENAME_MAX_LEN];
strcpy_safe(ppm_config_file, getenv("PPM_CONFIG_FILE"), FILENAME_MAX_LEN);
if (ppm_config_file[0] == '\0') {
strcpy_safe(ppm_config_file, CONFIG_FILE, FILENAME_MAX_LEN);
}
ppm_log(LOG_NOTICE, "ppm: reading config file from %s", ppm_config_file);
#endif
for (i = 0; i < CONF_MAX_SIZE; i++)
nbInClass[i] = 0;
/* Set default values */
conf fileConf[CONF_MAX_SIZE] = {
{"minQuality", typeInt, {.iVal = DEFAULT_QUALITY}, 0, 0
}
,
{"checkRDN", typeInt, {.iVal = 0}, 0, 0
}
,
{"forbiddenChars", typeStr, {.sVal = ""}, 0, 0
}
,
{"maxConsecutivePerClass", typeInt, {.iVal = 0}, 0, 0
}
,
{"useCracklib", typeInt, {.iVal = 0}, 0, 0
}
,
{"cracklibDict", typeStr, {.sVal = "/var/cache/cracklib/cracklib_dict"}, 0, 0
}
,
{"class-upperCase", typeStr, {.sVal = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"}, 0, 1
}
,
{"class-lowerCase", typeStr, {.sVal = "abcdefghijklmnopqrstuvwxyz"}, 0, 1
}
,
{"class-digit", typeStr, {.sVal = "0123456789"}, 0, 1
}
,
{"class-special", typeStr,
{.sVal = "<>,?;.:/!§ù%*µ^¨$£²&é~\"#'{([-|è`_\\ç^à@)]°=}+"}, 0, 1
}
};
numParam = 10;
#ifdef PPM_READ_FILE
/* Read configuration file (DEPRECATED) */
read_config_file(fileConf, &numParam, ppm_config_file);
#else
/* Read configuration attribute (pwdCheckModuleArg) */
read_config_attr(fileConf, &numParam, (*(struct berval*)pwdCheckModuleArg).bv_val);
#endif
minQuality = getValue(fileConf, numParam, "minQuality")->iVal;
checkRDN = getValue(fileConf, numParam, "checkRDN")->iVal;
strcpy_safe(forbiddenChars,
getValue(fileConf, numParam, "forbiddenChars")->sVal,
VALUE_MAX_LEN);
maxConsecutivePerClass = getValue(fileConf, numParam, "maxConsecutivePerClass")->iVal;
useCracklib = getValue(fileConf, numParam, "useCracklib")->iVal;
strcpy_safe(cracklibDict,
getValue(fileConf, numParam, "cracklibDict")->sVal,
VALUE_MAX_LEN);
/*The password must have at least minQuality strength points with one
* point granted if the password contains at least minForPoint characters for each class
* It must contains at least min chars of each class
* It must not contain any char in forbiddenChar */
for (i = 0; i < strlen(pPasswd); i++) {
int n;
for (n = 0; n < numParam; n++) {
if (strstr(fileConf[n].param, "class-") != NULL) {
if (strchr(fileConf[n].value.sVal, pPasswd[i]) != NULL) {
++(nbInClass[n]);
}
}
}
if (strchr(forbiddenChars, pPasswd[i]) != NULL) {
nForbiddenChars++;
}
}
// Password checking done, now loocking for minForPoint criteria
for (i = 0; i < CONF_MAX_SIZE; i++) {
if (strstr(fileConf[i].param, "class-") != NULL) {
if ((nbInClass[i] >= fileConf[i].minForPoint)
&& strlen(fileConf[i].value.sVal) != 0) {
// 1 point granted
++nQuality;
ppm_log(LOG_NOTICE, "ppm: 1 point granted for class %s",
fileConf[i].param);
}
}
}
if (nQuality < minQuality) {
mem_len = realloc_error_message(&szErrStr, mem_len,
strlen(PASSWORD_QUALITY_SZ) +
strlen(pEntry->e_nname.bv_val) + 4);
sprintf(szErrStr, PASSWORD_QUALITY_SZ, pEntry->e_nname.bv_val,
nQuality, minQuality);
goto fail;
}
// Password checking done, now loocking for constraintClass criteria
for (i = 0; i < CONF_MAX_SIZE; i++) {
if (strstr(fileConf[i].param, "class-") != NULL) {
if ((nbInClass[i] < fileConf[i].min) &&
strlen(fileConf[i].value.sVal) != 0) {
// constraint is not satisfied... goto fail
mem_len = realloc_error_message(&szErrStr, mem_len,
strlen(PASSWORD_CRITERIA) +
strlen(pEntry->e_nname.bv_val) +
2 + PARAM_MAX_LEN);
sprintf(szErrStr, PASSWORD_CRITERIA, pEntry->e_nname.bv_val,
fileConf[i].min, fileConf[i].param);
goto fail;
}
}
}
// Password checking done, now loocking for forbiddenChars criteria
if (nForbiddenChars > 0) { // at least 1 forbidden char... goto fail
mem_len = realloc_error_message(&szErrStr, mem_len,
strlen(PASSWORD_FORBIDDENCHARS) +
strlen(pEntry->e_nname.bv_val) + 2 +
VALUE_MAX_LEN);
sprintf(szErrStr, PASSWORD_FORBIDDENCHARS, pEntry->e_nname.bv_val,
nForbiddenChars, forbiddenChars);
goto fail;
}
// Password checking done, now loocking for maxConsecutivePerClass criteria
for (i = 0; i < CONF_MAX_SIZE; i++) {
if (strstr(fileConf[i].param, "class-") != NULL) {
if ( maxConsecutivePerClass != 0 &&
(maxConsPerClass(pPasswd,fileConf[i].value.sVal)
> maxConsecutivePerClass)) {
// Too much consecutive characters of the same class
ppm_log(LOG_NOTICE, "ppm: Too much consecutive chars for class %s",
fileConf[i].param);
mem_len = realloc_error_message(&szErrStr, mem_len,
strlen(PASSWORD_MAXCONSECUTIVEPERCLASS) +
strlen(pEntry->e_nname.bv_val) + 2 +
PARAM_MAX_LEN);
sprintf(szErrStr, PASSWORD_MAXCONSECUTIVEPERCLASS, pEntry->e_nname.bv_val,
maxConsecutivePerClass, fileConf[i].param);
goto fail;
}
}
}
#ifdef CRACKLIB
// Password checking done, now loocking for cracklib criteria
if ( useCracklib > 0 ) {
for( j = 0 ; j < 3 ; j++) {
strcpy_safe(cracklibDictFiles[j], cracklibDict, VALUE_MAX_LEN);
strcat(cracklibDictFiles[j], cracklibExt[j]);
if (( fd = fopen ( cracklibDictFiles[j], "r")) == NULL ) {
ppm_log(LOG_NOTICE, "ppm: Error while reading %s file",
cracklibDictFiles[j]);
mem_len = realloc_error_message(&szErrStr, mem_len,
strlen(GENERIC_ERROR));
sprintf(szErrStr, GENERIC_ERROR);
goto fail;
}
else {
fclose (fd);
}
}
res = (char *) FascistCheck (pPasswd, cracklibDict);
if ( res != NULL ) {
ppm_log(LOG_NOTICE, "ppm: cracklib does not validate password for entry %s",
pEntry->e_nname.bv_val);
mem_len = realloc_error_message(&szErrStr, mem_len,
strlen(PASSWORD_CRACKLIB) +
strlen(pEntry->e_nname.bv_val));
sprintf(szErrStr, PASSWORD_CRACKLIB, pEntry->e_nname.bv_val);
goto fail;
}
}
#endif
// Password checking done, now looking for checkRDN criteria
if (checkRDN == 1 && containsRDN(pPasswd, pEntry->e_nname.bv_val))
// RDN check enabled and a token from RDN is found in password: goto fail
{
mem_len = realloc_error_message(&szErrStr, mem_len,
strlen(RDN_TOKEN_FOUND) +
strlen(pEntry->e_nname.bv_val));
sprintf(szErrStr, RDN_TOKEN_FOUND, pEntry->e_nname.bv_val);
goto fail;
}
*ppErrStr = strdup("");
ber_memfree(szErrStr);
return (LDAP_SUCCESS);
fail:
*ppErrStr = strdup(szErrStr);
ber_memfree(szErrStr);
return (EXIT_FAILURE);
}

View File

@ -0,0 +1,66 @@
# minQuality parameter
# Format:
# minQuality [NUMBER]
# Description:
# One point is granted for each class for which MIN_FOR_POINT criteria is fulfilled.
# defines the minimum point numbers for the password to be accepted.
minQuality 3
# maxLength parameter
# Format:
# maxLength [NUMBER]
# Description:
# The password must not be more than [NUMBER] long. 0 means no limit is set.
maxLength 0
# checkRDN parameter
# Format:
# checkRDN [0 | 1]
# Description:
# If set to 1, password must not contain a token from the RDN.
# Tokens are separated by these delimiters : space tabulation _ - , ; £
checkRDN 0
# forbiddenChars parameter
# Format:
# forbiddenChars [CHARACTERS_FORBIDDEN]
# Description:
# Defines the forbidden characters list (no separator).
# If one of them is found in the password, then it is rejected.
forbiddenChars
# maxConsecutivePerClass parameter
# Format:
# maxConsecutivePerClass [NUMBER]
# Description:
# Defines the maximum number of consecutive character allowed for any class
maxConsecutivePerClass 0
# useCracklib parameter
# Format:
# useCracklib [0 | 1]
# Description:
# If set to 1, the password must pass the cracklib check
useCracklib 0
# cracklibDict parameter
# Format:
# cracklibDict [path_to_cracklib_dictionnary]
# Description:
# directory+filename-prefix that your version of CrackLib will go hunting for
# For example, /var/pw_dict resolves as /var/pw_dict.pwd,
# /var/pw_dict.pwi and /var/pw_dict.hwm dictionnary files
cracklibDict /var/cache/cracklib/cracklib_dict
# classes parameter
# Format:
# class-[CLASS_NAME] [CHARACTERS_DEFINING_CLASS] [MIN] [MIN_FOR_POINT]
# Description:
# [CHARACTERS_DEFINING_CLASS]: characters defining the class (no separator)
# [MIN]: If at least [MIN] characters of this class is not found in the password, then it is rejected
# [MIN_FOR_POINT]: one point is granted if password contains at least [MIN_FOR_POINT] character numbers of this class
class-upperCase ABCDEFGHIJKLMNOPQRSTUVWXYZ 0 1
class-lowerCase abcdefghijklmnopqrstuvwxyz 0 1
class-digit 0123456789 0 1
class-special <>,?;.:/!§ù%*µ^¨$£²&é~"#'{([-|è`_\ç^à@)]°=}+ 0 1

View File

@ -0,0 +1,125 @@
/*
* ppm.h for OpenLDAP
*
* See LICENSE, README and INSTALL files
*/
#ifndef PPM_H_
#define PPM_H_
#include <stdlib.h> // for type conversion, such as atoi...
#include <regex.h> // for matching allowedParameters / conf file
#include <string.h>
#include <ctype.h>
#include <portable.h>
#include <slap.h>
#if defined(DEBUG)
#include <syslog.h>
#endif
//#define PPM_READ_FILE 1 // old deprecated configuration mode
// 1: (deprecated) don't read pwdCheckModuleArg
// attribute, instead read config file
// 0: read pwdCheckModuleArg attribute
/* config file parameters (DEPRECATED) */
#ifndef CONFIG_FILE
#define CONFIG_FILE "/etc/openldap/ppm.conf"
#endif
#define FILENAME_MAX_LEN 512
#define DEFAULT_QUALITY 3
#define MEMORY_MARGIN 50
#define MEM_INIT_SZ 64
#define DN_MAX_LEN 512
#define CONF_MAX_SIZE 50
#define PARAM_MAX_LEN 32
#define VALUE_MAX_LEN 128
#define ATTR_NAME_MAX_LEN 150
#define PARAM_PREFIX_CLASS "class-"
#define TOKENS_DELIMITERS " ,;-_£\t"
#define DEBUG_MSG_MAX_LEN 256
#define PASSWORD_QUALITY_SZ \
"Password for dn=\"%s\" does not pass required number of strength checks (%d of %d)"
#define PASSWORD_CRITERIA \
"Password for dn=\"%s\" has not reached the minimum number of characters (%d) for class %s"
#define PASSWORD_MAXCONSECUTIVEPERCLASS \
"Password for dn=\"%s\" has reached the maximum number of characters (%d) for class %s"
#define PASSWORD_FORBIDDENCHARS \
"Password for dn=\"%s\" contains %d forbidden characters in %s"
#define RDN_TOKEN_FOUND \
"Password for dn=\"%s\" contains tokens from the RDN"
#define GENERIC_ERROR \
"Error while checking password"
#define PASSWORD_CRACKLIB \
"Password for dn=\"%s\" is too weak"
#define BAD_PASSWORD_SZ \
"Bad password for dn=\"%s\" because %s"
typedef union genValue {
int iVal;
char sVal[VALUE_MAX_LEN];
} genValue;
typedef enum {
typeInt,
typeStr
} valueType;
typedef struct params {
char param[PARAM_MAX_LEN];
valueType iType;
} params;
// allowed parameters loaded into configuration structure
// it also contains the type of the corresponding value
params allowedParameters[7] = {
{"^minQuality", typeInt},
{"^checkRDN", typeInt},
{"^forbiddenChars", typeStr},
{"^maxConsecutivePerClass", typeInt},
{"^useCracklib", typeInt},
{"^cracklibDict", typeStr},
{"^class-.*", typeStr}
};
// configuration structure, containing a parameter, a value,
// a corresponding min and minForPoint indicators if necessary
// and a type for the value (typeInt or typeStr)
typedef struct conf {
char param[PARAM_MAX_LEN];
valueType iType;
genValue value;
int min;
int minForPoint;
} conf;
void ppm_log(int priority, const char *format, ...);
int min(char *str1, char *str2);
#ifndef PPM_READ_FILE
static void read_config_attr(conf * fileConf, int *numParam, char *ppm_config_attr);
#endif
#ifdef PPM_READ_FILE
static void read_config_file(conf * fileConf, int *numParam, char *ppm_config_file);
#endif
int check_password(char *pPasswd, char **ppErrStr, Entry *e, void *pArg);
int maxConsPerClass(char *password, char *charClass);
void storeEntry(char *param, char *value, valueType valType,
char *min, char *minForPoint, conf * fileConf, int *numParam);
int typeParam(char* param);
genValue* getValue(conf *fileConf, int numParam, char* param);
void strcpy_safe(char *dest, char *src, int length_dest);
int ppm_test = 0;
#endif

View File

@ -0,0 +1,66 @@
#include <stdio.h>
#include <stdlib.h>
#include "ppm.h"
int main(int argc, char *argv[])
{
/*
* argv[1]: user
* argv[2]: password
* argv[3]: configuration file
*/
int ret = 1;
if(argc > 2)
{
printf("Testing user %s password: '%s' against %s policy config file \n",
argv[1], argv[2], argv[3]
);
/* format user entry */
char *errmsg = NULL;
Entry pEntry;
pEntry.e_nname.bv_val=argv[1];
pEntry.e_name.bv_val=argv[1];
/* get configuration file content */
struct berval pArg;
FILE *fp;
if ((fp = fopen(argv[3],"r")) == NULL)
{
fprintf(stderr,"Unable to open config file for reading\n");
return ret;
}
char *fcontent = NULL;
fseek(fp, 0, SEEK_END);
long fsize = ftell(fp);
fseek(fp, 0, SEEK_SET);
fcontent = malloc(fsize);
fread(fcontent, 1, fsize, fp);
fclose(fp);
pArg.bv_val = fcontent;
ppm_test=1; // enable ppm_test for informing ppm not to use syslog
ret = check_password(argv[2], &errmsg, &pEntry, &pArg);
if(ret == 0)
{
printf("Password is OK!\n");
}
else
{
printf("Password failed checks : %s\n", errmsg);
}
ber_memfree(errmsg);
return ret;
}
return ret;
}

View File

@ -0,0 +1,117 @@
#!/bin/bash
# Launch unitary tests
#
CONFIG_FILE="ppm.conf"
OLDAP_SOURCES="../../.."
CURRENT_DIR=$( dirname $0 )
LIB_PATH="${LD_LIBRARY_PATH}:${CURRENT_DIR}:${OLDAP_SOURCES}/libraries/liblber/.libs:${OLDAP_SOURCES}/libraries/libldap/.libs"
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m'
RESULT=0
PPM_CONF_1='minQuality 3
checkRDN 0
forbiddenChars
maxConsecutivePerClass 0
useCracklib 0
cracklibDict /var/cache/cracklib/cracklib_dict
class-upperCase ABCDEFGHIJKLMNOPQRSTUVWXYZ 0 1
class-lowerCase abcdefghijklmnopqrstuvwxyz 0 1
class-digit 0123456789 0 1
class-special <>,?;.:/!§ù%*µ^¨$£²&é~"#'\''{([-|è`_\ç^à@)]°=}+ 0 1'
PPM_CONF_2='minQuality 3
checkRDN 0
forbiddenChars à
maxConsecutivePerClass 5
useCracklib 0
cracklibDict /var/cache/cracklib/cracklib_dict
class-upperCase ABCDEFGHIJKLMNOPQRSTUVWXYZ 2 4
class-lowerCase abcdefghijklmnopqrstuvwxyz 3 4
class-digit 0123456789 2 4
class-special <>,?;.:/!§ù%*µ^¨$£²&é~"#'\''{([-|è`_\ç^à@)]°=}+ 0 4'
PPM_CONF_3='minQuality 3
checkRDN 1
forbiddenChars
maxConsecutivePerClass 0
useCracklib 0
cracklibDict /var/cache/cracklib/cracklib_dict
class-upperCase ABCDEFGHIJKLMNOPQRSTUVWXYZ 0 1
class-lowerCase abcdefghijklmnopqrstuvwxyz 0 1
class-digit 0123456789 0 1
class-special <>,?;.:/!§ù%*µ^¨$£²&é~"#'\''{([-|è`_\ç^à@)]°=}+ 0 1'
echo "$PPM_CONF_1" > ppm1.conf
echo "$PPM_CONF_2" > ppm2.conf
echo "$PPM_CONF_3" > ppm3.conf
launch_test()
{
# launch tests
# FORMAT: launch_test [conf_file] [password] [expected_result]
# [expected_result] = [PASS|FAIL]
local CONF="$1"
local USER="$2"
local PASS="$3"
local EXPECT="$4"
[[ $EXPECT == "PASS" ]] && EXP="0" || EXP="1"
LD_LIBRARY_PATH="${LIB_PATH}" ./ppm_test "${USER}" "${PASS}" "${CONF}"
RES="$?"
if [ "$RES" -eq "$EXP" ] ; then
echo -e "conf=${CONF} user=${USER} pass=${PASS} expect=${EXPECT}... ${GREEN}PASS${NC}"
else
echo -e "conf=${CONF} user=${USER} pass=${PASS} expect=${EXPECT}... ${RED}FAIL${NC}"
((RESULT+=1))
fi
echo
}
launch_test "ppm1.conf" "uid=test,ou=users,dc=my-domain,dc=com" "azerty" "FAIL"
launch_test "ppm1.conf" "uid=test,ou=users,dc=my-domain,dc=com" "azeRTY" "FAIL"
launch_test "ppm1.conf" "uid=test,ou=users,dc=my-domain,dc=com" "azeRTY123" "PASS"
launch_test "ppm1.conf" "uid=test,ou=users,dc=my-domain,dc=com" "azeRTY." "PASS"
launch_test "ppm2.conf" "uid=test,ou=users,dc=my-domain,dc=com" "AAaaa01AAaaa01AAaaa0" "PASS"
# forbidden char
launch_test "ppm2.conf" "uid=test,ou=users,dc=my-domain,dc=com" "AAaaa01AAaaa01AAaaaà" "FAIL"
# too much consecutive for upper
launch_test "ppm2.conf" "uid=test,ou=users,dc=my-domain,dc=com" "AAaaa01AAaaa01AAAAAA" "FAIL"
# not enough upper
launch_test "ppm2.conf" "uid=test,ou=users,dc=my-domain,dc=com" "Aaaaa01aaaaa01aa.;.;" "FAIL"
# not enough lower
launch_test "ppm2.conf" "uid=test,ou=users,dc=my-domain,dc=com" "aaAAA01BB0123AAA.;.;" "FAIL"
# not enough digit
launch_test "ppm2.conf" "uid=test,ou=users,dc=my-domain,dc=com" "1AAAA.;BBB.;.;AA.;.;" "FAIL"
# not enough points (no point for digit)
launch_test "ppm2.conf" "uid=test,ou=users,dc=my-domain,dc=com" "AAaaaBBBBaaa01AAaaaa" "FAIL"
# password in RDN
launch_test "ppm3.conf" "uid=User_Password10-test,ou=users,dc=my-domain,dc=com" "Password10" "FAIL"
launch_test "ppm3.conf" "uid=User_Passw0rd-test,ou=users,dc=my-domain,dc=com" "Password10" "PASS"
launch_test "ppm3.conf" "uid=User-Pw-Test,ou=users,dc=my-domain,dc=com" "Password10" "PASS"
echo "${RESULT} error(s) encountered"
rm ppm1.conf ppm2.conf ppm3.conf
exit ${RESULT}