1 2ppm.c - OpenLDAP password policy module 3 4version 2.0 5 6ppm.c is an OpenLDAP module for checking password quality when they are modified. 7Passwords are checked against the presence or absence of certain character classes. 8 9This module is used as an extension of the OpenLDAP password policy controls, 10see slapo-ppolicy(5) section pwdCheckModule. 11 12contributions 13------------- 14 15* 2014 - 2021 - David Coutadeur <david.coutadeur@gmail.com> - maintainer 16* 2015 - Daly Chikhaoui - Janua <dchikhaoui@janua.fr> - contribution on RDN checks 17* 2017 - tdb - Tim Bishop - contribution on some compilation improvements 18 19 20INSTALLATION 21------------ 22 23See INSTALL file 24 25 26USAGE 27----- 28 29Create a password policy entry and indicate the path of the ppm.so library 30and the content of the desired policy. 31Use a base64 tool to code / decode the content of the policy stored into 32pwdCheckModuleArg. Here is an example: 33 34``` 35dn: cn=default,ou=policies,dc=my-domain,dc=com 36objectClass: pwdPolicy 37objectClass: top 38objectClass: pwdPolicyChecker 39objectClass: person 40pwdCheckQuality: 2 41pwdAttribute: userPassword 42sn: default 43cn: default 44pwdMinLength: 6 45pwdCheckModule: /usr/local/lib/ppm.so 46pwdCheckModuleArg:: bWluUXVhbGl0eSAzCmNoZWNrUkROIDAKZm9yYmlkZGVuQ2hhcnMKbWF4Q29uc2VjdXRpdmVQZXJDbGFzcyAwCnVzZUNyYWNrbGliIDAKY3JhY2tsaWJEaWN0IC92YXIvY2FjaGUvY3JhY2tsaWIvY3JhY2tsaWJfZGljdApjbGFzcy11cHBlckNhc2UgQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVogMCAxCmNsYXNzLWxvd2VyQ2FzZSBhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5eiAwIDEKY2xhc3MtZGlnaXQgMDEyMzQ1Njc4OSAwIDEKY2xhc3Mtc3BlY2lhbCA8Piw/Oy46LyHCp8O5JSrCtV7CqCTCo8KyJsOpfiIjJ3soWy18w6hgX1zDp17DoEApXcKwPX0rIDAgMQ== 47``` 48 49 50See slapo-ppolicy for more information, but to sum up: 51- enable ppolicy overlay in your database. 52This example show the activation for a slapd.conf file 53(see slapd-config and slapo-ppolicy for more information for 54 cn=config configuration) 55 56``` 57overlay ppolicy 58ppolicy_default "cn=default,ou=policies,dc=my-domain,dc=com" 59#ppolicy_use_lockout # for having more infos about the lockout 60``` 61 62- define a default password policy in OpenLDAP configuration or 63use pwdPolicySubentry attribute to point to the given policy. 64 65 66 67 68Password checks 69--------------- 70 71- 4 character classes are defined by default: 72upper case, lower case, digits and special characters. 73 74- more character classes can be defined, just write your own. 75 76- passwords must match the amount of quality points. 77A point is validated when at least m characters of the corresponding 78character class are present in the password. 79 80- passwords must have at least n of the corresponding character class 81present, else they are rejected. 82 83- the two previous criteria are checked against any specific character class 84defined. 85 86- if a password contains any of the forbidden characters, then it is 87rejected. 88 89- if a password contains tokens from the RDN, then it is rejected. 90 91- if a password is too long, it can be rejected. 92 93- if a password does not pass cracklib check, it can be rejected. 94 95 96Configuration 97------------- 98 99Since OpenLDAP 2.5 version, ppm configuration is held in a binary 100attribute of the password policy: pwdCheckModuleArg 101The example file (/etc/openldap/ppm.example by default) is to be 102considered as an example configuration, to import in the pwdCheckModuleArg 103attribute. It is also used for testing passwords with the test program 104provided. 105If for some reasons, any parameter is not found, it will be given its 106default value. 107 108Note: you can still compile ppm to use the configuration file, by enabling 109PPM_READ_FILE in ppm.h (but this is deprecated now). If you decide to do so, 110you can use the PPM_CONFIG_FILE environment variable for overloading the 111configuration file path. 112 113The syntax of a configuration line is: 114parameter value [min] [minForPoint] 115 116with spaces being delimiters and Line Feed (LF) ending the line. 117Parameter names ARE case sensitive. 118 119The default configuration is the following: 120 121``` 122# minQuality parameter 123# Format: 124# minQuality [NUMBER] 125# Description: 126# One point is granted for each class for which MIN_FOR_POINT criteria is fulfilled. 127# defines the minimum point numbers for the password to be accepted. 128minQuality 3 129 130# checkRDN parameter 131# Format: 132# checkRDN [0 | 1] 133# Description: 134# If set to 1, password must not contain a token from the RDN. 135# Tokens are separated by the following delimiters : space tabulation _ - , ; £ 136checkRDN 0 137 138# forbiddenChars parameter 139# Format: 140# forbiddenChars [CHARACTERS_FORBIDDEN] 141# Description: 142# Defines the forbidden characters list (no separator). 143# If one of them is found in the password, then it is rejected. 144forbiddenChars 145 146# maxConsecutivePerClass parameter 147# Format: 148# maxConsecutivePerClass [NUMBER] 149# Description: 150# Defines the maximum number of consecutive character allowed for any class 151maxConsecutivePerClass 0 152 153# useCracklib parameter 154# Format: 155# useCracklib [0 | 1] 156# Description: 157# If set to 1, the password must pass the cracklib check 158useCracklib 0 159 160# cracklibDict parameter 161# Format: 162# cracklibDict [path_to_cracklib_dictionary] 163# Description: 164# directory+filename-prefix that your version of CrackLib will go hunting for 165# For example, /var/pw_dict resolves as /var/pw_dict.pwd, 166# /var/pw_dict.pwi and /var/pw_dict.hwm dictionary files 167cracklibDict /var/cache/cracklib/cracklib_dict 168 169# classes parameter 170# Format: 171# class-[CLASS_NAME] [CHARACTERS_DEFINING_CLASS] [MIN] [MIN_FOR_POINT] 172# Description: 173# [CHARACTERS_DEFINING_CLASS]: characters defining the class (no separator) 174# [MIN]: If at least [MIN] characters of this class is not found in the password, then it is rejected 175# [MIN_FOR_POINT]: one point is granted if password contains at least [MIN_FOR_POINT] character numbers of this class 176class-upperCase ABCDEFGHIJKLMNOPQRSTUVWXYZ 0 1 177class-lowerCase abcdefghijklmnopqrstuvwxyz 0 1 178class-digit 0123456789 0 1 179class-special <>,?;.:/!§ù%*µ^¨$£²&é~"#'{([-|è`_\ç^à@)]°=}+ 0 1 180``` 181 182Example 183------- 184 185With this policy: 186``` 187minQuality 4 188forbiddenChars .?, 189checkRDN 1 190class-upperCase ABCDEFGHIJKLMNOPQRSTUVWXYZ 0 5 191class-lowerCase abcdefghijklmnopqrstuvwxyz 0 12 192class-digit 0123456789 0 1 193class-special <>,?;.:/!§ù%*µ^¨$£²&é~"#'{([-|è`_\ç^à@)]°=}+ 0 1 194class-myClass :) 1 1`` 195``` 196 197the password 198 199ThereIsNoCowLevel) 200 201is working, because, 202- it has 4 character classes validated : upper, lower, special, and myClass 203- it has no character among .?, 204- it has at least one character among : or ) 205 206but it won't work for the user uid=John Cowlevel,ou=people,cn=example,cn=com, 207because the token "Cowlevel" from his RDN exists in the password (case insensitive). 208 209 210Logs 211---- 212If a user password is rejected by ppm, the user will get this type of message: 213 214Typical user message from ldappasswd(5): 215 Result: Constraint violation (19) 216 Additional info: Password for dn=\"%s\" does not pass required number of strength checks (2 of 3) 217 218A more detailed message is written to the server log. 219 220Server log: 221 222``` 223Feb 26 14:46:10 debian-10-64 slapd[1981]: conn=1000 op=16 MOD dn="uid=user,ou=persons,dc=my-domain,dc=com" 224Feb 26 14:46:10 debian-10-64 slapd[1981]: conn=1000 op=16 MOD attr=userPassword 225Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: entry uid=user,ou=persons,dc=my-domain,dc=com 226Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: Reading pwdCheckModuleArg attribute 227Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: RAW configuration: # minQuality parameter#012# Format:#012# minQuality [NUMBER]#012# Description:#012# One point is granted for each class for which MIN_FOR_POINT criteria is fulfilled.#012# defines the minimum point numbers for the password to be accepted.#012minQuality 3#012#012# checkRDN parameter#012# Format:#012# checkRDN [0 | 1]#012# Description:#012# If set to 1, password must not contain a token from the RDN.#012# Tokens are separated by the following delimiters : space tabulation _ - , ; £#012checkRDN 0#012#012# forbiddenChars parameter#012# Format:#012# forbiddenChars [CHARACTERS_FORBIDDEN]#012# Description:#012# Defines the forbidden characters list (no separator).#012# If one of them is found in the password, then it is rejected.#012forbiddenChars#012#012# maxConsecutivePerClass parameter#012# Format:#012# maxConsecutivePerClass [NUMBER]#012# Description:#012# Defines the maximum number of consecutive character allowed for any class#012maxConsecutivePerClass 0#012#012# useCracklib parameter#012# Format:#012# useCracklib [0 | 1]#012# Description:#012# If set to 1, the password must pass the cracklib check#012useCracklib 0#012#012# cracklibDict parameter#012# Format:#012# cracklibDict [path_to_cracklib_dictionary]#012# Description:#012# directory+filename-prefix that your version of CrackLib will go hunting for#012# For example, /var/pw_dict resolves as /var/pw_dict.pwd,#012# /var/pw_dict.pwi and /var/pw_dict.hwm dictionary files#012cracklibDict /var/cache/cracklib/cracklib_dict#012#012# classes parameter#012# Format:#012# class-[CLASS_NAME] [CHARACTERS_DEFINING_CLASS] [MIN] [MIN_FOR_POINT]#012# Description:#012# [CHARACTERS_DEFINING_CLASS]: characters defining the class (no separator)#012# [MIN]: If at least [MIN] characters of this class is not found in the password, then it is rejected#012# [MIN_FOR_POINT]: one point is granted if password contains at least [MIN_FOR_POINT] character numbers of this class#012class-upperCase ABCDEFGHIJKLMNOPQRSTUVWXYZ 0 1#012class-lowerCase abcdefghijklmnopqrstuvwxyz 0 1#012class-digit 0123456789 0 1#012class-special <>,?;.:/!§ù%*µ^¨$£²&é~"#'{([-|è`_\ç^à@)]°=}+ 0 1 228Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: Parsing pwdCheckModuleArg attribute 229Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: # minQuality parameter 230Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: # Format: 231Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: # minQuality [NUMBER] 232Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: # Description: 233Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: # One point is granted for each class for which MIN_FOR_POINT criteria is fulfilled. 234Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: # defines the minimum point numbers for the password to be accepted. 235Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: minQuality 3 236Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: Param = minQuality, value = 3, min = (null), minForPoint= (null) 237Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: Accepted replaced value: 3 238Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: # checkRDN parameter 239Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: # Format: 240Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: # checkRDN [0 | 1] 241Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: # Description: 242Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: # If set to 1, password must not contain a token from the RDN. 243Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: # Tokens are separated by the following delimiters : space tabulation _ - , ; £ 244Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: checkRDN 0 245Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: Param = checkRDN, value = 0, min = (null), minForPoint= (null) 246Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: Accepted replaced value: 0 247Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: # forbiddenChars parameter 248Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: # Format: 249Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: # forbiddenChars [CHARACTERS_FORBIDDEN] 250Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: # Description: 251Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: # Defines the forbidden characters list (no separator). 252Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: # If one of them is found in the password, then it is rejected. 253Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: forbiddenChars 254Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: No value, goto next parameter 255Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: # maxConsecutivePerClass parameter 256Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: # Format: 257Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: # maxConsecutivePerClass [NUMBER] 258Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: # Description: 259Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: # Defines the maximum number of consecutive character allowed for any class 260Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: maxConsecutivePerClass 0 261Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: Param = maxConsecutivePerClass, value = 0, min = (null), minForPoint= (null) 262Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: Accepted replaced value: 0 263Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: # useCracklib parameter 264Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: # Format: 265Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: # useCracklib [0 | 1] 266Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: # Description: 267Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: # If set to 1, the password must pass the cracklib check 268Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: useCracklib 0 269Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: Param = useCracklib, value = 0, min = (null), minForPoint= (null) 270Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: Accepted replaced value: 0 271Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: # cracklibDict parameter 272Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: # Format: 273Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: # cracklibDict [path_to_cracklib_dictionary] 274Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: # Description: 275Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: # directory+filename-prefix that your version of CrackLib will go hunting for 276Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: # For example, /var/pw_dict resolves as /var/pw_dict.pwd, 277Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: # /var/pw_dict.pwi and /var/pw_dict.hwm dictionary files 278Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: cracklibDict /var/cache/cracklib/cracklib_dict 279Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: Param = cracklibDict, value = /var/cache/cracklib/cracklib_dict, min = (null), minForPoint= (null) 280Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: Accepted replaced value: /var/cache/cracklib/cracklib_dict 281Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: # classes parameter 282Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: # Format: 283Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: # class-[CLASS_NAME] [CHARACTERS_DEFINING_CLASS] [MIN] [MIN_FOR_POINT] 284Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: # Description: 285Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: # [CHARACTERS_DEFINING_CLASS]: characters defining the class (no separator) 286Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: # [MIN]: If at least [MIN] characters of this class is not found in the password, then it is rejected 287Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: # [MIN_FOR_POINT]: one point is granted if password contains at least [MIN_FOR_POINT] character numbers of this class 288Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: class-upperCase ABCDEFGHIJKLMNOPQRSTUVWXYZ 0 1 289Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: Param = class-upperCase, value = ABCDEFGHIJKLMNOPQRSTUVWXYZ, min = 0, minForPoint= 1 290Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: Accepted replaced value: ABCDEFGHIJKLMNOPQRSTUVWXYZ 291Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: class-lowerCase abcdefghijklmnopqrstuvwxyz 0 1 292Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: Param = class-lowerCase, value = abcdefghijklmnopqrstuvwxyz, min = 0, minForPoint= 1 293Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: Accepted replaced value: abcdefghijklmnopqrstuvwxyz 294Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: class-digit 0123456789 0 1 295Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: Param = class-digit, value = 0123456789, min = 0, minForPoint= 1 296Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: Accepted replaced value: 0123456789 297Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: get line: class-special <>,?;.:/!§ù%*µ^¨$£²&é~"#'{([-|è`_\ç^à@)]°=}+ 0 1 298Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: Param = class-special, value = <>,?;.:/!§ù%*µ^¨$£²&é~"#'{([-|è`_\ç^à@)]°=}+, min = 0, minForPoint= 1 299Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: Accepted replaced value: <>,?;.:/!§ù%*µ^¨$£²&é~"#'{([-|è`_\ç^à@)]°=}+ 300Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: 1 point granted for class class-lowerCase 301Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: 1 point granted for class class-digit 302Feb 26 14:46:10 debian-10-64 slapd[1981]: ppm: Reallocating szErrStr from 64 to 173 303Feb 26 14:46:10 debian-10-64 slapd[1981]: check_password_quality: module error: (/usr/local/lib/ppm.so) Password for dn="uid=user,ou=persons,dc=my-domain,dc=com" does not pass required number of strength checks (2 of 3).[1] 304Feb 26 14:46:10 debian-10-64 slapd[1981]: conn=1000 op=16 RESULT tag=103 err=19 qtime=0.000020 etime=0.001496 text=Password for dn="uid=user,ou=persons,dc=my-domain,dc=com" does not pass required number of strength checks (2 of 3) 305``` 306 307 308Tests 309----- 310 311There is a unit test script: "unit_tests.sh" that illustrates checking some passwords. 312It is possible to test one particular password using directly the test program: 313 314``` 315cd /usr/local/lib 316LD_LIBRARY_PATH=. ./ppm_test "uid=test,ou=users,dc=my-domain,dc=com" "my_password" "/usr/local/etc/openldap/ppm.example" && echo OK 317``` 318 319 320 321HISTORY 322------- 323 324* 2021-02-23 David Coutadeur <david.coutadeur@gmail.com> 325 remove maxLength attribute (#21) 326 adapt the readme and documentation of ppm (#22) 327 prepare ppolicy10 in OpenLDAP 2.5 (#20, #23 and #24) 328 add pwdCheckModuleArg feature 329 Version 2.0 330* 2019-08-20 David Coutadeur <david.coutadeur@gmail.com> 331 adding debug symbols for ppm_test, 332 improve tests with the possibility to add username, 333 fix openldap crash when checkRDN=1 and username contains too short parts 334 Version 1.8 335* 2018-03-30 David Coutadeur <david.coutadeur@gmail.com> 336 various minor improvements provided by Tim Bishop (tdb) (compilation, test program, 337 imprvts in Makefile: new OLDAP_SOURCES variable pointing to OLDAP install. directory 338 Version 1.7 339* 2017-05-19 David Coutadeur <david.coutadeur@gmail.com> 340 Adds cracklib support 341 Readme adaptations and cleaning 342 Version 1.6 343* 2017-02-07 David Coutadeur <david.coutadeur@gmail.com> 344 Adds maxConsecutivePerClass (idea from Trevor Vaughan / tvaughan@onyxpoint.com) 345 Version 1.5 346* 2016-08-22 David Coutadeur <david.coutadeur@gmail.com> 347 Get config file from environment variable 348 Version 1.4 349* 2014-12-20 Daly Chikhaoui <dchikhaoui@janua.fr> 350 Adding checkRDN parameter 351 Version 1.3 352* 2014-10-28 David Coutadeur <david.coutadeur@gmail.com> 353 Adding maxLength parameter 354 Version 1.2 355* 2014-07-27 David Coutadeur <david.coutadeur@gmail.com> 356 Changing the configuration file and the configuration data structure 357 Version 1.1 358* 2014-04-04 David Coutadeur <david.coutadeur@gmail.com> 359 Version 1.0 360 361