mirror of
https://git.openldap.org/openldap/openldap.git
synced 2025-01-24 13:24:56 +08:00
ITS#8701 Implement account usability in ppolicy
This commit is contained in:
parent
63440f0379
commit
bdc9dbc511
@ -41,6 +41,10 @@ when considering a single-valued password attribute, while
|
||||
the userPassword attribute allows multiple values. This implementation
|
||||
enforces a single value for the userPassword attribute, despite
|
||||
its specification.
|
||||
.P
|
||||
In addition to supporting the IETF Password Policy, this module
|
||||
supports the SunDS Account Usability control (1.3.6.1.4.1.42.2.27.9.5.8)
|
||||
on search requests.
|
||||
|
||||
.SH CONFIGURATION
|
||||
These
|
||||
@ -977,6 +981,20 @@ policy option and when the lockout ends.
|
||||
USAGE directoryOperation )
|
||||
.RE
|
||||
|
||||
.SH SUNDS ACCOUNT USABILITY CONTROL
|
||||
.LP
|
||||
If the SunDS Account Usability control is used with a search request, the
|
||||
overlay will attach validity information to each entry provided all of the
|
||||
following are met:
|
||||
.IP \[bu] 2
|
||||
There is a password policy that applies to the entry
|
||||
.IP \[bu]
|
||||
The user has
|
||||
.B read
|
||||
access to the entry's password attribute.
|
||||
.IP \[bu]
|
||||
The configured password attribute is present in the entry
|
||||
|
||||
.SH EXAMPLES
|
||||
.LP
|
||||
.RS
|
||||
|
@ -67,6 +67,7 @@ typedef struct pw_conn {
|
||||
|
||||
static pw_conn *pwcons;
|
||||
static int ppolicy_cid;
|
||||
static int account_usability_cid;
|
||||
static int ov_count;
|
||||
|
||||
typedef struct pass_policy {
|
||||
@ -541,8 +542,6 @@ account_locked( Operation *op, Entry *e,
|
||||
{
|
||||
Attribute *la;
|
||||
|
||||
assert(mod != NULL);
|
||||
|
||||
if ( (la = attr_find( e->e_attrs, ad_pwdStartTime )) != NULL ) {
|
||||
BerVarray vals = la->a_nvals;
|
||||
time_t then, now = op->o_time;
|
||||
@ -641,13 +640,15 @@ account_locked( Operation *op, Entry *e,
|
||||
if (now < then + pp->pwdLockoutDuration)
|
||||
return 1;
|
||||
|
||||
m = ch_calloc( sizeof(Modifications), 1 );
|
||||
m->sml_op = LDAP_MOD_DELETE;
|
||||
m->sml_flags = 0;
|
||||
m->sml_type = ad_pwdAccountLockedTime->ad_cname;
|
||||
m->sml_desc = ad_pwdAccountLockedTime;
|
||||
m->sml_next = *mod;
|
||||
*mod = m;
|
||||
if ( mod != NULL ) {
|
||||
m = ch_calloc( sizeof(Modifications), 1 );
|
||||
m->sml_op = LDAP_MOD_DELETE;
|
||||
m->sml_flags = 0;
|
||||
m->sml_type = ad_pwdAccountLockedTime->ad_cname;
|
||||
m->sml_desc = ad_pwdAccountLockedTime;
|
||||
m->sml_next = *mod;
|
||||
*mod = m;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -662,6 +663,7 @@ account_locked( Operation *op, Entry *e,
|
||||
#define PPOLICY_GRACE 0x81L /* primitive + 1 */
|
||||
|
||||
static const char ppolicy_ctrl_oid[] = LDAP_CONTROL_PASSWORDPOLICYRESPONSE;
|
||||
static const char ppolicy_account_ctrl_oid[] = LDAP_CONTROL_X_ACCOUNT_USABILITY;
|
||||
|
||||
static LDAPControl *
|
||||
create_passcontrol( Operation *op, int exptime, int grace, LDAPPasswordPolicyError err )
|
||||
@ -750,6 +752,65 @@ add_passcontrol( Operation *op, SlapReply *rs, LDAPControl *ctrl )
|
||||
return oldctrls;
|
||||
}
|
||||
|
||||
static void
|
||||
add_account_control(
|
||||
Operation *op,
|
||||
SlapReply *rs,
|
||||
int available,
|
||||
int remaining,
|
||||
LDAPAccountUsabilityMoreInfo *more_info )
|
||||
{
|
||||
BerElementBuffer berbuf;
|
||||
BerElement *ber = (BerElement *) &berbuf;
|
||||
LDAPControl c = { 0 }, *cp = NULL, **ctrls;
|
||||
int i = 0;
|
||||
|
||||
BER_BVZERO( &c.ldctl_value );
|
||||
|
||||
ber_init2( ber, NULL, LBER_USE_DER );
|
||||
|
||||
if ( available ) {
|
||||
ber_put_int( ber, remaining, LDAP_TAG_X_ACCOUNT_USABILITY_AVAILABLE );
|
||||
} else {
|
||||
assert( more_info != NULL );
|
||||
|
||||
ber_start_seq( ber, LDAP_TAG_X_ACCOUNT_USABILITY_NOT_AVAILABLE );
|
||||
ber_put_boolean( ber, more_info->inactive, LDAP_TAG_X_ACCOUNT_USABILITY_INACTIVE );
|
||||
ber_put_boolean( ber, more_info->reset, LDAP_TAG_X_ACCOUNT_USABILITY_RESET );
|
||||
ber_put_boolean( ber, more_info->expired, LDAP_TAG_X_ACCOUNT_USABILITY_EXPIRED );
|
||||
ber_put_int( ber, more_info->remaining_grace, LDAP_TAG_X_ACCOUNT_USABILITY_REMAINING_GRACE );
|
||||
ber_put_int( ber, more_info->seconds_before_unlock, LDAP_TAG_X_ACCOUNT_USABILITY_UNTIL_UNLOCK );
|
||||
ber_put_seq( ber );
|
||||
}
|
||||
|
||||
if (ber_flatten2( ber, &c.ldctl_value, 0 ) == -1) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if ( rs->sr_ctrls != NULL ) {
|
||||
for ( ; rs->sr_ctrls[ i ] != NULL; i++ ) /* Count */;
|
||||
}
|
||||
|
||||
ctrls = op->o_tmprealloc( rs->sr_ctrls, sizeof(LDAPControl *)*( i + 2 ), op->o_tmpmemctx );
|
||||
if ( ctrls == NULL ) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
cp = op->o_tmpalloc( sizeof( LDAPControl ) + c.ldctl_value.bv_len, op->o_tmpmemctx );
|
||||
cp->ldctl_oid = (char *)ppolicy_account_ctrl_oid;
|
||||
cp->ldctl_iscritical = 0;
|
||||
cp->ldctl_value.bv_val = (char *)&cp[1];
|
||||
cp->ldctl_value.bv_len = c.ldctl_value.bv_len;
|
||||
AC_MEMCPY( cp->ldctl_value.bv_val, c.ldctl_value.bv_val, c.ldctl_value.bv_len );
|
||||
|
||||
ctrls[ i ] = cp;
|
||||
ctrls[ i + 1 ] = NULL;
|
||||
rs->sr_ctrls = ctrls;
|
||||
|
||||
fail:
|
||||
(void)ber_free_buf(ber);
|
||||
}
|
||||
|
||||
static void
|
||||
ppolicy_get_default( PassPolicy *pp )
|
||||
{
|
||||
@ -1799,6 +1860,153 @@ ppolicy_restrict(
|
||||
return SLAP_CB_CONTINUE;
|
||||
}
|
||||
|
||||
static int
|
||||
ppolicy_account_usability_entry_cb( Operation *op, SlapReply *rs )
|
||||
{
|
||||
slap_overinst *on = op->o_callback->sc_private;
|
||||
BackendInfo *bi = op->o_bd->bd_info;
|
||||
LDAPControl *ctrl = NULL;
|
||||
PassPolicy pp;
|
||||
Attribute *a;
|
||||
Entry *e = NULL;
|
||||
time_t pwtime = 0, seconds_until_expiry = -1, now = op->o_time;
|
||||
int isExpired = 0, grace = -1;
|
||||
|
||||
if ( rs->sr_type != REP_SEARCH ) {
|
||||
return SLAP_CB_CONTINUE;
|
||||
}
|
||||
|
||||
if ( be_entry_get_rw( op, &rs->sr_entry->e_nname, NULL, NULL, 0, &e ) != LDAP_SUCCESS ) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
op->o_bd->bd_info = (BackendInfo *)on;
|
||||
|
||||
if ( ppolicy_get( op, e, &pp ) != LDAP_SUCCESS ) {
|
||||
/* TODO: If there is no policy, should we check if */
|
||||
goto done;
|
||||
}
|
||||
|
||||
if ( !access_allowed( op, e, pp.ad, NULL, ACL_COMPARE, NULL ) ) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
if ( attr_find( e->e_attrs, pp.ad ) == NULL ) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
if ((a = attr_find( e->e_attrs, ad_pwdChangedTime )) != NULL) {
|
||||
pwtime = parse_time( a->a_nvals[0].bv_val );
|
||||
}
|
||||
|
||||
if ( pp.pwdMaxAge && pwtime ) {
|
||||
seconds_until_expiry = pwtime + pp.pwdMaxAge - now;
|
||||
if ( seconds_until_expiry <= 0 ) isExpired = 1;
|
||||
if ( pp.pwdGraceAuthNLimit ) {
|
||||
if ( !pp.pwdGraceExpiry || seconds_until_expiry + pp.pwdGraceExpiry > 0 ) {
|
||||
grace = pp.pwdGraceAuthNLimit;
|
||||
if ( attr_find( e->e_attrs, ad_pwdGraceUseTime ) ) {
|
||||
grace -= a->a_numvals;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( !isExpired && pp.pwdMaxIdle && (a = attr_find( e->e_attrs, ad_pwdLastSuccess )) ) {
|
||||
time_t lastbindtime = pwtime;
|
||||
|
||||
if ( (a = attr_find( e->e_attrs, ad_pwdLastSuccess )) != NULL ) {
|
||||
lastbindtime = parse_time( a->a_nvals[0].bv_val );
|
||||
}
|
||||
|
||||
if ( lastbindtime ) {
|
||||
int remaining_idle = lastbindtime + pp.pwdMaxIdle - now;
|
||||
if ( remaining_idle <= 0 ) {
|
||||
isExpired = 1;
|
||||
} else if ( seconds_until_expiry == -1 || remaining_idle < seconds_until_expiry ) {
|
||||
seconds_until_expiry = remaining_idle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( isExpired || account_locked( op, e, &pp, NULL ) ) {
|
||||
LDAPAccountUsabilityMoreInfo more_info = { 0, 0, 0, -1, -1 };
|
||||
time_t then, lockoutEnd = 0;
|
||||
|
||||
if ( isExpired ) more_info.remaining_grace = grace;
|
||||
|
||||
if ( (a = attr_find( e->e_attrs, ad_pwdAccountLockedTime )) != NULL ) {
|
||||
then = parse_time( a->a_vals[0].bv_val );
|
||||
if ( then == 0 )
|
||||
lockoutEnd = -1;
|
||||
|
||||
/* Still in the future? not yet in effect */
|
||||
if ( now < then )
|
||||
then = 0;
|
||||
|
||||
if ( !pp.pwdLockoutDuration )
|
||||
lockoutEnd = -1;
|
||||
|
||||
if ( now < then + pp.pwdLockoutDuration )
|
||||
lockoutEnd = then + pp.pwdLockoutDuration;
|
||||
}
|
||||
|
||||
a = attr_find( e->e_attrs, ad_pwdAccountLockedTime );
|
||||
if ( (a = attr_find( e->e_attrs, ad_pwdAccountTmpLockoutEnd )) != NULL ) {
|
||||
then = parse_time( a->a_vals[0].bv_val );
|
||||
if ( lockoutEnd != -1 && then > lockoutEnd )
|
||||
lockoutEnd = then;
|
||||
}
|
||||
|
||||
if ( lockoutEnd > now ) {
|
||||
more_info.inactive = 1;
|
||||
more_info.seconds_before_unlock = lockoutEnd - now;
|
||||
}
|
||||
|
||||
if ( pp.pwdMustChange &&
|
||||
(a = attr_find( e->e_attrs, ad_pwdReset )) &&
|
||||
bvmatch( &a->a_nvals[0], &slap_true_bv ) )
|
||||
{
|
||||
more_info.reset = 1;
|
||||
}
|
||||
|
||||
add_account_control( op, rs, 0, -1, &more_info );
|
||||
} else {
|
||||
add_account_control( op, rs, 1, seconds_until_expiry, NULL );
|
||||
}
|
||||
|
||||
done:
|
||||
op->o_bd->bd_info = bi;
|
||||
if ( e ) {
|
||||
be_entry_release_r( op, e );
|
||||
}
|
||||
return SLAP_CB_CONTINUE;
|
||||
}
|
||||
|
||||
static int
|
||||
ppolicy_search(
|
||||
Operation *op,
|
||||
SlapReply *rs )
|
||||
{
|
||||
slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
|
||||
int rc = ppolicy_restrict( op, rs );
|
||||
|
||||
if ( rc != SLAP_CB_CONTINUE ) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
if ( op->o_ctrlflag[account_usability_cid] ) {
|
||||
slap_callback *cb;
|
||||
|
||||
cb = op->o_tmpcalloc( sizeof(slap_callback), 1, op->o_tmpmemctx );
|
||||
|
||||
cb->sc_response = ppolicy_account_usability_entry_cb;
|
||||
cb->sc_private = on;
|
||||
overlay_callback_after_backover( op, cb, 1 );
|
||||
}
|
||||
|
||||
return SLAP_CB_CONTINUE;
|
||||
}
|
||||
|
||||
static int
|
||||
ppolicy_compare_response(
|
||||
Operation *op,
|
||||
@ -2801,6 +3009,23 @@ ppolicy_parseCtrl(
|
||||
return LDAP_SUCCESS;
|
||||
}
|
||||
|
||||
static int
|
||||
ppolicy_au_parseCtrl(
|
||||
Operation *op,
|
||||
SlapReply *rs,
|
||||
LDAPControl *ctrl )
|
||||
{
|
||||
if ( !BER_BVISNULL( &ctrl->ldctl_value ) ) {
|
||||
rs->sr_text = "account usability control value not absent";
|
||||
return LDAP_PROTOCOL_ERROR;
|
||||
}
|
||||
op->o_ctrlflag[account_usability_cid] = ctrl->ldctl_iscritical
|
||||
? SLAP_CONTROL_CRITICAL
|
||||
: SLAP_CONTROL_NONCRITICAL;
|
||||
|
||||
return LDAP_SUCCESS;
|
||||
}
|
||||
|
||||
static int
|
||||
attrPretty(
|
||||
Syntax *syntax,
|
||||
@ -2876,6 +3101,11 @@ ppolicy_db_open(
|
||||
ConfigReply *cr
|
||||
)
|
||||
{
|
||||
int rc;
|
||||
|
||||
if ( (rc = overlay_register_control( be, LDAP_CONTROL_X_ACCOUNT_USABILITY )) != LDAP_SUCCESS ) {
|
||||
return rc;
|
||||
}
|
||||
return overlay_register_control( be, LDAP_CONTROL_PASSWORDPOLICYREQUEST );
|
||||
}
|
||||
|
||||
@ -2887,6 +3117,7 @@ ppolicy_db_close(
|
||||
{
|
||||
#ifdef SLAP_CONFIG_DELETE
|
||||
overlay_unregister_control( be, LDAP_CONTROL_PASSWORDPOLICYREQUEST );
|
||||
overlay_unregister_control( be, LDAP_CONTROL_X_ACCOUNT_USABILITY );
|
||||
#endif /* SLAP_CONFIG_DELETE */
|
||||
|
||||
return 0;
|
||||
@ -2972,6 +3203,14 @@ int ppolicy_initialize()
|
||||
return code;
|
||||
}
|
||||
|
||||
code = register_supported_control( LDAP_CONTROL_X_ACCOUNT_USABILITY,
|
||||
SLAP_CTRL_SEARCH, NULL,
|
||||
ppolicy_au_parseCtrl, &account_usability_cid );
|
||||
if ( code != LDAP_SUCCESS ) {
|
||||
Debug( LDAP_DEBUG_ANY, "Failed to register control %d\n", code );
|
||||
return code;
|
||||
}
|
||||
|
||||
ldap_pvt_thread_mutex_init( &chk_syntax_mutex );
|
||||
|
||||
ppolicy.on_bi.bi_type = "ppolicy";
|
||||
@ -2985,7 +3224,7 @@ int ppolicy_initialize()
|
||||
ppolicy.on_bi.bi_op_compare = ppolicy_compare;
|
||||
ppolicy.on_bi.bi_op_delete = ppolicy_restrict;
|
||||
ppolicy.on_bi.bi_op_modify = ppolicy_modify;
|
||||
ppolicy.on_bi.bi_op_search = ppolicy_restrict;
|
||||
ppolicy.on_bi.bi_op_search = ppolicy_search;
|
||||
ppolicy.on_bi.bi_connection_destroy = ppolicy_connection_destroy;
|
||||
|
||||
ppolicy.on_bi.bi_cf_ocs = ppolicyocs;
|
||||
|
@ -89,8 +89,12 @@ if test $COUNT != 2 ; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Waiting 20 seconds for lockout to reset..."
|
||||
sleep 20
|
||||
DELAY=`$LDAPSEARCH -D "$MANAGERDN" -h $LOCALHOST -p $PORT1 -w $PASSWD \
|
||||
-b "$USER" -E accountUsability 1.1 | sed -n -e 's/.*seconds_before_unlock=\(\d*\)/\1/p'`
|
||||
|
||||
echo "Waiting $DELAY seconds for lockout to reset..."
|
||||
sleep $DELAY
|
||||
sleep 1
|
||||
|
||||
$LDAPSEARCH -e ppolicy -h $LOCALHOST -p $PORT1 -D "$USER" -w $PASS \
|
||||
-b "$BASEDN" -s base >> $SEARCHOUT 2>&1
|
||||
@ -101,9 +105,13 @@ if test $RC != 0 ; then
|
||||
exit $RC
|
||||
fi
|
||||
|
||||
DELAY=`$LDAPSEARCH -D "$MANAGERDN" -h $LOCALHOST -p $PORT1 -w $PASSWD \
|
||||
-b "$USER" -E accountUsability 1.1 | sed -n -e 's/.*expire=\(\d*\)/\1/p'`
|
||||
|
||||
echo "Testing password expiration"
|
||||
echo "Waiting 20 seconds for password to expire..."
|
||||
sleep 20
|
||||
echo "Waiting $DELAY seconds for password to expire..."
|
||||
sleep $DELAY
|
||||
sleep 1
|
||||
|
||||
$LDAPSEARCH -e ppolicy -h $LOCALHOST -p $PORT1 -D "$USER" -w $PASS \
|
||||
-b "$BASEDN" -s base > $SEARCHOUT 2>&1
|
||||
@ -467,8 +475,12 @@ fi
|
||||
$LDAPSEARCH -e ppolicy -h $LOCALHOST -p $PORT1 -D "$USER" -w $PASS \
|
||||
-b "$BASEDN" -s base > $SEARCHOUT 2>&1
|
||||
|
||||
echo "Waiting 20 seconds for password to expire..."
|
||||
sleep 20
|
||||
DELAY=`$LDAPSEARCH -D "$MANAGERDN" -h $LOCALHOST -p $PORT1 -w $PASSWD \
|
||||
-b "$USER" -E accountUsability 1.1 | sed -n -e 's/.*expire=\(\d*\)/\1/p'`
|
||||
|
||||
echo "Waiting $DELAY seconds for password to expire..."
|
||||
sleep $DELAY
|
||||
sleep 1
|
||||
|
||||
$LDAPSEARCH -e ppolicy -h $LOCALHOST -p $PORT1 -D "$USER" -w $PASS \
|
||||
-b "$BASEDN" -s base >> $SEARCHOUT 2>&1
|
||||
|
Loading…
Reference in New Issue
Block a user