44 Configuring a Customized Password Policy Plug-In
The following topics describe how to install, configure, and enable a customized password policy plug-in:
44.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
userpasswordattribute 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
44.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_addandpre_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:
44.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:.
44.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.
44.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
44.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;