]> git.ipfire.org Git - thirdparty/openldap.git/commitdiff
ITS#9279 Implement Netscape password policy controls in ppolicy
authorOndřej Kuzník <ondra@mistotebe.net>
Tue, 23 Jun 2020 12:31:11 +0000 (13:31 +0100)
committerQuanah Gibson-Mount <quanah@openldap.org>
Wed, 22 Jul 2020 22:11:44 +0000 (22:11 +0000)
doc/man/man5/slapo-ppolicy.5
servers/slapd/overlays/ppolicy.c

index 0b4c92e2be607a18df6fc2914d0578b8e50ada5e..6d67d151ab65c6126ce74bbe1182ed0c664b9207 100644 (file)
@@ -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 
index 362fe91e2c26e83e2ed424f42c11b27f276780f3..422a85ad428cbc8ad44dae4042de168ba94ee5e9 100644 (file)
@@ -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";