From: Ondřej Kuzník Date: Thu, 24 Oct 2019 13:09:56 +0000 (+0100) Subject: ITS#9156 Implement pwdMaxIdle X-Git-Tag: OPENLDAP_REL_ENG_2_5_0ALPHA~63^2~9 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=419b9ad202420b40ba7f0762765cfb11ceecdfa7;p=thirdparty%2Fopenldap.git ITS#9156 Implement pwdMaxIdle --- diff --git a/doc/man/man5/slapd-config.5 b/doc/man/man5/slapd-config.5 index 3fe6f0f912..c1716370e3 100644 --- a/doc/man/man5/slapd-config.5 +++ b/doc/man/man5/slapd-config.5 @@ -1429,6 +1429,12 @@ createTimestamp attributes for entries. It also controls the entryCSN and entryUUID attributes, which are needed by the syncrepl provider. By default, olcLastMod is TRUE. .TP +.B olcLastBind: TRUE | FALSE +Controls whether +.B slapd +will automatically maintain the pwdLastSuccess attribute for +entries. By default, olcLastBind is FALSE. +.TP .B olcLimits: [ [...]] Specify time and size limits based on the operation's initiator or base DN. diff --git a/doc/man/man5/slapd.conf.5 b/doc/man/man5/slapd.conf.5 index 0751bb6ac1..65b521f723 100644 --- a/doc/man/man5/slapd.conf.5 +++ b/doc/man/man5/slapd.conf.5 @@ -1364,6 +1364,12 @@ createTimestamp attributes for entries. It also controls the entryCSN and entryUUID attributes, which are needed by the syncrepl provider. By default, lastmod is on. .TP +.B lastbind on | off +Controls whether +.B slapd +will automatically maintain the pwdLastSuccess attribute for +entries. By default, lastbind is off. +.TP .B limits [ [...]] Specify time and size limits based on the operation's initiator or base DN. diff --git a/servers/slapd/bconfig.c b/servers/slapd/bconfig.c index 72ec3b18af..79a3fd1cfc 100644 --- a/servers/slapd/bconfig.c +++ b/servers/slapd/bconfig.c @@ -178,6 +178,7 @@ enum { CFG_MODLOAD, CFG_MODPATH, CFG_LASTMOD, + CFG_LASTBIND, CFG_AZPOLICY, CFG_AZREGEXP, CFG_AZDUC, @@ -442,6 +443,10 @@ static ConfigTable config_back_cf_table[] = { &config_generic, "( OLcfgDbAt:0.4 NAME 'olcLastMod' " "EQUALITY booleanMatch " "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "lastbind", "on|off", 2, 2, 0, ARG_DB|ARG_ON_OFF|ARG_MAGIC|CFG_LASTBIND, + &config_generic, "( OLcfgDbAt:0.22 NAME 'olcLastBind' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, { "ldapsyntax", "syntax", 2, 0, 0, ARG_PAREN|ARG_MAGIC|CFG_SYNTAX, &config_generic, "( OLcfgGlAt:85 NAME 'olcLdapSyntaxes' " @@ -981,7 +986,7 @@ static ConfigOCs cf_ocs[] = { "SUP olcConfig STRUCTURAL " "MUST olcDatabase " "MAY ( olcDisabled $ olcHidden $ olcSuffix $ olcSubordinate $ olcAccess $ " - "olcAddContentAcl $ olcLastMod $ olcLimits $ " + "olcAddContentAcl $ olcLastMod $ olcLastBind $ olcLimits $ " "olcMaxDerefDepth $ olcPlugin $ olcReadOnly $ olcReplica $ " "olcReplicaArgsFile $ olcReplicaPidFile $ olcReplicationInterval $ " "olcReplogFile $ olcRequires $ olcRestrict $ olcRootDN $ olcRootPW $ " @@ -1321,6 +1326,9 @@ config_generic(ConfigArgs *c) { case CFG_LASTMOD: c->value_int = (SLAP_NOLASTMOD(c->be) == 0); break; + case CFG_LASTBIND: + c->value_int = (SLAP_NOLASTMOD(c->be) == 0); + break; case CFG_SYNC_SUBENTRY: c->value_int = (SLAP_SYNC_SUBENTRY(c->be) != 0); break; @@ -1435,6 +1443,7 @@ config_generic(ConfigArgs *c) { case CFG_AZPOLICY: case CFG_DEPTH: case CFG_LASTMOD: + case CFG_LASTBIND: case CFG_MONITORING: case CFG_SASLSECP: case CFG_SSTR_IF_MAX: @@ -2276,6 +2285,13 @@ sortval_reject: SLAP_DBFLAGS(c->be) |= SLAP_DBFLAG_NOLASTMOD; break; + case CFG_LASTBIND: + if (c->value_int) + SLAP_DBFLAGS(c->be) |= SLAP_DBFLAG_LASTBIND; + else + SLAP_DBFLAGS(c->be) &= ~SLAP_DBFLAG_LASTBIND; + break; + case CFG_MIRRORMODE: if(c->value_int && !SLAP_SHADOW(c->be)) { snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> database is not a shadow", diff --git a/servers/slapd/bind.c b/servers/slapd/bind.c index e0e491444d..997c91c329 100644 --- a/servers/slapd/bind.c +++ b/servers/slapd/bind.c @@ -31,6 +31,7 @@ #include #include +#include "lutil.h" #include "slap.h" int @@ -399,6 +400,113 @@ cleanup:; return rs->sr_err; } +int +fe_op_lastbind( Operation *op ) +{ + Operation op2 = *op; + SlapReply r2 = { REP_RESULT }; + slap_callback cb = { NULL, slap_null_cb, NULL, NULL }; + LDAPControl c, *ca[2]; + Modifications *m; + Entry *e; + Attribute *a; + char nowstr[ LDAP_LUTIL_GENTIME_BUFSIZE ]; + struct berval timestamp; + time_t bindtime = (time_t)-1; + int rc; + + rc = be_entry_get_rw( op, &op->o_conn->c_ndn, NULL, NULL, 0, &e ); + if ( rc != LDAP_SUCCESS ) { + return -1; + } + + /* get authTimestamp attribute, if it exists */ + if ( (a = attr_find( e->e_attrs, slap_schema.si_ad_pwdLastSuccess )) != NULL ) { + struct lutil_tm tm; + struct lutil_timet tt; + + if ( lutil_parsetime( a->a_nvals[0].bv_val, &tm ) == 0 ) { + lutil_tm2time( &tm, &tt ); + bindtime = tt.tt_sec; + } + Debug( LDAP_DEBUG_ANY, "fe_op_lastbind: " + "old pwdLastSuccess value=%s %lds ago\n", + a->a_nvals[0].bv_val, bindtime == (time_t)-1 ? -1 : op->o_time - bindtime ); + + /* + * TODO: If the recorded bind time is within configurable precision, + * it doesn't need to be updated (save a write for nothing) + */ + if ( bindtime != (time_t)-1 && op->o_time <= bindtime ) { + be_entry_release_r( op, e ); + return LDAP_SUCCESS; + } + } + + /* update the authTimestamp in the user's entry with the current time */ + timestamp.bv_val = nowstr; + timestamp.bv_len = sizeof(nowstr); + slap_timestamp( &op->o_time, ×tamp ); + + m = ch_calloc( sizeof(Modifications), 1 ); + m->sml_op = LDAP_MOD_REPLACE; + m->sml_flags = 0; + m->sml_type = slap_schema.si_ad_pwdLastSuccess->ad_cname; + m->sml_desc = slap_schema.si_ad_pwdLastSuccess; + m->sml_numvals = 1; + m->sml_values = ch_calloc( sizeof(struct berval), 2 ); + m->sml_nvalues = ch_calloc( sizeof(struct berval), 2 ); + + ber_dupbv( &m->sml_values[0], ×tamp ); + ber_dupbv( &m->sml_nvalues[0], ×tamp ); + + be_entry_release_r( op, e ); + + op2.o_tag = LDAP_REQ_MODIFY; + op2.o_req_dn = op->o_conn->c_dn; + op2.o_req_ndn = op->o_conn->c_ndn; + op2.o_callback = &cb; + op2.orm_modlist = m; + op2.orm_no_opattrs = 0; + op2.o_dn = op->o_bd->be_rootdn; + op2.o_ndn = op->o_bd->be_rootndn; + + /* + * TODO: this is core+frontend, not everything works the same way? + */ + /* + * Code for forwarding of updates adapted from ppolicy.c of slapo-ppolicy + * + * If this server is a shadow and forward_updates is true, + * use the frontend to perform this modify. That will trigger + * the update referral, which can then be forwarded by the + * chain overlay. Obviously the updateref and chain overlay + * must be configured appropriately for this to be useful. + */ + if ( SLAP_SHADOW( op->o_bd ) ) { + /* Must use Relax control since these are no-user-mod */ + op2.o_relax = SLAP_CONTROL_CRITICAL; + op2.o_ctrls = ca; + ca[0] = &c; + ca[1] = NULL; + BER_BVZERO( &c.ldctl_value ); + c.ldctl_iscritical = 1; + c.ldctl_oid = LDAP_CONTROL_RELAX; + } else { + /* If not forwarding, don't update opattrs and don't replicate */ + if ( SLAP_SINGLE_SHADOW( op->o_bd )) { + op2.orm_no_opattrs = 1; + op2.o_dont_replicate = 1; + } + } + + rc = op->o_bd->be_modify( &op2, &r2 ); + slap_mods_free( m, 1 ); + +done: + return rc; +} + int fe_op_bind_success( Operation *op, SlapReply *rs ) { @@ -436,6 +544,10 @@ fe_op_bind_success( Operation *op, SlapReply *rs ) ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex ); + if ( SLAP_LASTBIND( op->o_bd ) ) { + fe_op_lastbind( op ); + } + /* send this here to avoid a race condition */ send_ldap_result( op, rs ); diff --git a/servers/slapd/overlays/ppolicy.c b/servers/slapd/overlays/ppolicy.c index ce570ce10a..a2cfa03157 100644 --- a/servers/slapd/overlays/ppolicy.c +++ b/servers/slapd/overlays/ppolicy.c @@ -72,6 +72,8 @@ typedef struct pass_policy { AttributeDescription *ad; /* attribute to which the policy applies */ int pwdMinAge; /* minimum time (seconds) until passwd can change */ int pwdMaxAge; /* time in seconds until pwd will expire after change */ + int pwdMaxIdle; /* number of seconds since last successful bind before + passwd gets locked out */ int pwdInHistory; /* number of previous passwords kept */ int pwdCheckQuality; /* 0 = don't check quality, 1 = check if possible, 2 = check mandatory; fail if not possible */ @@ -207,6 +209,7 @@ static struct schema_info { "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 " "SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )", &ad_pwdEndTime }, + /* Defined in schema_prep.c now { "( 1.3.6.1.4.1.42.2.27.8.1.29 " "NAME ( 'pwdLastSuccess' ) " "DESC 'The timestamp of the last successful authentication' " @@ -215,6 +218,7 @@ static struct schema_info { "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 " "SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )", &ad_pwdLastSuccess }, + */ { "( 1.3.6.1.4.1.42.2.27.8.1.33 " "NAME ( 'pwdAccountTmpLockoutEnd' ) " "DESC 'Temporary lockout end' " @@ -576,6 +580,24 @@ account_locked( Operation *op, Entry *e, } } + /* Only check if database maintains lastbind */ + if ( pp->pwdMaxIdle && SLAP_LASTBIND( op->o_bd ) ) { + time_t lastbindtime = (time_t)-1; + + la = attr_find( e->e_attrs, ad_pwdLastSuccess ); + if ( la == NULL ) { + la = attr_find( e->e_attrs, ad_pwdChangedTime ); + } + if ( la != NULL ) { + lastbindtime = parse_time( la->a_nvals[0].bv_val ); + } + + if ( lastbindtime != (time_t)-1 && + op->o_time > lastbindtime + pp->pwdMaxIdle ) { + return 1; + } + } + if ( (la = attr_find( e->e_attrs, ad_pwdAccountLockedTime )) != NULL ) { BerVarray vals = la->a_nvals; @@ -773,6 +795,9 @@ ppolicy_get( Operation *op, Entry *e, PassPolicy *pp ) if ( ( a = attr_find( pe->e_attrs, ad_pwdMaxAge ) ) && lutil_atoi( &pp->pwdMaxAge, a->a_vals[0].bv_val ) != 0 ) goto defaultpol; + if ( ( a = attr_find( pe->e_attrs, ad_pwdMaxIdle ) ) + && lutil_atoi( &pp->pwdMaxIdle, a->a_vals[0].bv_val ) != 0 ) + goto defaultpol; if ( ( a = attr_find( pe->e_attrs, ad_pwdInHistory ) ) && lutil_atoi( &pp->pwdInHistory, a->a_vals[0].bv_val ) != 0 ) goto defaultpol; @@ -1935,7 +1960,8 @@ ppolicy_modify( Operation *op, SlapReply *rs ) LDAPControl *ctrl = NULL; LDAPControl **oldctrls = NULL; int is_pwdexop = 0; - int got_del_grace = 0, got_del_lock = 0, got_pw = 0, got_del_fail = 0; + int got_del_grace = 0, got_del_lock = 0, got_pw = 0, got_del_fail = 0, + got_del_success = 0; int got_changed = 0, got_history = 0; op->o_bd->bd_info = (BackendInfo *)on->on_info; @@ -1949,11 +1975,12 @@ ppolicy_modify( Operation *op, SlapReply *rs ) */ if ( SLAPD_SYNC_IS_SYNCCONN( op->o_connid ) ) { Modifications **prev; - Attribute *a_grace, *a_lock, *a_fail; + Attribute *a_grace, *a_lock, *a_fail, *a_success; a_grace = attr_find( e->e_attrs, ad_pwdGraceUseTime ); a_lock = attr_find( e->e_attrs, ad_pwdAccountLockedTime ); a_fail = attr_find( e->e_attrs, ad_pwdFailureTime ); + a_success = attr_find( e->e_attrs, ad_pwdLastSuccess ); for( prev = &op->orm_modlist, ml = *prev; ml; ml = *prev ) { @@ -1988,6 +2015,13 @@ ppolicy_modify( Operation *op, SlapReply *rs ) got_del_fail = 1; } } + if ( ml->sml_desc == ad_pwdLastSuccess ) { + if ( !a_success || got_del_success ) { + drop = ml->sml_op == LDAP_MOD_DELETE; + } else { + got_del_success = 1; + } + } if ( drop ) { *prev = ml->sml_next; ml->sml_next = NULL; @@ -1999,7 +2033,7 @@ ppolicy_modify( Operation *op, SlapReply *rs ) } /* If we're resetting the password, make sure grace, accountlock, - * and failure also get removed. + * success, and failure also get removed. */ if ( got_pw ) { if ( a_grace && !got_del_grace ) { @@ -2039,6 +2073,18 @@ ppolicy_modify( Operation *op, SlapReply *rs ) ml->sml_next = NULL; *prev = ml; } + if ( a_success && !got_del_success ) { + ml = (Modifications *) ch_malloc( sizeof( Modifications ) ); + ml->sml_op = LDAP_MOD_DELETE; + ml->sml_flags = SLAP_MOD_INTERNAL; + ml->sml_type.bv_val = NULL; + ml->sml_desc = ad_pwdLastSuccess; + ml->sml_numvals = 0; + ml->sml_values = NULL; + ml->sml_nvalues = NULL; + ml->sml_next = NULL; + *prev = ml; + } } op->o_bd->bd_info = (BackendInfo *)on->on_info; be_entry_release_r( op, e ); @@ -2145,6 +2191,8 @@ ppolicy_modify( Operation *op, SlapReply *rs ) got_del_lock = 1; } else if ( ml->sml_desc == ad_pwdFailureTime ) { got_del_fail = 1; + } else if ( ml->sml_desc == ad_pwdLastSuccess ) { + got_del_success = 1; } } if ( ml->sml_desc == ad_pwdChangedTime ) { @@ -2463,6 +2511,17 @@ do_modify: modtail = mods; } + /* TODO: do we remove pwdLastSuccess or set it to 'now'? */ + if (!got_del_success && attr_find(e->e_attrs, ad_pwdLastSuccess )){ + mods = (Modifications *) ch_calloc( sizeof( Modifications ), 1 ); + mods->sml_op = LDAP_MOD_DELETE; + mods->sml_flags = SLAP_MOD_INTERNAL; + mods->sml_desc = ad_pwdLastSuccess; + mods->sml_next = NULL; + modtail->sml_next = mods; + modtail = mods; + } + /* Delete all pwdInHistory attribute */ if (!got_history && pp.pwdInHistory == 0 && attr_find(e->e_attrs, ad_pwdHistory )){ @@ -2769,6 +2828,7 @@ int ppolicy_initialize() SLAP_AT_MANAGEABLE; } } + ad_pwdLastSuccess = slap_schema.si_ad_pwdLastSuccess; { Syntax *syn; MatchingRule *mr; diff --git a/servers/slapd/schema_prep.c b/servers/slapd/schema_prep.c index 9c3612fd37..e87c6fde55 100644 --- a/servers/slapd/schema_prep.c +++ b/servers/slapd/schema_prep.c @@ -1019,6 +1019,19 @@ static struct slap_schema_ad_map { NULL, NULL, NULL, NULL, NULL, offsetof(struct slap_internal_schema, si_ad_pKCS8PrivateKey) }, + { "pwdLastSuccess", "( 1.3.6.1.4.1.42.2.27.8.1.29 NAME 'pwdLastSuccess' " + "DESC 'The timestamp of the last successful authentication' " + "EQUALITY generalizedTimeMatch " + "ORDERING generalizedTimeOrderingMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 " + "SINGLE-VALUE " + "NO-USER-MODIFICATION " + "USAGE directoryOperation )", + NULL, 0, + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + offsetof(struct slap_internal_schema, si_ad_pwdLastSuccess) }, + { NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0 } }; diff --git a/servers/slapd/slap.h b/servers/slapd/slap.h index 18663979aa..e151a46017 100644 --- a/servers/slapd/slap.h +++ b/servers/slapd/slap.h @@ -987,6 +987,9 @@ struct slap_internal_schema { /* privateKeys */ AttributeDescription *si_ad_pKCS8PrivateKey; + /* ppolicy lastbind equivalent */ + AttributeDescription *si_ad_pwdLastSuccess; + /* Undefined Attribute Type */ AttributeType *si_at_undefined; @@ -1867,10 +1870,12 @@ struct BackendDB { #define SLAP_DBFLAG_SYNC_SUBENTRY 0x40000U /* use subentry for context */ #define SLAP_DBFLAG_MULTI_SHADOW 0x80000U /* uses mirrorMode/multi-master */ #define SLAP_DBFLAG_DISABLED 0x100000U +#define SLAP_DBFLAG_LASTBIND 0x200000U slap_mask_t be_flags; #define SLAP_DBFLAGS(be) ((be)->be_flags) #define SLAP_NOLASTMOD(be) (SLAP_DBFLAGS(be) & SLAP_DBFLAG_NOLASTMOD) #define SLAP_LASTMOD(be) (!SLAP_NOLASTMOD(be)) +#define SLAP_LASTBIND(be) (SLAP_DBFLAGS(be) & SLAP_DBFLAG_LASTBIND) #define SLAP_DBHIDDEN(be) (SLAP_DBFLAGS(be) & SLAP_DBFLAG_HIDDEN) #define SLAP_DBDISABLED(be) (SLAP_DBFLAGS(be) & SLAP_DBFLAG_DISABLED) #define SLAP_DB_ONE_SUFFIX(be) (SLAP_DBFLAGS(be) & SLAP_DBFLAG_ONE_SUFFIX)