summaryrefslogtreecommitdiffstats
path: root/ldap/servers/plugins
diff options
context:
space:
mode:
authorRich Megginson <rmeggins@redhat.com>2010-09-17 08:18:29 -0600
committerRich Megginson <rmeggins@redhat.com>2010-10-01 17:02:53 -0600
commit32e2b04dd1d98d96d90fdfaa3841524b3003dcdb (patch)
tree5cf8dfd0a10441354b4ba9d3c7b514270839ef66 /ldap/servers/plugins
parent4f410d762b008da8e2e43e29100c2c04ff332fbb (diff)
downloadds-32e2b04dd1d98d96d90fdfaa3841524b3003dcdb.tar.gz
ds-32e2b04dd1d98d96d90fdfaa3841524b3003dcdb.tar.xz
ds-32e2b04dd1d98d96d90fdfaa3841524b3003dcdb.zip
add the account policy plugin and related server code, schema, and config
Add the account policy plugin and related server code, schema, and config A new switch to configure has been added --enable-acctpolicy - this is enabled by default - so the plugin and the schema will be built and installed by default the plugin will be in dse.ldif, but will be disabled by default The original contribution had some minor problems with the schema and config entries - these have been cleaned up The original contribution had a few memory leaks - these have been cleaned up
Diffstat (limited to 'ldap/servers/plugins')
-rw-r--r--ldap/servers/plugins/acctpolicy/acct_config.c123
-rw-r--r--ldap/servers/plugins/acctpolicy/acct_init.c183
-rw-r--r--ldap/servers/plugins/acctpolicy/acct_plugin.c333
-rw-r--r--ldap/servers/plugins/acctpolicy/acct_util.c245
-rw-r--r--ldap/servers/plugins/acctpolicy/acctpolicy.h79
-rw-r--r--ldap/servers/plugins/acctpolicy/sampleconfig.ldif40
-rw-r--r--ldap/servers/plugins/acctpolicy/samplepolicy.ldif27
7 files changed, 1030 insertions, 0 deletions
diff --git a/ldap/servers/plugins/acctpolicy/acct_config.c b/ldap/servers/plugins/acctpolicy/acct_config.c
new file mode 100644
index 00000000..11473208
--- /dev/null
+++ b/ldap/servers/plugins/acctpolicy/acct_config.c
@@ -0,0 +1,123 @@
+/******************************************************************************
+Copyright (C) 2009 Hewlett-Packard Development Company, L.P.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+version 2 as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+Contributors:
+Hewlett-Packard Development Company, L.P.
+******************************************************************************/
+
+#include <stdio.h>
+#include <string.h>
+#include "slapi-plugin.h"
+#include "acctpolicy.h"
+#include "nspr.h"
+
+/* Globals */
+static acctPluginCfg globalcfg;
+
+/* Local function prototypes */
+static int acct_policy_entry2config( Slapi_Entry *e,
+ acctPluginCfg *newcfg );
+
+/*
+ Creates global config structure from config entry at plugin startup
+*/
+int
+acct_policy_load_config_startup( Slapi_PBlock* pb, void* plugin_id ) {
+ acctPluginCfg *newcfg;
+ Slapi_Entry *config_entry = NULL;
+ Slapi_DN *config_sdn = NULL;
+ int rc;
+
+ /* Retrieve the config entry */
+ config_sdn = slapi_sdn_new_dn_byref( PLUGIN_CONFIG_DN );
+ rc = slapi_search_internal_get_entry( config_sdn, NULL, &config_entry,
+ plugin_id);
+ slapi_sdn_free( &config_sdn );
+
+ if( rc != LDAP_SUCCESS || config_entry == NULL ) {
+ slapi_log_error( SLAPI_LOG_FATAL, PLUGIN_NAME,
+ "Failed to retrieve configuration entry %s: %d\n",
+ PLUGIN_CONFIG_DN, rc );
+ return( -1 );
+ }
+
+ newcfg = get_config();
+ rc = acct_policy_entry2config( config_entry, newcfg );
+
+ slapi_entry_free( config_entry );
+
+ return( rc );
+}
+
+/*
+ Parses config entry into config structure, caller is responsible for
+ allocating the config structure memory
+*/
+static int
+acct_policy_entry2config( Slapi_Entry *e, acctPluginCfg *newcfg ) {
+ const char *config_val;
+
+ if( newcfg == NULL ) {
+ slapi_log_error( SLAPI_LOG_FATAL, PLUGIN_NAME,
+ "Failed to allocate configuration structure\n" );
+ return( -1 );
+ }
+
+ memset( newcfg, 0, sizeof( acctPluginCfg ) );
+
+ newcfg->state_attr_name = get_attr_string_val( e, CFG_LASTLOGIN_STATE_ATTR );
+ if( newcfg->state_attr_name == NULL ) {
+ newcfg->state_attr_name = slapi_ch_strdup( DEFAULT_LASTLOGIN_STATE_ATTR );
+ }
+
+ newcfg->alt_state_attr_name = get_attr_string_val( e, CFG_ALT_LASTLOGIN_STATE_ATTR );
+ if( newcfg->alt_state_attr_name == NULL ) {
+ newcfg->alt_state_attr_name = slapi_ch_strdup( DEFAULT_ALT_LASTLOGIN_STATE_ATTR );
+ }
+
+ newcfg->spec_attr_name = get_attr_string_val( e, CFG_SPEC_ATTR );
+ if( newcfg->spec_attr_name == NULL ) {
+ newcfg->spec_attr_name = slapi_ch_strdup( DEFAULT_SPEC_ATTR );
+ }
+
+ newcfg->limit_attr_name = get_attr_string_val( e, CFG_INACT_LIMIT_ATTR );
+ if( newcfg->limit_attr_name == NULL ) {
+ newcfg->limit_attr_name = slapi_ch_strdup( DEFAULT_INACT_LIMIT_ATTR );
+ }
+
+ config_val = get_attr_string_val( e, CFG_RECORD_LOGIN );
+ if( strcasecmp( config_val, "true" ) == 0 ||
+ strcasecmp( config_val, "yes" ) == 0 ||
+ strcasecmp( config_val, "on" ) == 0 ||
+ strcasecmp( config_val, "1" ) == 0 ) {
+ newcfg->always_record_login = 1;
+ } else {
+ newcfg->always_record_login = 0;
+ }
+ slapi_ch_free_string(&config_val);
+
+ return( 0 );
+}
+
+/*
+ Returns a pointer to config structure for use by any code needing to look
+ at, for example, attribute mappings
+*/
+acctPluginCfg*
+get_config() {
+ return( &globalcfg );
+}
+
diff --git a/ldap/servers/plugins/acctpolicy/acct_init.c b/ldap/servers/plugins/acctpolicy/acct_init.c
new file mode 100644
index 00000000..6f33434c
--- /dev/null
+++ b/ldap/servers/plugins/acctpolicy/acct_init.c
@@ -0,0 +1,183 @@
+/******************************************************************************
+Copyright (C) 2009 Hewlett-Packard Development Company, L.P.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+version 2 as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+Contributors:
+Hewlett-Packard Development Company, L.P.
+******************************************************************************/
+
+/* Example enabling and config entries
+dn: cn=Account Policy Plugin,cn=plugins,cn=config
+objectClass: top
+objectClass: nsSlapdPlugin
+objectClass: extensibleObject
+cn: Account Policy Plugin
+nsslapd-pluginPath: /path/to/libacctpolicy-plugin.sl
+nsslapd-pluginInitfunc: acct_policy_init
+nsslapd-pluginType: object
+nsslapd-pluginEnabled: on
+nsslapd-plugin-depends-on-type: database
+nsslapd-pluginId: Account Policy Plugin
+
+dn: cn=config,cn=Account Policy Plugin,cn=plugins,cn=config
+objectClass: top
+objectClass: extensibleObject
+cn: config
+alwaysrecordlogin: yes
+stateattrname: lastLoginTime
+altstateattrname: createTimestamp
+specattrname: acctPolicySubentry
+limitattrname: accountInactivityLimit
+*/
+
+#include <stdio.h>
+#include <string.h>
+#include "slapi-plugin.h"
+#include "acctpolicy.h"
+
+static Slapi_PluginDesc plugin_desc = { PLUGIN_NAME, PLUGIN_VENDOR,
+ PLUGIN_VERSION, PLUGIN_DESC };
+static Slapi_PluginDesc pre_plugin_desc = { PRE_PLUGIN_NAME, PLUGIN_VENDOR,
+ PLUGIN_VERSION, PLUGIN_DESC };
+static Slapi_PluginDesc post_plugin_desc = { PRE_PLUGIN_NAME, PLUGIN_VENDOR,
+ PLUGIN_VERSION, PLUGIN_DESC };
+
+/* Local function prototypes */
+int acct_policy_start( Slapi_PBlock *pb );
+int acct_policy_init( Slapi_PBlock *pb );
+int acct_preop_init( Slapi_PBlock *pb );
+int acct_postop_init( Slapi_PBlock *pb );
+int acct_bind_preop( Slapi_PBlock *pb );
+int acct_bind_postop( Slapi_PBlock *pb );
+
+/*
+ Master init function for the account plugin
+*/
+int
+acct_policy_init( Slapi_PBlock *pb )
+{
+ void *plugin_id;
+
+ if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION,
+ SLAPI_PLUGIN_VERSION_01 ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION,
+ (void *)&plugin_desc ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_START_FN,
+ (void *)acct_policy_start ) != 0 ) {
+ slapi_log_error( SLAPI_LOG_FATAL, PLUGIN_NAME,
+ "acct_policy_init registration failed\n" );
+ return( CALLBACK_ERR );
+ }
+
+ if( slapi_pblock_get( pb, SLAPI_PLUGIN_IDENTITY, &plugin_id ) != 0 ) {
+ slapi_log_error( SLAPI_LOG_FATAL, PLUGIN_NAME,
+ "acct_policy_init failed to get plugin identity\n" );
+ return( CALLBACK_ERR );
+ }
+
+ set_identity( plugin_id );
+
+ /* Register the pre and postop plugins */
+ if( slapi_register_plugin("preoperation", 1, "acct_preop_init",
+ acct_preop_init, PRE_PLUGIN_DESC, NULL, plugin_id) != 0 ||
+ slapi_register_plugin("postoperation", 1, "acct_postop_init",
+ acct_postop_init, POST_PLUGIN_DESC, NULL, plugin_id) != 0 ) {
+ slapi_log_error( SLAPI_LOG_FATAL, PLUGIN_NAME,
+ "acct_policy_init failed to register callbacks\n" );
+ return( CALLBACK_ERR );
+ }
+
+ return( CALLBACK_OK );
+}
+
+/*
+ Plugin startup function, when this is called any other plugins should
+ already be initialized, so it's safe to e.g. perform internal searches,
+ which is needed to retrieve the plugin configuration
+*/
+int
+acct_policy_start( Slapi_PBlock *pb ) {
+ acctPluginCfg *cfg;
+ void *plugin_id = get_identity();
+
+ /* Load plugin configuration */
+ if( acct_policy_load_config_startup( pb, plugin_id ) ) {
+ slapi_log_error( SLAPI_LOG_FATAL, PLUGIN_NAME,
+ "acct_policy_start failed to load configuration\n" );
+ return( CALLBACK_ERR );
+ }
+
+ /* Show the configuration */
+ cfg = get_config();
+ slapi_log_error( SLAPI_LOG_PLUGIN, PLUGIN_NAME, "acct_policy_start config: "
+ "stateAttrName=%s altStateAttrName=%s specAttrName=%s limitAttrName=%s "
+ "alwaysRecordLogin=%d\n",
+ cfg->state_attr_name, cfg->alt_state_attr_name, cfg->spec_attr_name,
+ cfg->limit_attr_name, cfg->always_record_login);
+ return( CALLBACK_OK );
+}
+
+int
+acct_preop_init( Slapi_PBlock *pb ) {
+ /* Which slapi plugin API we're compatible with. */
+ if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION,
+ SLAPI_PLUGIN_VERSION_01 ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION,
+ (void *)&pre_plugin_desc ) != 0 ) {
+ slapi_log_error( SLAPI_LOG_FATAL, PRE_PLUGIN_NAME,
+ "Failed to set plugin version or description\n" );
+ return( CALLBACK_ERR );
+ }
+
+ if ( slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_BIND_FN,
+ (void *) acct_bind_preop ) != 0 ) {
+ slapi_log_error( SLAPI_LOG_FATAL, PRE_PLUGIN_NAME,
+ "Failed to set plugin callback function\n" );
+ return( CALLBACK_ERR );
+ }
+
+ return( CALLBACK_OK );
+}
+
+int
+acct_postop_init( Slapi_PBlock *pb )
+{
+ void *plugin_id = get_identity();
+
+ if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION,
+ SLAPI_PLUGIN_VERSION_01 ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION,
+ (void *)&post_plugin_desc ) != 0 ) {
+ slapi_log_error( SLAPI_LOG_FATAL, POST_PLUGIN_NAME,
+ "Failed to set plugin version or name\n" );
+ return( CALLBACK_ERR );
+ }
+
+ if ( slapi_pblock_set( pb, SLAPI_PLUGIN_POST_BIND_FN,
+ (void *)acct_bind_postop ) != 0 ) {
+ slapi_log_error( SLAPI_LOG_FATAL, POST_PLUGIN_NAME,
+ "Failed to set plugin callback function\n" );
+ return( CALLBACK_ERR );
+ }
+
+ if( slapi_pblock_get( pb, SLAPI_PLUGIN_IDENTITY, &plugin_id ) != 0 ) {
+ slapi_log_error( SLAPI_LOG_FATAL, POST_PLUGIN_NAME,
+ "Failed to get plugin identity\n" );
+ return( CALLBACK_ERR );
+ }
+
+ return( CALLBACK_OK );
+}
+
diff --git a/ldap/servers/plugins/acctpolicy/acct_plugin.c b/ldap/servers/plugins/acctpolicy/acct_plugin.c
new file mode 100644
index 00000000..74dc3adf
--- /dev/null
+++ b/ldap/servers/plugins/acctpolicy/acct_plugin.c
@@ -0,0 +1,333 @@
+/******************************************************************************
+Copyright (C) 2009 Hewlett-Packard Development Company, L.P.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+version 2 as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+Contributors:
+Hewlett-Packard Development Company, L.P.
+******************************************************************************/
+
+/* Account Policy plugin */
+
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <time.h>
+#include "slapi-plugin.h"
+#include "acctpolicy.h"
+
+/*
+ Checks bind entry for last login state and compares current time with last
+ login time plus the limit to decide whether to deny the bind.
+*/
+static int
+acct_inact_limit( Slapi_PBlock *pb, char *dn, Slapi_Entry *target_entry, acctPolicy *policy )
+{
+ char *lasttimestr = NULL;
+ time_t lim_t, last_t, cur_t;
+ int rc = 0; /* Optimistic default */
+ acctPluginCfg *cfg;
+ void *plugin_id;
+
+ cfg = get_config();
+ plugin_id = get_identity();
+ if( ( lasttimestr = get_attr_string_val( target_entry,
+ cfg->state_attr_name ) ) != NULL ) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, PRE_PLUGIN_NAME,
+ "\"%s\" login timestamp is %s\n", dn, lasttimestr );
+ } else if( ( lasttimestr = get_attr_string_val( target_entry,
+ cfg->alt_state_attr_name ) ) != NULL ) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, PRE_PLUGIN_NAME,
+ "\"%s\" alternate timestamp is %s\n", dn, lasttimestr );
+ } else {
+ slapi_log_error( SLAPI_LOG_PLUGIN, PRE_PLUGIN_NAME,
+ "\"%s\" has no login or creation timestamp\n", dn );
+ rc = -1;
+ goto done;
+ }
+
+ last_t = gentimeToEpochtime( lasttimestr );
+ cur_t = time( (time_t*)0 );
+ lim_t = policy->inactivitylimit;
+
+ /* Finally do the time comparison */
+ if( cur_t > last_t + lim_t ) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, PRE_PLUGIN_NAME,
+ "\"%s\" has exceeded inactivity limit (%ld > (%ld + %ld))\n",
+ dn, cur_t, last_t, lim_t );
+ rc = 1;
+ goto done;
+ } else {
+ slapi_log_error( SLAPI_LOG_PLUGIN, PRE_PLUGIN_NAME,
+ "\"%s\" is within inactivity limit (%ld < (%ld + %ld))\n",
+ dn, cur_t, last_t, lim_t );
+ }
+
+done:
+ /* Deny bind; the account has exceeded the inactivity limit */
+ if( rc == 1 ) {
+ slapi_send_ldap_result( pb, LDAP_CONSTRAINT_VIOLATION, NULL,
+ "Account inactivity limit exceeded."
+ " Contact system administrator to reset.", 0, NULL );
+ }
+
+ slapi_ch_free_string( &lasttimestr );
+
+ return( rc );
+}
+
+/*
+ This is called after binds, it updates an attribute in the account
+ with the current time.
+*/
+static int
+acct_record_login( Slapi_PBlock *modpb, char *dn )
+{
+ int ldrc;
+ int rc = 0; /* Optimistic default */
+ LDAPMod *mods[2];
+ LDAPMod mod;
+ struct berval *vals[2];
+ struct berval val;
+ char *timestr = NULL;
+ acctPluginCfg *cfg;
+ void *plugin_id;
+
+ cfg = get_config();
+ plugin_id = get_identity();
+
+ timestr = epochtimeToGentime( time( (time_t*)0 ) );
+ val.bv_val = timestr;
+ val.bv_len = strlen( val.bv_val );
+
+ vals [0] = &val;
+ vals [1] = NULL;
+
+ mod.mod_op = LDAP_MOD_REPLACE | LDAP_MOD_BVALUES;
+ mod.mod_type = cfg->state_attr_name;
+ mod.mod_bvalues = vals;
+
+ mods[0] = &mod;
+ mods[1] = NULL;
+
+ modpb = slapi_pblock_new();
+
+ slapi_modify_internal_set_pb( modpb, dn, mods, NULL, NULL,
+ plugin_id, SLAPI_OP_FLAG_NO_ACCESS_CHECK |
+ SLAPI_OP_FLAG_BYPASS_REFERRALS );
+ slapi_modify_internal_pb( modpb );
+
+ slapi_pblock_get( modpb, SLAPI_PLUGIN_INTOP_RESULT, &ldrc );
+
+ if (ldrc != LDAP_SUCCESS) {
+ slapi_log_error( SLAPI_LOG_FATAL, POST_PLUGIN_NAME,
+ "Recording %s=%s failed on \"%s\" err=%d\n", cfg->state_attr_name,
+ timestr, dn, ldrc );
+ rc = -1;
+ goto done;
+ } else {
+ slapi_log_error( SLAPI_LOG_PLUGIN, POST_PLUGIN_NAME,
+ "Recorded %s=%s on \"%s\"\n", cfg->state_attr_name, timestr, dn );
+ }
+
+done:
+ if( timestr ) {
+ slapi_ch_free_string( &timestr );
+ }
+
+ return( rc );
+}
+
+/*
+ Handles bind preop callbacks
+*/
+int
+acct_bind_preop( Slapi_PBlock *pb )
+{
+ char *dn = NULL;
+ Slapi_DN *sdn = NULL;
+ Slapi_Entry *target_entry = NULL;
+ int rc = 0; /* Optimistic default */
+ int ldrc;
+ acctPolicy *policy = NULL;
+ void *plugin_id;
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, PRE_PLUGIN_NAME,
+ "=> acct_bind_preop\n" );
+
+ plugin_id = get_identity();
+
+ /* This does not give a copy, so don't free it */
+ if( slapi_pblock_get( pb, SLAPI_BIND_TARGET, &dn ) != 0 ) {
+ slapi_log_error( SLAPI_LOG_FATAL, PRE_PLUGIN_NAME,
+ "Error retrieving target DN\n" );
+ rc = -1;
+ goto done;
+ }
+
+ /* The plugin wouldn't get called for anonymous binds but let's check */
+ if ( dn == NULL ) {
+ goto done;
+ }
+
+ sdn = slapi_sdn_new_dn_byref( dn );
+
+ ldrc = slapi_search_internal_get_entry( sdn, NULL, &target_entry,
+ plugin_id );
+
+ /* There was a problem retrieving the entry */
+ if( ldrc != LDAP_SUCCESS ) {
+ if( ldrc != LDAP_NO_SUCH_OBJECT ) {
+ /* The problem is not a bad bind or virtual entry; halt bind */
+ slapi_log_error( SLAPI_LOG_FATAL, PRE_PLUGIN_NAME,
+ "Failed to retrieve entry \"%s\": %d\n", dn, ldrc );
+ rc = -1;
+ }
+ goto done;
+ }
+
+ if( get_acctpolicy( pb, target_entry, plugin_id, &policy ) ) {
+ slapi_log_error( SLAPI_LOG_FATAL, PRE_PLUGIN_NAME,
+ "Account Policy object for \"%s\" is missing\n", dn );
+ rc = -1;
+ goto done;
+ }
+
+ /* Null policy means target isnt's under the influence of a policy */
+ if( policy == NULL ) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, PRE_PLUGIN_NAME,
+ "\"%s\" is not governed by an account policy\n", dn);
+ goto done;
+ }
+
+ /* Check whether the account is in violation of inactivity limit */
+ rc = acct_inact_limit( pb, dn, target_entry, policy );
+
+ /* ...Any additional account policy enforcement goes here... */
+
+done:
+ /* Internal error */
+ if( rc == -1 ) {
+ slapi_send_ldap_result( pb, LDAP_UNWILLING_TO_PERFORM, NULL, NULL, 0, NULL );
+ }
+
+ if( target_entry ) {
+ slapi_entry_free( target_entry );
+ }
+
+ if( sdn ) {
+ slapi_sdn_free( &sdn );
+ }
+
+ if( policy ) {
+ free_acctpolicy( &policy );
+ }
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, PRE_PLUGIN_NAME,
+ "<= acct_bind_preop\n" );
+
+ return( rc == 0 ? CALLBACK_OK : CALLBACK_ERR );
+}
+
+/*
+ This is called after binds, it updates an attribute in the entry that the
+ bind DN corresponds to with the current time if it has an account policy
+ specifier.
+*/
+int
+acct_bind_postop( Slapi_PBlock *pb )
+{
+ char *dn = NULL;
+ Slapi_PBlock *modpb = NULL;
+ int ldrc, tracklogin = 0;
+ int rc = 0; /* Optimistic default */
+ Slapi_DN *sdn = NULL;
+ Slapi_Entry *target_entry = NULL;
+ acctPluginCfg *cfg;
+ void *plugin_id;
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, POST_PLUGIN_NAME,
+ "=> acct_bind_postop\n" );
+
+ plugin_id = get_identity();
+
+ /* Retrieving SLAPI_CONN_DN from the pb gives a copy */
+ if( slapi_pblock_get( pb, SLAPI_CONN_DN, &dn ) != 0 ) {
+ slapi_log_error( SLAPI_LOG_FATAL, POST_PLUGIN_NAME,
+ "Error retrieving bind DN\n" );
+ rc = -1;
+ goto done;
+ }
+
+ /* Client is anonymously bound */
+ if( dn == NULL ) {
+ goto done;
+ }
+
+ cfg = get_config();
+ tracklogin = cfg->always_record_login;
+
+ /* We're not always tracking logins, so check whether the entry is
+ covered by an account policy to decide whether we should track */
+ if( tracklogin == 0 ) {
+ sdn = slapi_sdn_new_dn_byref( dn );
+ ldrc = slapi_search_internal_get_entry( sdn, NULL, &target_entry,
+ plugin_id );
+
+ if( ldrc != LDAP_SUCCESS ) {
+ slapi_log_error( SLAPI_LOG_FATAL, POST_PLUGIN_NAME,
+ "Failed to retrieve entry \"%s\": %d\n", dn, ldrc );
+ rc = -1;
+ goto done;
+ } else {
+ if( target_entry && has_attr( target_entry,
+ cfg->spec_attr_name, NULL ) ) {
+ /* This account has a policy specifier */
+ tracklogin = 1;
+ }
+ }
+ }
+
+ if( tracklogin ) {
+ rc = acct_record_login( modpb, dn );
+ }
+
+ /* ...Any additional account policy postops go here... */
+
+done:
+ if( rc == -1 ) {
+ slapi_send_ldap_result( pb, LDAP_UNWILLING_TO_PERFORM, NULL, NULL, 0, NULL );
+ }
+
+ if( modpb ) {
+ slapi_pblock_destroy( modpb );
+ }
+
+ if( target_entry ) {
+ slapi_entry_free( target_entry );
+ }
+
+ if( sdn ) {
+ slapi_sdn_free( &sdn );
+ }
+
+ if( dn ) {
+ slapi_ch_free_string( &dn );
+ }
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, POST_PLUGIN_NAME,
+ "<= acct_bind_postop\n" );
+
+ return( rc == 0 ? CALLBACK_OK : CALLBACK_ERR );
+}
diff --git a/ldap/servers/plugins/acctpolicy/acct_util.c b/ldap/servers/plugins/acctpolicy/acct_util.c
new file mode 100644
index 00000000..0f5eb5a6
--- /dev/null
+++ b/ldap/servers/plugins/acctpolicy/acct_util.c
@@ -0,0 +1,245 @@
+/******************************************************************************
+Copyright (C) 2009 Hewlett-Packard Development Company, L.P.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+version 2 as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+Contributors:
+Hewlett-Packard Development Company, L.P.
+******************************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <ctype.h>
+#include "slapi-plugin.h"
+#include "acctpolicy.h"
+
+/* Globals */
+static void* plugin_id = NULL;
+
+/*
+ Checks whether an entry has a particular attribute type, and optionally
+ returns the value. Only for use with single-valued attributes - it returns
+ the first value it finds.
+*/
+int
+has_attr( Slapi_Entry* target_entry, char* attr_name, char** val ) {
+ Slapi_ValueSet *values = NULL;
+ Slapi_Value* sval;
+ char *actual_type_name = NULL;
+ int type_name_disposition = 0, attr_free_flags = 0, rc = 0;
+
+ /* Use vattr interface to support virtual attributes, e.g.
+ acctPolicySubentry has a good chance of being supplied by CoS */
+ if ( slapi_vattr_values_get( target_entry, attr_name, &values, &type_name_disposition, &actual_type_name, 0, &attr_free_flags) == 0) {
+ if( slapi_valueset_first_value( values, &sval ) == -1 ) {
+ rc = 0;
+ } else {
+ rc = 1;
+ if( val ) {
+ /* Caller wants a copy of the found attribute's value */
+ *val = slapi_ch_strdup( slapi_value_get_string( sval ) );
+ }
+ }
+ } else {
+ rc = 0;
+ }
+
+ slapi_vattr_values_free(&values, &actual_type_name, attr_free_flags);
+ return( rc );
+}
+
+/*
+ Lazy wrapper for has_attr()
+*/
+char*
+get_attr_string_val( Slapi_Entry* target_entry, char* attr_name ) {
+ char* ret = NULL;
+ has_attr( target_entry, attr_name, &ret );
+ return( ret );
+}
+
+/*
+ Given an entry, provide the account policy in effect for that entry.
+ Returns non-0 if function fails. If account policy comes back NULL, it's
+ not an error; the entry is simply not covered by a policy.
+*/
+int
+get_acctpolicy( Slapi_PBlock *pb, Slapi_Entry *target_entry, void *plugin_id,
+ acctPolicy **policy ) {
+ Slapi_DN *sdn = NULL;
+ Slapi_Entry *policy_entry = NULL;
+ Slapi_Attr *attr;
+ Slapi_Value *sval = NULL;
+ int ldrc;
+ char *attr_name;
+ char *policy_dn = NULL;
+ acctPluginCfg *cfg;
+ int rc = 0;
+
+ cfg = get_config();
+
+ if( policy == NULL ) {
+ /* Bad parameter */
+ return( -1 );
+ }
+
+ *policy = NULL;
+
+ /* Return success and NULL policy */
+ policy_dn = get_attr_string_val( target_entry, cfg->spec_attr_name );
+ if( policy_dn == NULL ) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, PLUGIN_NAME,
+ "\"%s\" is not governed by an account inactivity "
+ " policy\n", slapi_entry_get_ndn( target_entry ) );
+ return( rc );
+ }
+
+ sdn = slapi_sdn_new_dn_byref( policy_dn );
+ ldrc = slapi_search_internal_get_entry( sdn, NULL, &policy_entry,
+ plugin_id );
+ slapi_sdn_free( &sdn );
+
+ /* There should be a policy but it can't be retrieved; fatal error */
+ if( policy_entry == NULL ) {
+ if( ldrc != LDAP_NO_SUCH_OBJECT ) {
+ slapi_log_error( SLAPI_LOG_FATAL, PLUGIN_NAME,
+ "Error retrieving policy entry \"%s\": %d\n", policy_dn, ldrc );
+ } else {
+ slapi_log_error( SLAPI_LOG_PLUGIN, PLUGIN_NAME,
+ "Policy entry \"%s\" is missing: %d\n", policy_dn, ldrc );
+ }
+ rc = -1;
+ goto done;
+ }
+
+ *policy = (acctPolicy *)slapi_ch_calloc( 1, sizeof( acctPolicy ) );
+
+ for( slapi_entry_first_attr( policy_entry, &attr ); attr != NULL;
+ slapi_entry_next_attr( policy_entry, attr, &attr ) ) {
+ slapi_attr_get_type(attr, &attr_name);
+ if( !strcasecmp( attr_name, cfg->limit_attr_name ) ) {
+ if( slapi_attr_first_value( attr, &sval ) == 0 ) {
+ (*policy)->inactivitylimit = slapi_value_get_ulong( sval );
+ }
+ }
+ }
+done:
+ slapi_ch_free_string( &policy_dn );
+ slapi_entry_free( policy_entry );
+ return( rc );
+}
+
+/*
+ Frees an account policy allocated by get_acctpolicy()
+*/
+void
+free_acctpolicy( acctPolicy **policy ) {
+ slapi_ch_free( (void**)policy );
+ return;
+}
+
+/*
+ Plugin plumbing
+*/
+void
+set_identity(void *identity) {
+ plugin_id = identity;
+}
+
+/*
+ Plugin plumbing
+*/
+void*
+get_identity() {
+ return( plugin_id );
+}
+
+/*
+ A more flexible atoi(), converts to integer and returns the characters
+ between (src+offset) and (src+offset+len). No support for negative numbers,
+ which doesn't affect our time parsing.
+*/
+int
+antoi( char *src, int offset, int len ) {
+ int pow = 1, res = 0;
+
+ if( len < 0 ) {
+ return( -1 );
+ }
+ while( --len != -1 ) {
+ if( !isdigit( src[offset+len] ) ) {
+ res = -1;
+ break;
+ } else {
+ res += ( src[offset+len] - '0' ) * pow ;
+ pow *= 10;
+ }
+ }
+ return( res );
+}
+
+/*
+ Converts generalized time to UNIX GMT time. For example:
+ "20060807211257Z" -> 1154981577
+*/
+time_t
+gentimeToEpochtime( char *gentimestr ) {
+ time_t epochtime, cur_local_epochtime, cur_gm_epochtime, zone_offset;
+ struct tm t, *cur_gm_time;
+
+ /* Find the local offset from GMT */
+ cur_gm_time = (struct tm*)slapi_ch_calloc( 1, sizeof( struct tm ) );
+ cur_local_epochtime = time( (time_t)0 );
+ gmtime_r( &cur_local_epochtime, cur_gm_time );
+ cur_gm_epochtime = mktime( cur_gm_time );
+ free( cur_gm_time );
+ zone_offset = cur_gm_epochtime - cur_local_epochtime;
+
+ /* Parse generalizedtime string into a tm struct */
+ t.tm_year = antoi( gentimestr, 0, 4 ) - 1900;
+ t.tm_mon = antoi( gentimestr, 4, 2 ) - 1;
+ t.tm_mday = antoi( gentimestr, 6, 2 );
+ t.tm_hour = antoi( gentimestr, 8, 2 );
+ t.tm_min = antoi( gentimestr, 10, 2 );
+ t.tm_sec = antoi( gentimestr, 12, 2 );
+ t.tm_isdst = 0; /* DST does not apply to UTC */
+
+ /* Turn tm object into local epoch time */
+ epochtime = mktime( &t );
+
+ /* Turn local epoch time into GMT epoch time */
+ epochtime -= zone_offset;
+
+ return( epochtime );
+}
+
+/*
+ Converts UNIX time to generalized time. For example:
+ 1154981577 -> "20060807211257Z"
+*/
+char*
+epochtimeToGentime( time_t epochtime ) {
+ char *gentimestr;
+ struct tm t;
+
+ gmtime_r( &epochtime, &t );
+ gentimestr = slapi_ch_malloc( 20 );
+ /* Format is YYYYmmddHHMMSSZ (15+1 chars) */
+ strftime( gentimestr, 16, "%Y%m%d%H%M%SZ", &t );
+
+ return( gentimestr );
+}
+
diff --git a/ldap/servers/plugins/acctpolicy/acctpolicy.h b/ldap/servers/plugins/acctpolicy/acctpolicy.h
new file mode 100644
index 00000000..bc8ecb3a
--- /dev/null
+++ b/ldap/servers/plugins/acctpolicy/acctpolicy.h
@@ -0,0 +1,79 @@
+/******************************************************************************
+Copyright (C) 2009 Hewlett-Packard Development Company, L.P.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+version 2 as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+Contributors:
+Hewlett-Packard Development Company, L.P.
+******************************************************************************/
+
+#include "nspr.h"
+
+#define SLAPI_OP_FLAG_BYPASS_REFERRALS 0x40000
+
+#define CFG_LASTLOGIN_STATE_ATTR "stateAttrName"
+#define CFG_ALT_LASTLOGIN_STATE_ATTR "altStateAttrName"
+#define CFG_SPEC_ATTR "specAttrName"
+#define CFG_INACT_LIMIT_ATTR "limitAttrName"
+#define CFG_RECORD_LOGIN "alwaysRecordLogin"
+
+#define DEFAULT_LASTLOGIN_STATE_ATTR "lastLoginTime"
+#define DEFAULT_ALT_LASTLOGIN_STATE_ATTR "createTimestamp"
+#define DEFAULT_SPEC_ATTR "acctPolicySubentry"
+#define DEFAULT_INACT_LIMIT_ATTR "accountInactivityLimit"
+#define DEFAULT_RECORD_LOGIN 1
+
+#define PLUGIN_VENDOR "Hewlett-Packard Company"
+#define PLUGIN_VERSION "1.0"
+#define PLUGIN_CONFIG_DN "cn=config,cn=Account Policy Plugin,cn=plugins,cn=config"
+
+#define PLUGIN_NAME "acct-policy"
+#define PLUGIN_DESC "Account Policy Plugin"
+#define PRE_PLUGIN_NAME "acct-policy-preop"
+#define PRE_PLUGIN_DESC "Account Policy Pre-Op Plugin"
+#define POST_PLUGIN_NAME "acct-policy-postop"
+#define POST_PLUGIN_DESC "Account Policy Post-Op Plugin"
+
+#define CALLBACK_OK 0
+#define CALLBACK_ERR -1
+#define CALLBACK_HANDLED 1
+
+typedef struct acct_plugin_cfg {
+ char* state_attr_name;
+ char* alt_state_attr_name;
+ char* spec_attr_name;
+ char* limit_attr_name;
+ int always_record_login;
+} acctPluginCfg;
+
+typedef struct accountpolicy {
+ unsigned long inactivitylimit;
+} acctPolicy;
+
+/* acct_util.c */
+int get_acctpolicy( Slapi_PBlock *pb, Slapi_Entry *target_entry,
+ void *plugin_id, acctPolicy **policy );
+void free_acctpolicy( acctPolicy **policy );
+int has_attr( Slapi_Entry* target_entry, char* attr_name,
+ char** val );
+char* get_attr_string_val( Slapi_Entry* e, char* attr_name );
+void* get_identity();
+void set_identity(void*);
+time_t gentimeToEpochtime( char *gentimestr );
+char* epochtimeToGentime( time_t epochtime );
+
+/* acct_config.c */
+int acct_policy_load_config_startup( Slapi_PBlock* pb, void* plugin_id );
+acctPluginCfg* get_config();
+
diff --git a/ldap/servers/plugins/acctpolicy/sampleconfig.ldif b/ldap/servers/plugins/acctpolicy/sampleconfig.ldif
new file mode 100644
index 00000000..7f611605
--- /dev/null
+++ b/ldap/servers/plugins/acctpolicy/sampleconfig.ldif
@@ -0,0 +1,40 @@
+# Copyright (C) 2009 Hewlett-Packard Development Company, L.P.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# version 2 as published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# Contributors:
+# Hewlett-Packard Development Company, L.P.
+
+dn: cn=Account Policy Plugin,cn=plugins,cn=config
+objectClass: top
+objectClass: nsSlapdPlugin
+objectClass: extensibleObject
+cn: Account Policy Plugin
+nsslapd-pluginPath: /path/to/libacctpolicy-plugin.so
+nsslapd-pluginInitfunc: acct_policy_init
+nsslapd-pluginType: object
+nsslapd-pluginEnabled: on
+nsslapd-plugin-depends-on-type: database
+nsslapd-pluginId: acct-policy
+
+dn: cn=config,cn=Account Policy Plugin,cn=plugins,cn=config
+objectClass: top
+objectClass: extensibleObject
+cn: config
+alwaysrecordlogin: yes
+stateattrname: lastLoginTime
+altstateattrname: createTimestamp
+specattrname: acctPolicySubentry
+limitattrname: accountInactivityLimit
+
diff --git a/ldap/servers/plugins/acctpolicy/samplepolicy.ldif b/ldap/servers/plugins/acctpolicy/samplepolicy.ldif
new file mode 100644
index 00000000..13aa6e95
--- /dev/null
+++ b/ldap/servers/plugins/acctpolicy/samplepolicy.ldif
@@ -0,0 +1,27 @@
+# Copyright (C) 2009 Hewlett-Packard Development Company, L.P.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# version 2 as published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# Contributors:
+# Hewlett-Packard Development Company, L.P.
+
+dn: cn=AccountPolicy,dc=example,dc=com
+objectClass: top
+objectClass: ldapsubentry
+objectClass: extensibleObject
+objectClass: accountpolicy
+# 86400 seconds per day * 30 days = 2592000 seconds
+accountInactivityLimit: 2592000
+cn: AccountPolicy
+