From: Ondřej Kuzník Date: Tue, 8 Feb 2022 16:46:59 +0000 (+0000) Subject: ITS#9343 Allow a list of default policies X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=950ff8a5f099a07e774526a4f1d7d1c0b58abb56;p=thirdparty%2Fopenldap.git ITS#9343 Allow a list of default policies --- diff --git a/doc/man/man5/slapo-ppolicy.5 b/doc/man/man5/slapo-ppolicy.5 index 1aa2a4f500..a57ae60338 100644 --- a/doc/man/man5/slapo-ppolicy.5 +++ b/doc/man/man5/slapo-ppolicy.5 @@ -55,9 +55,20 @@ after the .B overlay directive. .TP +.B ppolicy_rules +Specify which pwdPolicy object to use when no specific policy is set on +a given user's entry. If there is no pwdPolicySubentry set, the URIs are +checked in order and the first one to match will apply. If one is selected +and the object at +.B policyDN +does not exist or is not a password policy, then no policies will be +enforced. +.TP .B ppolicy_default Specify the DN of the pwdPolicy object to use when no specific policy is -set on a given user's entry. If there is no specific policy for an entry +set on a given user's entry and none of the +.B ppolicy_rules +apply. If there is no specific policy for an entry and no default is given, then no policies will be enforced. .TP .B ppolicy_forward_updates diff --git a/servers/slapd/overlays/ppolicy.c b/servers/slapd/overlays/ppolicy.c index f36124a67f..66b2ec0bae 100644 --- a/servers/slapd/overlays/ppolicy.c +++ b/servers/slapd/overlays/ppolicy.c @@ -53,9 +53,19 @@ typedef int (check_func)( char *passwd, struct berval *errmsg, Entry *ent, struct berval *arg ); #define ERRBUFSIZ 256 +typedef struct policy_rule { + struct berval uri; /* straight from configuration, unparsed below */ + struct berval base; + int scope; + Filter *filter; + struct berval policy_dn; /* DN of policy entry to select */ + struct policy_rule *next; +} policy_rule; + /* Per-instance configuration information */ typedef struct pp_info { struct berval def_policy; /* DN of default policy subentry */ + struct policy_rule *policy_rules; int use_lockout; /* send AccountLocked result? */ int hash_passwords; /* transparently hash cleartext pwds */ int forward_updates; /* use frontend for policy state updates */ @@ -444,9 +454,10 @@ enum { PPOLICY_USE_LOCKOUT, PPOLICY_DISABLE_WRITE, PPOLICY_CHECK_MODULE, + PPOLICY_DEFAULT_RULES, }; -static ConfigDriver ppolicy_cf_default, ppolicy_cf_checkmod; +static ConfigDriver ppolicy_cf_default, ppolicy_cf_rule, ppolicy_cf_checkmod; static ConfigTable ppolicycfg[] = { { "ppolicy_default", "policyDN", 2, 2, 0, @@ -501,6 +512,13 @@ static ConfigTable ppolicycfg[] = { "EQUALITY caseExactIA5Match " "SYNTAX OMsIA5String " "SINGLE-VALUE )", NULL, NULL }, + { "ppolicy_rules", "URL> bi; + pp_info *pi = (pp_info *)on->on_bi.bi_private; + policy_rule *pr = NULL, **prp; + LDAPURLDesc *lud = NULL; + struct berval bv; + int i, rc = ARG_BAD_CONF; + + assert( c->type == PPOLICY_DEFAULT_RULES ); + Debug( LDAP_DEBUG_TRACE, "==> ppolicy_rules\n" ); + + if ( c->op == SLAP_CONFIG_EMIT ) { + if ( pi->policy_rules ) { + Attribute a = { + .a_desc = c->ca_desc->ad, + .a_vals = c->rvalue_vals, + }; + + for ( pr = pi->policy_rules; pr; pr = pr->next ) { + bv.bv_len = pr->uri.bv_len + pr->policy_dn.bv_len + + STRLENOF("\"\" \"\""); + bv.bv_val = ch_malloc( bv.bv_len + 1 ); + + snprintf( bv.bv_val, bv.bv_len + 1, "\"%s\" \"%s\"", + pr->uri.bv_val, pr->policy_dn.bv_val ); + ber_bvarray_add( &a.a_vals, &bv ); + a.a_numvals++; + } + + ordered_value_renumber( &a ); + c->rvalue_vals = a.a_vals; + return LDAP_SUCCESS; + } + return 1; + } else if ( c->op == LDAP_MOD_DELETE ) { + if ( pi->policy_rules ) { + for ( prp = &pi->policy_rules, i=0; *prp; i++ ) { + pr = *prp; + + if ( c->valx == -1 || i == c->valx ) { + *prp = pr->next; + pr->next = NULL; + + ch_free( pr->uri.bv_val ); + ch_free( pr->base.bv_val ); + ch_free( pr->policy_dn.bv_val ); + filter_free( pr->filter ); + ch_free( pr ); + + if ( i == c->valx ) + break; + } else { + prp = &pr->next; + } + } + } + return LDAP_SUCCESS; + } + + if ( ldap_url_parse_ext( c->argv[1], &lud, 0 ) != LDAP_SUCCESS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "ppolicy_rules: bad policy URL"); + return rc; + } + + pr = ch_calloc( 1, sizeof(policy_rule) ); + ber_str2bv( c->argv[1], 0, 1, &pr->uri ); + pr->scope = lud->lud_scope; + + ber_str2bv( lud->lud_dn, 0, 0, &bv ); + if ( dnNormalize( 0, NULL, NULL, &bv, &pr->base, NULL ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "ppolicy_rules: bad URL base" ); + rc = ARG_BAD_CONF; + goto done; + } + + if ( lud->lud_filter ) { + pr->filter = str2filter( lud->lud_filter ); + if ( !pr->filter ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "ppolicy_rules: bad filter" ); + rc = ARG_BAD_CONF; + goto done; + } + } + + ber_str2bv( c->argv[2], 0, 0, &bv ); + if ( dnNormalize( 0, NULL, NULL, &bv, &pr->policy_dn, NULL ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "ppolicy_rules: bad policy DN" ); + rc = ARG_BAD_CONF; + goto done; + } + + rc = LDAP_SUCCESS; + for ( i = 0, prp = &pi->policy_rules; + *prp && ( c->valx < 0 || i < c->valx ); + prp = &(*prp)->next, i++ ) + /* advance to the desired position */ ; + pr->next = *prp; + *prp = pr; + +done: + ldap_free_urldesc( lud ); + if ( rc != LDAP_SUCCESS ) { + ch_free( pr->uri.bv_val ); + ch_free( pr->policy_dn.bv_val ); + filter_free( pr->filter ); + ch_free( pr ); + } + return rc; +} + #ifdef SLAPD_MODULES static int ppolicy_cf_checkmod( ConfigArgs *c ) @@ -969,10 +1103,22 @@ ppolicy_get( Operation *op, Entry *e, PassPolicy *pp ) ad = ad_pwdPolicySubentry; if ( (a = attr_find( e->e_attrs, ad )) == NULL ) { + policy_rule *pr = pi->policy_rules; /* - * entry has no password policy assigned - use default + * entry has no password policy assigned - find the default one */ - vals = &pi->def_policy; + for ( pr = pi->policy_rules; pr; pr = pr->next ) { + if ( !dnIsSuffixScope( &e->e_nname, &pr->base, pr->scope ) ) continue; + if ( pr->filter && test_filter( op, e, pr->filter ) != LDAP_COMPARE_TRUE ) continue; + + /* We found a match */ + break; + } + if ( pr ) { + vals = &pr->policy_dn; + } else { + vals = &pi->def_policy; + } if ( !vals->bv_val ) goto defaultpol; } else { @@ -3358,10 +3504,21 @@ ppolicy_db_destroy( { slap_overinst *on = (slap_overinst *) be->bd_info; pp_info *pi = on->on_bi.bi_private; + policy_rule *pr = pi->policy_rules, *next; on->on_bi.bi_private = NULL; ldap_pvt_thread_mutex_destroy( &pi->pwdFailureTime_mutex ); free( pi->def_policy.bv_val ); + while ( pr ) { + next = pr->next; + + ch_free( pr->uri.bv_val ); + ch_free( pr->base.bv_val ); + ch_free( pr->policy_dn.bv_val ); + filter_free( pr->filter ); + ch_free( pr ); + pr = next; + } free( pi ); ov_count--; diff --git a/tests/data/ppolicy.ldif b/tests/data/ppolicy.ldif index d4d697dd48..4f2ba936a9 100644 --- a/tests/data/ppolicy.ldif +++ b/tests/data/ppolicy.ldif @@ -36,6 +36,47 @@ pwdFailureCountInterval: 120 pwdSafeModify: TRUE pwdLockout: TRUE +dn: cn=Idle Expiration Policy, ou=Policies, dc=example, dc=com +objectClass: top +objectClass: device +objectClass: pwdPolicy +cn: Idle Expiration Policy +pwdAttribute: 2.5.4.35 +pwdLockoutDuration: 15 +pwdInHistory: 6 +pwdCheckQuality: 2 +pwdExpireWarning: 10 +pwdMaxIdle: 15 +pwdMinLength: 5 +pwdMaxLength: 13 +pwdGraceAuthnLimit: 3 +pwdAllowUserChange: TRUE +pwdMustChange: TRUE +pwdMaxFailure: 3 +pwdFailureCountInterval: 120 +pwdSafeModify: TRUE +pwdLockout: TRUE + +dn: cn=Stricter Policy, ou=Policies, dc=example, dc=com +objectClass: top +objectClass: device +objectClass: pwdPolicy +cn: Stricter Policy +pwdAttribute: 2.5.4.35 +pwdLockoutDuration: 15 +pwdInHistory: 6 +pwdCheckQuality: 2 +pwdExpireWarning: 10 +pwdMaxAge: 15 +pwdMinLength: 5 +pwdMaxLength: 13 +pwdAllowUserChange: TRUE +pwdMustChange: TRUE +pwdMaxFailure: 3 +pwdFailureCountInterval: 120 +pwdSafeModify: TRUE +pwdLockout: TRUE + dn: uid=nd, ou=People, dc=example, dc=com objectClass: top objectClass: person diff --git a/tests/data/slapd-ppolicy.conf b/tests/data/slapd-ppolicy.conf index 561e7cefcc..a3b4054507 100644 --- a/tests/data/slapd-ppolicy.conf +++ b/tests/data/slapd-ppolicy.conf @@ -38,6 +38,8 @@ rootpw secret lastbind on overlay ppolicy +ppolicy_rules ldap:///uid=ndadmin,ou=People,dc=example,dc=com??base "cn=No Policy,ou=Policies,dc=example,dc=com" +ppolicy_rules "ldap:///???(description=idle)" "cn=Idle Expiration Policy, ou=Policies, dc=example, dc=com" ppolicy_default "cn=Standard Policy,ou=Policies,dc=example,dc=com" ppolicy_use_lockout diff --git a/tests/scripts/test022-ppolicy b/tests/scripts/test022-ppolicy index 6a9cc6caed..2b2c8887de 100755 --- a/tests/scripts/test022-ppolicy +++ b/tests/scripts/test022-ppolicy @@ -244,6 +244,16 @@ fi echo "Testing failed logins when password/policy missing..." +$LDAPSEARCH -e ppolicy -H $URI1 \ + -D "$PWADMIN" -w hasnopolicy \ + -b "$BASEDN" -s base > $SEARCHOUT 2>&1 +RC=$? +if test $RC = 0 ; then + echo "Password accepted ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit 1 +fi + $LDAPSEARCH -e ppolicy -H $URI1 \ -D "uid=test, ou=People,$BASEDN" -w hasnopolicy \ -b "$BASEDN" -s base > $SEARCHOUT 2>&1 @@ -470,15 +480,13 @@ if test $RC = 0 ; then fi echo "Testing idle password expiration" -echo "Reconfiguring policy to replace expiration with idle expiration..." +echo "Switching to a policy with idle expiration..." $LDAPMODIFY -v -D "$MANAGERDN" -H $URI1 -w $PASSWD >> \ $TESTOUT 2>&1 << EOMODS -dn: cn=Standard Policy, ou=Policies, dc=example, dc=com +dn: $USER changetype: modify -delete: pwdMaxAge -- -add: pwdMaxIdle -pwdMaxIdle: 15 +add: description +description: idle EOMODS RC=$? @@ -508,15 +516,13 @@ if test $RC != 49 ; then exit 1 fi -echo "Reverting policy changes..." +echo "Reverting to Standard policy..." $LDAPMODIFY -v -D "$MANAGERDN" -H $URI1 -w $PASSWD >> \ $TESTOUT 2>&1 << EOMODS -dn: cn=Standard Policy, ou=Policies, dc=example, dc=com +dn: $USER changetype: modify -delete: pwdMaxIdle -- -add: pwdMaxAge -pwdMaxAge: 30 +delete: description +description: idle EOMODS RC=$? @@ -698,15 +704,29 @@ if test $RC != 0 ; then fi echo "Reconfiguring policy to remove grace logins..." -$LDAPMODIFY -v -D "$MANAGERDN" -H $URI1 -w $PASSWD >> \ +$LDAPMODIFY -v -D cn=config -H $URI1 -y $CONFIGPWF >> \ $TESTOUT 2>&1 << EOMODS -dn: cn=Standard Policy, ou=Policies, dc=example, dc=com +dn: olcOverlay={0}ppolicy,olcDatabase={1}$BACKEND,cn=config changetype: modify -delete: pwdGraceAuthnLimit -- -replace: pwdMaxAge -pwdMaxAge: 15 -- +add: olcPPolicyRules +olcPPolicyRules: {0}"ldap:///dc=example,dc=com???(!(description=grace))" + "cn=Stricter Policy, ou=Policies, dc=example, dc=com" + +EOMODS +RC=$? +if test $RC != 0 ; then + echo "ldapmodify failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi + +$LDAPMODIFY -v -D cn=config -H $URI2 -y $CONFIGPWF >> \ + $TESTOUT 2>&1 << EOMODS +dn: olcOverlay={0}ppolicy,olcDatabase={1}$BACKEND,cn=config +changetype: modify +add: olcPPolicyRules +olcPPolicyRules: {0}"ldap:///dc=example,dc=com???(!(description=grace))" + "cn=Stricter Policy, ou=Policies, dc=example, dc=com" EOMODS RC=$?