45 Configuring a Customized Password Policy Plug-In
The following topics describe how to install, configure, and enable a customized password policy plug-in:
45.1 Configuring a Customized Password Policy Plug-in
When a user wants to add or modify a password, customized password value checking takes place as follows:
-
The client sends the directory server either an ldapadd or ldapmodify request.
-
Before the directory server makes the addition or modification, it passes the password value to the plug-in.
-
The plug-in
-
Parses the entry
-
Captures the
userpassword
attribute value in clear text -
Implements whatever password value checking you have specified
-
-
If the password meets the specification, then the plug-in notifies the directory server accordingly, and the directory server makes the addition or modification.
Otherwise, the plug-in sends one of the following error messages to the directory server, which, in turn, passes it to the client.
ldap_add: UnKnown Error Encountered ldap_add: additional info: PASSWORD POLICY VIOLATION:0000X, less than 8 chars ldap_add: UnKnown Error Encountered ldap_add: additional info: PASSWORD POLICY VIOLATION:0000X, contains dictionary word
The same logic applies to the PRE ldapmodify plug-in.
The various kinds of value checks that a password policy plug-in could perform include:
-
Minimum and maximum number of alphabetic characters
-
Maximum number of numeric characters
-
Minimum and maximum number of punctuation characters
-
Maximum number of consecutive characters
-
Maximum number of instances of any character
-
Whether it is a dictionary word
45.2 Managing a Customized Password Policy Plug-in
The example in this section uses the PL/SQL program, pluginpkg.sql
.
The example in this section uses the PL/SQL program, pluginpkg.sql
. Sample PL/SQL Package pluginpkg.sql Contents. In general, this package contains:
-
Two plug-in modules:
pre_add
andpre_modify
-
One value checking function,
isGoodPwd
, which verifies that a password meets the minimum length requirement of eight characters and that it does not contain a dictionary word that is longer than four characters
Thus, in this example, if you try to add a user with the userpassword
value less than eight characters, then the request is rejected. Similarly, if you try to modify a user password, and the new password value is less than eight characters, then the request is rejected. Also, if you try to add or modify a user with the userpassword
supersunday, the password is rejected because super and sunday are dictionary words.
The dictionary is a list of words longer than four characters, initially stored in a file called words.txt. Before we implement the plug-in, we set up a database table and store the words into the table. To set up the table we use create.sql
, which has the following contents:
drop table mydic; create table mydic (word varchar2(1024)); commit; exit;
Then we load the words into the table using the sqlldr command:
sqlldr control=words.txt userid=ods/ods_password
This section contains the following topics:
45.2.1 Loading and Registering the PL/SQL Program
This section describes the procedure to load and register the PL/SQL program.
After you implement the standalone value checking PL/SQL program, do the following:.
45.2.2 Coding the Password Policy Plug-in
You can use standard PL/SQL character functions to process the password value.
Download any PL/SQL program that can do regular expression. The important thing is to integrate the value checking functions with your plug-in modules.
45.2.3 Debugging the Password Policy Plug-in
First you enable the directory server plug-in to help examine the process and content of plug-ins.
To setup the directory server plug-in debugging, execute the following command:
sqlplus ods @$ORACLE_HOME/ldap/admin/oidspdsu.pls
To enable directory server plug-in debugging, execute the following command:
sqlplus ods @$ORACLE_HOME/ldap/admin/oidspdon.pls
To disable directory server plug-in debugging, execute the following command:
sqlplus ods @$ORACLE_HOME/ldap/admin/oidspdof.pls
To show directory server plug-in debugging messages, execute the following command:
sqlplus ods @$ORACLE_HOME/ldap/admin/oidspdsh.pls
To delete directory server plug-in debugging messages, execute the following command:
sqlplus ods @$ORACLE_HOME/ldap/admin/oidspdde.pls
45.2.4 Sample PL/SQL Package pluginpkg.sql Contents
The script pluginpkg.sql
, as used in this example, contains the following:
CREATE OR REPLACE PACKAGE pwd_plugin AS PROCEDURE pre_add (ldapplugincontext IN ODS.plugincontext, dn IN VARCHAR2, entry IN ODS.entryobj, rc OUT INTEGER, errormsg OUT VARCHAR2 ); PROCEDURE pre_modify (ldapplugincontext IN ODS.plugincontext, dn IN VARCHAR2, mods IN ODS.modlist, rc OUT INTEGER, errormsg OUT VARCHAR2 ); -- Function: isGoodPwd -- Parameter: inpwd -- Purpose: 1. check if the password is at least -- 8 characters long -- 2. check if the password contains a -- dictionary word (longer than 4 characters) FUNCTION isGoodPwd(inpwd IN VARCHAR2) RETURN INTEGER; END pwd_plugin; / show error CREATE OR REPLACE PACKAGE BODY pwd_plugin AS FUNCTION isGoodPwd(inpwd IN VARCHAR2) RETURN INTEGER IS i NUMBER; ret NUMBER DEFAULT 1; minpwdlen NUMBER DEFAULT 8; len NUMBER DEFAULT 0; lcount NUMBER DEFAULT 0; matched VARCHAR2(1024) DEFAULT NULL; CURSOR c1 IS SELECT word FROM mydic WHERE length(word) > 4 AND instr(lower(inpwd), lower(word), 1, 1) > 0; BEGIN plg_debug( '=== begin of ISGOODPWD ==='); plg_debug( 'password = ' || inpwd); len := LENGTH(inpwd); plg_debug( 'password length = ' || len); IF len < minpwdlen THEN RETURN 0; ELSE OPEN c1; LOOP FETCH c1 INTO matched; EXIT WHEN c1%NOTFOUND; lcount := lcount + 1; END LOOP; plg_debug( 'count = ' || lcount); IF lcount > 0 THEN RETURN 2; ELSE RETURN ret; END IF; END IF; plg_debug( '=== end of ISGOODPWD ==='); EXCEPTION WHEN OTHERS THEN plg_debug( 'Exception in isGoodPwd(). Error code is ' || TO_CHAR(SQLCODE)); plg_debug( ' ' || Sqlerrm); RETURN 0; END; PROCEDURE pre_add (ldapplugincontext IN ODS.plugincontext, dn IN VARCHAR2, entry IN ODS.entryobj, rc OUT INTEGER, errormsg OUT VARCHAR2 ) IS inpwd VARCHAR2(256) DEFAULT NULL; ret NUMBER DEFAULT 1; BEGIN plg_debug( '=== begin of PRE_ADD_PLUGIN ==='); plg_debug( 'dn = ' || dn); plg_debug( 'entry obj ' || ':entryname = ' || entry.entryname); FOR l_counter1 IN 1..entry.attr.COUNT LOOP plg_debug( 'attrname[' || l_counter1 || '] = ' || entry.attr(l_counter1).attrname); FOR l_counter2 IN 1..entry.attr(l_counter1).attrval.COUNT LOOP plg_debug( entry.attr(l_counter1).attrname || '[' || l_counter1 || ']' || '.val[' || l_counter2 || '] = ' || entry.attr(l_counter1).attrval(l_counter2)); END LOOP; IF entry.attr(l_counter1).attrname = 'userpassword' THEN inpwd := entry.attr(l_counter1).attrval(1); -- assuming only one attr val for userpassword END IF; END LOOP; IF (inpwd IS NOT NULL) THEN ret := isGoodPwd(inpwd); END IF; IF (inpwd IS NULL OR ret = 0) THEN rc := 1; errormsg := 'PASSWORD POLICY VIOLATION:0000X, less than 8 chars'; plg_debug( ' we got an invalid password, too short '); ELSIF (ret = 2) THEN rc := 1; errormsg := 'PASSWORD POLICY VIOLATION:0000X, contains dictionary word'; plg_debug( ' we got an invalid password, dictionary word '); ELSE plg_debug( ' we got a good password '); rc := 0; errormsg := 'no pre_mod plguin error msg'; END IF; plg_debug( '=== end of PRE_ADD_PLUGIN ==='); EXCEPTION WHEN OTHERS THEN plg_debug( 'Exception in PRE_ADD plugin. Error code is ' || TO_CHAR(SQLCODE)); plg_debug( ' ' || Sqlerrm); rc := 1; errormsg := 'exception: pre_add plguin'; END; PROCEDURE pre_modify (ldapplugincontext IN ODS.plugincontext, dn IN VARCHAR2, mods IN ODS.modlist, rc OUT INTEGER, errormsg OUT VARCHAR2 ) IS old_passwd VARCHAR2(256) DEFAULT NULL; new_passwd VARCHAR2(256) DEFAULT NULL; ret NUMBER DEFAULT 1; BEGIN plg_debug( '=== begin of PRE_MOD_PLUGIN ==='); plg_debug( dn); FOR l_counter1 IN 1..mods.COUNT LOOP IF (mods(l_counter1).operation = 2) AND (mods(l_counter1).type = 'userpassword') THEN FOR l_counter2 IN 1..mods(l_counter1).vals.COUNT LOOP new_passwd := mods(l_counter1).vals(l_counter2).val; END LOOP; END IF; IF (mods(l_counter1).operation = 0) AND (mods(l_counter1).type = 'userpassword') THEN FOR l_counter2 IN 1..mods(l_counter1).vals.COUNT LOOP new_passwd := mods(l_counter1).vals(l_counter2).val; END LOOP; END IF; IF (mods(l_counter1).operation = 1) AND (mods(l_counter1).type = 'userpassword') THEN FOR l_counter2 IN 1..mods(l_counter1).vals.COUNT LOOP old_passwd := mods(l_counter1).vals(l_counter2).val; END LOOP; END IF; END LOOP; plg_debug(' new password: ' || new_passwd); plg_debug(' old password: ' || old_passwd); IF (new_passwd IS NOT NULL) THEN ret := isGoodPwd(new_passwd); END IF; IF (new_passwd IS NULL OR ret = 0) THEN rc := 1; errormsg := 'PASSWORD POLICY VIOLATION:0000X, less than 8 chars'; plg_debug( ' we got an invalid password, too short '); ELSIF (ret = 2) THEN rc := 1; errormsg := 'PASSWORD POLICY VIOLATION:0000X, contains dictionary word'; plg_debug( ' we got an invalid password, dictionary word '); ELSE plg_debug( ' we got a good password '); rc := 0; errormsg := 'no pre_mod plguin error msg'; END IF; plg_debug( '=== end of PRE_MOD_PLUGIN ==='); EXCEPTION WHEN OTHERS THEN plg_debug( 'Exception in PRE_MODIFY plugin. Error code is ' || TO_CHAR(SQLCODE)); plg_debug( ' ' || Sqlerrm); rc := 1; errormsg := 'exception: pre_mod plguin'; END; END pwd_plugin; / show error EXIT;