From 094848b6de91179f7ef703a275fa75f08763438d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= <ondra@mistotebe.net> 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 | 81 ++++++++++++++++++++++++++++++-- 2 files changed, 85 insertions(+), 5 deletions(-) diff --git a/doc/man/man5/slapo-ppolicy.5 b/doc/man/man5/slapo-ppolicy.5 index 0b4c92e2be..6d67d151ab 100644 --- a/doc/man/man5/slapo-ppolicy.5 +++ b/doc/man/man5/slapo-ppolicy.5 @@ -36,6 +36,9 @@ 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 can +send the Netscape Password validity controls when configured to do so. .SH CONFIGURATION These @@ -84,6 +87,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 362fe91e2c..422a85ad42 100644 --- a/servers/slapd/overlays/ppolicy.c +++ b/servers/slapd/overlays/ppolicy.c @@ -55,6 +55,7 @@ typedef struct pp_info { int use_lockout; /* send AccountLocked result? */ int hash_passwords; /* transparently hash cleartext pwds */ int forward_updates; /* use frontend for policy state updates */ + int send_netscape_controls; /* send netscape password controls */ } pp_info; /* Our per-connection info - note, it is not per-instance, it is @@ -243,6 +244,13 @@ static ConfigTable ppolicycfg[] = { "( OLcfgOvAt:12.3 NAME 'olcPPolicyUseLockout' " "DESC 'Warn clients with AccountLocked' " "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 } }; @@ -252,7 +260,8 @@ static ConfigOCs ppolicyocs[] = { "DESC 'Password Policy configuration' " "SUP olcOverlayConfig " "MAY ( olcPPolicyDefault $ olcPPolicyHashCleartext $ " - "olcPPolicyUseLockout $ olcPPolicyForwardUpdates ) )", + "olcPPolicyUseLockout $ olcPPolicyForwardUpdates $ " + "olcPPolicySendNetscapeControls ) )", Cft_Overlay, ppolicycfg }, { NULL, 0, NULL } }; @@ -376,6 +385,8 @@ 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_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 ) @@ -435,6 +446,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 ) { @@ -913,7 +960,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; @@ -944,6 +993,7 @@ ppolicy_bind_response( Operation *op, SlapReply *rs ) { ppbind *ppb = op->o_callback->sc_private; slap_overinst *on = ppb->on; + pp_info *pi = on->on_bi.bi_private; Modifications *mod = ppb->mod, *m; int pwExpired = 0; int ngut = -1, warn = -1, age, rc; @@ -955,6 +1005,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 */ @@ -1233,7 +1284,6 @@ locked: Operation op2 = *op; SlapReply r2 = { REP_RESULT }; slap_callback cb = { NULL, slap_null_cb, NULL, NULL }; - pp_info *pi = on->on_bi.bi_private; LDAPControl c, *ca[2]; op2.o_tag = LDAP_REQ_MODIFY; @@ -1273,14 +1323,20 @@ locked: } if ( ppb->send_ctrl ) { - LDAPControl *ctrl = NULL; - pp_info *pi = on->on_bi.bi_private; /* 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; } @@ -2512,6 +2568,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, 0, 0 ); + 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, 0, 0 ); + return code; + } + ldap_pvt_thread_mutex_init( &chk_syntax_mutex ); ppolicy.on_bi.bi_type = "ppolicy"; -- GitLab