ITS#9279 Implement Netscape password policy controls in ppolicy

This commit is contained in:
Ondřej Kuzník 2020-06-23 13:31:11 +01:00 committed by Quanah Gibson-Mount
parent fd921e7121
commit a49b553676
2 changed files with 83 additions and 4 deletions

View file

@ -44,7 +44,8 @@ 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.
on search requests and can send the Netscape Password validity controls
when configured to do so.
.SH CONFIGURATION
These
@ -93,6 +94,12 @@ that sending the
error code provides useful information
to an attacker; sites that are sensitive to security issues should not
enable this option.
.TP
.B ppolicy_send_netscape_controls
If set, ppolicy will send the password policy expired (2.16.840.1.113730.3.4.4)
and password policy expiring (2.16.840.1.113730.3.4.5) controls when
appropriate. The controls are not sent for bind requests where the Password
policy control has already been requested. Default is not to send the controls.
.SH OBJECT CLASS
The

View file

@ -56,6 +56,7 @@ typedef struct pp_info {
int hash_passwords; /* transparently hash cleartext pwds */
int forward_updates; /* use frontend for policy state updates */
int disable_write;
int send_netscape_controls; /* send netscape password controls */
} pp_info;
/* Our per-connection info - note, it is not per-instance, it is
@ -456,6 +457,14 @@ static ConfigTable ppolicycfg[] = {
(void *)offsetof(pp_info,disable_write),
"( OLcfgOvAt:12.5 NAME 'olcPPolicyDisableWrite' "
"DESC 'Prevent all policy overlay writes' "
"EQUALITY booleanMatch "
"SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
{ "ppolicy_send_netscape_controls", "on|off", 1, 2, 0,
ARG_ON_OFF|ARG_OFFSET,
(void *)offsetof(pp_info,send_netscape_controls),
"( OLcfgOvAt:12.6 NAME 'olcPPolicySendNetscapeControls' "
"DESC 'Send Netscape policy controls' "
"EQUALITY booleanMatch "
"SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
{ NULL, NULL, 0, 0, 0, ARG_IGNORED }
};
@ -467,7 +476,7 @@ static ConfigOCs ppolicyocs[] = {
"SUP olcOverlayConfig "
"MAY ( olcPPolicyDefault $ olcPPolicyHashCleartext $ "
"olcPPolicyUseLockout $ olcPPolicyForwardUpdates $ "
"olcPPolicyDisableWrite ) )",
"olcPPolicyDisableWrite $ olcPPolicySendNetscapeControls ) )",
Cft_Overlay, ppolicycfg },
{ NULL, 0, NULL }
};
@ -665,6 +674,8 @@ account_locked( Operation *op, Entry *e,
static const char ppolicy_ctrl_oid[] = LDAP_CONTROL_PASSWORDPOLICYRESPONSE;
static const char ppolicy_account_ctrl_oid[] = LDAP_CONTROL_X_ACCOUNT_USABILITY;
static const char ppolicy_pwd_expired_oid[] = LDAP_CONTROL_X_PASSWORD_EXPIRED;
static const char ppolicy_pwd_expiring_oid[] = LDAP_CONTROL_X_PASSWORD_EXPIRING;
static LDAPControl *
create_passcontrol( Operation *op, int exptime, int grace, LDAPPasswordPolicyError err )
@ -724,6 +735,42 @@ fail:
return cp;
}
static LDAPControl *
create_passexpiry( Operation *op, int expired, int warn )
{
BerElementBuffer berbuf;
BerElement *ber = (BerElement *) &berbuf;
LDAPControl c = { 0 }, *cp;
char buf[sizeof("-2147483648")];
struct berval bv = { .bv_val = buf, .bv_len = sizeof(buf) };
int rc;
BER_BVZERO( &c.ldctl_value );
bv.bv_len = snprintf( bv.bv_val, bv.bv_len, "%d", warn );
ber_init2( ber, NULL, LBER_USE_DER );
ber_printf( ber, "O", &bv );
if (ber_flatten2( ber, &c.ldctl_value, 0 ) == -1) {
return NULL;
}
cp = op->o_tmpalloc( sizeof( LDAPControl ) + c.ldctl_value.bv_len, op->o_tmpmemctx );
if ( expired ) {
cp->ldctl_oid = (char *)ppolicy_pwd_expired_oid;
} else {
cp->ldctl_oid = (char *)ppolicy_pwd_expiring_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 );
fail:
(void)ber_free_buf(ber);
return cp;
}
static LDAPControl **
add_passcontrol( Operation *op, SlapReply *rs, LDAPControl *ctrl )
{
@ -1332,7 +1379,9 @@ ctrls_cleanup( Operation *op, SlapReply *rs, LDAPControl **oldctrls )
assert( rs->sr_ctrls[0] != NULL );
for ( n = 0; rs->sr_ctrls[n]; n++ ) {
if ( rs->sr_ctrls[n]->ldctl_oid == ppolicy_ctrl_oid ) {
if ( rs->sr_ctrls[n]->ldctl_oid == ppolicy_ctrl_oid ||
rs->sr_ctrls[n]->ldctl_oid == ppolicy_pwd_expired_oid ||
rs->sr_ctrls[n]->ldctl_oid == ppolicy_pwd_expiring_oid ) {
op->o_tmpfree( rs->sr_ctrls[n], op->o_tmpmemctx );
rs->sr_ctrls[n] = (LDAPControl *)(-1);
break;
@ -1375,6 +1424,7 @@ ppolicy_bind_response( Operation *op, SlapReply *rs )
char nowstr_usec[ LDAP_LUTIL_GENTIME_BUFSIZE+8 ];
struct berval timestamp, timestamp_usec;
BackendInfo *bi = op->o_bd->bd_info;
LDAPControl *ctrl = NULL;
Entry *e;
/* If we already know it's locked, just get on with it */
@ -1727,13 +1777,20 @@ locked:
}
if ( ppb->send_ctrl ) {
LDAPControl *ctrl = NULL;
/* Do we really want to tell that the account is locked? */
if ( ppb->pErr == PP_accountLocked && !pi->use_lockout ) {
ppb->pErr = PP_noError;
}
ctrl = create_passcontrol( op, warn, ngut, ppb->pErr );
} else if ( pi->send_netscape_controls ) {
if ( ppb->pErr != PP_noError || ngut > 0 ) {
ctrl = create_passexpiry( op, 1, 0 );
} else if ( warn > 0 ) {
ctrl = create_passexpiry( op, 0, warn );
}
}
if ( ctrl ) {
ppb->oldctrls = add_passcontrol( op, rs, ctrl );
op->o_callback->sc_cleanup = ppolicy_ctrls_cleanup;
}
@ -3212,6 +3269,21 @@ int ppolicy_initialize()
return code;
}
/* We don't expect to receive these controls, only send them */
code = register_supported_control( LDAP_CONTROL_X_PASSWORD_EXPIRED,
0, NULL, NULL, NULL );
if ( code != LDAP_SUCCESS ) {
Debug( LDAP_DEBUG_ANY, "Failed to register control %d\n", code );
return code;
}
code = register_supported_control( LDAP_CONTROL_X_PASSWORD_EXPIRING,
0, NULL, NULL, NULL );
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";