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: <selector> <limit> [<limit> [...]]
Specify time and size limits based on the operation's initiator or
base DN.
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 <selector> <limit> [<limit> [...]]
Specify time and size limits based on the operation's initiator or
base DN.
CFG_MODLOAD,
CFG_MODPATH,
CFG_LASTMOD,
+ CFG_LASTBIND,
CFG_AZPOLICY,
CFG_AZREGEXP,
CFG_AZDUC,
&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' "
"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 $ "
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;
case CFG_AZPOLICY:
case CFG_DEPTH:
case CFG_LASTMOD:
+ case CFG_LASTBIND:
case CFG_MONITORING:
case CFG_SASLSECP:
case CFG_SSTR_IF_MAX:
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",
#include <ac/string.h>
#include <ac/socket.h>
+#include "lutil.h"
#include "slap.h"
int
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 )
{
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 );
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 */
"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' "
"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' "
}
}
+ /* 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;
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;
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;
*/
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 ) {
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;
}
/* 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 ) {
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 );
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 ) {
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 )){
SLAP_AT_MANAGEABLE;
}
}
+ ad_pwdLastSuccess = slap_schema.si_ad_pwdLastSuccess;
{
Syntax *syn;
MatchingRule *mr;
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 }
};
/* privateKeys */
AttributeDescription *si_ad_pKCS8PrivateKey;
+ /* ppolicy lastbind equivalent */
+ AttributeDescription *si_ad_pwdLastSuccess;
+
/* Undefined Attribute Type */
AttributeType *si_at_undefined;
#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)