diff options
Diffstat (limited to 'ldap')
-rw-r--r-- | ldap/ldif/template-dse.ldif.in | 18 | ||||
-rw-r--r-- | ldap/schema/60acctpolicy.ldif | 47 | ||||
-rw-r--r-- | ldap/servers/plugins/acctpolicy/acct_config.c | 123 | ||||
-rw-r--r-- | ldap/servers/plugins/acctpolicy/acct_init.c | 183 | ||||
-rw-r--r-- | ldap/servers/plugins/acctpolicy/acct_plugin.c | 333 | ||||
-rw-r--r-- | ldap/servers/plugins/acctpolicy/acct_util.c | 245 | ||||
-rw-r--r-- | ldap/servers/plugins/acctpolicy/acctpolicy.h | 79 | ||||
-rw-r--r-- | ldap/servers/plugins/acctpolicy/sampleconfig.ldif | 40 | ||||
-rw-r--r-- | ldap/servers/plugins/acctpolicy/samplepolicy.ldif | 27 | ||||
-rw-r--r-- | ldap/servers/slapd/mapping_tree.c | 3 | ||||
-rw-r--r-- | ldap/servers/slapd/slapi-plugin.h | 1 |
11 files changed, 1098 insertions, 1 deletions
diff --git a/ldap/ldif/template-dse.ldif.in b/ldap/ldif/template-dse.ldif.in index ca2ede8a..f2b92a52 100644 --- a/ldap/ldif/template-dse.ldif.in +++ b/ldap/ldif/template-dse.ldif.in @@ -955,3 +955,21 @@ objectclass: top objectclass: extensibleObject cn: tasks +dn: cn=Account Policy Plugin,cn=plugins,cn=config +objectClass: top +objectClass: nsSlapdPlugin +objectClass: extensibleObject +cn: Account Policy Plugin +nsslapd-pluginPath: libacctpolicy-plugin +nsslapd-pluginInitfunc: acct_policy_init +nsslapd-pluginType: object +nsslapd-pluginEnabled: off +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: no + diff --git a/ldap/schema/60acctpolicy.ldif b/ldap/schema/60acctpolicy.ldif new file mode 100644 index 00000000..40c092ec --- /dev/null +++ b/ldap/schema/60acctpolicy.ldif @@ -0,0 +1,47 @@ +# 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. +# +# Schema for the account policy plugin +# +dn: cn=schema +## +## lastLoginTime holds login state in user entries (GeneralizedTime syntax) +attributeTypes: ( 2.16.840.1.113719.1.1.4.1.35 NAME 'lastLoginTime' + DESC 'Last login time' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 SINGLE-VALUE USAGE directoryOperation + X-ORIGIN 'Account Policy Plugin' ) +## +## acctPolicySubentry is an an account policy pointer (DN syntax) +attributeTypes: ( 1.3.6.1.4.1.11.1.3.2.1.2 NAME 'acctPolicySubentry' + DESC 'Account policy pointer' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE USAGE directoryOperation + X-ORIGIN 'Account Policy Plugin' ) +## +## accountInactivityLimit specifies inactivity limit in accountPolicy objects +## (DirectoryString syntax) +attributeTypes: ( 1.3.6.1.4.1.11.1.3.2.1.3 NAME 'accountInactivityLimit' + DESC 'Account inactivity limit' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE + X-ORIGIN 'Account Policy Plugin' ) +## +## accountPolicy is the objectclass of account policy subentries +objectClasses: ( 1.3.6.1.4.1.11.1.3.2.2.1 NAME 'accountPolicy' + DESC 'Account policy entry' + SUP top AUXILIARY MAY ( accountInactivityLimit ) + X-ORIGIN 'Account Policy Plugin' ) + 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( ×tr ); + } + + 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 |