]> git.ipfire.org Git - thirdparty/openldap.git/commitdiff
ITS#9156 Implement pwdMaxIdle
authorOndřej Kuzník <ondra@mistotebe.net>
Thu, 24 Oct 2019 13:09:56 +0000 (14:09 +0100)
committerOndřej Kuzník <ondra@mistotebe.net>
Thu, 23 Jan 2020 23:46:58 +0000 (23:46 +0000)
doc/man/man5/slapd-config.5
doc/man/man5/slapd.conf.5
servers/slapd/bconfig.c
servers/slapd/bind.c
servers/slapd/overlays/ppolicy.c
servers/slapd/schema_prep.c
servers/slapd/slap.h

index 3fe6f0f912bf887e662013f7e9c9f9e6b293772e..c1716370e39595af6465117d29275619c27d297b 100644 (file)
@@ -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.
index 0751bb6ac1f1a64949dda578124683678e277e88..65b521f7233637acf486b1021e098aee280771d8 100644 (file)
@@ -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.
index 72ec3b18af57973ca63c606f46579f042df898fe..79a3fd1cfcb33aa764cdd4973601136f539d23e2 100644 (file)
@@ -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",
index e0e491444d9ff04e65608a83f10b841a61c7c9d5..997c91c32973e91b56c3f4583997fdaa81c16d02 100644 (file)
@@ -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 );
 
index ce570ce10a7f07f0c173e85f4bf9434827b528bf..a2cfa03157b194c09875e25f69735f5a9c10b15d 100644 (file)
@@ -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;
index 9c3612fd37ef5f7e68a94d8bf1cde8f433ed0465..e87c6fde556fc022e3fa753e39da60fbe6aa8add 100644 (file)
@@ -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 }
 };
 
index 18663979aa6562555fd167f0c396ede23172cb08..e151a460171734b51120b2c6355bb02598f0dde2 100644 (file)
@@ -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)