summaryrefslogtreecommitdiffstats
path: root/ldap/servers
diff options
context:
space:
mode:
Diffstat (limited to 'ldap/servers')
-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
-rw-r--r--ldap/servers/slapd/mapping_tree.c3
-rw-r--r--ldap/servers/slapd/slapi-plugin.h1
9 files changed, 1033 insertions, 1 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
+
diff --git a/ldap/servers/slapd/mapping_tree.c b/ldap/servers/slapd/mapping_tree.c
index 813c6049..0f635609 100644
--- a/ldap/servers/slapd/mapping_tree.c
+++ b/ldap/servers/slapd/mapping_tree.c
@@ -2509,7 +2509,8 @@ static int mtn_get_be(mapping_tree_node *target_node, Slapi_PBlock *pb,
((cid != NULL) && (pw_get_componentID() != NULL) && (pw_get_componentID() == cid)) ||
operation_is_flag_set(op, OP_FLAG_LEGACY_REPLICATION_DN) || /* 4.0 lgacy update */
operation_is_flag_set(op, OP_FLAG_REPLICATED) || /* 5.0 replication update */
- operation_is_flag_set(op, OP_FLAG_TOMBSTONE_ENTRY); /* 5.1 fix to enable tombstone delete on a R-O consumer */
+ operation_is_flag_set(op, OP_FLAG_TOMBSTONE_ENTRY) || /* 5.1 fix to enable tombstone delete on a R-O consumer */
+ operation_is_flag_set(op, SLAPI_OP_FLAG_BYPASS_REFERRALS); /* 6.1 fix to allow internal updates from plugins on R-O consumer */
if ((target_node->mtn_state == MTN_BACKEND) ||
(target_node->mtn_state == MTN_CONTAINER ) ||
((target_node->mtn_state == MTN_REFERRAL_ON_UPDATE) &&
diff --git a/ldap/servers/slapd/slapi-plugin.h b/ldap/servers/slapd/slapi-plugin.h
index 893359c9..8ef2ef8d 100644
--- a/ldap/servers/slapd/slapi-plugin.h
+++ b/ldap/servers/slapd/slapi-plugin.h
@@ -173,6 +173,7 @@ NSPR_API(PRUint32) PR_fprintf(struct PRFileDesc* fd, const char *fmt, ...)
#define SLAPI_OP_FLAG_INTERNAL 0x00020 /* An operation generated by the core server or a plugin. */
#define SLAPI_OP_FLAG_NEVER_CHAIN 0x00800 /* Do not chain the operation */
#define SLAPI_OP_FLAG_NO_ACCESS_CHECK 0x10000 /* Do not check for access control - bypass them */
+#define SLAPI_OP_FLAG_BYPASS_REFERRALS 0x40000 /* Useful for performing internal operations on read-only replica */
#define SLAPI_OC_FLAG_REQUIRED 0x0001
#define SLAPI_OC_FLAG_ALLOWED 0x0002