]> git.ipfire.org Git - thirdparty/openldap.git/commitdiff
provide ppm v2.2 (#9846)
authorDavid Coutadeur <david.coutadeur@gmail.com>
Wed, 11 May 2022 17:16:31 +0000 (19:16 +0200)
committerQuanah Gibson-Mount <quanah@openldap.org>
Thu, 19 May 2022 15:33:18 +0000 (15:33 +0000)
contrib/slapd-modules/ppm/CHANGELOG.md
contrib/slapd-modules/ppm/CONTRIBUTIONS.md
contrib/slapd-modules/ppm/INSTALL.md
contrib/slapd-modules/ppm/Makefile
contrib/slapd-modules/ppm/ppm.c
contrib/slapd-modules/ppm/ppm.example
contrib/slapd-modules/ppm/ppm.h
contrib/slapd-modules/ppm/ppm.md
contrib/slapd-modules/ppm/ppm_test.c
contrib/slapd-modules/ppm/slapm-ppm.5
contrib/slapd-modules/ppm/unit_tests.sh

index 4ccb34fc4cbb163703ff2986c057c1406df726eb..e3d2ac0203eb19bbc8aae2b10a0e690e249bdf09 100644 (file)
@@ -1,5 +1,12 @@
 # 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
index 0d563d8ef0b8a3665e47cbec6e505600850c90fe..3d8b8acbc5f3aa2c1572fc9279d8e10ef81a0e99 100644 (file)
@@ -1,5 +1,5 @@
 # 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
index 6052dc60918b4f9cc1e7c7d2aaabbe5fb82a45c3..b4cba409476e190112cf13a2d4b3c95afb51844e 100644 (file)
@@ -20,7 +20,7 @@ You can optionally customize some variables if you don't want the default ones:
 - 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
@@ -43,7 +43,7 @@ Here is an illustrative example showing how to overload some options:
 
 ```
 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
index 7b6efaddd5a1fb97458af18af30ea13bb7d506ef..835dd6746f770f51e760e5950a492649814041ff 100644 (file)
@@ -65,7 +65,7 @@ MDDOC=ppm.md
 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
index ded597b4368f3c3b0a516ed8fa4d5f247e38db12..de8a49cf6c78584e38dcb79df0f55e2c2a3fc9e1 100644 (file)
@@ -6,10 +6,14 @@
 
 
 /*
-  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
@@ -120,11 +124,13 @@ 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 i = 0;
     int iMin;
     int iMinForPoint;
+    int iMax;
     if (min == NULL || strcmp(min,"") == 0)
       iMin = 0;
     else
@@ -135,6 +141,11 @@ storeEntry(char *param, char *value, valueType valType,
     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))
@@ -147,6 +158,7 @@ storeEntry(char *param, char *value, valueType valType,
                 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);
@@ -165,6 +177,7 @@ storeEntry(char *param, char *value, valueType valType,
         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",
@@ -228,7 +241,7 @@ typeParam(char* param)
         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++;
@@ -262,17 +275,21 @@ typeParam(char* param)
             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
             {
@@ -310,7 +327,7 @@ typeParam(char* param)
     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++;
@@ -333,17 +350,21 @@ typeParam(char* param)
             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
             {
@@ -360,14 +381,22 @@ typeParam(char* param)
 #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);
     }
@@ -511,14 +540,23 @@ containsAttributes(char* passwd, Entry* pEntry, char* checkAttributes)
 
 
 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;
@@ -554,7 +592,11 @@ check_password(char *pPasswd, struct berval *ppErrmsg, Entry *e, void *pArg)
 #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;
@@ -564,43 +606,40 @@ check_password(char *pPasswd, struct berval *ppErrmsg, Entry *e, void *pArg)
     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;
@@ -627,10 +666,14 @@ check_password(char *pPasswd, struct berval *ppErrmsg, Entry *e, void *pArg)
                 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++) {
@@ -649,7 +692,7 @@ check_password(char *pPasswd, struct berval *ppErrmsg, Entry *e, void *pArg)
     }
 
     // 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) {
@@ -662,33 +705,68 @@ check_password(char *pPasswd, struct berval *ppErrmsg, Entry *e, void *pArg)
     }
 
     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);
@@ -698,7 +776,7 @@ check_password(char *pPasswd, struct berval *ppErrmsg, Entry *e, void *pArg)
     }
 
     // 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)
@@ -706,7 +784,11 @@ check_password(char *pPasswd, struct berval *ppErrmsg, Entry *e, void *pArg)
                 // 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);
@@ -726,7 +808,11 @@ check_password(char *pPasswd, struct berval *ppErrmsg, Entry *e, void *pArg)
             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;
@@ -740,7 +826,11 @@ check_password(char *pPasswd, struct berval *ppErrmsg, Entry *e, void *pArg)
         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);
@@ -755,7 +845,11 @@ check_password(char *pPasswd, struct berval *ppErrmsg, Entry *e, void *pArg)
     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);
@@ -764,10 +858,15 @@ check_password(char *pPasswd, struct berval *ppErrmsg, Entry *e, void *pArg)
     }
 
     // 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);
@@ -775,12 +874,22 @@ check_password(char *pPasswd, struct berval *ppErrmsg, Entry *e, void *pArg)
         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);
 
 }
index 73296ca785f6fcaf7acd716edefa93bc8faa4305..9507348d20ec29f675cedb887925447725649ec5 100644 (file)
@@ -15,7 +15,7 @@
 #   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
@@ -90,7 +90,8 @@ cracklibDict /var/cache/cracklib/cracklib_dict
 # [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
index bdfd519e0697f1eefd70500dd66cdd6f8e5446d8..5e07d98ea9b5a50859c41db17c903cea78802155 100644 (file)
 #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
@@ -31,6 +36,9 @@
 
 #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 \
@@ -104,6 +114,7 @@ typedef struct conf {
     genValue value;
     int min;
     int minForPoint;
+    int max;
 } conf;
 
 void ppm_log(int priority, const char *format, ...);
@@ -114,10 +125,16 @@ int min(char *str1, char *str2);
 #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);
index 8cfeae2051692853e9182bebb7b8505040538291..f5ec92169abe70f50b1e5b1849fa0d0192f3a0c4 100644 (file)
@@ -28,7 +28,9 @@ see slapo-ppolicy(5) section **pwdCheckModule**.
 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
@@ -41,26 +43,35 @@ pwdAttribute: userPassword
 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
 
@@ -78,7 +89,10 @@ 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 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
@@ -112,7 +126,7 @@ configuration file path.
 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.
@@ -184,15 +198,16 @@ cracklibDict /var/cache/cracklib/cracklib_dict
 
 # 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
@@ -203,11 +218,11 @@ minQuality 4
 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:
@@ -241,7 +256,7 @@ While evaluating a password change, you should observe something looking at this
 ```
 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)
