# CHANGELOG
+* 2022-05-17 David Coutadeur <david.coutadeur@gmail.com>
+ implement a maximum number of characters for each class #18
+ upgrade documentation for new olcPPolicyCheckModule in OpenLDAP 2.6 #30
+ Make one unique code of development for 2.5 and 2.6 OpenLDAP versions #35
+ fix segmentation fault in ppm_test #36
+ various minor fixes and optimizations
+ Version 2.2
* 2022-03-22 David Coutadeur <david.coutadeur@gmail.com>
Reject password if it contains tokens from an attribute of the LDAP entry #17
Version 2.1
# CONTRIBUTIONS
-* 2014 - 2021 - David Coutadeur <david.coutadeur@gmail.com> - maintainer
+* 2014 - 2022 - 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
- etcdir: used to compose default sysconfdir location (defaults to $prefix/etc)
- sysconfdir: where the ppm example policy is to be deployed (defaults to $prefix/$etcdir/$ldap_subdir)
- LDAP_SRC: path to OpenLDAP source directory
-- Options in OPTS variable:
+- Options in DEFS variable:
CONFIG_FILE: (DEPRECATED) path to a ppm configuration file (see PPM_READ_FILE in ppm.h)
note: ppm configuration now lies into pwdCheckModuleArg password policy attribute
provided example file is only helpful as an example or for testing
```
make clean
-make LDAP_SRC=../../.. prefix=/usr/local libdir=/usr/local/lib
+make LDAP_SRC=../../.. prefix=/usr/local libdir=/usr/local/lib
make test LDAP_SRC=../../..
make doc prefix=/usr/local
make install prefix=/usr/local libdir=/usr/local/lib
all: ppm $(TEST)
$(TEST): ppm
- $(CC) $(CFLAGS) $(OPT) $(CPPFLAGS) $(LDFLAGS) $(INCS) $(LDAP_LIBS) -Wl,-rpath=. -o $(TEST) ppm_test.c $(PROGRAMS) $(LDAP_LIBS) $(CRACKLIB)
+ $(CC) $(CFLAGS) $(OPT) $(CPPFLAGS) $(DEFS) $(LDFLAGS) $(INCS) -Wl,-rpath=. -o $(TEST) ppm_test.c $(PROGRAMS) $(LDAP_LIBS) $(CRACKLIB)
ppm.o:
$(CC) $(CFLAGS) $(OPT) $(CPPFLAGS) $(DEFS) -c $(INCS) ppm.c
/*
- password policy module is called with:
+ password policy module is called with (openldap 2.6):
int check_password (char *pPasswd, struct berval *ppErrmsg, Entry *e, void *pArg)
+ password policy module is called with (openldap 2.5):
+ int check_password (char *pPasswd, char **ppErrStr, Entry *e, void *pArg)
+
*pPasswd: new password
+ **ppErrStr: pointer to the string containing the error message
*ppErrmsg: pointer to a struct berval containing space for an error message of length bv_len
*e: pointer to the current user entry
*pArg: pointer to a struct berval holding the value of pwdCheckModuleArg attr
void
storeEntry(char *param, char *value, valueType valType,
- char *min, char *minForPoint, conf * fileConf, int *numParam)
+ char *min, char *minForPoint, char *max, conf * fileConf,
+ int *numParam)
{
int i = 0;
int iMin;
int iMinForPoint;
+ int iMax;
if (min == NULL || strcmp(min,"") == 0)
iMin = 0;
else
else
iMinForPoint = atoi(minForPoint);
+ if (max == NULL || strcmp(max,"") == 0)
+ iMax = 0;
+ else
+ iMax = atoi(max);
+
// First scan parameters
for (i = 0; i < *numParam; i++) {
if ((strlen(param) == strlen(fileConf[i].param))
strcpy_safe(fileConf[i].value.sVal, value, VALUE_MAX_LEN);
fileConf[i].min = iMin;
fileConf[i].minForPoint = iMinForPoint;
+ fileConf[i].max = iMax;
if(valType == typeInt)
ppm_log(LOG_NOTICE, "ppm: Accepted replaced value: %d",
fileConf[i].value.iVal);
strcpy_safe(fileConf[i].value.sVal, value, VALUE_MAX_LEN);
fileConf[*numParam].min = iMin;
fileConf[*numParam].minForPoint = iMinForPoint;
+ fileConf[*numParam].max = iMax;
++(*numParam);
if(valType == typeInt)
ppm_log(LOG_NOTICE, "ppm: Accepted new value: %d",
ppm_log(LOG_NOTICE, "ppm: get line: %s",token);
char *start = token;
char *word, *value;
- char *min, *minForPoint;;
+ char *min, *minForPoint, *max;
while (isspace(*start) && isascii(*start))
start++;
if (minForPoint != NULL)
if (strchr(minForPoint, '\n') != NULL)
strchr(minForPoint, '\n')[0] = '\0';
+ max = strtok_r(NULL, " \t", &saveptr2);
+ if (max != NULL)
+ if (strchr(max, '\n') != NULL)
+ strchr(max, '\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);
+ "ppm: Param = %s, value = %s, min = %s, minForPoint = %s, max = %s",
+ word, value, min, minForPoint, max);
storeEntry(word, value, allowedParameters[nParam].iType,
- min, minForPoint, fileConf, numParam);
+ min, minForPoint, max, fileConf, numParam);
}
else
{
while (fgets(line, 256, config) != NULL) {
char *start = line;
char *word, *value;
- char *min, *minForPoint;;
+ char *min, *minForPoint, *max;
while (isspace(*start) && isascii(*start))
start++;
if (minForPoint != NULL)
if (strchr(minForPoint, '\n') != NULL)
strchr(minForPoint, '\n')[0] = '\0';
+ max = strtok(NULL, " \t");
+ if (max != NULL)
+ if (strchr(max, '\n') != NULL)
+ strchr(max, '\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);
+ "ppm: Param = %s, value = %s, min = %s, minForPoint = %s, max = %s",
+ word, value, min, minForPoint, max);
storeEntry(word, value, allowedParameters[nParam].iType,
- min, minForPoint, fileConf, numParam);
+ min, minForPoint, max, fileConf, numParam);
}
else
{
#endif
static int
+#if OLDAP_VERSION == 0x0205
+realloc_error_message(char **target, int curlen, int nextlen)
+#else
realloc_error_message(const char *orig, char **target, int curlen, int nextlen)
+#endif
{
if (curlen < nextlen + MEMORY_MARGIN) {
ppm_log(LOG_WARNING,
"ppm: Reallocating szErrStr from %d to %d", curlen,
nextlen + MEMORY_MARGIN);
+#if OLDAP_VERSION == 0x0205
+ ber_memfree(*target);
+#else
if (*target != orig)
ber_memfree(*target);
+#endif
curlen = nextlen + MEMORY_MARGIN;
*target = (char *) ber_memalloc(curlen);
}
int
+#if OLDAP_VERSION == 0x0205
+check_password(char *pPasswd, char **ppErrStr, Entry *e, void *pArg)
+#else
check_password(char *pPasswd, struct berval *ppErrmsg, Entry *e, void *pArg)
+#endif
{
Entry *pEntry = e;
struct berval *pwdCheckModuleArg = pArg;
+#if OLDAP_VERSION == 0x0205
+ char *szErrStr = (char *) ber_memalloc(MEM_INIT_SZ);
+ int mem_len = MEM_INIT_SZ;
+#else
char *origmsg = ppErrmsg->bv_val;
char *szErrStr = origmsg;
int mem_len = ppErrmsg->bv_len;
+#endif
int numParam = 0; // Number of params in current configuration
int useCracklib;
#else
if ( !pwdCheckModuleArg || !pwdCheckModuleArg->bv_val ) {
ppm_log(LOG_ERR, "ppm: No config provided in pwdCheckModuleArg");
+#if OLDAP_VERSION == 0x0205
+ mem_len = realloc_error_message(&szErrStr, mem_len,
+#else
mem_len = realloc_error_message(origmsg, &szErrStr, mem_len,
+#endif
strlen(GENERIC_ERROR));
sprintf(szErrStr, GENERIC_ERROR);
goto fail;
ppm_log(LOG_NOTICE, "ppm: RAW configuration: %s", pwdCheckModuleArg->bv_val);
#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
+ {"minQuality", typeInt, {.iVal = DEFAULT_QUALITY}, 0, 0, 0
}
,
- {"checkRDN", typeInt, {.iVal = 0}, 0, 0
+ {"checkRDN", typeInt, {.iVal = 0}, 0, 0, 0
}
,
- {"forbiddenChars", typeStr, {.sVal = ""}, 0, 0
+ {"forbiddenChars", typeStr, {.sVal = ""}, 0, 0, 0
}
,
- {"maxConsecutivePerClass", typeInt, {.iVal = 0}, 0, 0
+ {"maxConsecutivePerClass", typeInt, {.iVal = 0}, 0, 0, 0
}
,
- {"useCracklib", typeInt, {.iVal = 0}, 0, 0
+ {"useCracklib", typeInt, {.iVal = 0}, 0, 0, 0
}
,
- {"cracklibDict", typeStr, {.sVal = "/var/cache/cracklib/cracklib_dict"}, 0, 0
+ {"cracklibDict", typeStr, {.sVal = "/var/cache/cracklib/cracklib_dict"}, 0, 0, 0
}
,
- {"class-upperCase", typeStr, {.sVal = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"}, 0, 1
+ {"class-upperCase", typeStr, {.sVal = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"}, 0, 1, 0
}
,
- {"class-lowerCase", typeStr, {.sVal = "abcdefghijklmnopqrstuvwxyz"}, 0, 1
+ {"class-lowerCase", typeStr, {.sVal = "abcdefghijklmnopqrstuvwxyz"}, 0, 1, 0
}
,
- {"class-digit", typeStr, {.sVal = "0123456789"}, 0, 1
+ {"class-digit", typeStr, {.sVal = "0123456789"}, 0, 1, 0
}
,
{"class-special", typeStr,
- {.sVal = "<>,?;.:/!§ù%*µ^¨$£²&é~\"#'{([-|è`_\\ç^à@)]°=}+"}, 0, 1
+ {.sVal = "<>,?;.:/!§ù%*µ^¨$£²&é~\"#'{([-|è`_\\ç^à@)]°=}+"}, 0, 1, 0
}
,
- {"checkAttributes", typeStr, {.sVal = ""}, 0, 0
+ {"checkAttributes", typeStr, {.sVal = ""}, 0, 0, 0
}
};
numParam = 11;
getValue(fileConf, numParam, "checkAttributes")->sVal,
VALUE_MAX_LEN);
+ for (i = 0; i < numParam; i++)
+ nbInClass[i] = 0;
+
/*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 contains at most max chars of each class
* It must not contain any char in forbiddenChar */
for (i = 0; i < strlen(pPasswd); i++) {
}
// Password checking done, now loocking for minForPoint criteria
- for (i = 0; i < CONF_MAX_SIZE; i++) {
+ for (i = 0; i < numParam; i++) {
if (strstr(fileConf[i].param, "class-") != NULL) {
if ((nbInClass[i] >= fileConf[i].minForPoint)
&& strlen(fileConf[i].value.sVal) != 0) {
}
if (nQuality < minQuality) {
+#if OLDAP_VERSION == 0x0205
+ mem_len = realloc_error_message(&szErrStr, mem_len,
+#else
mem_len = realloc_error_message(origmsg, &szErrStr, mem_len,
+#endif
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++) {
+
+ // Password checking done, now loocking for minimum criteria
+ for (i = 0; i < numParam; 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
+#if OLDAP_VERSION == 0x0205
+ mem_len = realloc_error_message(&szErrStr, mem_len,
+#else
mem_len = realloc_error_message(origmsg, &szErrStr, mem_len,
- strlen(PASSWORD_CRITERIA) +
+#endif
+ strlen(PASSWORD_MIN_CRITERIA) +
strlen(pEntry->e_nname.bv_val) +
2 + PARAM_MAX_LEN);
- sprintf(szErrStr, PASSWORD_CRITERIA, pEntry->e_nname.bv_val,
+ sprintf(szErrStr, PASSWORD_MIN_CRITERIA, pEntry->e_nname.bv_val,
fileConf[i].min, fileConf[i].param);
goto fail;
}
}
}
+ // Password checking done, now loocking for maximum criteria
+ for (i = 0; i < numParam; i++) {
+ if (strstr(fileConf[i].param, "class-") != NULL) {
+ if ( (fileConf[i].max != 0) &&
+ (nbInClass[i] > fileConf[i].max) &&
+ strlen(fileConf[i].value.sVal) != 0) {
+ // constraint is not satisfied... goto fail
+#if OLDAP_VERSION == 0x0205
+ mem_len = realloc_error_message(&szErrStr, mem_len,
+#else
+ mem_len = realloc_error_message(origmsg, &szErrStr, mem_len,
+#endif
+ strlen(PASSWORD_MAX_CRITERIA) +
+ strlen(pEntry->e_nname.bv_val) +
+ 2 + PARAM_MAX_LEN);
+ sprintf(szErrStr, PASSWORD_MAX_CRITERIA, pEntry->e_nname.bv_val,
+ fileConf[i].max, fileConf[i].param);
+ goto fail;
+ }
+ }
+ }
+
// Password checking done, now loocking for forbiddenChars criteria
if (nForbiddenChars > 0) { // at least 1 forbidden char... goto fail
+#if OLDAP_VERSION == 0x0205
+ mem_len = realloc_error_message(&szErrStr, mem_len,
+#else
mem_len = realloc_error_message(origmsg, &szErrStr, mem_len,
+#endif
strlen(PASSWORD_FORBIDDENCHARS) +
strlen(pEntry->e_nname.bv_val) + 2 +
VALUE_MAX_LEN);
}
// Password checking done, now loocking for maxConsecutivePerClass criteria
- for (i = 0; i < CONF_MAX_SIZE; i++) {
+ for (i = 0; i < numParam; i++) {
if (strstr(fileConf[i].param, "class-") != NULL) {
if ( maxConsecutivePerClass != 0 &&
(maxConsPerClass(pPasswd,fileConf[i].value.sVal)
// Too much consecutive characters of the same class
ppm_log(LOG_NOTICE, "ppm: Too much consecutive chars for class %s",
fileConf[i].param);
+#if OLDAP_VERSION == 0x0205
+ mem_len = realloc_error_message(&szErrStr, mem_len,
+#else
mem_len = realloc_error_message(origmsg, &szErrStr, mem_len,
+#endif
strlen(PASSWORD_MAXCONSECUTIVEPERCLASS) +
strlen(pEntry->e_nname.bv_val) + 2 +
PARAM_MAX_LEN);
if (( fd = fopen ( cracklibDictFiles[j], "r")) == NULL ) {
ppm_log(LOG_NOTICE, "ppm: Error while reading %s file",
cracklibDictFiles[j]);
+#if OLDAP_VERSION == 0x0205
+ mem_len = realloc_error_message(&szErrStr, mem_len,
+#else
mem_len = realloc_error_message(origmsg, &szErrStr, mem_len,
+#endif
strlen(GENERIC_ERROR));
sprintf(szErrStr, GENERIC_ERROR);
goto fail;
if ( res != NULL ) {
ppm_log(LOG_NOTICE, "ppm: cracklib does not validate password for entry %s",
pEntry->e_nname.bv_val);
+#if OLDAP_VERSION == 0x0205
+ mem_len = realloc_error_message(&szErrStr, mem_len,
+#else
mem_len = realloc_error_message(origmsg, &szErrStr, mem_len,
+#endif
strlen(PASSWORD_CRACKLIB) +
strlen(pEntry->e_nname.bv_val));
sprintf(szErrStr, PASSWORD_CRACKLIB, pEntry->e_nname.bv_val);
if (checkRDN == 1 && containsRDN(pPasswd, pEntry->e_nname.bv_val))
// RDN check enabled and a token from RDN is found in password: goto fail
{
+#if OLDAP_VERSION == 0x0205
+ mem_len = realloc_error_message(&szErrStr, mem_len,
+#else
mem_len = realloc_error_message(origmsg, &szErrStr, mem_len,
+#endif
strlen(RDN_TOKEN_FOUND) +
strlen(pEntry->e_nname.bv_val));
sprintf(szErrStr, RDN_TOKEN_FOUND, pEntry->e_nname.bv_val);
}
// Password checking done, now looking for checkAttributes criteria
- if (containsAttributes(pPasswd, pEntry, checkAttributes))
+ if ( strcmp(checkAttributes, "") !=0 &&
+ containsAttributes(pPasswd, pEntry, checkAttributes))
// A token from an attribute is found in password: goto fail
{
+#if OLDAP_VERSION == 0x0205
+ mem_len = realloc_error_message(&szErrStr, mem_len,
+#else
mem_len = realloc_error_message(origmsg, &szErrStr, mem_len,
+#endif
strlen(ATTR_TOKEN_FOUND) +
strlen(pEntry->e_nname.bv_val));
sprintf(szErrStr, ATTR_TOKEN_FOUND, pEntry->e_nname.bv_val);
goto fail;
}
+#if OLDAP_VERSION == 0x0205
+ *ppErrStr = strdup("");
+ ber_memfree(szErrStr);
+#else
szErrStr[0] = '\0';
+#endif
return (LDAP_SUCCESS);
fail:
+#if OLDAP_VERSION == 0x0205
+ *ppErrStr = strdup(szErrStr);
+ ber_memfree(szErrStr);
+#else
ppErrmsg->bv_val = szErrStr;
ppErrmsg->bv_len = mem_len;
+#endif
return (EXIT_FAILURE);
}
# cn: default
# pwdMinLength: 6
# pwdCheckModule: /usr/local/lib/ppm.so
-# pwdCheckModuleArg:: bWluUXVhbGl0eSAzCmNoZWNrUkROIDAKY2hlY2tBdHRyaWJ1dGVzCmZvcmJpZGRlbkNoYXJzCm1heENvbnNlY3V0aXZlUGVyQ2xhc3MgMAp1c2VDcmFja2xpYiAwCmNyYWNrbGliRGljdCAvdmFyL2NhY2hlL2NyYWNrbGliL2NyYWNrbGliX2RpY3QKY2xhc3MtdXBwZXJDYXNlIEFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaIDAgMQpjbGFzcy1sb3dlckNhc2UgYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXogMCAxCmNsYXNzLWRpZ2l0IDAxMjM0NTY3ODkgMCAxCmNsYXNzLXNwZWNpYWwgPD4sPzsuOi8hwqfDuSUqwrVewqgkwqPCsibDqX4iIyd7KFstfMOoYF9cw6dew6BAKV3CsD19KyAwIDEK
+# pwdCheckModuleArg:: bWluUXVhbGl0eSAzCmNoZWNrUkROIDAKY2hlY2tBdHRyaWJ1dGVzCmZvcmJpZGRlbkNoYXJzCm1heENvbnNlY3V0aXZlUGVyQ2xhc3MgMAp1c2VDcmFja2xpYiAwCmNyYWNrbGliRGljdCAvdmFyL2NhY2hlL2NyYWNrbGliL2NyYWNrbGliX2RpY3QKY2xhc3MtdXBwZXJDYXNlIEFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaIDAgMSAwCmNsYXNzLWxvd2VyQ2FzZSBhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5eiAwIDEgMApjbGFzcy1kaWdpdCAwMTIzNDU2Nzg5IDAgMSAwCmNsYXNzLXNwZWNpYWwgPD4sPzsuOi8hwqfDuSUqwrVewqgkwqPCsibDqX4iIyd7KFstfMOoYF9cw6dew6BAKV3CsD19KyAwIDEgMAo=
#
# Different parameters are separated by a linefeed (\n)
# Parameters starting with a # are ignored
# [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
+# [MAX]: if > [MAX] occurrences of characters from this class are found, then the password is rejected (0 means no maximum)
+class-upperCase ABCDEFGHIJKLMNOPQRSTUVWXYZ 0 1 0
+class-lowerCase abcdefghijklmnopqrstuvwxyz 0 1 0
+class-digit 0123456789 0 1 0
+class-special <>,?;.:/!§ù%*µ^¨$£²&é~"#'{([-|è`_\ç^à@)]°=}+ 0 1 0
#include <syslog.h>
#endif
+// Get OpenLDAP version
+#define OLDAP_VERSION ((LDAP_VENDOR_VERSION_MAJOR << 8) | LDAP_VENDOR_VERSION_MINOR)
+// OLDAP_VERSION = 0x0205 // (v2.5)
+// OLDAP_VERSION = 0x0206 // (v2.6)
+
//#define PPM_READ_FILE 1 // old deprecated configuration mode
// 1: (deprecated) don't read pwdCheckModuleArg
// attribute, instead read config file
#define DEFAULT_QUALITY 3
#define MEMORY_MARGIN 50
+#if OLDAP_VERSION == 0x0205
+ #define MEM_INIT_SZ 64
+#endif
#define DN_MAX_LEN 512
#define CONF_MAX_SIZE 50
#define PASSWORD_QUALITY_SZ \
"Password for dn=\"%s\" does not pass required number of strength checks (%d of %d)"
-#define PASSWORD_CRITERIA \
+#define PASSWORD_MIN_CRITERIA \
"Password for dn=\"%s\" has not reached the minimum number of characters (%d) for class %s"
+#define PASSWORD_MAX_CRITERIA \
+ "Password for dn=\"%s\" has reached the maximum 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 \
genValue value;
int min;
int minForPoint;
+ int max;
} conf;
void ppm_log(int priority, const char *format, ...);
#ifdef PPM_READ_FILE
static void read_config_file(conf * fileConf, int *numParam, char *ppm_config_file);
#endif
-int check_password(char *pPasswd, struct berval *ppErrmsg, Entry *e, void *pArg);
+
+#if OLDAP_VERSION == 0x0205
+ int check_password(char *pPasswd, char **ppErrStr, Entry *e, void *pArg);
+#else
+ int check_password(char *pPasswd, struct berval *ppErrmsg, Entry *e, void *pArg);
+#endif
int maxConsPerClass(char *password, char *charClass);
void storeEntry(char *param, char *value, valueType valType,
- char *min, char *minForPoint, conf * fileConf, int *numParam);
+ char *min, char *minForPoint, char *max, 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);
Create a password policy entry and indicate the path of the ppm.so library
and the content of the desired policy.
Use a base64 tool to code / decode the content of the policy stored into
-**pwdCheckModuleArg**. Here is an example:
+**pwdCheckModuleArg**.
+
+Here is an example for OpenLDAP 2.6:
```
dn: cn=default,ou=policies,dc=my-domain,dc=com
sn: default
cn: default
pwdMinLength: 6
-pwdCheckModule: /usr/local/lib/ppm.so
-pwdCheckModuleArg:: bWluUXVhbGl0eSAzCmNoZWNrUkROIDAKY2hlY2tBdHRyaWJ1dGVzCmZvcmJpZGRlbkNoYXJzCm1heENvbnNlY3V0aXZlUGVyQ2xhc3MgMAp1c2VDcmFja2xpYiAwCmNyYWNrbGliRGljdCAvdmFyL2NhY2hlL2NyYWNrbGliL2NyYWNrbGliX2RpY3QKY2xhc3MtdXBwZXJDYXNlIEFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaIDAgMQpjbGFzcy1sb3dlckNhc2UgYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXogMCAxCmNsYXNzLWRpZ2l0IDAxMjM0NTY3ODkgMCAxCmNsYXNzLXNwZWNpYWwgPD4sPzsuOi8hwqfDuSUqwrVewqgkwqPCsibDqX4iIyd7KFstfMOoYF9cw6dew6BAKV3CsD19KyAwIDEK
+pwdCheckModuleArg:: bWluUXVhbGl0eSAzCmNoZWNrUkROIDAKY2hlY2tBdHRyaWJ1dGVzCmZvcmJpZGRlbkNoYXJzCm1heENvbnNlY3V0aXZlUGVyQ2xhc3MgMAp1c2VDcmFja2xpYiAwCmNyYWNrbGliRGljdCAvdmFyL2NhY2hlL2NyYWNrbGliL2NyYWNrbGliX2RpY3QKY2xhc3MtdXBwZXJDYXNlIEFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaIDAgMSAwCmNsYXNzLWxvd2VyQ2FzZSBhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5eiAwIDEgMApjbGFzcy1kaWdpdCAwMTIzNDU2Nzg5IDAgMSAwCmNsYXNzLXNwZWNpYWwgPD4sPzsuOi8hwqfDuSUqwrVewqgkwqPCsibDqX4iIyd7KFstfMOoYF9cw6dew6BAKV3CsD19KyAwIDEgMAoK
+pwdUseCheckModule: TRUE
```
+For OpenLDAP 2.5, you must add a **pwdCheckModule** attribute pointing
+to the ppm module (for example /usr/local/lib/ppm.so),
+and remove the **pwdUseCheckModule** attribute.
+
+
See **slapo-ppolicy** for more information, but to sum up:
- enable ppolicy overlay in your database.
- define a default password policy in OpenLDAP configuration or use pwdPolicySubentry attribute to point to the given policy.
-This example show the activation for a **slapd.conf** file
+This example show the activation for a **slapd.conf** file for OpenLDAP 2.6
(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_check_module /usr/local/openldap/libexec/openldap/ppm.so
#ppolicy_use_lockout # for having more infos about the lockout
```
+For OpenLDAP 2.5, you must remove **ppolicy_check_module** parameter as
+it is managed in the password policy definition
+
# FEATURES
- passwords must have at least n of the corresponding character class
present, else they are rejected.
-- the two previous criteria are checked against any specific character class
+- passwords must have at the most x occurrences of characters from the
+corresponding character class, else they are rejected.
+
+- the three previous criteria are checked against any specific character class
defined.
- if a password contains any of the forbidden characters, then it is
The syntax of a configuration line is:
```
-parameter value [min] [minForPoint]
+parameter value [min] [minForPoint] [max]
```
with spaces being delimiters and Line Feed (LF) ending the line.
# classes parameter
# Format:
-# class-[CLASS_NAME] [CHARACTERS_DEFINING_CLASS] [MIN] [MIN_FOR_POINT]
+# class-[CLASS_NAME] [CHARACTERS_DEFINING_CLASS] [MIN] [MIN_FOR_POINT] [MAX]
# 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
+# [MAX]: if > [MAX] occurrences of characters from this class are found, then the password is rejected (0 means no maximum)
+class-upperCase ABCDEFGHIJKLMNOPQRSTUVWXYZ 0 1 0
+class-lowerCase abcdefghijklmnopqrstuvwxyz 0 1 0
+class-digit 0123456789 0 1 0
+class-special <>,?;.:/!§ù%*µ^¨$£²&é~"#'{([-|è`_\ç^à@)]°=}+ 0 1 0
```
# EXAMPLE
forbiddenChars .?,
checkRDN 1
checkAttributes mail
-class-upperCase ABCDEFGHIJKLMNOPQRSTUVWXYZ 0 5
-class-lowerCase abcdefghijklmnopqrstuvwxyz 0 12
-class-digit 0123456789 0 1
-class-special <>,?;.:/!§ù%*µ^¨$£²&é~"#'{([-|è`_\ç^à@)]°=}+ 0 1
-class-myClass :) 1 1``
+class-upperCase ABCDEFGHIJKLMNOPQRSTUVWXYZ 0 5 0
+class-lowerCase abcdefghijklmnopqrstuvwxyz 0 12 0
+class-digit 0123456789 0 1 0
+class-special <>,?;.:/!§ù%*µ^¨$£²&é~"#'{([-|è`_\ç^à@)]°=}+ 0 1 0
+class-myClass :) 1 1 0
```
the password **ThereIsNoCowLevel)** is working, because:
```
ppm: entry uid=jack.oneill,ou=people,dc=my-domain,dc=com
ppm: Reading pwdCheckModuleArg attribute
-ppm: RAW configuration: minQuality 3#012checkRDN 0#012checkAttributes mail,uid#012forbiddenChars#012maxConsecutivePerClass 0#012useCracklib 0#012cracklibDict /var/cache/cracklib/cracklib_dict#012class-upperCase ABCDEFGHIJKLMNOPQRSTUVWXYZ 0 1#012class-lowerCase abcdefghijklmnopqrstuvwxyz 0 1#012class-digit 0123456789 0 1#012class-special <>,?;.:/!§ù%*µ^¨$£²&é~"#'{([-|è`_\ç^à@)]°=}+ 0 1
+ppm: RAW configuration: minQuality 3#012checkRDN 0#012checkAttributes mail,uid#012forbiddenChars#012maxConsecutivePerClass 0#012useCracklib 0#012cracklibDict /var/cache/cracklib/cracklib_dict#012class-upperCase ABCDEFGHIJKLMNOPQRSTUVWXYZ 0 1 0#012class-lowerCase abcdefghijklmnopqrstuvwxyz 0 1 0#012class-digit 0123456789 0 1 0#012class-special <>,?;.:/!§ù%*µ^¨$£²&é~"#'{([-|è`_\ç^à@)]°=}+ 0 1 0
ppm: Parsing pwdCheckModuleArg attribute
ppm: get line: minQuality 3
ppm: Param = minQuality, value = 3, min = (null), minForPoint= (null)
ppm: get line: cracklibDict /var/cache/cracklib/cracklib_dict
ppm: Param = cracklibDict, value = /var/cache/cracklib/cracklib_dict, min = (null), minForPoint= (null)
ppm: Accepted replaced value: /var/cache/cracklib/cracklib_dict
-ppm: get line: class-upperCase ABCDEFGHIJKLMNOPQRSTUVWXYZ 0 1
-ppm: Param = class-upperCase, value = ABCDEFGHIJKLMNOPQRSTUVWXYZ, min = 0, minForPoint= 1
+ppm: get line: class-upperCase ABCDEFGHIJKLMNOPQRSTUVWXYZ 0 1 0
+ppm: Param = class-upperCase, value = ABCDEFGHIJKLMNOPQRSTUVWXYZ, min = 0, minForPoint = 1, max = 0
ppm: Accepted replaced value: ABCDEFGHIJKLMNOPQRSTUVWXYZ
-ppm: get line: class-lowerCase abcdefghijklmnopqrstuvwxyz 0 1
-ppm: Param = class-lowerCase, value = abcdefghijklmnopqrstuvwxyz, min = 0, minForPoint= 1
+ppm: get line: class-lowerCase abcdefghijklmnopqrstuvwxyz 0 1 0
+ppm: Param = class-lowerCase, value = abcdefghijklmnopqrstuvwxyz, min = 0, minForPoint = 1, max = 0
ppm: Accepted replaced value: abcdefghijklmnopqrstuvwxyz
-ppm: get line: class-digit 0123456789 0 1
-ppm: Param = class-digit, value = 0123456789, min = 0, minForPoint= 1
+ppm: get line: class-digit 0123456789 0 1 0
+ppm: Param = class-digit, value = 0123456789, min = 0, minForPoint = 1, max = 0
ppm: Accepted replaced value: 0123456789
-ppm: get line: class-special <>,?;.:/!§ù%*µ^¨$£²&é~"#'{([-|è`_\ç^à@)]°=}+ 0 1
-ppm: Param = class-special, value = <>,?;.:/!§ù%*µ^¨$£²&é~"#'{([-|è`_\ç^à@)]°=}+, min = 0, minForPoint= 1
+ppm: get line: class-special <>,?;.:/!§ù%*µ^¨$£²&é~"#'{([-|è`_\ç^à@)]°=}+ 0 1 0
+ppm: Param = class-special, value = <>,?;.:/!§ù%*µ^¨$£²&é~"#'{([-|è`_\ç^à@)]°=}+, min = 0, minForPoint = 1, max = 0
ppm: Accepted replaced value: <>,?;.:/!§ù%*µ^¨$£²&é~"#'{([-|è`_\ç^à@)]°=}+
ppm: 1 point granted for class class-upperCase
ppm: 1 point granted for class class-lowerCase
**ppm.so**
-> ppm library, loaded by the **pwdCheckModule** attribute of given password policy
+> ppm library, loaded by the **pwdCheckModule** attribute of given password policy (OpenLDAP 2.5)
+> or by the **ppolicy_check_module** / **olcPPolicyCheckModule** parameters of the ppolicy overlay (OpenLDAP 2.6)
**ppm_test**
# ACKNOWLEDGEMENTS
-This module was developed in 2014-2021 by David Coutadeur.
+This module was developed in 2014-2022 by David Coutadeur.
);
/* format user entry */
+#if OLDAP_VERSION == 0x0205
+ char *errmsg = NULL;
+#else
char errbuf[256];
struct berval errmsg = { sizeof(errbuf)-1, errbuf };
+#endif
Entry pEntry;
pEntry.e_nname.bv_val=argv[1];
pEntry.e_name.bv_val=argv[1];
}
else
{
+#if OLDAP_VERSION == 0x0205
+ printf("Password failed checks : %s\n", errmsg);
+#else
printf("Password failed checks : %s\n", errmsg.bv_val);
+#endif
}
+#if OLDAP_VERSION == 0x0205
+ ber_memfree(errmsg);
+#else
if (errmsg.bv_val != errbuf)
ber_memfree(errmsg.bv_val);
+#endif
return ret;
}
library and the content of the desired policy.
Use a base64 tool to code / decode the content of the policy stored into
\f[B]pwdCheckModuleArg\f[R].
-Here is an example:
+.PP
+Here is an example for OpenLDAP 2.6:
.IP
.nf
\f[C]
sn: default
cn: default
pwdMinLength: 6
-pwdCheckModule: /usr/local/lib/ppm.so
-pwdCheckModuleArg:: bWluUXVhbGl0eSAzCmNoZWNrUkROIDAKY2hlY2tBdHRyaWJ1dGVzCmZvcmJpZGRlbkNoYXJzCm1heENvbnNlY3V0aXZlUGVyQ2xhc3MgMAp1c2VDcmFja2xpYiAwCmNyYWNrbGliRGljdCAvdmFyL2NhY2hlL2NyYWNrbGliL2NyYWNrbGliX2RpY3QKY2xhc3MtdXBwZXJDYXNlIEFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaIDAgMQpjbGFzcy1sb3dlckNhc2UgYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXogMCAxCmNsYXNzLWRpZ2l0IDAxMjM0NTY3ODkgMCAxCmNsYXNzLXNwZWNpYWwgPD4sPzsuOi8hwqfDuSUqwrVewqgkwqPCsibDqX4iIyd7KFstfMOoYF9cw6dew6BAKV3CsD19KyAwIDEK
+pwdCheckModuleArg:: bWluUXVhbGl0eSAzCmNoZWNrUkROIDAKY2hlY2tBdHRyaWJ1dGVzCmZvcmJpZGRlbkNoYXJzCm1heENvbnNlY3V0aXZlUGVyQ2xhc3MgMAp1c2VDcmFja2xpYiAwCmNyYWNrbGliRGljdCAvdmFyL2NhY2hlL2NyYWNrbGliL2NyYWNrbGliX2RpY3QKY2xhc3MtdXBwZXJDYXNlIEFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaIDAgMSAwCmNsYXNzLWxvd2VyQ2FzZSBhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5eiAwIDEgMApjbGFzcy1kaWdpdCAwMTIzNDU2Nzg5IDAgMSAwCmNsYXNzLXNwZWNpYWwgPD4sPzsuOi8hwqfDuSUqwrVewqgkwqPCsibDqX4iIyd7KFstfMOoYF9cw6dew6BAKV3CsD19KyAwIDEgMAoK
+pwdUseCheckModule: TRUE
\f[R]
.fi
.PP
+For OpenLDAP 2.5, you must add a \f[B]pwdCheckModule\f[R] attribute
+pointing to the ppm module (for example /usr/local/lib/ppm.so), and
+remove the \f[B]pwdUseCheckModule\f[R] attribute.
+.PP
See \f[B]slapo-ppolicy\f[R] for more information, but to sum up:
.IP \[bu] 2
enable ppolicy overlay in your database.
define a default password policy in OpenLDAP configuration or use
pwdPolicySubentry attribute to point to the given policy.
.PP
-This example show the activation for a \f[B]slapd.conf\f[R] file (see
-\f[B]slapd-config\f[R] and \f[B]slapo-ppolicy\f[R] for more information
-for \f[B]cn=config\f[R] configuration)
+This example show the activation for a \f[B]slapd.conf\f[R] file for
+OpenLDAP 2.6 (see \f[B]slapd-config\f[R] and \f[B]slapo-ppolicy\f[R] for
+more information for \f[B]cn=config\f[R] configuration)
.IP
.nf
\f[C]
overlay ppolicy
ppolicy_default \[dq]cn=default,ou=policies,dc=my-domain,dc=com\[dq]
+ppolicy_check_module /usr/local/openldap/libexec/openldap/ppm.so
#ppolicy_use_lockout # for having more infos about the lockout
\f[R]
.fi
+.PP
+For OpenLDAP 2.5, you must remove \f[B]ppolicy_check_module\f[R]
+parameter as it is managed in the password policy definition
.SH FEATURES
.PP
Here are the main features:
passwords must have at least n of the corresponding character class
present, else they are rejected.
.IP \[bu] 2
-the two previous criteria are checked against any specific character
+passwords must have at the most x occurrences of characters from the
+corresponding character class, else they are rejected.
+.IP \[bu] 2
+the three previous criteria are checked against any specific character
class defined.
.IP \[bu] 2
if a password contains any of the forbidden characters, then it is
Since OpenLDAP 2.5 version, ppm configuration is held in a binary
attribute of the password policy: \f[B]pwdCheckModuleArg\f[R]
.PP
-The example file (\f[B]ETCDIR/ppm.example\f[R] by default) is to be
+The example file (\f[B]/usr/local/etc/openldap/ppm.example\f[R] by default) is to be
considered as an example configuration, to import in the
\f[B]pwdCheckModuleArg\f[R] attribute.
It is also used for testing passwords with the test program provided.
.IP
.nf
\f[C]
-parameter value [min] [minForPoint]
+parameter value [min] [minForPoint] [max]
\f[R]
.fi
.PP
# classes parameter
# Format:
-# class-[CLASS_NAME] [CHARACTERS_DEFINING_CLASS] [MIN] [MIN_FOR_POINT]
+# class-[CLASS_NAME] [CHARACTERS_DEFINING_CLASS] [MIN] [MIN_FOR_POINT] [MAX]
# 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 <>,?;.:/!\[sc]\[`u]%*\[mc]\[ha]\[ad]$\[Po]\[S2]&\['e]\[ti]\[dq]#\[aq]{([-|\[`e]\[ga]_\[rs]\[,c]\[ha]\[`a]\[at])]\[de]=}+ 0 1
+# [MAX]: if > [MAX] occurrences of characters from this class are found, then the password is rejected (0 means no maximum)
+class-upperCase ABCDEFGHIJKLMNOPQRSTUVWXYZ 0 1 0
+class-lowerCase abcdefghijklmnopqrstuvwxyz 0 1 0
+class-digit 0123456789 0 1 0
+class-special <>,?;.:/!\[sc]\[`u]%*\[mc]\[ha]\[ad]$\[Po]\[S2]&\['e]\[ti]\[dq]#\[aq]{([-|\[`e]\[ga]_\[rs]\[,c]\[ha]\[`a]\[at])]\[de]=}+ 0 1 0
\f[R]
.fi
.SH EXAMPLE
forbiddenChars .?,
checkRDN 1
checkAttributes mail
-class-upperCase ABCDEFGHIJKLMNOPQRSTUVWXYZ 0 5
-class-lowerCase abcdefghijklmnopqrstuvwxyz 0 12
-class-digit 0123456789 0 1
-class-special <>,?;.:/!\[sc]\[`u]%*\[mc]\[ha]\[ad]$\[Po]\[S2]&\['e]\[ti]\[dq]#\[aq]{([-|\[`e]\[ga]_\[rs]\[,c]\[ha]\[`a]\[at])]\[de]=}+ 0 1
-class-myClass :) 1 1\[ga]\[ga]
+class-upperCase ABCDEFGHIJKLMNOPQRSTUVWXYZ 0 5 0
+class-lowerCase abcdefghijklmnopqrstuvwxyz 0 12 0
+class-digit 0123456789 0 1 0
+class-special <>,?;.:/!\[sc]\[`u]%*\[mc]\[ha]\[ad]$\[Po]\[S2]&\['e]\[ti]\[dq]#\[aq]{([-|\[`e]\[ga]_\[rs]\[,c]\[ha]\[`a]\[at])]\[de]=}+ 0 1 0
+class-myClass :) 1 1 0
\f[R]
.fi
.PP
.PP
A more detailed message is written to the server log.
.PP
-While evaluating a password change, you should observe something looking at this in the logs:
+While evaluating a password change, you should observe something looking
+at this in the logs:
.IP
.nf
\f[C]
ppm: entry uid=jack.oneill,ou=people,dc=my-domain,dc=com
ppm: Reading pwdCheckModuleArg attribute
-ppm: RAW configuration: minQuality 3#012checkRDN 0#012checkAttributes mail,uid#012forbiddenChars#012maxConsecutivePerClass 0#012useCracklib 0#012cracklibDict /var/cache/cracklib/cracklib_dict#012class-upperCase ABCDEFGHIJKLMNOPQRSTUVWXYZ 0 1#012class-lowerCase abcdefghijklmnopqrstuvwxyz 0 1#012class-digit 0123456789 0 1#012class-special <>,?;.:/!§ù%*µ^¨$£²&é~"#'{([-|è`_\ç^à@)]°=}+ 0 1
+ppm: RAW configuration: minQuality 3#012checkRDN 0#012checkAttributes mail,uid#012forbiddenChars#012maxConsecutivePerClass 0#012useCracklib 0#012cracklibDict /var/cache/cracklib/cracklib_dict#012class-upperCase ABCDEFGHIJKLMNOPQRSTUVWXYZ 0 1 0#012class-lowerCase abcdefghijklmnopqrstuvwxyz 0 1 0#012class-digit 0123456789 0 1 0#012class-special <>,?;.:/!\[sc]\[`u]%*\[mc]\[ha]\[ad]$\[Po]\[S2]&\['e]\[ti]\[dq]#\[aq]{([-|\[`e]\[ga]_\[rs]\[,c]\[ha]\[`a]\[at])]\[de]=}+ 0 1 0
ppm: Parsing pwdCheckModuleArg attribute
ppm: get line: minQuality 3
ppm: Param = minQuality, value = 3, min = (null), minForPoint= (null)
ppm: get line: cracklibDict /var/cache/cracklib/cracklib_dict
ppm: Param = cracklibDict, value = /var/cache/cracklib/cracklib_dict, min = (null), minForPoint= (null)
ppm: Accepted replaced value: /var/cache/cracklib/cracklib_dict
-ppm: get line: class-upperCase ABCDEFGHIJKLMNOPQRSTUVWXYZ 0 1
-ppm: Param = class-upperCase, value = ABCDEFGHIJKLMNOPQRSTUVWXYZ, min = 0, minForPoint= 1
+ppm: get line: class-upperCase ABCDEFGHIJKLMNOPQRSTUVWXYZ 0 1 0
+ppm: Param = class-upperCase, value = ABCDEFGHIJKLMNOPQRSTUVWXYZ, min = 0, minForPoint = 1, max = 0
ppm: Accepted replaced value: ABCDEFGHIJKLMNOPQRSTUVWXYZ
-ppm: get line: class-lowerCase abcdefghijklmnopqrstuvwxyz 0 1
-ppm: Param = class-lowerCase, value = abcdefghijklmnopqrstuvwxyz, min = 0, minForPoint= 1
+ppm: get line: class-lowerCase abcdefghijklmnopqrstuvwxyz 0 1 0
+ppm: Param = class-lowerCase, value = abcdefghijklmnopqrstuvwxyz, min = 0, minForPoint = 1, max = 0
ppm: Accepted replaced value: abcdefghijklmnopqrstuvwxyz
-ppm: get line: class-digit 0123456789 0 1
-ppm: Param = class-digit, value = 0123456789, min = 0, minForPoint= 1
+ppm: get line: class-digit 0123456789 0 1 0
+ppm: Param = class-digit, value = 0123456789, min = 0, minForPoint = 1, max = 0
ppm: Accepted replaced value: 0123456789
-ppm: get line: class-special <>,?;.:/!§ù%*µ^¨$£²&é~"#'{([-|è`_\ç^à@)]°=}+ 0 1
-ppm: Param = class-special, value = <>,?;.:/!§ù%*µ^¨$£²&é~"#'{([-|è`_\ç^à@)]°=}+, min = 0, minForPoint= 1
-ppm: Accepted replaced value: <>,?;.:/!§ù%*µ^¨$£²&é~"#'{([-|è`_\ç^à@)]°=}+
+ppm: get line: class-special <>,?;.:/!\[sc]\[`u]%*\[mc]\[ha]\[ad]$\[Po]\[S2]&\['e]\[ti]\[dq]#\[aq]{([-|\[`e]\[ga]_\[rs]\[,c]\[ha]\[`a]\[at])]\[de]=}+ 0 1 0
+ppm: Param = class-special, value = <>,?;.:/!\[sc]\[`u]%*\[mc]\[ha]\[ad]$\[Po]\[S2]&\['e]\[ti]\[dq]#\[aq]{([-|\[`e]\[ga]_\[rs]\[,c]\[ha]\[`a]\[at])]\[de]=}+, min = 0, minForPoint = 1, max = 0
+ppm: Accepted replaced value: <>,?;.:/!\[sc]\[`u]%*\[mc]\[ha]\[ad]$\[Po]\[S2]&\['e]\[ti]\[dq]#\[aq]{([-|\[`e]\[ga]_\[rs]\[,c]\[ha]\[`a]\[at])]\[de]=}+
ppm: 1 point granted for class class-upperCase
ppm: 1 point granted for class class-lowerCase
ppm: Reallocating szErrStr from 64 to 179
-check_password_quality: module error: (/usr/local/openldap/libexec/openldap/ppm.so) Password for dn="uid=jack.oneill,ou=people,dc=my-domain,dc=com" does not pass required number of strength checks (2 of 3).[1]
+check_password_quality: module error: (/usr/local/openldap/libexec/openldap/ppm.so) Password for dn=\[dq]uid=jack.oneill,ou=people,dc=my-domain,dc=com\[dq] does not pass required number of strength checks (2 of 3).[1]
\f[R]
.fi
.SH TESTS
.fi
.SH FILES
.PP
-\f[B]ETCDIR/ppm.example\f[R]
+\f[B]/usr/local/etc/openldap/ppm.example\f[R]
.RS
.PP
example of ppm configuration to be inserted in
.RS
.PP
ppm library, loaded by the \f[B]pwdCheckModule\f[R] attribute of given
-password policy
+password policy (OpenLDAP 2.5) or by the \f[B]ppolicy_check_module\f[R]
+/ \f[B]olcPPolicyCheckModule\f[R] parameters of the ppolicy overlay
+(OpenLDAP 2.6)
.RE
.PP
\f[B]ppm_test\f[R]
\f[B]slapd.conf\f[R](5)
.SH ACKNOWLEDGEMENTS
.PP
-This module was developed in 2014-2021 by David Coutadeur.
+This module was developed in 2014-2022 by David Coutadeur.
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'
+class-upperCase ABCDEFGHIJKLMNOPQRSTUVWXYZ 0 1 0
+class-lowerCase abcdefghijklmnopqrstuvwxyz 0 1 0
+class-digit 0123456789 0 1 0
+class-special <>,?;.:/!§ù%*µ^¨$£²&é~"#'\''{([-|è`_\ç^à@)]°=}+ 0 1 0'
PPM_CONF_2='minQuality 3
checkRDN 0
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'
+class-upperCase ABCDEFGHIJKLMNOPQRSTUVWXYZ 2 4 10
+class-lowerCase abcdefghijklmnopqrstuvwxyz 3 4 12
+class-digit 0123456789 2 4 10
+class-special <>,?;.:/!§ù%*µ^¨$£²&é~"#'\''{([-|è`_\ç^à@)]°=}+ 0 4 10'
PPM_CONF_3='minQuality 3
checkRDN 1
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'
+class-upperCase ABCDEFGHIJKLMNOPQRSTUVWXYZ 0 1 0
+class-lowerCase abcdefghijklmnopqrstuvwxyz 0 1 0
+class-digit 0123456789 0 1 0
+class-special <>,?;.:/!§ù%*µ^¨$£²&é~"#'\''{([-|è`_\ç^à@)]°=}+ 0 1 0'
echo "$PPM_CONF_1" > ppm1.conf
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
+# 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"
+# too much upper
+launch_test "ppm2.conf" "uid=test,ou=users,dc=my-domain,dc=com" "AAaa01AAaa01AA..AA..AAAA" "FAIL"
+# too much lower
+launch_test "ppm2.conf" "uid=test,ou=users,dc=my-domain,dc=com" "AAaaa01AAaaa01AAaaa0aaaa" "FAIL"
+# too much digit
+launch_test "ppm2.conf" "uid=test,ou=users,dc=my-domain,dc=com" "AA11aa11AA11aa11..11..11" "FAIL"
+# too much special
+launch_test "ppm2.conf" "uid=test,ou=users,dc=my-domain,dc=com" "AA..aa..11..AA..aa..11.." "FAIL"
# password in RDN
launch_test "ppm3.conf" "uid=User_Password10-test,ou=users,dc=my-domain,dc=com" "Password10" "FAIL"