Commit 419b9ad2 authored by Ondřej Kuzník's avatar Ondřej Kuzník
Browse files

ITS#9156 Implement pwdMaxIdle

parent 8c10b048
......@@ -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: <selector> <limit> [<limit> [...]]
Specify time and size limits based on the operation's initiator or
base DN.
......
......@@ -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 <selector> <limit> [<limit> [...]]
Specify time and size limits based on the operation's initiator or
base DN.
......
......@@ -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",
......
......@@ -31,6 +31,7 @@
#include <ac/string.h>
#include <ac/socket.h>
#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, &timestamp );
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], &timestamp );
ber_dupbv( &m->sml_nvalues[0], &timestamp );
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 );
......
......@@ -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;
......
......@@ -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 }
};
......
......@@ -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)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment