mirror of
https://git.openldap.org/openldap/openldap.git
synced 2024-12-03 02:41:24 +08:00
Proposing ppolicy extended module for OpenLDAP (issue #7832)
This commit is contained in:
parent
f2ddf89e3c
commit
f3975b3f90
44
contrib/slapd-modules/ppm/INSTALL.md
Normal file
44
contrib/slapd-modules/ppm/INSTALL.md
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
INSTALLATION
|
||||||
|
============
|
||||||
|
|
||||||
|
Build dependencies
|
||||||
|
------------------
|
||||||
|
OpenLDAP sources must be available. For an easier build, copy all ppm module
|
||||||
|
into contrib/slapd-modules OpenLDAP source directory.
|
||||||
|
|
||||||
|
Build
|
||||||
|
-----
|
||||||
|
Be sure to have copied ppm module into contrib/slapd-modules OpenLDAP source
|
||||||
|
directory.
|
||||||
|
|
||||||
|
Adapt the Makefile command to indicate:
|
||||||
|
OLDAP_SOURCES : should point to OpenLDAP source directory
|
||||||
|
CONFIG: where the ppm.conf example configuration file will finally stand
|
||||||
|
note: ppm configuration now lies into pwdCheckModuleArg password policy attribute
|
||||||
|
provided config file is only helpful as an example or for testing
|
||||||
|
LIBDIR: where the library will be installed
|
||||||
|
DEBUG: If defined, ppm logs its actions with syslog
|
||||||
|
|
||||||
|
If necessary, you can also adapt some OpenLDAP source directories (if changed):
|
||||||
|
LDAP_INC : OpenLDAP headers directory
|
||||||
|
LDAP_LIBS : OpenLDAP built libraries directory
|
||||||
|
|
||||||
|
then type:
|
||||||
|
|
||||||
|
```
|
||||||
|
make clean
|
||||||
|
make CONFIG=/etc/openldap/ppm.conf OLDAP_SOURCES=../../..
|
||||||
|
make test
|
||||||
|
make install CONFIG=/etc/openldap/ppm.conf LIBDIR=/usr/lib/openldap
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
For LTB build, use rather:
|
||||||
|
|
||||||
|
```
|
||||||
|
make clean
|
||||||
|
make "CONFIG=/usr/local/openldap/etc/openldap/ppm.conf" "OLDAP_SOURCES=.."
|
||||||
|
make test
|
||||||
|
make install CONFIG=/usr/local/openldap/etc/openldap/ppm.conf LIBDIR=/usr/local/openldap/lib64
|
||||||
|
```
|
||||||
|
|
50
contrib/slapd-modules/ppm/LICENSE
Normal file
50
contrib/slapd-modules/ppm/LICENSE
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
OpenLDAP Public License
|
||||||
|
|
||||||
|
The OpenLDAP Public License
|
||||||
|
Version 2.8.1, 25 November 2003
|
||||||
|
|
||||||
|
Redistribution and use of this software and associated documentation
|
||||||
|
("Software"), with or without modification, are permitted provided
|
||||||
|
that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions in source form must retain copyright statements
|
||||||
|
and notices,
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce applicable copyright
|
||||||
|
statements and notices, this list of conditions, and the following
|
||||||
|
disclaimer in the documentation and/or other materials provided
|
||||||
|
with the distribution, and
|
||||||
|
|
||||||
|
3. Redistributions must contain a verbatim copy of this document.
|
||||||
|
|
||||||
|
The OpenLDAP Foundation may revise this license from time to time.
|
||||||
|
Each revision is distinguished by a version number. You may use
|
||||||
|
this Software under terms of this license revision or under the
|
||||||
|
terms of any subsequent revision of the license.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE OPENLDAP FOUNDATION AND ITS
|
||||||
|
CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
|
||||||
|
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
|
||||||
|
SHALL THE OPENLDAP FOUNDATION, ITS CONTRIBUTORS, OR THE AUTHOR(S)
|
||||||
|
OR OWNER(S) OF THE SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||||
|
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||||
|
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
The names of the authors and copyright holders must not be used in
|
||||||
|
advertising or otherwise to promote the sale, use or other dealing
|
||||||
|
in this Software without specific, written prior permission. Title
|
||||||
|
to copyright in this Software shall at all times remain with copyright
|
||||||
|
holders.
|
||||||
|
|
||||||
|
OpenLDAP is a registered trademark of the OpenLDAP Foundation.
|
||||||
|
|
||||||
|
Copyright 1999-2003 The OpenLDAP Foundation, Redwood City,
|
||||||
|
California, USA. All rights reserved. Permission to copy and
|
||||||
|
distribute verbatim copies of this document is granted.
|
||||||
|
|
67
contrib/slapd-modules/ppm/Makefile
Normal file
67
contrib/slapd-modules/ppm/Makefile
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
# contrib/slapd-modules/ppm/Makefile
|
||||||
|
# Copyright 2014 David Coutadeur, Paris. All Rights Reserved.
|
||||||
|
#
|
||||||
|
|
||||||
|
CC=gcc
|
||||||
|
|
||||||
|
# Path of OpenLDAP sources
|
||||||
|
OLDAP_SOURCES=../../..
|
||||||
|
# Where the ppm configuration file should be installed
|
||||||
|
CONFIG=/etc/openldap/ppm.conf
|
||||||
|
# Path of OpenLDAP installed libs, where the ppm library should be installed
|
||||||
|
LIBDIR=/usr/lib/openldap
|
||||||
|
|
||||||
|
OPT=-g -O2 -Wall -fpic \
|
||||||
|
-DCONFIG_FILE="\"$(CONFIG)\"" \
|
||||||
|
-DDEBUG
|
||||||
|
|
||||||
|
# Where to find the OpenLDAP headers.
|
||||||
|
|
||||||
|
LDAP_INC=-I$(OLDAP_SOURCES)/include \
|
||||||
|
-I$(OLDAP_SOURCES)/servers/slapd
|
||||||
|
|
||||||
|
# Where to find the OpenLDAP libraries.
|
||||||
|
|
||||||
|
LDAP_LIBS=-L$(OLDAP_SOURCES)/libraries/liblber/.libs \
|
||||||
|
-L$(OLDAP_SOURCES)/libraries/libldap/.libs
|
||||||
|
|
||||||
|
CRACK_INC=-DCRACKLIB
|
||||||
|
|
||||||
|
INCS=$(LDAP_INC) $(CRACK_INC)
|
||||||
|
|
||||||
|
LDAP_LIB=-lldap -llber
|
||||||
|
|
||||||
|
CRACK_LIB=-lcrack
|
||||||
|
|
||||||
|
LIBS=$(LDAP_LIB) $(CRACK_LIB)
|
||||||
|
|
||||||
|
TESTS=./unit_tests.sh
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
all: ppm ppm_test
|
||||||
|
|
||||||
|
ppm_test:
|
||||||
|
$(CC) -g $(LDAP_INC) $(LDAP_LIBS) -Wl,-rpath=. -o ppm_test ppm_test.c ppm.so $(LIBS)
|
||||||
|
|
||||||
|
ppm.o:
|
||||||
|
$(CC) $(OPT) -c $(INCS) ppm.c
|
||||||
|
|
||||||
|
ppm: ppm.o
|
||||||
|
$(CC) $(LDAP_INC) -shared -o ppm.so ppm.o $(CRACK_LIB)
|
||||||
|
|
||||||
|
install: ppm
|
||||||
|
cp -f ppm.so $(LIBDIR)
|
||||||
|
cp -f ppm_test $(LIBDIR)
|
||||||
|
cp -f ppm.conf $(CONFIG)
|
||||||
|
|
||||||
|
.PHONY: clean
|
||||||
|
|
||||||
|
clean:
|
||||||
|
$(RM) -f ppm.o ppm.so ppm.lo ppm_test
|
||||||
|
$(RM) -rf .libs
|
||||||
|
|
||||||
|
test: ppm ppm_test
|
||||||
|
$(TESTS)
|
||||||
|
|
||||||
|
|
5
contrib/slapd-modules/ppm/NOTICES
Normal file
5
contrib/slapd-modules/ppm/NOTICES
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
The attached modifications to OpenLDAP Software are subject to the following notice:
|
||||||
|
|
||||||
|
Copyright 2021 David Coutadeur
|
||||||
|
Redistribution and use in source and binary forms, with or without modification, are permitted only as authorized by the OpenLDAP Public License.
|
||||||
|
|
292
contrib/slapd-modules/ppm/README.md
Normal file
292
contrib/slapd-modules/ppm/README.md
Normal file
@ -0,0 +1,292 @@
|
|||||||
|
|
||||||
|
ppm.c - OpenLDAP password policy module
|
||||||
|
|
||||||
|
version 2.0
|
||||||
|
|
||||||
|
ppm.c is an OpenLDAP module for checking password quality when they are modified.
|
||||||
|
Passwords are checked against the presence or absence of certain character classes.
|
||||||
|
|
||||||
|
This module is used as an extension of the OpenLDAP password policy controls,
|
||||||
|
see slapo-ppolicy(5) section pwdCheckModule.
|
||||||
|
|
||||||
|
contributions
|
||||||
|
-------------
|
||||||
|
|
||||||
|
* 2014 - 2021 - David Coutadeur <david.coutadeur@gmail.com> - maintainer
|
||||||
|
* 2015 - Daly Chikhaoui - Janua <dchikhaoui@janua.fr> - contribution on RDN checks
|
||||||
|
* 2017 - tdb - Tim Bishop - contribution on some compilation improvements
|
||||||
|
|
||||||
|
|
||||||
|
INSTALLATION
|
||||||
|
------------
|
||||||
|
|
||||||
|
See INSTALL file
|
||||||
|
|
||||||
|
|
||||||
|
USAGE
|
||||||
|
-----
|
||||||
|
|
||||||
|
Create a password policy entry and indicate the fresh compiled
|
||||||
|
library ppm.so:
|
||||||
|
|
||||||
|
dn: cn=default,ou=policies,dc=my-domain,dc=com
|
||||||
|
objectClass: pwdPolicy
|
||||||
|
objectClass: pwdPolicyChecker
|
||||||
|
objectClass: person
|
||||||
|
objectClass: top
|
||||||
|
cn: default
|
||||||
|
sn: default
|
||||||
|
pwdAttribute: userPassword
|
||||||
|
pwdCheckQuality: 2
|
||||||
|
...
|
||||||
|
pwdCheckModule: /path/to/new/ppm.so
|
||||||
|
pwdCheckModuleArg: [see configuration section]
|
||||||
|
|
||||||
|
|
||||||
|
See slapo-ppolicy for more information, but to sum up:
|
||||||
|
- enable ppolicy overlay in your database.
|
||||||
|
This example show the activation for a slapd.conf file
|
||||||
|
(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_use_lockout # for having more infos about the lockout
|
||||||
|
```
|
||||||
|
|
||||||
|
- define a default password policy in OpenLDAP configuration or
|
||||||
|
use pwdPolicySubentry attribute to point to the given policy.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Password checks
|
||||||
|
---------------
|
||||||
|
|
||||||
|
- 4 character classes are defined by default:
|
||||||
|
upper case, lower case, digits and special characters.
|
||||||
|
|
||||||
|
- more character classes can be defined, just write your own.
|
||||||
|
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
- passwords must have at least n of the corresponding character class
|
||||||
|
present, else they are rejected.
|
||||||
|
|
||||||
|
- the two previous criterias are checked against any specific character class
|
||||||
|
defined.
|
||||||
|
|
||||||
|
- if a password contains any of the forbidden characters, then it is
|
||||||
|
rejected.
|
||||||
|
|
||||||
|
- if a password contains tokens from the RDN, then it is rejected.
|
||||||
|
|
||||||
|
- if a password is too long, it can be rejected.
|
||||||
|
|
||||||
|
- if a password does not pass cracklib check, it can be rejected.
|
||||||
|
|
||||||
|
|
||||||
|
Configuration
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Since OpenLDAP 2.5 version, ppm configuration is held in a binary
|
||||||
|
attribute of the password policy: pwdCheckModuleArg
|
||||||
|
The configuration file (/etc/openldap/ppm.conf by default) is to be
|
||||||
|
considered as an example configuration, to import in the pwdCheckModuleArg
|
||||||
|
attribute. It is also used for testing passwords with the test program
|
||||||
|
provided.
|
||||||
|
If for some reasons, any parameter is not found, it will be given its
|
||||||
|
default value.
|
||||||
|
|
||||||
|
Note: you can still compile ppm to use the configuration file, by enabling
|
||||||
|
PPM_READ_FILE in ppm.h (but this is deprecated now). If you decide to do so,
|
||||||
|
you can use the PPM_CONFIG_FILE environment variable for overloading the
|
||||||
|
configuration file path.
|
||||||
|
|
||||||
|
The syntax of a configuration line is:
|
||||||
|
parameter value [min] [minForPoint]
|
||||||
|
|
||||||
|
with spaces being delimiters and Line Feed (LF) ending the line.
|
||||||
|
Parameter names ARE case sensitive.
|
||||||
|
|
||||||
|
The default configuration is the following:
|
||||||
|
|
||||||
|
```
|
||||||
|
# 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 _ - , ; £
|
||||||
|
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_dictionnary]
|
||||||
|
# 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 dictionnary 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 <>,?;.:/!§ù%*µ^¨$£²&é~"#'{([-|è`_\ç^à@)]°=}+ 0 1
|
||||||
|
```
|
||||||
|
|
||||||
|
Example
|
||||||
|
-------
|
||||||
|
|
||||||
|
With this policy:
|
||||||
|
```
|
||||||
|
minQuality 4
|
||||||
|
forbiddenChars .?,
|
||||||
|
checkRDN 1
|
||||||
|
class-upperCase ABCDEFGHIJKLMNOPQRSTUVWXYZ 0 5
|
||||||
|
class-lowerCase abcdefghijklmnopqrstuvwxyz 0 12
|
||||||
|
class-digit 0123456789 0 1
|
||||||
|
class-special <>,?;.:/!§ù%*µ^¨$£²&é~"#'{([-|è`_\ç^à@)]°=}+ 0 1
|
||||||
|
class-myClass :) 1 1``
|
||||||
|
```
|
||||||
|
|
||||||
|
the password
|
||||||
|
|
||||||
|
ThereIsNoCowLevel)
|
||||||
|
|
||||||
|
is working, because,
|
||||||
|
- it has 4 character classes validated : upper, lower, special, and myClass
|
||||||
|
- it has no character among .?,
|
||||||
|
- it has at least one character among : or )
|
||||||
|
|
||||||
|
but it won't work for the user uid=John Cowlevel,ou=people,cn=example,cn=com,
|
||||||
|
because the token "Cowlevel" from his RDN exists in the password (case insensitive).
|
||||||
|
|
||||||
|
|
||||||
|
Logs
|
||||||
|
----
|
||||||
|
If a user password is rejected by ppm, the user will get this type of message:
|
||||||
|
|
||||||
|
Typical user message from ldappasswd(5):
|
||||||
|
Result: Constraint violation (19)
|
||||||
|
Additional info: Password for dn=\"%s\" does not pass required number of strength checks (2 of 3)
|
||||||
|
|
||||||
|
A more detailed message is written to the server log.
|
||||||
|
|
||||||
|
Server log:
|
||||||
|
|
||||||
|
```
|
||||||
|
Jul 27 20:09:14 machine slapd[20270]: ppm: Opening file /etc/openldap/ppm.conf
|
||||||
|
Jul 27 20:09:14 machine slapd[20270]: ppm: Param = minQuality, value = 3, min = (null), minForPoint= (null)
|
||||||
|
Jul 27 20:09:14 machine slapd[20270]: ppm: Accepted replaced value: 3
|
||||||
|
Jul 27 20:09:14 machine slapd[20270]: ppm: Param = forbiddenChars, value = , min = (null), minForPoint= (null)
|
||||||
|
Jul 27 20:09:14 machine slapd[20270]: ppm: Accepted replaced value:
|
||||||
|
Jul 27 20:09:14 machine slapd[20270]: ppm: Param = class-upperCase, value = ABCDEFGHIJKLMNOPQRSTUVWXYZ, min = 0, minForPoint= 5
|
||||||
|
Jul 27 20:09:14 machine slapd[20270]: ppm: Accepted replaced value: ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
||||||
|
Jul 27 20:09:14 machine slapd[20270]: ppm: Param = class-lowerCase, value = abcdefghijklmnopqrstuvwxyz, min = 0, minForPoint= 12
|
||||||
|
Jul 27 20:09:14 machine slapd[20270]: ppm: Accepted replaced value: abcdefghijklmnopqrstuvwxyz
|
||||||
|
Jul 27 20:09:14 machine slapd[20270]: ppm: Param = class-digit, value = 0123456789, min = 0, minForPoint= 1
|
||||||
|
Jul 27 20:09:14 machine slapd[20270]: ppm: Accepted replaced value: 0123456789
|
||||||
|
Jul 27 20:09:14 machine slapd[20270]: ppm: Param = class-special, value = <>,?;.:/!§ù%*µ^¨$£²&é~"#'{([-|è`_\ç^à @)]°=}+, min = 0, minForPoint= 1
|
||||||
|
Jul 27 20:09:14 machine slapd[20270]: ppm: Accepted replaced value: <>,?;.:/!§ù%*µ^¨$£²&é~"#'{([-|è`_\ç^à @)]°=}+
|
||||||
|
Jul 27 20:09:14 machine slapd[20270]: ppm: Param = class-myClass, value = :), min = 1, minForPoint= 1
|
||||||
|
Jul 27 20:09:14 machine slapd[20270]: ppm: Accepted new value:
|
||||||
|
Jul 27 20:09:14 machine slapd[20270]: ppm: 1 point granted for class class-upperCase
|
||||||
|
Jul 27 20:09:14 machine slapd[20270]: ppm: 1 point granted for class class-lowerCase
|
||||||
|
Jul 27 20:09:14 machine slapd[20270]: ppm: 1 point granted for class class-digit
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Tests
|
||||||
|
-----
|
||||||
|
|
||||||
|
There is a unit test script: "unit_tests.sh" that illustrates checking some passwords.
|
||||||
|
It is possible to test one particular password using directly the test program:
|
||||||
|
|
||||||
|
```
|
||||||
|
cd /usr/local/openldap/lib64
|
||||||
|
LD_LIBRARY_PATH=. ./ppm_test "uid=test,ou=users,dc=my-domain,dc=com" "my_password" "/usr/local/openldap/etc/openldap/ppm.conf" && echo OK
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
HISTORY
|
||||||
|
-------
|
||||||
|
|
||||||
|
* 2021-02-23 David Coutadeur <david.coutadeur@gmail.com>
|
||||||
|
remove maxLength attribute (#21)
|
||||||
|
adapt the readme and documentation of ppm (#22)
|
||||||
|
prepare ppolicy10 in OpenLDAP 2.5 (#20, #23 and #24)
|
||||||
|
add pwdCheckModuleArg feature
|
||||||
|
Version 2.0
|
||||||
|
* 2019-08-20 David Coutadeur <david.coutadeur@gmail.com>
|
||||||
|
adding debug symbols for ppm_test,
|
||||||
|
improve tests with the possibility to add username,
|
||||||
|
fix openldap crash when checkRDN=1 and username contains too short parts
|
||||||
|
Version 1.8
|
||||||
|
* 2018-03-30 David Coutadeur <david.coutadeur@gmail.com>
|
||||||
|
various minor improvements provided by Tim Bishop (tdb) (compilation, test program,
|
||||||
|
imprvts in Makefile: new OLDAP_SOURCES variable pointing to OLDAP instal. directory
|
||||||
|
Version 1.7
|
||||||
|
* 2017-05-19 David Coutadeur <david.coutadeur@gmail.com>
|
||||||
|
Adds cracklib support
|
||||||
|
Readme adaptations and cleaning
|
||||||
|
Version 1.6
|
||||||
|
* 2017-02-07 David Coutadeur <david.coutadeur@gmail.com>
|
||||||
|
Adds maxConsecutivePerClass (idea from Trevor Vaughan / tvaughan@onyxpoint.com)
|
||||||
|
Version 1.5
|
||||||
|
* 2016-08-22 David Coutadeur <david.coutadeur@gmail.com>
|
||||||
|
Get config file from environment variable
|
||||||
|
Version 1.4
|
||||||
|
* 2014-12-20 Daly Chikhaoui <dchikhaoui@janua.fr>
|
||||||
|
Adding checkRDN parameter
|
||||||
|
Version 1.3
|
||||||
|
* 2014-10-28 David Coutadeur <david.coutadeur@gmail.com>
|
||||||
|
Adding maxLength parameter
|
||||||
|
Version 1.2
|
||||||
|
* 2014-07-27 David Coutadeur <david.coutadeur@gmail.com>
|
||||||
|
Changing the configuration file and the configuration data structure
|
||||||
|
Version 1.1
|
||||||
|
* 2014-04-04 David Coutadeur <david.coutadeur@gmail.com>
|
||||||
|
Version 1.0
|
||||||
|
|
679
contrib/slapd-modules/ppm/ppm.c
Normal file
679
contrib/slapd-modules/ppm/ppm.c
Normal file
@ -0,0 +1,679 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
ppm_log(LOG_NOTICE, "ppm: entry %s", pEntry->e_nname.bv_val);
|
||||||
|
|
||||||
|
struct berval *pwdCheckModuleArg = pArg;
|
||||||
|
/* Determine if config file is to be read (DEPRECATED) */
|
||||||
|
#ifdef PPM_READ_FILE
|
||||||
|
ppm_log(LOG_NOTICE, "ppm: Not reading pwdCheckModuleArg attribute");
|
||||||
|
ppm_log(LOG_NOTICE, "ppm: instead, read configuration file (deprecated)");
|
||||||
|
#else
|
||||||
|
ppm_log(LOG_NOTICE, "ppm: Reading pwdCheckModuleArg attribute");
|
||||||
|
ppm_log(LOG_NOTICE, "ppm: RAW configuration: %s",
|
||||||
|
(*(struct berval*)pwdCheckModuleArg).bv_val);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
/* Determine config file (DEPRECATED) */
|
||||||
|
#ifdef PPM_READ_FILE
|
||||||
|
char ppm_config_file[FILENAME_MAX_LEN];
|
||||||
|
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);
|
||||||
|
#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);
|
||||||
|
|
||||||
|
}
|
66
contrib/slapd-modules/ppm/ppm.conf
Normal file
66
contrib/slapd-modules/ppm/ppm.conf
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# maxLength parameter
|
||||||
|
# Format:
|
||||||
|
# maxLength [NUMBER]
|
||||||
|
# Description:
|
||||||
|
# The password must not be more than [NUMBER] long. 0 means no limit is set.
|
||||||
|
maxLength 0
|
||||||
|
|
||||||
|
# checkRDN parameter
|
||||||
|
# Format:
|
||||||
|
# checkRDN [0 | 1]
|
||||||
|
# Description:
|
||||||
|
# If set to 1, password must not contain a token from the RDN.
|
||||||
|
# Tokens are separated by these delimiters : space tabulation _ - , ; £
|
||||||
|
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_dictionnary]
|
||||||
|
# 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 dictionnary 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 <>,?;.:/!§ù%*µ^¨$£²&é~"#'{([-|è`_\ç^à@)]°=}+ 0 1
|
125
contrib/slapd-modules/ppm/ppm.h
Normal file
125
contrib/slapd-modules/ppm/ppm.h
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
/*
|
||||||
|
* ppm.h for OpenLDAP
|
||||||
|
*
|
||||||
|
* See LICENSE, README and INSTALL files
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef PPM_H_
|
||||||
|
#define PPM_H_
|
||||||
|
|
||||||
|
#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>
|
||||||
|
|
||||||
|
#if defined(DEBUG)
|
||||||
|
#include <syslog.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//#define PPM_READ_FILE 1 // old deprecated configuration mode
|
||||||
|
// 1: (deprecated) don't read pwdCheckModuleArg
|
||||||
|
// attribute, instead read config file
|
||||||
|
// 0: read pwdCheckModuleArg attribute
|
||||||
|
|
||||||
|
/* config file parameters (DEPRECATED) */
|
||||||
|
#ifndef CONFIG_FILE
|
||||||
|
#define CONFIG_FILE "/etc/openldap/ppm.conf"
|
||||||
|
#endif
|
||||||
|
#define FILENAME_MAX_LEN 512
|
||||||
|
|
||||||
|
#define DEFAULT_QUALITY 3
|
||||||
|
#define MEMORY_MARGIN 50
|
||||||
|
#define MEM_INIT_SZ 64
|
||||||
|
#define DN_MAX_LEN 512
|
||||||
|
|
||||||
|
#define CONF_MAX_SIZE 50
|
||||||
|
#define PARAM_MAX_LEN 32
|
||||||
|
#define VALUE_MAX_LEN 128
|
||||||
|
#define ATTR_NAME_MAX_LEN 150
|
||||||
|
|
||||||
|
#define PARAM_PREFIX_CLASS "class-"
|
||||||
|
#define TOKENS_DELIMITERS " ,;-_£\t"
|
||||||
|
|
||||||
|
|
||||||
|
#define DEBUG_MSG_MAX_LEN 256
|
||||||
|
|
||||||
|
#define PASSWORD_QUALITY_SZ \
|
||||||
|
"Password for dn=\"%s\" does not pass required number of strength checks (%d of %d)"
|
||||||
|
#define PASSWORD_CRITERIA \
|
||||||
|
"Password for dn=\"%s\" has not reached the minimum 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 \
|
||||||
|
"Password for dn=\"%s\" contains %d forbidden characters in %s"
|
||||||
|
#define RDN_TOKEN_FOUND \
|
||||||
|
"Password for dn=\"%s\" contains tokens from the RDN"
|
||||||
|
#define GENERIC_ERROR \
|
||||||
|
"Error while checking password"
|
||||||
|
#define PASSWORD_CRACKLIB \
|
||||||
|
"Password for dn=\"%s\" is too weak"
|
||||||
|
#define BAD_PASSWORD_SZ \
|
||||||
|
"Bad password for dn=\"%s\" because %s"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
typedef union genValue {
|
||||||
|
int iVal;
|
||||||
|
char sVal[VALUE_MAX_LEN];
|
||||||
|
} genValue;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
typeInt,
|
||||||
|
typeStr
|
||||||
|
} valueType;
|
||||||
|
|
||||||
|
typedef struct params {
|
||||||
|
char param[PARAM_MAX_LEN];
|
||||||
|
valueType iType;
|
||||||
|
} params;
|
||||||
|
|
||||||
|
// allowed parameters loaded into configuration structure
|
||||||
|
// it also contains the type of the corresponding value
|
||||||
|
params allowedParameters[7] = {
|
||||||
|
{"^minQuality", typeInt},
|
||||||
|
{"^checkRDN", typeInt},
|
||||||
|
{"^forbiddenChars", typeStr},
|
||||||
|
{"^maxConsecutivePerClass", typeInt},
|
||||||
|
{"^useCracklib", typeInt},
|
||||||
|
{"^cracklibDict", typeStr},
|
||||||
|
{"^class-.*", typeStr}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// configuration structure, containing a parameter, a value,
|
||||||
|
// a corresponding min and minForPoint indicators if necessary
|
||||||
|
// and a type for the value (typeInt or typeStr)
|
||||||
|
typedef struct conf {
|
||||||
|
char param[PARAM_MAX_LEN];
|
||||||
|
valueType iType;
|
||||||
|
genValue value;
|
||||||
|
int min;
|
||||||
|
int minForPoint;
|
||||||
|
} conf;
|
||||||
|
|
||||||
|
void ppm_log(int priority, const char *format, ...);
|
||||||
|
int min(char *str1, char *str2);
|
||||||
|
#ifndef PPM_READ_FILE
|
||||||
|
static void read_config_attr(conf * fileConf, int *numParam, char *ppm_config_attr);
|
||||||
|
#endif
|
||||||
|
#ifdef PPM_READ_FILE
|
||||||
|
static void read_config_file(conf * fileConf, int *numParam, char *ppm_config_file);
|
||||||
|
#endif
|
||||||
|
int check_password(char *pPasswd, char **ppErrStr, Entry *e, void *pArg);
|
||||||
|
int maxConsPerClass(char *password, char *charClass);
|
||||||
|
void storeEntry(char *param, char *value, valueType valType,
|
||||||
|
char *min, char *minForPoint, 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);
|
||||||
|
|
||||||
|
|
||||||
|
int ppm_test = 0;
|
||||||
|
|
||||||
|
#endif
|
66
contrib/slapd-modules/ppm/ppm_test.c
Normal file
66
contrib/slapd-modules/ppm/ppm_test.c
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include "ppm.h"
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* argv[1]: user
|
||||||
|
* argv[2]: password
|
||||||
|
* argv[3]: configuration file
|
||||||
|
*/
|
||||||
|
|
||||||
|
int ret = 1;
|
||||||
|
|
||||||
|
if(argc > 2)
|
||||||
|
{
|
||||||
|
printf("Testing user %s password: '%s' against %s policy config file \n",
|
||||||
|
argv[1], argv[2], argv[3]
|
||||||
|
);
|
||||||
|
|
||||||
|
/* format user entry */
|
||||||
|
char *errmsg = NULL;
|
||||||
|
Entry pEntry;
|
||||||
|
pEntry.e_nname.bv_val=argv[1];
|
||||||
|
pEntry.e_name.bv_val=argv[1];
|
||||||
|
|
||||||
|
/* get configuration file content */
|
||||||
|
struct berval pArg;
|
||||||
|
FILE *fp;
|
||||||
|
if ((fp = fopen(argv[3],"r")) == NULL)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"Unable to open config file for reading\n");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
char *fcontent = NULL;
|
||||||
|
fseek(fp, 0, SEEK_END);
|
||||||
|
long fsize = ftell(fp);
|
||||||
|
fseek(fp, 0, SEEK_SET);
|
||||||
|
fcontent = malloc(fsize);
|
||||||
|
fread(fcontent, 1, fsize, fp);
|
||||||
|
fclose(fp);
|
||||||
|
pArg.bv_val = fcontent;
|
||||||
|
|
||||||
|
ppm_test=1; // enable ppm_test for informing ppm not to use syslog
|
||||||
|
|
||||||
|
ret = check_password(argv[2], &errmsg, &pEntry, &pArg);
|
||||||
|
|
||||||
|
if(ret == 0)
|
||||||
|
{
|
||||||
|
printf("Password is OK!\n");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
printf("Password failed checks : %s\n", errmsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
ber_memfree(errmsg);
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
117
contrib/slapd-modules/ppm/unit_tests.sh
Executable file
117
contrib/slapd-modules/ppm/unit_tests.sh
Executable file
@ -0,0 +1,117 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Launch unitary tests
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
CONFIG_FILE="ppm.conf"
|
||||||
|
|
||||||
|
OLDAP_SOURCES="../../.."
|
||||||
|
CURRENT_DIR=$( dirname $0 )
|
||||||
|
LIB_PATH="${LD_LIBRARY_PATH}:${CURRENT_DIR}:${OLDAP_SOURCES}/libraries/liblber/.libs:${OLDAP_SOURCES}/libraries/libldap/.libs"
|
||||||
|
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
RESULT=0
|
||||||
|
|
||||||
|
PPM_CONF_1='minQuality 3
|
||||||
|
checkRDN 0
|
||||||
|
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'
|
||||||
|
|
||||||
|
PPM_CONF_2='minQuality 3
|
||||||
|
checkRDN 0
|
||||||
|
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'
|
||||||
|
|
||||||
|
PPM_CONF_3='minQuality 3
|
||||||
|
checkRDN 1
|
||||||
|
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'
|
||||||
|
|
||||||
|
|
||||||
|
echo "$PPM_CONF_1" > ppm1.conf
|
||||||
|
echo "$PPM_CONF_2" > ppm2.conf
|
||||||
|
echo "$PPM_CONF_3" > ppm3.conf
|
||||||
|
|
||||||
|
|
||||||
|
launch_test()
|
||||||
|
{
|
||||||
|
# launch tests
|
||||||
|
# FORMAT: launch_test [conf_file] [password] [expected_result]
|
||||||
|
# [expected_result] = [PASS|FAIL]
|
||||||
|
|
||||||
|
local CONF="$1"
|
||||||
|
local USER="$2"
|
||||||
|
local PASS="$3"
|
||||||
|
local EXPECT="$4"
|
||||||
|
|
||||||
|
[[ $EXPECT == "PASS" ]] && EXP="0" || EXP="1"
|
||||||
|
|
||||||
|
LD_LIBRARY_PATH="${LIB_PATH}" ./ppm_test "${USER}" "${PASS}" "${CONF}"
|
||||||
|
RES="$?"
|
||||||
|
|
||||||
|
if [ "$RES" -eq "$EXP" ] ; then
|
||||||
|
echo -e "conf=${CONF} user=${USER} pass=${PASS} expect=${EXPECT}... ${GREEN}PASS${NC}"
|
||||||
|
else
|
||||||
|
echo -e "conf=${CONF} user=${USER} pass=${PASS} expect=${EXPECT}... ${RED}FAIL${NC}"
|
||||||
|
((RESULT+=1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
launch_test "ppm1.conf" "uid=test,ou=users,dc=my-domain,dc=com" "azerty" "FAIL"
|
||||||
|
launch_test "ppm1.conf" "uid=test,ou=users,dc=my-domain,dc=com" "azeRTY" "FAIL"
|
||||||
|
launch_test "ppm1.conf" "uid=test,ou=users,dc=my-domain,dc=com" "azeRTY123" "PASS"
|
||||||
|
launch_test "ppm1.conf" "uid=test,ou=users,dc=my-domain,dc=com" "azeRTY." "PASS"
|
||||||
|
|
||||||
|
|
||||||
|
launch_test "ppm2.conf" "uid=test,ou=users,dc=my-domain,dc=com" "AAaaa01AAaaa01AAaaa0" "PASS"
|
||||||
|
# forbidden char
|
||||||
|
launch_test "ppm2.conf" "uid=test,ou=users,dc=my-domain,dc=com" "AAaaa01AAaaa01AAaaaà" "FAIL"
|
||||||
|
# too much consecutive for upper
|
||||||
|
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
|
||||||
|
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"
|
||||||
|
|
||||||
|
# password in RDN
|
||||||
|
launch_test "ppm3.conf" "uid=User_Password10-test,ou=users,dc=my-domain,dc=com" "Password10" "FAIL"
|
||||||
|
launch_test "ppm3.conf" "uid=User_Passw0rd-test,ou=users,dc=my-domain,dc=com" "Password10" "PASS"
|
||||||
|
launch_test "ppm3.conf" "uid=User-Pw-Test,ou=users,dc=my-domain,dc=com" "Password10" "PASS"
|
||||||
|
|
||||||
|
|
||||||
|
echo "${RESULT} error(s) encountered"
|
||||||
|
|
||||||
|
rm ppm1.conf ppm2.conf ppm3.conf
|
||||||
|
exit ${RESULT}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user