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