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