summaryrefslogtreecommitdiffstats
path: root/contrib/slapd-modules/ppm/slapm-ppm.5
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/slapd-modules/ppm/slapm-ppm.5')
-rw-r--r--contrib/slapd-modules/ppm/slapm-ppm.5360
1 files changed, 360 insertions, 0 deletions
diff --git a/contrib/slapd-modules/ppm/slapm-ppm.5 b/contrib/slapd-modules/ppm/slapm-ppm.5
new file mode 100644
index 0000000..10e9c8d
--- /dev/null
+++ b/contrib/slapd-modules/ppm/slapm-ppm.5
@@ -0,0 +1,360 @@
+.\" Automatically generated by Pandoc 2.9.2.1
+.\"
+.TH "ppm" "5" "August 24, 2021" "ppm" "File Formats Manual"
+.hy
+.SH NAME
+.PP
+ppm (Password Policy Module) - extension of the password policy overlay
+.SH SYNOPSIS
+.PP
+ETCDIR/ppm.example
+.SH DESCRIPTION
+.PP
+\f[B]ppm\f[R] is an OpenLDAP module for checking password quality when
+they are modified.
+Passwords are checked against the presence or absence of certain
+character classes.
+.PP
+This module is used as an extension of the OpenLDAP password policy
+controls, see slapo-ppolicy(5) section \f[B]pwdCheckModule\f[R].
+.SH USAGE
+.PP
+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:
+.IP
+.nf
+\f[C]
+dn: cn=default,ou=policies,dc=my-domain,dc=com
+objectClass: pwdPolicy
+objectClass: top
+objectClass: pwdPolicyChecker
+objectClass: person
+pwdCheckQuality: 2
+pwdAttribute: userPassword
+sn: default
+cn: default
+pwdMinLength: 6
+pwdCheckModule: /usr/local/lib/ppm.so
+pwdCheckModuleArg:: bWluUXVhbGl0eSAzCmNoZWNrUkROIDAKZm9yYmlkZGVuQ2hhcnMKbWF4Q29uc2VjdXRpdmVQZXJDbGFzcyAwCnVzZUNyYWNrbGliIDAKY3JhY2tsaWJEaWN0IC92YXIvY2FjaGUvY3JhY2tsaWIvY3JhY2tsaWJfZGljdApjbGFzcy11cHBlckNhc2UgQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVogMCAxCmNsYXNzLWxvd2VyQ2FzZSBhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5eiAwIDEKY2xhc3MtZGlnaXQgMDEyMzQ1Njc4OSAwIDEKY2xhc3Mtc3BlY2lhbCA8Piw/Oy46LyHCp8O5JSrCtV7CqCTCo8KyJsOpfiIjJ3soWy18w6hgX1zDp17DoEApXcKwPX0rIDAgMQ==
+\f[R]
+.fi
+.PP
+See \f[B]slapo-ppolicy\f[R] for more information, but to sum up:
+.IP \[bu] 2
+enable ppolicy overlay in your database.
+.IP \[bu] 2
+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)
+.IP
+.nf
+\f[C]
+overlay ppolicy
+ppolicy_default \[dq]cn=default,ou=policies,dc=my-domain,dc=com\[dq]
+#ppolicy_use_lockout # for having more infos about the lockout
+\f[R]
+.fi
+.SH FEATURES
+.PP
+Here are the main features:
+.IP \[bu] 2
+4 character classes are defined by default: upper case, lower case,
+digits and special characters.
+.IP \[bu] 2
+more character classes can be defined, just write your own.
+.IP \[bu] 2
+passwords must match the amount of quality points.
+A point is validated when at least m characters of the corresponding
+character class are present in the password.
+.IP \[bu] 2
+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
+class defined.
+.IP \[bu] 2
+if a password contains any of the forbidden characters, then it is
+rejected.
+.IP \[bu] 2
+if a password contains tokens from the RDN, then it is rejected.
+.IP \[bu] 2
+if a password does not pass cracklib check, then it is rejected.
+.SH CONFIGURATION
+.PP
+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
+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.
+.PP
+If for some reasons, any parameter is not found, it will be given its
+default value.
+.PP
+Note: you can still compile ppm to use the configuration file, by
+enabling \f[B]PPM_READ_FILE\f[R] in \f[B]ppm.h\f[R] (but this is
+deprecated now).
+If you decide to do so, you can use the \f[B]PPM_CONFIG_FILE\f[R]
+environment variable for overloading the configuration file path.
+.PP
+The syntax of a configuration line is:
+.IP
+.nf
+\f[C]
+parameter value [min] [minForPoint]
+\f[R]
+.fi
+.PP
+with spaces being delimiters and Line Feed (LF) ending the line.
+.PP
+Parameter names \f[B]are\f[R] case sensitive.
+.PP
+Lines beginning by a \f[B]#\f[R] are considered as comments.
+.PP
+The default configuration is the following:
+.IP
+.nf
+\f[C]
+# minQuality parameter
+# Format:
+# minQuality [NUMBER]
+# Description:
+# One point is granted for each class for which MIN_FOR_POINT criteria is fulfilled.
+# defines the minimum point numbers for the password to be accepted.
+minQuality 3
+
+# checkRDN parameter
+# Format:
+# checkRDN [0 | 1]
+# Description:
+# If set to 1, password must not contain a token from the RDN.
+# Tokens are separated by the following delimiters : space tabulation _ - , ; \[Po]
+checkRDN 0
+
+# forbiddenChars parameter
+# Format:
+# forbiddenChars [CHARACTERS_FORBIDDEN]
+# Description:
+# Defines the forbidden characters list (no separator).
+# If one of them is found in the password, then it is rejected.
+forbiddenChars
+
+# maxConsecutivePerClass parameter
+# Format:
+# maxConsecutivePerClass [NUMBER]
+# Description:
+# Defines the maximum number of consecutive character allowed for any class
+maxConsecutivePerClass 0
+
+# useCracklib parameter
+# Format:
+# useCracklib [0 | 1]
+# Description:
+# If set to 1, the password must pass the cracklib check
+useCracklib 0
+
+# cracklibDict parameter
+# Format:
+# cracklibDict [path_to_cracklib_dictionary]
+# Description:
+# directory+filename-prefix that your version of CrackLib will go hunting for
+# For example, /var/pw_dict resolves as /var/pw_dict.pwd,
+# /var/pw_dict.pwi and /var/pw_dict.hwm dictionary files
+cracklibDict /var/cache/cracklib/cracklib_dict
+
+# classes parameter
+# Format:
+# class-[CLASS_NAME] [CHARACTERS_DEFINING_CLASS] [MIN] [MIN_FOR_POINT]
+# 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
+\f[R]
+.fi
+.SH EXAMPLE
+.PP
+With this policy:
+.IP
+.nf
+\f[C]
+minQuality 4
+forbiddenChars .?,
+checkRDN 1
+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]
+\f[R]
+.fi
+.PP
+the password \f[B]ThereIsNoCowLevel)\f[R] is working, because:
+.IP \[bu] 2
+it has 4 character classes validated : upper, lower, special, and
+myClass
+.IP \[bu] 2
+it has no character among .?,
+.IP \[bu] 2
+it has at least one character among : or )
+.PP
+but it won\[cq]t work for the user uid=John
+Cowlevel,ou=people,cn=example,cn=com, because the token
+\[lq]Cowlevel\[rq] from his RDN exists in the password (case
+insensitive).
+.SH LOGS
+.PP
+If a user password is rejected by \f[B]ppm\f[R], the user will get this
+type of message:
+.PP
+Typical user message from ldappasswd(5):
+.IP
+.nf
+\f[C]
+ Result: Constraint violation (19)
+ Additional info: Password for dn=\[rs]\[dq]%s\[rs]\[dq] does not pass required number of strength checks (2 of 3)
+\f[R]
+.fi
+.PP
+A more detailed message is written to the server log.
+.PP
+Server log:
+.IP
+.nf
+\f[C]
+Feb 26 14:46:10 debian-11-64 slapd[1981]: conn=1000 op=16 MOD dn=\[dq]uid=user,ou=persons,dc=my-domain,dc=com\[dq]
+Feb 26 14:46:10 debian-11-64 slapd[1981]: conn=1000 op=16 MOD attr=userPassword
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: entry uid=user,ou=persons,dc=my-domain,dc=com
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: Reading pwdCheckModuleArg attribute
+Feb 26 14:46:10 debian-11-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 _ - , ; \[Po]#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 <>,?;.:/!\[sc]\[`u]%*\[mc]\[ha]\[ad]$\[Po]\[S2]&\['e]\[ti]\[dq]#\[aq]{([-|\[`e]\[ga]_\[rs]\[,c]\[ha]\[`a]\[at])]\[de]=}+ 0 1
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: Parsing pwdCheckModuleArg attribute
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: # minQuality parameter
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: # Format:
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: # minQuality [NUMBER]
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: # Description:
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: # One point is granted for each class for which MIN_FOR_POINT criteria is fulfilled.
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: # defines the minimum point numbers for the password to be accepted.
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: minQuality 3
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: Param = minQuality, value = 3, min = (null), minForPoint= (null)
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: Accepted replaced value: 3
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: # checkRDN parameter
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: # Format:
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: # checkRDN [0 | 1]
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: # Description:
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: # If set to 1, password must not contain a token from the RDN.
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: # Tokens are separated by the following delimiters : space tabulation _ - , ; \[Po]
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: checkRDN 0
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: Param = checkRDN, value = 0, min = (null), minForPoint= (null)
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: Accepted replaced value: 0
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: # forbiddenChars parameter
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: # Format:
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: # forbiddenChars [CHARACTERS_FORBIDDEN]
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: # Description:
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: # Defines the forbidden characters list (no separator).
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: # If one of them is found in the password, then it is rejected.
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: forbiddenChars
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: No value, goto next parameter
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: # maxConsecutivePerClass parameter
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: # Format:
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: # maxConsecutivePerClass [NUMBER]
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: # Description:
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: # Defines the maximum number of consecutive character allowed for any class
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: maxConsecutivePerClass 0
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: Param = maxConsecutivePerClass, value = 0, min = (null), minForPoint= (null)
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: Accepted replaced value: 0
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: # useCracklib parameter
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: # Format:
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: # useCracklib [0 | 1]
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: # Description:
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: # If set to 1, the password must pass the cracklib check
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: useCracklib 0
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: Param = useCracklib, value = 0, min = (null), minForPoint= (null)
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: Accepted replaced value: 0
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: # cracklibDict parameter
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: # Format:
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: # cracklibDict [path_to_cracklib_dictionary]
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: # Description:
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: # directory+filename-prefix that your version of CrackLib will go hunting for
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: # For example, /var/pw_dict resolves as /var/pw_dict.pwd,
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: # /var/pw_dict.pwi and /var/pw_dict.hwm dictionary files
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: cracklibDict /var/cache/cracklib/cracklib_dict
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: Param = cracklibDict, value = /var/cache/cracklib/cracklib_dict, min = (null), minForPoint= (null)
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: Accepted replaced value: /var/cache/cracklib/cracklib_dict
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: # classes parameter
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: # Format:
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: # class-[CLASS_NAME] [CHARACTERS_DEFINING_CLASS] [MIN] [MIN_FOR_POINT]
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: # Description:
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: # [CHARACTERS_DEFINING_CLASS]: characters defining the class (no separator)
+Feb 26 14:46:10 debian-11-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
+Feb 26 14:46:10 debian-11-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
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: class-upperCase ABCDEFGHIJKLMNOPQRSTUVWXYZ 0 1
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: Param = class-upperCase, value = ABCDEFGHIJKLMNOPQRSTUVWXYZ, min = 0, minForPoint= 1
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: Accepted replaced value: ABCDEFGHIJKLMNOPQRSTUVWXYZ
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: class-lowerCase abcdefghijklmnopqrstuvwxyz 0 1
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: Param = class-lowerCase, value = abcdefghijklmnopqrstuvwxyz, min = 0, minForPoint= 1
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: Accepted replaced value: abcdefghijklmnopqrstuvwxyz
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: get line: class-digit 0123456789 0 1
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: Param = class-digit, value = 0123456789, min = 0, minForPoint= 1
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: Accepted replaced value: 0123456789
+Feb 26 14:46:10 debian-11-64 slapd[1981]: 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
+Feb 26 14:46:10 debian-11-64 slapd[1981]: 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
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: Accepted replaced value: <>,?;.:/!\[sc]\[`u]%*\[mc]\[ha]\[ad]$\[Po]\[S2]&\['e]\[ti]\[dq]#\[aq]{([-|\[`e]\[ga]_\[rs]\[,c]\[ha]\[`a]\[at])]\[de]=}+
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: 1 point granted for class class-lowerCase
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: 1 point granted for class class-digit
+Feb 26 14:46:10 debian-11-64 slapd[1981]: ppm: Reallocating szErrStr from 64 to 173
+Feb 26 14:46:10 debian-11-64 slapd[1981]: check_password_quality: module error: (/usr/local/lib/ppm.so) Password for dn=\[dq]uid=user,ou=persons,dc=my-domain,dc=com\[dq] does not pass required number of strength checks (2 of 3).[1]
+Feb 26 14:46:10 debian-11-64 slapd[1981]: conn=1000 op=16 RESULT tag=103 err=19 qtime=0.000020 etime=0.001496 text=Password for dn=\[dq]uid=user,ou=persons,dc=my-domain,dc=com\[dq] does not pass required number of strength checks (2 of 3)
+\f[R]
+.fi
+.SH TESTS
+.PP
+There is a unit test script: \f[B]unit_tests.sh\f[R] that illustrates
+checking some passwords.
+.PP
+It is possible to test one particular password using directly the test
+program:
+.IP
+.nf
+\f[C]
+cd /usr/local/lib
+LD_LIBRARY_PATH=. ./ppm_test \[dq]uid=test,ou=users,dc=my-domain,dc=com\[dq] \[dq]my_password\[dq] \[dq]/usr/local/etc/openldap/ppm.example\[dq] && echo OK
+\f[R]
+.fi
+.SH FILES
+.PP
+\f[B]ETCDIR/ppm.example\f[R]
+.RS
+.PP
+example of ppm configuration to be inserted in
+\f[B]pwdCheckModuleArg\f[R] attribute of given password policy
+.RE
+.PP
+\f[B]ppm.so\f[R]
+.RS
+.PP
+ppm library, loaded by the \f[B]pwdCheckModule\f[R] attribute of given
+password policy
+.RE
+.PP
+\f[B]ppm_test\f[R]
+.RS
+.PP
+small test program for checking password in a command-line
+.RE
+.SH SEE ALSO
+.PP
+\f[B]slapo-ppolicy\f[R](5), \f[B]slapd-config\f[R](5),
+\f[B]slapd.conf\f[R](5)
+.SH ACKNOWLEDGEMENTS
+.PP
+This module was developed in 2014-2021 by David Coutadeur.