From: David Coutadeur Date: Wed, 11 May 2022 17:16:31 +0000 (+0200) Subject: provide ppm v2.2 (#9846) X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c34d419f3549c89b49855504368f27588874dc10;p=thirdparty%2Fopenldap.git provide ppm v2.2 (#9846) --- diff --git a/contrib/slapd-modules/ppm/CHANGELOG.md b/contrib/slapd-modules/ppm/CHANGELOG.md index 4ccb34fc4c..e3d2ac0203 100644 --- a/contrib/slapd-modules/ppm/CHANGELOG.md +++ b/contrib/slapd-modules/ppm/CHANGELOG.md @@ -1,5 +1,12 @@ # CHANGELOG +* 2022-05-17 David Coutadeur + 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 Reject password if it contains tokens from an attribute of the LDAP entry #17 Version 2.1 diff --git a/contrib/slapd-modules/ppm/CONTRIBUTIONS.md b/contrib/slapd-modules/ppm/CONTRIBUTIONS.md index 0d563d8ef0..3d8b8acbc5 100644 --- a/contrib/slapd-modules/ppm/CONTRIBUTIONS.md +++ b/contrib/slapd-modules/ppm/CONTRIBUTIONS.md @@ -1,5 +1,5 @@ # CONTRIBUTIONS -* 2014 - 2021 - David Coutadeur - maintainer +* 2014 - 2022 - David Coutadeur - maintainer * 2015 - Daly Chikhaoui - Janua - contribution on RDN checks * 2017 - tdb - Tim Bishop - contribution on some compilation improvements diff --git a/contrib/slapd-modules/ppm/INSTALL.md b/contrib/slapd-modules/ppm/INSTALL.md index 6052dc6091..b4cba40947 100644 --- a/contrib/slapd-modules/ppm/INSTALL.md +++ b/contrib/slapd-modules/ppm/INSTALL.md @@ -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 diff --git a/contrib/slapd-modules/ppm/Makefile b/contrib/slapd-modules/ppm/Makefile index 7b6efaddd5..835dd6746f 100644 --- a/contrib/slapd-modules/ppm/Makefile +++ b/contrib/slapd-modules/ppm/Makefile @@ -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 diff --git a/contrib/slapd-modules/ppm/ppm.c b/contrib/slapd-modules/ppm/ppm.c index ded597b436..de8a49cf6c 100644 --- a/contrib/slapd-modules/ppm/ppm.c +++ b/contrib/slapd-modules/ppm/ppm.c @@ -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); } diff --git a/contrib/slapd-modules/ppm/ppm.example b/contrib/slapd-modules/ppm/ppm.example index 73296ca785..9507348d20 100644 --- a/contrib/slapd-modules/ppm/ppm.example +++ b/contrib/slapd-modules/ppm/ppm.example @@ -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 diff --git a/contrib/slapd-modules/ppm/ppm.h b/contrib/slapd-modules/ppm/ppm.h index bdfd519e06..5e07d98ea9 100644 --- a/contrib/slapd-modules/ppm/ppm.h +++ b/contrib/slapd-modules/ppm/ppm.h @@ -18,6 +18,11 @@ #include #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 @@ -47,8 +55,10 @@ #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); diff --git a/contrib/slapd-modules/ppm/ppm.md b/contrib/slapd-modules/ppm/ppm.md index 8cfeae2051..f5ec92169a 100644 --- a/contrib/slapd-modules/ppm/ppm.md +++ b/contrib/slapd-modules/ppm/ppm.md @@ -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. diff --git a/contrib/slapd-modules/ppm/ppm_test.c b/contrib/slapd-modules/ppm/ppm_test.c index 3754149680..637c13ccdd 100644 --- a/contrib/slapd-modules/ppm/ppm_test.c +++ b/contrib/slapd-modules/ppm/ppm_test.c @@ -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; } diff --git a/contrib/slapd-modules/ppm/slapm-ppm.5 b/contrib/slapd-modules/ppm/slapm-ppm.5 index c84927de32..a852037fe3 100644 --- a/contrib/slapd-modules/ppm/slapm-ppm.5 +++ b/contrib/slapd-modules/ppm/slapm-ppm.5 @@ -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. diff --git a/contrib/slapd-modules/ppm/unit_tests.sh b/contrib/slapd-modules/ppm/unit_tests.sh index c152c968e7..31a3aee839 100755 --- a/contrib/slapd-modules/ppm/unit_tests.sh +++ b/contrib/slapd-modules/ppm/unit_tests.sh @@ -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"