]> 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 18:57:38 +0000 (18:57 +0000)
doc/man/man5/slapo-ppolicy.5
servers/slapd/overlays/ppolicy.c

index 2e61f67a48bfdce6e6508905c549e9c3dc523477..42da2aad24afc4f085690f54fd014e1091ab61b8 100644 (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 
index b6743ebb789872c291fc1d5ddae5c12869824277..9527d457f4563ed0cdb961b58fe90d54e7c852d1 100644 (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";