@@ -263,17 +278,17 @@ ppm:  Accepted replaced value: 0
 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
@@ -302,7 +317,8 @@ LD_LIBRARY_PATH=. ./ppm_test "uid=test,ou=users,dc=my-domain,dc=com" "my_passwor
 
 **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**
 
@@ -315,4 +331,4 @@ LD_LIBRARY_PATH=. ./ppm_test "uid=test,ou=users,dc=my-domain,dc=com" "my_passwor
 
 # ACKNOWLEDGEMENTS
 
-This module was developed in 2014-2021 by David Coutadeur.
+This module was developed in 2014-2022 by David Coutadeur.
index 3754149680f7bd5a22beb5bd5017cba5f85410b6..637c13ccdd099b0f70beb3560feb2540d0143850 100644 (file)
@@ -19,8 +19,12 @@ int main(int argc, char *argv[])
           );
 
     /* 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];
@@ -52,11 +56,19 @@ int main(int argc, char *argv[])
     }
     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;
 
   }
index c84927de3282c8770187d4c532820694df00252b..a852037fe393643794cc29cac8bac9404f9235c0 100644 (file)
@@ -23,7 +23,8 @@ 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
 \f[B]pwdCheckModuleArg\f[R].
-Here is an example:
+.PP
+Here is an example for OpenLDAP 2.6:
 .IP
 .nf
 \f[C]
@@ -37,11 +38,15 @@ pwdAttribute: userPassword
 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.
@@ -49,17 +54,21 @@ 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:
@@ -76,7 +85,10 @@ character class are present in the password.
 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
@@ -93,7 +105,7 @@ if a password does not pass cracklib check, then it is rejected.
 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.
@@ -111,7 +123,7 @@ The syntax of a configuration line is:
 .IP
 .nf
 \f[C]
-parameter value [min] [minForPoint]
+parameter value [min] [minForPoint] [max]
 \f[R]
 .fi
 .PP
@@ -185,15 +197,16 @@ cracklibDict /var/cache/cracklib/cracklib_dict
 
 # 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
@@ -206,11 +219,11 @@ minQuality 4
 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
@@ -247,13 +260,14 @@ Typical user message from ldappasswd(5):
 .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)
@@ -275,22 +289,22 @@ ppm:  Accepted replaced value: 0
 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
@@ -309,7 +323,7 @@ LD_LIBRARY_PATH=. ./ppm_test \[dq]uid=test,ou=users,dc=my-domain,dc=com\[dq] \[d
 .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
@@ -320,7 +334,9 @@ 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]
@@ -334,4 +350,4 @@ small test program for checking password in a command-line
 \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.
index c152c968e77fcfe870af8b1cb15f733dc7794bfa..31a3aee8397b70b20af920a1f18745d52ff2f23f 100755 (executable)
@@ -23,10 +23,10 @@ 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'
+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
@@ -34,10 +34,10 @@ 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'
+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
@@ -45,10 +45,10 @@ 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'
+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
@@ -98,12 +98,20 @@ launch_test "ppm2.conf" "uid=test,ou=users,dc=my-domain,dc=com" "AAaaa01AAaaa01A
 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"