From a49b55367696531e7fddc4dc74dfb652a4e949b0 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Tue, 23 Jun 2020 13:31:11 +0100 Subject: [PATCH] ITS#9279 Implement Netscape password policy controls in ppolicy --- doc/man/man5/slapo-ppolicy.5 | 9 +++- servers/slapd/overlays/ppolicy.c | 78 ++++++++++++++++++++++++++++++-- 2 files changed, 83 insertions(+), 4 deletions(-) diff --git a/doc/man/man5/slapo-ppolicy.5 b/doc/man/man5/slapo-ppolicy.5 index 2e61f67a48..42da2aad24 100644 --- a/doc/man/man5/slapo-ppolicy.5 +++ b/doc/man/man5/slapo-ppolicy.5 @@ -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 diff --git a/servers/slapd/overlays/ppolicy.c b/servers/slapd/overlays/ppolicy.c index b6743ebb78..9527d457f4 100644 --- a/servers/slapd/overlays/ppolicy.c +++ b/servers/slapd/overlays/ppolicy.c @@ -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"; -- 2.47.3